Всего 125 356 комментариев

Дмитрий
14 ноября 2025, 17:31
0
Не совсем понимаю, что это даст? не могли бы вы обьяснить?

И вот еще что, в описании pdoResources не нашел &templates… что это дает?
Павел Романов
14 ноября 2025, 17:28
0
Тогда так:

[[pdoResources?
&tpl=`category_tpl`
&parents=`6`
&templates=`***` // тут ID шаблона категории 
&limit=`0`
&sortby=`{ "menuindex":"DESC"}`
&outputSeparator=`,`
]]

Чанк category_tpl:
[[msProducts?
&tpl=`razmetka-tovarov`
&parents=`[[+id]]`
&templates=`***` // тут ID шаблона товара 
&limit=`16`
&sortby=`{ "menuindex":"DESC"}`
&outputSeparator=`,`
]]
Дмитрий
14 ноября 2025, 17:20
0
Кстати, как я писал выше, если я буду использовать &offset, то перестает работать функция tplLast, а для меня она важна
Дмитрий
14 ноября 2025, 17:16
0
каталог (6)
----категория 1 (7)
--------товары около 100 шт
----категория 2 (8)
--------товары около 100 шт
и так далее

мне нужно вывести из каждой категории по 16 товаров

у меня это реализовано, скажем так, циклом в цикле
Павел Романов
14 ноября 2025, 17:01
0
При таком вызове выведутся 16 товаров, которые расположены ниже ресурса с id = 6.
При &limit=`0` выведутся все товары, которые находятся ниже ресурса с id = 6.

Какая у Вас структура каталога и что именно Вы хотите получить в результате?
Дмитрий
14 ноября 2025, 16:50
0
[[msProducts?
&tpl=`razmetka-tovarov`
&parents=`6`
&templates=`***` // тут ID шаблона товара 
&limit=`16`
&offset=`0`
&sortby=`{ "menuindex":"DESC"}`
&outputSeparator=`,`
]]
Плохой вариант, при таком раскладе у меня выведется из первой подпапки 16 позиций, что будет явно противоречить действительности.
Павел Романов
14 ноября 2025, 12:14
0
А почему не сделать так:

[[msProducts?
&tpl=`razmetka-tovarov`
&parents=`6`
&templates=`***` // тут ID шаблона товара 
&limit=`16`
&offset=`0`
&sortby=`{ "menuindex":"DESC"}`
&outputSeparator=`,`
]]
Если надо используйте &offset в вызове для сдвига выборки.

В чанке просто:
{
            "@type": "ListItem",
            "position": [[+idx]],
            "item": {
                "@type": "Product",
                "@id": "https://mydomain.ru/[[~[[+id]]]]",
                "name": "[[+pagetitle]]",
                "image": "https://mydomainru{$thumb}",
                "description": "[[+description]]",
                "offers": {
                    "@type": "Offer",
                    "url": "https://mydomain/[[~[[+id]]]]",
                    "priceCurrency": "RUB",
                    "price": "{$price | replace : " " : ""}",
                    "availability": "https://schema.org/InStock"
                }
            }
        }
Алексей
12 ноября 2025, 09:40
0
Спасибо большое за точную подсказку, в этой настройке удалил и заработало.… я его блок где-то закомментировал, возможно в /assets/js/scripts.js, думал не актуальный.

