UNPKG

lu2

Version:

Simple and flexible UI component library based on native HTML and JavaScript

452 lines (394 loc) 15.2 kB
/** * @Pagination.js * @author XboxYan(yanwenbin) * @version * @Created: 20-04-22 * @edit: 20-04-22 */ class UiPagination extends HTMLElement { static get observedAttributes () { return ['per', 'total', 'current', 'loading']; } constructor ({per, total, current, loading, href, container = null} = {}) { super(); const isLink = href || this.href; const el = isLink ? 'a' : 'button'; if (per) { this.per = per; } if (total) { this.total = total; } if (current) { this.setAttribute('current', current); } this.loading = loading; this.innerHTML = `<fieldset class="ui-page-wrap"> <${el} class="ui-page-item ui-page-prev"> <svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="200" height="200" viewBox="0 0 200 200"><path d="M85.876,100.5l49.537-50.526c4.089-4.215,4.089-11.049,0-15.262 c-4.089-4.218-10.719-4.218-14.808,0L63.586,92.868c-4.089,4.215-4.089,11.049,0,15.264l57.018,58.156 c4.089,4.215,10.719,4.215,14.808,0c4.089-4.215,4.089-11.049,0-15.262L85.876,100.5z"></path></svg> </${el}> <div class="ui-page"></div> <${el} class="ui-page-item ui-page-next"> <svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="200" height="200" viewBox="0 0 200 200"><path d="M85.876,100.5l49.537-50.526c4.089-4.215,4.089-11.049,0-15.262 c-4.089-4.218-10.719-4.218-14.808,0L63.586,92.868c-4.089,4.215-4.089,11.049,0,15.264l57.018,58.156 c4.089,4.215,10.719,4.215,14.808,0c4.089-4.215,4.089-11.049,0-15.262L85.876,100.5z"></path></svg> </${el}> </fieldset>`; // trigger元素的获取 this.element = new Proxy(this.element || {}, { get: (target, prop) => { if (prop == 'trigger') { return this.htmlFor && document.getElementById(this.htmlFor); } return target[prop]; }, set (target, prop, value) { // 赋值 target[prop] = value; return true; } }); // 如果存在容器,就append进去 if (typeof container == 'string') { container = document.querySelector(container); } if (container) { container.append(this); } } get per () { return Number(this.getAttribute('per')) || 15; } get simple () { return this.getAttribute('mode') === 'short'; } get total () { return Number(this.getAttribute('total')) || 0; } get current () { return Number(this.getAttribute('current')) || 1; } get loading () { return this.getAttribute('loading') !== null; } get href () { //?page=1 return this.getAttribute('href'); } set current (value) { this.setAttribute('current', Math.min(Math.max(1, value), this.count)); } set per (value) { this.setAttribute('per', value); } set total (value) { this.setAttribute('total', value); } set loading (value) { if (!value) { this.removeAttribute('loading'); } else { this.setAttribute('loading', ''); } } get htmlFor () { return this.getAttribute('for'); } set htmlFor (value) { this.setAttribute('for', value); } // 渲染方法 render (per, total) { let elePage = this.element.page; if (!elePage) { return; } // 如果是链接,使用 <a> 元素 // 否则,使用 <button> 元素 const item = this.href ? 'a' : 'button'; this.count = Math.ceil(total / per) || 1; const current = Math.min(Math.max(1, this.current), this.count); // 如果是简单分页模式 if (this.simple) { elePage.innerHTML = `<div class="ui-page-simple ui-page-item" >${current} / ${this.count}</div>`; } else { // 生成一个长度为count,且最大长度为10的数组 [undefined,undefined,undefined,undefined,undefined,...] const arr = Array.from({length: this.count}).splice(0, 7); // 将数组映射成每一页的节点 const html = arr.map((el, index) => { return `<${item} class="ui-page-item" data-current="${index + 1}" aria-label="第${index + 1}页,共${this.count}页">${index + 1}</${item}>`; }).join(''); elePage.innerHTML = html; } // 存在同时改变多个自定义元素属性的情况,此时应该执行一次 clearTimeout(this.timerRender); this.timerRender = setTimeout(() => { this.updatePage(current); }); } updatePage (current = this.current) { // 元素获取 let elePage = this.element.page; let elePrev = this.element.prev; let eleNext = this.element.next; if (current == 1) { elePrev.setAttribute('disabled', true); elePrev.setAttribute('aria-label', '已经是第一页了'); elePrev.removeAttribute('href'); } else { elePrev.removeAttribute('disabled'); elePrev.setAttribute('aria-label', `上一页,当前第${current}页`); elePrev.href = this.href ? this.href.replace(/\${current}/g, current - 1) : 'javascript:;'; } if (current == this.count) { eleNext.setAttribute('disabled', true); eleNext.setAttribute('aria-label', '已经是最后一页了'); eleNext.removeAttribute('href'); } else { eleNext.removeAttribute('disabled'); eleNext.setAttribute('aria-label', `下一页,当前第${current}页`); eleNext.href = this.href ? this.href.replace(/\${current}/g, current + 1) : 'javascript:;'; } if (this.simple) { elePage.querySelector('.ui-page-simple').textContent = current + ' / ' + this.count; } else if (this.count > 7) { let place = []; switch (current) { case 1: case 2: case 3: case 4: place = [1, 2, 3, 4, 5, 'next', this.count]; break; case this.count: case this.count - 1: case this.count - 2: case this.count - 3: place = [1, 'pre', this.count - 4, this.count - 3, this.count - 2, this.count - 1, this.count]; break; default: place = [1, 'pre', current - 1, current, current + 1, 'next', this.count]; break; } elePage.querySelectorAll('.ui-page-item').forEach((el, i) => { if (typeof place[i] === 'number') { el.dataset.current = place[i]; el.textContent = place[i]; el.disabled = false; el.href = 'javascript:;'; if (place[i] == current) { el.setAttribute('current', ''); } else { el.removeAttribute('current'); } el.removeAttribute('disabled'); el.setAttribute('aria-label', `第${place[i]}页,共${this.count}页`); if (this.href) { el.href = this.href.replace(/\${current}/g, el.dataset.current); } } else { el.textContent = '...'; el.removeAttribute('current'); el.removeAttribute('data-current'); el.removeAttribute('aria-label'); el.setAttribute('disabled', true); el.removeAttribute('href'); } }); } else { elePage.querySelectorAll('.ui-page-item').forEach((el) => { if (el.dataset.current == current) { el.setAttribute('current', ''); } else { el.removeAttribute('current'); } if (this.href) { el.href = this.href.replace(/\${current}/g, el.dataset.current); } }); } } connectedCallback () { // 一些元素内容 let eleWrap = this.querySelector('fieldset'); if (!eleWrap) { return; } // 前后按钮和分页主体元素 let elePage = eleWrap.querySelector('.ui-page'); let elePrev = eleWrap.firstElementChild; let eleNext = eleWrap.lastElementChild; // 全局暴露 this.element.wrap = eleWrap; this.element.page = elePage; this.element.prev = elePrev; this.element.next = eleNext; // 开启渲染 this.render(this.per, this.total); // 事件的处理 elePage.addEventListener('click', (ev) => { const item = ev.target.closest('.ui-page-item'); if (item) { this.nativeClick = true; this.current = Number(item.dataset.current); } }); elePrev.addEventListener('click', () => { this.nativeClick = true; this.current--; }); eleNext.addEventListener('click', () => { this.nativeClick = true; this.current++; }); // 全局事件 this.dispatchEvent(new CustomEvent('connected', { detail: { type: 'ui-pagination' } })); this.isConnectedCallback = true; // 分页内容准备完毕 this.dispatchEvent(new CustomEvent('DOMContentLoaded')); } attributeChangedCallback (name, oldValue, newValue) { if (!this.element || oldValue === newValue) { return; } let eleTrigger = this.element.trigger; let eleWrap = this.element.wrap; if (name == 'per') { this.render(newValue, this.total); // 普通元素分页数据per数据同步 if (eleTrigger) { eleTrigger.dataset.per = newValue; } } else if (name == 'total') { this.render(this.per, newValue); // 普通元素分页数据total数据同步 if (eleTrigger) { eleTrigger.dataset.total = newValue; } } else if (name == 'loading') { eleWrap.disabled = newValue !== null; // 普通元素分页数据loading状态同步 if (eleTrigger) { eleTrigger.dataset.loading = newValue !== null; } } else if (name == 'current' && oldValue !== newValue) { // 一定程度上避免冗余的渲染 clearTimeout(this.timerRender); this.timerRender = setTimeout(() => { this.updatePage(Number(newValue)); }); // 普通元素分页数据信息的实时同步 if (eleTrigger) { eleTrigger.dataset.current = newValue; } if (this.nativeClick) { this.nativeClick = false; this.dispatchEvent(new CustomEvent('change', { detail: { current: Number(newValue), per: this.per, total: this.total } })); if (eleTrigger && eleTrigger != this) { eleTrigger.dispatchEvent(new CustomEvent('change', { detail: { current: Number(newValue), per: this.per, total: this.total } })); } } } } } if (!customElements.get('ui-pagination')) { customElements.define('ui-pagination', UiPagination); } window.Pagination = UiPagination; // 给 HTML 元素扩展 pagination 方法 HTMLElement.prototype.pagination = function (options) { if (this.matches('ui-pagination') || this['ui-pagination']) { return this; } const { total = 0, current = 1, per = 15, href = null, loading = false } = this.dataset; let objParams = Object.assign({}, { per, total, href, loading }, options || {}); const pagination = new Pagination(objParams); const strId = this.id || ('lulu_' + Math.random()).replace('0.', ''); this.innerHTML = ''; this.id = strId; this['ui-pagination'] = pagination; pagination.htmlFor = strId; pagination.setAttribute('current', current); // 删除自定义元素,隐藏不必要的细节 pagination.addEventListener('connected', () => { // 所有普通元素也触发 connected 生命周期事件 this.dispatchEvent(new CustomEvent('connected', { detail: { type: 'ui-pagination' } })); // 原分页从页面中删除 pagination.remove(); // DOM 执行完毕 this.dispatchEvent(new CustomEvent('DOMContentLoaded')); }); document.body.append(pagination); this.append.apply(this, [...pagination.children]); // 设置定义完毕标志量 this.setAttribute('defined', ''); return this; }; (function () { const initAllPagination = (ele) => { const elePaginations = ele || document.querySelectorAll('[is-pagination]'); elePaginations.forEach(item => { item.pagination(); }); }; /** * 初始化并监听页面包含is-pagination属性的DOM节点变化 */ const autoInitAndWatchingIsPaginationAttr = () => { // 先实例化已有is-pagination属性的DOM节点,再监听后续的节点变化 initAllPagination(); const observer = new MutationObserver(mutationsList => { mutationsList.forEach(mutation => { mutation.addedNodes && mutation.addedNodes.forEach(eleAdd => { if (!eleAdd.tagName) { return; } if (eleAdd.hasAttribute('is-pagination')) { initAllPagination([eleAdd]); } else { initAllPagination(eleAdd.querySelectorAll('[is-pagination]')); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); }; if (document.readyState != 'loading') { autoInitAndWatchingIsPaginationAttr(); } else { window.addEventListener('DOMContentLoaded', autoInitAndWatchingIsPaginationAttr); } })(); /**/export default UiPagination;