Евгений

Евгений

С нами с 02 июня 2015; Место в рейтинге пользователей: #1358
Евгений
16 ноября 2025, 14:36
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
  };
})();
Евгений
16 октября 2025, 10:42
0
@ВитОс то есть, Ваше предложение — создать одно tv для хранения, а второе для выбора из первого?
Если так, то да, согласен. Я сейчас как раз «откатился» к тому, что создал отдельную техническую страницу, завел там tv для хранения списка значений и «подсасываю» значения в другую тв-шку.
Но я чот прям хочу заморочиться на красоте решения:) И, как будто бы, всё получилось, кроме последнего, самого важного шага — сохранения migx значения путём обращения к migx через js =)

*не могу с уверенностью сказать, сколько значений там будет, так как это полностью будет зависеть от фантазии заказчика, а она у него богатая:)
Евгений
15 октября 2025, 22:19
0
Уважаемый @ВитОс я как раз не хочу усложнять.
Задача казалась простой: сделать аналог мультивыбора: туда можно вбить значение, которого нет в предустановленном списке, и далее оно будет показываться.
Здесь, вместо простого строкового значения я хочу сохранять пару (название+текст) и в дальнейшем иметь возможность выбрать только что добавленный вариант. Путём некоторых размышлений пришёл к такому варианту. Храниться этот список думал в modx_migx_configelements

Если есть вариант проще и нативнее, то я Вас с удовольствием почитаю! подскажете?
Евгений
14 октября 2025, 17:06
0
@Сергей Карпович @Тодор Спасибо Вам огромное! Всё заработало как надо после внесения этих 2х изменений!

Буду также благодарен, если посмотрите на ещё один мой вопрос: modx.pro/help/25368
Евгений
13 октября 2025, 22:15
0
Спасибо, Сергей. Закрадывалась такая мысль, что имена пересекаются. Попробую реализовать.
Евгений
12 октября 2025, 15:20
0
Пробовал создавать отдельный чанк с таким же содержимым (вызов сниппета getImageList). Результат такой же
Евгений
14 ноября 2023, 14:52
0
Все оказалось как нельзя проще в моем случае. Надо было внимательнее прочитать первичный пост и инфу по ссылке modhost.pro/help/email.
Я не добавил префикс ssl в настройки.
Сейчас всё пашет! Спасибо автору топика!
Евгений
14 ноября 2023, 14:31
0
Ребят, всем привет! Внезапно возникла такая же проблема.
МОДХ 2.7.2
PHP FastCGI (Apache) 5.6.36 (alt)
FormIT 4.2.7
AjaxForm 1.2.2

Почта прикручена через Яндекс-домены, раньше все работало чётко, но месяца полтора-два назад письма перестали приходить. Ошибка в логах такая же: «Произошла ошибка при попытке отправить почту. Ошибка соединения с SMTP-сервером github.com/PHPMailer/PHPMailer/wiki/Troubleshooting»

Проверил доступ к почте-«отправщику» и отправку/приёмку писем на неё. Все в порядке.
Также проделал ту же операцию через аутлук — и там все прекрасно.
Обратился даже в службу поддержки Яндекса и мне там сказали, что все настройки корректны.

Я реально в тупике, подскажите, плз, где искать косяк или может решение уже есть готовое у кого, кто сталкивался с этим

Вот код чанка АяксФормы:
[[!AjaxForm?
    &snippet=`FormIt`
    &form=`activitieAjaxForm`
    &hooks=`recaptchav2,email`
    &emailTpl=`activitieReport`
    &emailSubject=`Заявка на запись на занятие "[[*pagetitle]]" в ТикТак`
    &emailTo=`gde-pochta@yandex.ru`
    &emailFrom=`[[++emailsender]]`
    &validate=`name:required, phone:required`
    &validationErrorMessage=`<p class="formMessage">[[%formit.validation_error_message? &namespace=`formit` &topic=`default`]]</p>`
    &successMessage=`Ваша заявка была успешно отправлена`
]]
Евгений
15 июня 2019, 11:59
0
Я так посмотрел, у этого дополнения 323 закачки.
У всех всё в порядке? Отзовитесь:)
Евгений
15 июня 2019, 11:58
0
не, там отключили Discontrol и через импорт поменяли старые/новые цены. Проблема осталась. До сих пор непонятно что делать и откуда растут «ноги» этого задорного поведения плагина.
Разраб модуля пока молчит.
Евгений
14 февраля 2019, 16:10
0
Странно, хук Math до сих пор есть
components/formit/src/FormIt/Hook/Math.php


NOTE: The form fields 'op1', 'op2' and 'operator' are not used anymore from FormIt version 2.2.11 and up.
не совсем понятно, а что ж тогда используется…
Евгений
24 августа 2018, 23:00
0
смотри комментарий от hecby
Евгений
24 августа 2018, 22:59
0
Огромное спасибо! файл удалил, все заработало!
Вот вообще не думал, что до контроллеров в манагере доберутся эти… эм. водолазы =)