«delivery»:
{«transport»:"{core_path}components\/minishop2\/custom\/delivery\/msdeliveryhandlertransport.class.php",
Алексей
11 ноября 2025, 10:43
0
Когда пишу так запрос, все работает:

Акушер-гинеколог==24||COVID-19==37||Аллерголог-Иммунолог==38
В SQL как в первом, так и во втором случае все добавляется правильно «37||24»
Алексей
11 ноября 2025, 10:08
0
Добрый день, может кто поможет на MODX 2.8.8 ставлю ExtraFields все работает все классно, но если делаю тип поля «выпадающей список мультивыбор» из ресурсов, такое условие:

@SELECT `pagetitle`,`id` FROM `modx_site_content` WHERE `parent` = 23 ORDER BY `pagetitle` ASC
то фронтент всего сайта становится белый, в логах ошибок нет, в админке мультивыбор работает. Выявил что если отключаю системное событие OnMODXInit то сайт оживает. Подскажите в чем может быть проблема?
Konstantin
10 ноября 2025, 17:36
0
Заработало! да я по привычке в hook записал, а надо было так:
'filterresources' => [
        'snippet' => 'filterresources',
        'resultBlockSelector' => '#filter-results',
        'resultShowMethod'=>'insert',
    ],
Спасибо тебе большое!
Артур Шевченко
10 ноября 2025, 17:21
1
+1
Каким сниппетом обрабатывается данный пресет по-твоему?
'filter_resources' => [
        'hooks' => 'filter_resources',
        'resultBlockSelector' => '#filter-results',
        'resultShowMethod'=>'insert',
    ],
Konstantin
10 ноября 2025, 17:11
0
Спасибо Артур за ответ, удалил. Удалил также из сниппета всю логику вообще для теста, оставил только
return $SendIt->success('ок', ['html' => 'TEST']);
Ошибки ушли только после того как в названии сниппета убрал нижнее подчеркивание. Но данные пришедшие из сниппета не подставляются в указанный в пресете 'resultBlockSelector' => '#filter-results'. Что-то нужно еще прописать в js?
Артур Шевченко
10 ноября 2025, 15:29
+1
Логи сервера смотри. Но скорее всего путь к какому-то обработчику указан неверное в ms2_services
Артур Шевченко
10 ноября 2025, 15:28
0
$SendIt = $modx->getService('sendit', 'SendIt', MODX_CORE_PATH.'components/sendit/model/sendit/');
Удали
Alexandr
10 ноября 2025, 10:29
0
Спасибо. Вроде получилось, но не могу понять как передать дополнительные поля в CRM и почему-то время не правильное передает, +7 часов.
Prihod
09 ноября 2025, 23:05
0
да, только для импорта данные должны быть указаны в JSON формате
Артур Шевченко
09 ноября 2025, 19:55
0
Чего ж вы репозитории не делаете?
Евгений
09 ноября 2025, 16:46
0
Вот мой вариант. Тоже тут решил заняться вопросом избавления себя от jquery… :)
Сделал отдельный файл pdopage-no-jquery.js. Запихнул его в /assets/components/custom/
Комментарии решил оставить.
Получилось, вроде, неплохо, но кажется, что есть что ещё можно доработать

Вызов PdoPage
<div id="pdopage">
	<div class="section__pagelist">
		[[!pdoPage?
			&parents=`[[*id]]`
			&depth=`0`
			&tpl=`tpl_example`
			&sortby=`{"publishedon": "DESC"}`
			&limit=`6`
			&tplPageActive=`@INLINE <li class="active"><span class="page-link">[[+pageNo]]</span></li>`
			&ajax=`1`
			&ajaxElemRows=`#pdopage .section__pagelist`
			&ajaxElemMore=`#pdopage .btn-loadmore`
    		        &ajaxTplMore=`@INLINE <button class="btn btn-primary btn-sm btn-loadmore">Загрузить ещё</button>`
			&ajaxMode=`button`
			&frontend_js=`[[++assets_url]]/components/custom/pdopage-no-jquery.js`
		]]
	</div>
	[[-!+page.nav]]
</div>
Проверил во всех модах: дефолт (там, собственно, скрипт и не нужен), button, scroll

