PdoPage без jQuery

Давно делал костылями пагинацию без jQuery
недавно дошли руки поковырять PdoPage

Скажу сразу в скрипте могут быть баги
У меня с ресурсами работает отлично
Замените assets/components/pdotools/js/pdopage.min.js
Стартый лучше на всякий случай перименуйте пусть будет на такой
if (typeof(pdoPage) == 'undefined') {
    pdoPage = {callbacks: {}, keys: {}, configs: {}};
}

pdoPage.Reached = false;

pdoPage.initialize = function (config) {
    if (pdoPage.keys[config['pageVarKey']] == undefined) {
        var tkey = config['pageVarKey'];
        var tparams = pdoPage.Hash.get();
        var tpage = tparams[tkey] == undefined ? 1 : tparams[tkey];
        pdoPage.keys[tkey] = Number(tpage);
        pdoPage.configs[tkey] = config;
    }

    switch (config['mode']) {
        case 'default':
            document.addEventListener('click', function(e) {
                // Используем closest для поиска радителя
                var target = e.target.closest(config['link']);
                if (target) {
                    e.preventDefault();
                    
                    var href = target.getAttribute('href');
                    var key = config['pageVarKey'];
                    
                    if (!href) {
                        console.error('href is undefined');
                        return;
                    }
                    
                    var match = href.match(new RegExp(key + '=(\\d+)'));
                    var page = !match ? 1 : match[1];
                    
                    if (pdoPage.keys[key] != page) {
                        if (config.history) {
                            if (page == 1) {
                                pdoPage.Hash.remove(key);
                            } else {
                                pdoPage.Hash.add(key, page);
                            }
                        }
                        pdoPage.loadPage(href, config);
                    }
                }
            });

            if (config.history) {
                window.addEventListener('popstate', function (e) {
                    if (e.state && e.state['pdoPage']) {
                        pdoPage.loadPage(e.state['pdoPage'], config);
                    }
                });

                history.replaceState({pdoPage: window.location.href}, '');
            }
            break;

        case 'scroll':
        case 'button':
            if (config.history) {
                console.warn('Sticky pagination requires jQuery in this version');
            } else {
                var pagination = document.querySelector(config.pagination);
                if (pagination) pagination.style.display = 'none';
            }

            var key = config['pageVarKey'];

            if (config['mode'] == 'button') {
                // Добавляем кноку
                var rows = document.querySelector(config['rows']);
                if (rows && config['moreTpl']) {
                    rows.insertAdjacentHTML('afterend', config['moreTpl']);
                }
                
                var has_results = false;
                var links = document.querySelectorAll(config['link']);
                for (var i = 0; i < links.length; i++) {
                    var href = links[i].getAttribute('href');
                    var match = href.match(new RegExp(key + '=(\\d+)'));
                    var page = !match ? 1 : match[1];
                    if (page > pdoPage.keys[key]) {
                        has_results = true;
                        break;
                    }
                }
                
                var moreBtn = document.querySelector(config['more']);
                if (moreBtn && !has_results) {
                    moreBtn.style.display = 'none';
                }

                document.addEventListener('click', function(e) {
                    if (e.target.matches(config['more'])) {
                        e.preventDefault();
                        pdoPage.addPage(config);
                    }
                });
            } else {
                // Скрол
                var wrapper = document.querySelector(config['wrapper']);
                window.addEventListener('scroll', function () {
                    if (!pdoPage.Reached && wrapper) {
                        var scrollTop = window.pageYOffset || document.documentElement.scrollTop;
                        var windowHeight = window.innerHeight;
                        var wrapperHeight = wrapper.offsetHeight;
                        var wrapperTop = wrapper.getBoundingClientRect().top + scrollTop;
                        
                        if (scrollTop + windowHeight >= wrapperTop + wrapperHeight - 100) {
                            pdoPage.Reached = true;
                            pdoPage.addPage(config);
                        }
                    }
                });
            }
            break;
    }
};

pdoPage.addPage = function (config) {
    var key = config['pageVarKey'];
    var current = pdoPage.keys[key] || 1;
    var links = document.querySelectorAll(config['link']);
    
    for (var i = 0; i < links.length; i++) {
        var href = links[i].getAttribute('href');
        var match = href.match(new RegExp(key + '=(\\d+)'));
        var page = !match ? 1 : Number(match[1]);
        if (page > current) {
            if (config.history) {
                if (page == 1) {
                    pdoPage.Hash.remove(key);
                } else {
                    pdoPage.Hash.add(key, page);
                }
            }
            pdoPage.loadPage(href, config, 'append');
            break;
        }
    }
};

