vue-paginate
Version:
A simple vue.js plugin to paginate data
595 lines (549 loc) • 17.4 kB
JavaScript
/**
* vue-paginate v3.6.0
* (c) 2018 Taha Shashtari
* @license MIT
*/
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
typeof define === 'function' && define.amd ? define(factory) :
(global.VuePaginate = factory());
}(this, function () { 'use strict';
var warn = function () {}
var formatComponentName
var hasConsole = typeof console !== 'undefined'
warn = function (msg, vm, type) {
if ( type === void 0 ) type = 'error';
if (hasConsole) {
console[type]("[vue-paginate]: " + msg + " " + (
vm ? formatLocation(formatComponentName(vm)) : ''
))
}
}
formatComponentName = function (vm) {
if (vm.$root === vm) {
return 'root instance'
}
var name = vm._isVue
? vm.$options.name || vm.$options._componentTag
: vm.name
return (
(name ? ("component <" + name + ">") : "anonymous component") +
(vm._isVue && vm.$options.__file ? (" at " + (vm.$options.__file)) : '')
)
}
var formatLocation = function (str) {
if (str === 'anonymous component') {
str += " - use the \"name\" option for better debugging messages."
}
return ("\n(found in " + str + ")")
}
var Paginate = {
name: 'paginate',
props: {
name: {
type: String,
required: true
},
list: {
type: Array,
required: true
},
per: {
type: Number,
default: 3,
validator: function validator (value) {
return value > 0
}
},
tag: {
type: String,
default: 'ul'
},
container: {
type: Object,
default: null
}
},
data: function data () {
return {
initialListSize: this.list.length
}
},
computed: {
parent: function parent () {
return this.container ? this.container : this.$parent
},
currentPage: {
get: function get () {
if (this.parent.paginate[this.name]) {
return this.parent.paginate[this.name].page
}
},
set: function set (page) {
this.parent.paginate[this.name].page = page
}
},
pageItemsCount: function pageItemsCount () {
var numOfItems = this.list.length
var first = this.currentPage * this.per + 1
var last = Math.min((this.currentPage * this.per) + this.per, numOfItems)
return (first + "-" + last + " of " + numOfItems)
},
lastPage: function lastPage () {
return Math.ceil(this.list.length / this.per)
}
},
mounted: function mounted () {
if (this.per <= 0) {
warn(("<paginate name=\"" + (this.name) + "\"> 'per' prop can't be 0 or less."), this.parent)
}
if (!this.parent.paginate[this.name]) {
warn(("'" + (this.name) + "' is not registered in 'paginate' array."), this.parent)
return
}
this.paginateList()
},
watch: {
currentPage: function currentPage () {
this.paginateList()
},
list: function list () {
if (this.currentPage >= this.lastPage) {
this.currentPage = this.lastPage - 1
}
this.paginateList()
},
per: function per () {
this.currentPage = 0
this.paginateList()
}
},
methods: {
paginateList: function paginateList () {
var index = this.currentPage * this.per
var paginatedList = this.list.slice(index, index + this.per)
this.parent.paginate[this.name].list = paginatedList
},
goToPage: function goToPage (page) {
var lastPage = Math.ceil(this.list.length / this.per)
if (page > lastPage) {
warn(("You cannot go to page " + page + ". The last page is " + lastPage + "."), this.parent)
return
}
this.currentPage = page - 1
}
},
render: function render (h) {
return h(this.tag, {}, this.$slots.default)
}
}
var LEFT_ARROW = '«'
var RIGHT_ARROW = '»'
var ELLIPSES = '…'
var LimitedLinksGenerator = function LimitedLinksGenerator (listOfPages, currentPage, limit) {
this.listOfPages = listOfPages
this.lastPage = listOfPages.length - 1
this.currentPage = currentPage === this.lastPage
? this.lastPage - 1
: currentPage
this.limit = limit
};
LimitedLinksGenerator.prototype.generate = function generate () {
var firstHalf = this._buildFirstHalf()
var secondHalf = this._buildSecondHalf()
return firstHalf.concat( secondHalf)
};
LimitedLinksGenerator.prototype._buildFirstHalf = function _buildFirstHalf () {
var firstHalf = this._allPagesButLast()
.slice(
this._currentChunkIndex(),
this._currentChunkIndex() + this.limit
)
// Add backward ellipses with first page if needed
if (this.currentPage >= this.limit) {
firstHalf.unshift(ELLIPSES)
firstHalf.unshift(0)
}
// Add ellipses if needed
if (this.lastPage - this.limit > this._currentChunkIndex()) {
firstHalf.push(ELLIPSES)
}
return firstHalf
};
LimitedLinksGenerator.prototype._buildSecondHalf = function _buildSecondHalf () {
var secondHalf = [this.lastPage]
return secondHalf
};
LimitedLinksGenerator.prototype._currentChunkIndex = function _currentChunkIndex () {
var currentChunk = Math.floor(this.currentPage / this.limit)
return currentChunk * this.limit
};
LimitedLinksGenerator.prototype._allPagesButLast = function _allPagesButLast () {
var this$1 = this;
return this.listOfPages.filter(function (n) { return n !== this$1.lastPage; })
};
var PaginateLinks = {
name: 'paginate-links',
props: {
for: {
type: String,
required: true
},
limit: {
type: Number,
default: 0
},
simple: {
type: Object,
default: null,
validator: function validator (obj) {
return obj.prev && obj.next
}
},
stepLinks: {
type: Object,
default: function () {
return {
prev: LEFT_ARROW,
next: RIGHT_ARROW
}
},
validator: function validator$1 (obj) {
return obj.prev && obj.next
}
},
showStepLinks: {
type: Boolean
},
hideSinglePage: {
type: Boolean
},
classes: {
type: Object,
default: null
},
async: {
type: Boolean,
default: false
},
container: {
type: Object,
default: null
}
},
data: function data () {
return {
listOfPages: [],
numberOfPages: 0,
target: null
}
},
computed: {
parent: function parent () {
return this.container ? this.container.el : this.$parent
},
state: function state () {
return this.container ? this.container.state : this.$parent.paginate[this.for]
},
currentPage: {
get: function get () {
if (this.state) {
return this.state.page
}
},
set: function set (page) {
this.state.page = page
}
}
},
mounted: function mounted () {
var this$1 = this;
if (this.simple && this.limit) {
warn(("<paginate-links for=\"" + (this.for) + "\"> 'simple' and 'limit' props can't be used at the same time. In this case, 'simple' will take precedence, and 'limit' will be ignored."), this.parent, 'warn')
}
if (this.simple && !this.simple.next) {
warn(("<paginate-links for=\"" + (this.for) + "\"> 'simple' prop doesn't contain 'next' value."), this.parent)
}
if (this.simple && !this.simple.prev) {
warn(("<paginate-links for=\"" + (this.for) + "\"> 'simple' prop doesn't contain 'prev' value."), this.parent)
}
if (this.stepLinks && !this.stepLinks.next) {
warn(("<paginate-links for=\"" + (this.for) + "\"> 'step-links' prop doesn't contain 'next' value."), this.parent)
}
if (this.stepLinks && !this.stepLinks.prev) {
warn(("<paginate-links for=\"" + (this.for) + "\"> 'step-links' prop doesn't contain 'prev' value."), this.parent)
}
this.$nextTick(function () {
this$1.updateListOfPages()
})
},
watch: {
'state': {
handler: function handler () {
this.updateListOfPages()
},
deep: true
},
currentPage: function currentPage (toPage, fromPage) {
this.$emit('change', toPage + 1, fromPage + 1)
}
},
methods: {
updateListOfPages: function updateListOfPages () {
this.target = getTargetPaginateComponent(this.parent.$children, this.for)
if (!this.target) {
if (this.async) { return }
warn(("<paginate-links for=\"" + (this.for) + "\"> can't be used without its companion <paginate name=\"" + (this.for) + "\">"), this.parent)
warn("To fix that issue you may need to use :async=\"true\" on <paginate-links> component to allow for asyncronous rendering", this.parent, 'warn')
return
}
this.numberOfPages = Math.ceil(this.target.list.length / this.target.per)
this.listOfPages = getListOfPageNumbers(this.numberOfPages)
}
},
render: function render (h) {
var this$1 = this;
if (!this.target && this.async) { return null }
var links = this.simple
? getSimpleLinks(this, h)
: this.limit > 1
? getLimitedLinks(this, h)
: getFullLinks(this, h)
if (this.hideSinglePage && this.numberOfPages <= 1) {
return null
}
var el = h('ul', {
class: ['paginate-links', this.for]
}, links)
if (this.classes) {
this.$nextTick(function () {
addAdditionalClasses(el.elm, this$1.classes)
})
}
return el
}
}
function getFullLinks (vm, h) {
var allLinks = vm.showStepLinks
? [vm.stepLinks.prev ].concat( vm.listOfPages, [vm.stepLinks.next])
: vm.listOfPages
return allLinks.map(function (link) {
var data = {
on: {
click: function (e) {
e.preventDefault()
vm.currentPage = getTargetPageForLink(
link,
vm.limit,
vm.currentPage,
vm.listOfPages,
vm.stepLinks
)
}
}
}
var liClasses = getClassesForLink(
link,
vm.currentPage,
vm.listOfPages.length - 1,
vm.stepLinks
)
var linkText = link === vm.stepLinks.next || link === vm.stepLinks.prev
? link
: link + 1 // it means it's a number
return h('li', { class: liClasses }, [h('a', data, linkText)])
})
}
function getLimitedLinks (vm, h) {
var limitedLinks = new LimitedLinksGenerator(
vm.listOfPages,
vm.currentPage,
vm.limit,
vm.stepLinks
).generate()
limitedLinks = vm.showStepLinks
? [vm.stepLinks.prev ].concat( limitedLinks, [vm.stepLinks.next])
: limitedLinks
var limitedLinksMetadata = getLimitedLinksMetadata(limitedLinks)
return limitedLinks.map(function (link, index) {
var data = {
on: {
click: function (e) {
e.preventDefault()
vm.currentPage = getTargetPageForLink(
link,
vm.limit,
vm.currentPage,
vm.listOfPages,
vm.stepLinks,
limitedLinksMetadata[index]
)
}
}
}
var liClasses = getClassesForLink(
link,
vm.currentPage,
vm.listOfPages.length - 1,
vm.stepLinks
)
// If the link is a number,
// then incremented by 1 (since it's 0 based).
// otherwise, do nothing (so, it's a symbol).
var text = (link === parseInt(link, 10)) ? link + 1 : link
return h('li', { class: liClasses }, [h('a', data, text)])
})
}
function getSimpleLinks (vm, h) {
var lastPage = vm.listOfPages.length - 1
var prevData = {
on: {
click: function (e) {
e.preventDefault()
if (vm.currentPage > 0) { vm.currentPage -= 1 }
}
}
}
var nextData = {
on: {
click: function (e) {
e.preventDefault()
if (vm.currentPage < lastPage) { vm.currentPage += 1 }
}
}
}
var nextListData = { class: ['next', vm.currentPage >= lastPage ? 'disabled' : ''] }
var prevListData = { class: ['prev', vm.currentPage <= 0 ? 'disabled' : ''] }
var prevLink = h('li', prevListData, [h('a', prevData, vm.simple.prev)])
var nextLink = h('li', nextListData, [h('a', nextData, vm.simple.next)])
return [prevLink, nextLink]
}
function getTargetPaginateComponent (children, targetName) {
return children
.filter(function (child) { return (child.$vnode.componentOptions.tag === 'paginate'); })
.find(function (child) { return child.name === targetName; })
}
function getListOfPageNumbers (numberOfPages) {
// converts number of pages into an array
// that contains each individual page number
// For Example: 4 => [0, 1, 2, 3]
return Array.apply(null, { length: numberOfPages })
.map(function (val, index) { return index; })
}
function getClassesForLink(link, currentPage, lastPage, ref) {
var prev = ref.prev;
var next = ref.next;
var liClass = []
if (link === prev) {
liClass.push('left-arrow')
} else if (link === next) {
liClass.push('right-arrow')
} else if (link === ELLIPSES) {
liClass.push('ellipses')
} else {
liClass.push('number')
}
if (link === currentPage) {
liClass.push('active')
}
if (link === prev && currentPage <= 0) {
liClass.push('disabled')
} else if (link === next && currentPage >= lastPage) {
liClass.push('disabled')
}
return liClass
}
function getTargetPageForLink (link, limit, currentPage, listOfPages, ref, metaData) {
var prev = ref.prev;
var next = ref.next;
if ( metaData === void 0 ) metaData = null;
var currentChunk = Math.floor(currentPage / limit)
if (link === prev) {
return (currentPage - 1) < 0 ? 0 : currentPage - 1
} else if (link === next) {
return (currentPage + 1 > listOfPages.length - 1)
? listOfPages.length - 1
: currentPage + 1
} else if (metaData && metaData === 'right-ellipses') {
return (currentChunk + 1) * limit
} else if (metaData && metaData === 'left-ellipses') {
var chunkContent = listOfPages.slice(currentChunk * limit, currentChunk * limit + limit)
var isLastPage = currentPage === listOfPages.length - 1
if (isLastPage && chunkContent.length === 1) {
currentChunk--
}
return (currentChunk - 1) * limit + limit - 1
}
// which is number
return link
}
/**
* Mainly used here to check whether the displayed
* ellipses is for showing previous or next links
*/
function getLimitedLinksMetadata (limitedLinks) {
return limitedLinks.map(function (link, index) {
if (link === ELLIPSES && limitedLinks[index - 1] === 0) {
return 'left-ellipses'
} else if (link === ELLIPSES && limitedLinks[index - 1] !== 0) {
return 'right-ellipses'
}
return link
})
}
function addAdditionalClasses (linksContainer, classes) {
Object.keys(classes).forEach(function (selector) {
if (selector === 'ul') {
var selectorValue = classes['ul']
if (Array.isArray(selectorValue)) {
selectorValue.forEach(function (c) { return linksContainer.classList.add(c); })
} else {
linksContainer.classList.add(selectorValue)
}
}
linksContainer.querySelectorAll(selector).forEach(function (node) {
var selectorValue = classes[selector]
if (Array.isArray(selectorValue)) {
selectorValue.forEach(function (c) { return node.classList.add(c); })
} else {
node.classList.add(selectorValue)
}
})
})
}
function paginateDataGenerator (listNames) {
if ( listNames === void 0 ) listNames = [];
return listNames.reduce(function (curr, listName) {
curr[listName] = {
list: [],
page: 0
}
return curr
}, {})
}
var vuePaginate = {}
vuePaginate.install = function (Vue) {
Vue.mixin({
created: function created () {
if (this.paginate !== 'undefined' && this.paginate instanceof Array) {
this.paginate = paginateDataGenerator(this.paginate)
}
},
methods: {
paginated: function paginated (listName) {
if (!this.paginate || !this.paginate[listName]) {
warn(("'" + listName + "' is not registered in 'paginate' array."), this)
return
}
return this.paginate[listName].list
}
}
})
Vue.component('paginate', Paginate)
Vue.component('paginate-links', PaginateLinks)
}
if (typeof window !== 'undefined' && window.Vue) {
window.Vue.use(vuePaginate)
}
return vuePaginate;
}));