Сам js-код
const pdoPage = (function () {
  /*
  Параметры, которые прокидывает PdoPage в конфиг
  const basePdoToolsConfig = {
    wrapper: '#pdopage',
    rows: '#pdopage .rows',
    pagination: '#pdopage .pagination',
    link: '#pdopage .pagination a',
    more: '#pdopage .btn-more',
    moreTpl: '<button class='btn btn-primary btn-more'>Загрузить ещё</button>',
    mode: 'button',
    history: 0,
    pageVarKey: 'page',
    pageLimit: '6',
    assetsUrl: '/assets/components/pdotools/',
    connectorUrl: '/assets/components/pdotools/connector.php',
    pageId: 1,
    hash: 'string',
    scrollTop: true,
  };*/

  const additionalConfig = {
    scrollOffset: 150,
    moreBtnLoadingHTML: '<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span> Загружаем...',
    rowsContainerLoadingClass: 'state--progress',
  };

  let keys = {};
  let configs = {};
  let isLoading = false;
  let isEndReached = false;

  function getHashParams() {
    const hash = window.location.hash.substring(1);
    return Object.fromEntries(new URLSearchParams(hash));
  }
  function setHashParams(params) {
    const hash = new URLSearchParams(params).toString();
    window.location.hash = hash;
  }
  function removeHashKey(key) {
    const params = getHashParams();
    delete params[key];
    setHashParams(params);
  }
  function addHashKey(key, value) {
    const params = getHashParams();
    params[key] = value;
    setHashParams(params);
  }

  function insertMoreButton(config) {
    const containerWrapper = document.querySelector(config.wrapper);
    const moreBtn = document.querySelector(config.more);

    if (!containerWrapper || moreBtn !== null) return;

    const moreTpl = config.moreTpl;
    containerWrapper.insertAdjacentHTML('beforeend', moreTpl);
  }

  function removeMoreButton(config) {
    const moreBtn = document.querySelector(config.more);

    if (moreBtn) moreBtn.remove();
  }

  function setLoadingState(config) {
    const moreBtn = document.querySelector(config.more);
    const containerRows = document.querySelector(config.rows);

    containerRows.classList.add(additionalConfig.rowsContainerLoadingClass);

    if (moreBtn) {
      if (!config.moreBtnText) {
        config.moreBtnText = moreBtn.textContent;
      }
      moreBtn.disabled = 'disabled';
      moreBtn.innerHTML = additionalConfig.moreBtnLoadingHTML;
    }
  }

  function removeLoadingState(config) {
    const moreBtn = document.querySelector(config.more);
    const containerRows = document.querySelector(config.rows);

    containerRows.classList.remove(additionalConfig.rowsContainerLoadingClass);

    if (moreBtn) {
      moreBtn.disabled = '';
      moreBtn.textContent = config.moreBtnText;
    }
  }

  function loadPage(url, config, append = false) {
    if (isLoading || isEndReached) return;

    isLoading = true;

    setLoadingState(config);

    fetch(url, { headers: { 'X-Requested-With': 'XMLHttpRequest' } })
      .then((res) => {
        if (!res.ok) throw new Error(`Ошибка сети: ${res.status}`);

        return res.json();
      })
      .then((data) => {
        // data.output — HTML-контент
        // data.pagination — HTML пагинации
        // data.pages — всего страниц
        // data.page — текущая страница

        const containerRows = document.querySelector(config.rows);
        const containerPagination = document.querySelector(config.pagination);

        if (!containerRows) {
          throw new Error('Контейнер ajaxElemRows не найден в DOM');
        }

        if (append) {
          containerRows.insertAdjacentHTML('beforeend', data.output);
        } else {
          containerRows.innerHTML = data.output;
        }

        if (containerPagination) {
          containerPagination.innerHTML = data.pagination;
        }

        const currentPage = Number(data.page) || 1;
        const totalPages = Number(data.pages) || 1;

        // Проверка на конец данных
        if (currentPage >= totalPages || !data.output.trim()) {
          isEndReached = true;
          removeMoreButton(config);
          if (config.onEnd) config.onEnd();
        } else {
          isEndReached = false;
          if (config.mode === 'button') {
            removeLoadingState(config);
            insertMoreButton(config);
          }
        }

        if (config.callback && typeof config.callback === 'function') {
          config.callback();
        }
      })
      .catch((err) => {
        console.error(err);
      })
      .finally(() => {
        isLoading = false;
      });
  }

  function init(config) {
    if (!config) {
      console.error('pdoPage: конфигурация не передана');
      return;
    }

    configs[config.pageVarKey] = config;

    const pageKey = config.pageVarKey;
    const wrapperSelector = config.wrapper;
    const moreBtnSelector = config.more;
    const linkSelector = config.link;

    const params = new URLSearchParams(window.location.search);
    const pageParam = params.get(pageKey) || getHashParams()[pageKey] || '1';
    keys[pageKey] = Number(pageParam);

    switch (config.mode) {
      case 'default':
        document.addEventListener('click', (e) => {
          const target = e.target.closest(linkSelector);
          if (!target) return;
          e.preventDefault();

          const href = target.href;
          const regex = new RegExp(pageKey + '=(\\d+)');
          const match = href.match(regex);
          const page = match ? Number(match[1]) : 1;

          if (keys[pageKey] === page) return;

          keys[pageKey] = page;
          isEndReached = false;

          if (config.ajaxHistory) {
            if (page === 1) {
              removeHashKey(pageKey);
              history.pushState({}, '', href.split('?')[0]);
            } else {
              addHashKey(pageKey, page);
              history.pushState({}, '', href);
            }
          }

          loadPage(href, config, false);
        });
        break;

      case 'button':
        const container = document.querySelector(wrapperSelector);
        if (!container) {
          console.error(`pdoPage: не найден контейнер ${wrapperSelector}`);
          return;
        }

        insertMoreButton(config);

        container.addEventListener('click', (e) => {
          e.preventDefault();

          const target = e.target.closest(moreBtnSelector);
          if (!target) return;

          if (isLoading || isEndReached) return;

          const nextPage = keys[pageKey] + 1;
          const baseUrl =
            target.dataset.baseUrl || window.location.href.split('?')[0];
          const newUrl = new URL(baseUrl);
          newUrl.searchParams.set(pageKey, nextPage);

          keys[pageKey] = nextPage;

          if (config.ajaxHistory) {
            addHashKey(pageKey, nextPage);
            history.pushState({}, '', newUrl);
          }

          loadPage(newUrl.toString(), config, true);
        });
        break;

      case 'scroll':
        if (!config.scrollOffset) {
          config.scrollOffset = additionalConfig.scrollOffset;
        }

        window.addEventListener('scroll', () => {
          if (isLoading || isEndReached) return;

          const containerRows = document.querySelector(config.rows);
          if (!containerRows) return;

          // Получаем позицию bottom элемента со списком относительно документа
          const rect = containerRows.getBoundingClientRect();
          const scrollY =
            window.pageYOffset || document.documentElement.scrollTop;
          const elementBottom = rect.bottom + scrollY;

          // Текущая позиция прокрутки + высота окна
          const scrollPos = window.pageYOffset + window.innerHeight;

          // Сравниваем: если прокрутка достигла или прошла элемент + offset
          if (scrollPos >= elementBottom + config.scrollOffset) {
            const nextPage = keys[pageKey] + 1;
            const baseUrl = window.location.href.split('?')[0];
            const newUrl = new URL(baseUrl);
            newUrl.searchParams.set(pageKey, nextPage);

            keys[pageKey] = nextPage;

            if (config.ajaxHistory) {
              addHashKey(pageKey, nextPage);
              history.pushState({}, '', newUrl);
            }

            loadPage(newUrl.toString(), config, true);
          }
        });
        break;
    }

    if (config.history) {
      window.addEventListener('popstate', () => {
        const params = new URLSearchParams(window.location.search);
        const page = params.get(pageKey) || getHashParams()[pageKey] || '1';

        if (keys[pageKey] !== Number(page)) {
          keys[pageKey] = Number(page);
          const baseUrl = window.location.origin + window.location.pathname;
          const newUrl = baseUrl + '?' + pageKey + '=' + page;

          loadPage(newUrl, config, false);
        }
      });
    }
  }

  return {
    initialize: init
  };
})();
Nail
07 ноября 2025, 20:16
0
У меня была проблема с тем, что сделки не попадали в воронку. Хотя в списке контакт создавался. В итоге надо было просто поставить галочку в сист. настройках. Этот компонент работает на старом апи. Сейчас у амо по-другому устроено.