pdoPage.loadPage = function (href, config, mode) {
    var wrapper = document.querySelector(config['wrapper']);
    var rows = document.querySelector(config['rows']);
    var pagination = document.querySelector(config['pagination']);
    var key = config['pageVarKey'];
    var match = href.match(new RegExp(key + '=(\\d+)'));
    var page = !match ? 1 : Number(match[1]);
    if (!mode) {
        mode = 'replace';
    }

    if (pdoPage.keys[key] == page) {
        return;
    }
    
    // Блокируем если множественные запросы
    if (wrapper && wrapper.classList.contains('loading')) {
        return;
    }
    
    if (pdoPage.callbacks['before'] && typeof(pdoPage.callbacks['before']) == 'function') {
        pdoPage.callbacks['before'].apply(this, [config]);
    } else {
        if (config['mode'] != 'scroll' && wrapper) {
            wrapper.style.opacity = '0.3';
        }
        if (wrapper) wrapper.classList.add('loading');
    }

    var params = pdoPage.Hash.get();
    for (var i in params) {
        if (params.hasOwnProperty(i) && pdoPage.keys[i] && i != key) {
            delete(params[i]);
        }
    }
    params[key] = pdoPage.keys[key] = page;
    params['pageId'] = config['pageId'];
    params['hash'] = config['hash'];

    // Формируем данные
    var formData = new URLSearchParams();
    for (var param in params) {
        if (params.hasOwnProperty(param)) {
            formData.append(param, params[param]);
        }
    }
    
    fetch(config['connectorUrl'], {
        method: 'POST',
        headers: {
            'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
            'X-Requested-With': 'XMLHttpRequest'
        },
        body: formData
    })
    .then(response => {

        if (!response.ok) {
            throw new Error('HTTP error! status: ' + response.status);
        }
        
        return response.text().then(text => {

            if (!text || text.trim() === '') {
                throw new Error('Empty response from server');
            }
            
            try {
                return JSON.parse(text);
            } catch (e) {
                throw new Error('Invalid JSON response: ' + e.message);
            }
        });
    })
    .then(response => {
        console.log('Parsed response:', response);
        
        if (!response || typeof response !== 'object') {
            throw new Error('Invalid response format');
        }
        
        if (response.success === false) {
            throw new Error('Server returned error: ' + (response.message || 'Unknown error'));
        }

        if (pagination && response['pagination'] !== undefined) {
            pagination.innerHTML = response['pagination'];
        }
        
        if (mode == 'append') {
            if (rows && response['output'] !== undefined) {
                rows.insertAdjacentHTML('beforeend', response['output']);
            }
            if (config['mode'] == 'button') {
                var moreBtn = document.querySelector(config['more']);
                if (moreBtn) {
                    if (response['pages'] == response['page']) {
                        moreBtn.style.display = 'none';
                    } else {
                        moreBtn.style.display = '';
                    }
                }
            } else if (config['mode'] == 'scroll') {
                pdoPage.Reached = false;
            }
        } else {
            if (rows && response['output'] !== undefined) {
                rows.innerHTML = response['output'];
            }
        }

        if (pdoPage.callbacks['after'] && typeof(pdoPage.callbacks['after']) == 'function') {
            pdoPage.callbacks['after'].apply(this, [config, response]);
        } else {
            if (wrapper) {
                wrapper.classList.remove('loading');
                if (config['mode'] != 'scroll') {
                    wrapper.style.opacity = '1';
                    if (config['mode'] == 'default' && config['scrollTop'] !== false) {
                        var wrapperPosition = wrapper.getBoundingClientRect().top + window.pageYOffset;
                        window.scrollTo(0, (wrapperPosition - 50) || 0);
                    }
                }
            }
        }
        
        pdoPage.updateTitle(config, response);
        
        // Trigger
        var event = new CustomEvent('pdopage_load', {
            detail: [config, response]
        });
        document.dispatchEvent(event);
    })
    .catch(error => {
        console.error('Error loading page:', error);
        
        // Показываем если есть ошибки
        if (wrapper) {
            var errorMsg = document.createElement('div');
            errorMsg.className = 'pdoPage-error';
            errorMsg.style.cssText = 'background: #ffebee; color: #c62828; padding: 10px; margin: 10px 0; border: 1px solid #ffcdd2; border-radius: 4px;';
            errorMsg.textContent = 'Ошибка загрузки страницы: ' + error.message;
            
            if (rows) {
                rows.appendChild(errorMsg);
            }
            
            wrapper.classList.remove('loading');
            wrapper.style.opacity = '1';
        }
        
        // Fallback через 2 секунды
        setTimeout(function() {
            console.log('Fallback to regular page load:', href);
            window.location.href = href;
        }, 2000);
    });
};

pdoPage.stickyPagination = function (config) {
    console.warn('Sticky pagination requires jQuery');
};

