import supportDom from '../decorators/supportDom' import { range, noop, toInt } from '../utils' import { $, $$ } from '../utils/dom'
const ACTIVE_CLASSNAME = 'js-active' const DOTS = '⸱⸱⸱'
@supportDom export default class Pagination {
constructor(config) { this.dom = config.dom this.page = config.page || 1 this.total = config.total || 0 this.maxVisiblePage = config.maxVisiblePage || 7 this.currentPageNode = null this.change = config.change || noop this.init() this.addEvents() } init() { const { dom } = this this.ul = $('[data-pagination]', dom) this.prevBtn = $('[data-prev]', dom) this.nextBtn = $('[data-next]', dom) this.input = $('[data-page-input]', dom) this.drawPages() } clearPages() { const { ul } = this $$('[data-page-item]', ul) .concat($$('[data-page-dots]', ul)) .map(item => item.parentNode) .forEach(item => item.remove()) } getLiNode(pageOrDots) { const li = document.createElement('li') li.className = 'page-item' const anchor = document.createElement('a') anchor.className = 'page-link' if (pageOrDots === DOTS) { anchor.setAttribute('data-page-dots', '') anchor.textContent = DOTS } else { const page = pageOrDots anchor.setAttribute('data-page-item', page) anchor.textContent = page } li.appendChild(anchor) return li } setActive(page) { const { currentPageNode } = this if (currentPageNode) { currentPageNode.classList.remove(ACTIVE_CLASSNAME) } const anchor = $(`[data-page-item="${page}"]`) if (! anchor) { return } anchor.classList.add(ACTIVE_CLASSNAME) this.currentPageNode = anchor this.page = page } insertPageLi(li) { this.ul.insertBefore(li, this.nextBtn.parentNode) } hideInput() { this.input.parentNode.style.display = 'none' } showInput() { this.input.parentNode.style.display = 'flex' } drawRegularPages(currentPage = this.page) { this.hideInput() range(1, this.total + 1) .forEach(page => { const li = this.getLiNode(page) this.insertPageLi(li) }) this.setActive(currentPage) } drawPagesWithInput(page = this.page) { this.clearPages() this.showInput() const { total } = this const beforeLast = total - 1 const isFirst = (page === 1) const isSecond = (page === 2) const isBeforeLast = (page === beforeLast) const isLast = (page === total) // Consider the five cases below: // [1] 2 ... 9 10 // 1 [2] 3 ... 10 // 1 ... [5] ... 10 // 1 ... 8 [9] 10 // 1 2 ... 9 [10] const firstLi = this.getLiNode(1) this.insertPageLi(firstLi) const secondText = ((page <= 3) || isLast) ? 2 : DOTS const secondLi = this.getLiNode(secondText) this.insertPageLi(secondLi) const thirdText = (function(p) { if (isFirst || isLast) { return DOTS } if (isSecond) { return 3 } if (isBeforeLast) { return beforeLast - 1 } return p })(page) const thirdLi = this.getLiNode(thirdText) this.insertPageLi(thirdLi) const fourthText = (function(p) { if (isFirst || isBeforeLast || isLast) { return beforeLast } if (p === (beforeLast - 1)) { return beforeLast } return DOTS })(page) const fourthLi = this.getLiNode(fourthText) this.insertPageLi(fourthLi) const lastLi = this.getLiNode(total) this.insertPageLi(lastLi) this.setActive(page) } isInputView() { const { total } = this return ((total > this.maxVisiblePage) && (total > 5)) } drawPages() { this.clearPages() if (this.isInputView()) { this.drawPagesWithInput() } else { this.drawRegularPages() } } isPrevBtn(target) { return ('prev' in target.dataset) } isNextBtn(target) { return ('next' in target.dataset) } isPageBtn(target) { return ('pageItem' in target.dataset) } isDotBtn(target) { return ('pageDots' in target.dataset) } handlePageClick(target) { const page = toInt(target.dataset.pageItem) if (page === this.page) { return } this.setActive(page) this.change(page) } handleDotBtnClick() { this.input.focus() } setActiveAndChangeInputViewIfNeeded(page) { if (this.isInputView()) { return this.drawPagesWithInput(page) } this.setActive(page) } isValidPage(page) { return (! isNaN(page)) && (page >= 1) && (page <= this.total) } setInputDanger() { this.input.classList.add('is-invalid') } setInputNormal() { this.input.classList.remove('is-invalid') } addEvents() { this.addEvent(this.ul, 'click', event => { let { target } = event if (target.tagName === 'I') { target = target.parentNode } const { page } = this if (this.isNextBtn(target) && (page < this.total)) { const nextPage = page + 1 this.setActiveAndChangeInputViewIfNeeded(nextPage) this.change(nextPage) } else if (this.isPageBtn(target)) { this.handlePageClick(target) } else if (this.isPrevBtn(target) && (page > 1)) { const prevPage = page - 1 this.setActiveAndChangeInputViewIfNeeded(prevPage) this.change(prevPage) } else if (this.isDotBtn(target)) { this.handleDotBtnClick() } }) this.addEvent(this.input, 'change', event => { const page = toInt(event.target.value) if (! this.isValidPage(page)) { return this.setInputDanger() } if (page === this.page) { return } this.setInputNormal() this.setActiveAndChangeInputViewIfNeeded(page) this.change(page) }) } setTotal(total) { this.total = total this.drawPages() } destroy() { this.clearPages() }
}