pdoPage.updateTitle = function (config, response) {
    if (typeof(pdoTitle) == 'undefined') {
        return;
    }
    var title = document.querySelector('title');
    if (!title) return;
    
    var separator = pdoTitle.separator || ' / ';
    var tpl = pdoTitle.tpl;

    var newTitle = [];
    var items = title.textContent.split(separator);
    var pcre = new RegExp('^' + tpl.split(' ')[0] + ' ');
    for (var i = 0; i < items.length; i++) {
        if (i === 1 && response.page && response.page > 1) {
            newTitle.push(tpl.replace('{page}', response.page).replace('{pageCount}', response.pages));
        }
        if (!items[i].match(pcre)) {
            newTitle.push(items[i]);
        }
    }
    title.textContent = newTitle.join(separator);
};

pdoPage.Hash = {
    get: function () {
        var vars = {}, hash, splitter, hashes;
        if (!this.oldbrowser()) {
            var pos = window.location.href.indexOf('?');
            hashes = (pos != -1) ? decodeURIComponent(window.location.href.substr(pos + 1)).replace('+', ' ') : '';
            splitter = '&';
        } else {
            hashes = decodeURIComponent(window.location.hash.substr(1)).replace('+', ' ');
            splitter = '/';
        }

        if (hashes.length == 0) {
            return vars;
        } else {
            hashes = hashes.split(splitter);
        }

        var matches, key;
        for (var i in hashes) {
            if (hashes.hasOwnProperty(i)) {
                hash = hashes[i].split('=');
                if (typeof hash[1] == 'undefined') {
                    vars['anchor'] = hash[0];
                } else {
                    matches = hash[0].match(/\[(.*?|)\]$/);
                    if (matches) {
                        key = hash[0].replace(matches[0], '');
                        if (!vars.hasOwnProperty(key)) {
                            if (matches[1] == '') {
                                vars[key] = [];
                            } else {
                                vars[key] = {};
                            }
                        }
                        if (vars[key] instanceof Array) {
                            vars[key].push(hash[1]);
                        } else {
                            vars[key][matches[1]] = hash[1];
                        }
                    } else {
                        vars[hash[0]] = hash[1];
                    }
                }
            }
        }
        return vars;
    },

    set: function (vars) {
        var hash = '';
        for (var i in vars) {
            if (vars.hasOwnProperty(i)) {
                if (typeof vars[i] == 'object') {
                    for (var j in vars[i]) {
                        if (vars[i].hasOwnProperty(j)) {
                            if (vars[i] instanceof Array) {
                                hash += '&' + i + '[]=' + vars[i][j];
                            } else {
                                hash += '&' + i + '[' + j + ']=' + vars[i][j];
                            }
                        }
                    }
                } else {
                    hash += '&' + i + '=' + vars[i];
                }
            }
        }

        if (!this.oldbrowser()) {
            if (hash.length != 0) {
                hash = '?' + hash.substr(1);
            }
            window.history.pushState({pdoPage: document.location.pathname + hash}, '', document.location.pathname + hash);
        } else {
            window.location.hash = hash.substr(1);
        }
    },

    add: function (key, val) {
        var hash = this.get();
        hash[key] = val;
        this.set(hash);
    },

    remove: function (key) {
        var hash = this.get();
        delete hash[key];
        this.set(hash);
    },

    clear: function () {
        this.set({});
    },

    oldbrowser: function () {
        return !(window.history && history.pushState);
    }
};

if (typeof(document.querySelector) == 'undefined') {
    console.log("This browser doesn't support querySelector which is required for pdoPage.");
}
Если найдете косяки в коде напишите сюда
ВитОс
01 октября 2025, 18:25
modx.pro
2
574
+8

Комментарии: 5

Артур Шевченко
01 октября 2025, 18:43
0
Плюс тебе за старания, но почему же ты не сделал публичный репозиторий для кода? Ну в целом в SendIt тоже есть пагинация и нет jQuery)))
    ВитОс
    01 октября 2025, 23:15
    0
    да по хорошему нужно весь PdoTools подправить
    тут писали давно что хотят обновить и тишина
    Думаю заняться если Василий даст добро
    Будет у меня первая такая штука
      Василий Наумкин
      02 октября 2025, 05:10
      +3
      Даю добро!

      Правда, меня не нужно спрашивать, потому что всеми старыми дополнениями управляет сообщество уже года 4 как.
        ВитОс
        03 октября 2025, 10:25
        0
        А как обновить его. Просто тут выложить?
        На GitHub давно новых релизов не было
          Артур Шевченко
          03 октября 2025, 14:35
          0
          Сделать форк нужного репозитория. Внести правки. Сделать PR в основной репозиторий.
    Авторизуйтесь или зарегистрируйтесь, чтобы оставлять комментарии.
    5