/** * jquery jpages v0.7 * client side pagination with jquery * http://luis-almeida.github.com/jpages * * licensed under the mit license. * copyright 2012 luís almeida * https://github.com/luis-almeida */ ;(function($, window, document, undefined) { var name = "jpages", instance = null, defaults = { containerid: "", first: false, previous: "← previous", next: "next →", last: false, links: "numeric", // blank || title startpage: 1, perpage: 10, midrange: 5, startrange: 1, endrange: 1, keybrowse: false, scrollbrowse: false, pause: 0, clickstop: false, delay: 50, direction: "forward", // backwards || auto || random || animation: "", // http://daneden.me/animate/ - any entrance animations fallback: 400, minheight: true, callback: undefined // function( pages, items ) { } }; function plugin(element, options) { this.options = $.extend({}, defaults, options); this._container = $("#" + this.options.containerid); if (!this._container.length) return; this.jqwindow = $(window); this.jqdocument = $(document); this._holder = $(element); this._nav = {}; this._first = $(this.options.first); this._previous = $(this.options.previous); this._next = $(this.options.next); this._last = $(this.options.last); /* only visible items! */ this._items = this._container.children(":visible"); this._itemsshowing = $([]); this._itemshiding = $([]); this._numpages = math.ceil(this._items.length / this.options.perpage); this._currentpagenum = this.options.startpage; this._clicked = false; this._cssanimsupport = this.getcssanimationsupport(); this.init(); } plugin.prototype = { constructor : plugin, getcssanimationsupport : function() { var animation = false, animationstring = 'animation', keyframeprefix = '', domprefixes = 'webkit moz o ms khtml'.split(' '), pfx = '', elm = this._container.get(0); if (elm.style.animationname) animation = true; if (animation === false) { for (var i = 0; i < domprefixes.length; i++) { if (elm.style[domprefixes[i] + 'animationname'] !== undefined) { pfx = domprefixes[i]; animationstring = pfx + 'animation'; keyframeprefix = '-' + pfx.tolowercase() + '-'; animation = true; break; } } } return animation; }, init : function() { this.setstyles(); this.setnav(); this.paginate(this._currentpagenum); this.setminheight(); }, setstyles : function() { var requiredstyles = ""; $(requiredstyles).appendto("head"); if (this._cssanimsupport && this.options.animation.length) this._items.addclass("animated jp-hidden"); else this._items.hide(); }, setnav : function() { var navhtml = this.writenav(); this._holder.each(this.bind(function(index, element) { var holder = $(element); holder.html(navhtml); this.cachenavelements(holder, index); this.bindnavhandlers(index); this.disablenavselection(element); }, this)); if (this.options.keybrowse) this.bindnavkeybrowse(); if (this.options.scrollbrowse) this.bindnavscrollbrowse(); }, writenav : function() { var i = 1, navhtml; navhtml = this.writebtn("first") + this.writebtn("previous"); for (; i <= this._numpages; i++) { if (i === 1 && this.options.startrange === 0) navhtml += "..."; if (i > this.options.startrange && i <= this._numpages - this.options.endrange) navhtml += ""; else navhtml += ""; switch (this.options.links) { case "numeric": navhtml += i; break; case "blank": break; case "title": var title = this._items.eq(i - 1).attr("data-title"); navhtml += title !== undefined ? title : ""; break; } navhtml += ""; if (i === this.options.startrange || i === this._numpages - this.options.endrange) navhtml += "..."; } navhtml += this.writebtn("next") + this.writebtn("last") + ""; return navhtml; }, writebtn : function(which) { return this.options[which] !== false && !$(this["_" + which]).length ? "" + this.options[which] + "" : ""; }, cachenavelements : function(holder, index) { this._nav[index] = {}; this._nav[index].holder = holder; this._nav[index].first = this._first.length ? this._first : this._nav[index].holder.find("a.jp-first"); this._nav[index].previous = this._previous.length ? this._previous : this._nav[index].holder.find("a.jp-previous"); this._nav[index].next = this._next.length ? this._next : this._nav[index].holder.find("a.jp-next"); this._nav[index].last = this._last.length ? this._last : this._nav[index].holder.find("a.jp-last"); this._nav[index].fstbreak = this._nav[index].holder.find("span:first"); this._nav[index].lstbreak = this._nav[index].holder.find("span:last"); this._nav[index].pages = this._nav[index].holder.find("a").not(".jp-first, .jp-previous, .jp-next, .jp-last"); this._nav[index].permpages = this._nav[index].pages.slice(0, this.options.startrange) .add(this._nav[index].pages.slice(this._numpages - this.options.endrange, this._numpages)); this._nav[index].pagesshowing = $([]); this._nav[index].currentpage = $([]); }, bindnavhandlers : function(index) { var nav = this._nav[index]; // default nav nav.holder.bind("click.jpages", this.bind(function(evt) { var newpage = this.getnewpage(nav, $(evt.target)); if (this.validnewpage(newpage)) { this._clicked = true; this.paginate(newpage); } evt.preventdefault(); }, this)); // custom first if (this._first.length) { this._first.bind("click.jpages", this.bind(function() { if (this.validnewpage(1)) { this._clicked = true; this.paginate(1); } }, this)); } // custom previous if (this._previous.length) { this._previous.bind("click.jpages", this.bind(function() { var newpage = this._currentpagenum - 1; if (this.validnewpage(newpage)) { this._clicked = true; this.paginate(newpage); } }, this)); } // custom next if (this._next.length) { this._next.bind("click.jpages", this.bind(function() { var newpage = this._currentpagenum + 1; if (this.validnewpage(newpage)) { this._clicked = true; this.paginate(newpage); } }, this)); } // custom last if (this._last.length) { this._last.bind("click.jpages", this.bind(function() { if (this.validnewpage(this._numpages)) { this._clicked = true; this.paginate(this._numpages); } }, this)); } }, disablenavselection : function(element) { if (typeof element.onselectstart != "undefined") element.onselectstart = function() { return false; }; else if (typeof element.style.mozuserselect != "undefined") element.style.mozuserselect = "none"; else element.onmousedown = function() { return false; }; }, bindnavkeybrowse : function() { this.jqdocument.bind("keydown.jpages", this.bind(function(evt) { var target = evt.target.nodename.tolowercase(); if (this.elemscrolledintoview() && target !== "input" && target != "textarea") { var newpage = this._currentpagenum; if (evt.which == 37) newpage = this._currentpagenum - 1; if (evt.which == 39) newpage = this._currentpagenum + 1; if (this.validnewpage(newpage)) { this._clicked = true; this.paginate(newpage); } } }, this)); }, elemscrolledintoview : function() { var docviewtop, docviewbottom, elemtop, elembottom; docviewtop = this.jqwindow.scrolltop(); docviewbottom = docviewtop + this.jqwindow.height(); elemtop = this._container.offset().top; elembottom = elemtop + this._container.height(); return ((elembottom >= docviewtop) && (elemtop <= docviewbottom)); // comment above and uncomment below if you want keybrowse to happen // only when container is completely visible in the page /*return ((elembottom >= docviewtop) && (elemtop <= docviewbottom) && (elembottom <= docviewbottom) && (elemtop >= docviewtop) );*/ }, bindnavscrollbrowse : function() { this._container.bind("mousewheel.jpages dommousescroll.jpages", this.bind(function(evt) { var newpage = (evt.originalevent.wheeldelta || -evt.originalevent.detail) > 0 ? (this._currentpagenum - 1) : (this._currentpagenum + 1); if (this.validnewpage(newpage)) { this._clicked = true; this.paginate(newpage); } evt.preventdefault(); return false; }, this)); }, getnewpage : function(nav, target) { if (target.is(nav.currentpage)) return this._currentpagenum; if (target.is(nav.pages)) return nav.pages.index(target) + 1; if (target.is(nav.first)) return 1; if (target.is(nav.last)) return this._numpages; if (target.is(nav.previous)) return nav.pages.index(nav.currentpage); if (target.is(nav.next)) return nav.pages.index(nav.currentpage) + 2; }, validnewpage : function(newpage) { return newpage !== this._currentpagenum && newpage > 0 && newpage <= this._numpages; }, paginate : function(page) { var itemrange, pageinterval; itemrange = this.updateitems(page); pageinterval = this.updatepages(page); this._currentpagenum = page; if ($.isfunction(this.options.callback)) this.callback(page, itemrange, pageinterval); this.updatepause(); }, updateitems : function(page) { var range = this.getitemrange(page); this._itemshiding = this._itemsshowing; this._itemsshowing = this._items.slice(range.start, range.end); if (this._cssanimsupport && this.options.animation.length) this.cssanimations(page); else this.jqanimations(page); return range; }, getitemrange : function(page) { var range = {}; range.start = (page - 1) * this.options.perpage; range.end = range.start + this.options.perpage; if (range.end > this._items.length) range.end = this._items.length; return range; }, cssanimations : function(page) { clearinterval(this._delay); this._itemshiding .removeclass(this.options.animation + " jp-invisible") .addclass("jp-hidden"); this._itemsshowing .removeclass("jp-hidden") .addclass("jp-invisible"); this._itemsoriented = this.getdirecteditems(page); this._index = 0; this._delay = setinterval(this.bind(function() { if (this._index === this._itemsoriented.length) clearinterval(this._delay); else { this._itemsoriented .eq(this._index) .removeclass("jp-invisible") .addclass(this.options.animation); } this._index = this._index + 1; }, this), this.options.delay); }, jqanimations : function(page) { clearinterval(this._delay); this._itemshiding.addclass("jp-hidden"); this._itemsshowing.fadeto(0, 0).removeclass("jp-hidden"); this._itemsoriented = this.getdirecteditems(page); this._index = 0; this._delay = setinterval(this.bind(function() { if (this._index === this._itemsoriented.length) clearinterval(this._delay); else { this._itemsoriented .eq(this._index) .fadeto(this.options.fallback, 1); } this._index = this._index + 1; }, this), this.options.delay); }, getdirecteditems : function(page) { var itemstoshow; switch (this.options.direction) { case "backwards": itemstoshow = $(this._itemsshowing.get().reverse()); break; case "random": itemstoshow = $(this._itemsshowing.get().sort(function() { return (math.round(math.random()) - 0.5); })); break; case "auto": itemstoshow = page >= this._currentpagenum ? this._itemsshowing : $(this._itemsshowing.get().reverse()); break; default: itemstoshow = this._itemsshowing; } return itemstoshow; }, updatepages : function(page) { var interval, index, nav; interval = this.getinterval(page); for (index in this._nav) { if (this._nav.hasownproperty(index)) { nav = this._nav[index]; this.updatebtns(nav, page); this.updatecurrentpage(nav, page); this.updatepagesshowing(nav, interval); this.updatebreaks(nav, interval); } } return interval; }, getinterval : function(page) { var nehalf, upperlimit, start, end; nehalf = math.ceil(this.options.midrange / 2); upperlimit = this._numpages - this.options.midrange; start = page > nehalf ? math.max(math.min(page - nehalf, upperlimit), 0) : 0; end = page > nehalf ? math.min(page + nehalf - (this.options.midrange % 2 > 0 ? 1 : 0), this._numpages) : math.min(this.options.midrange, this._numpages); return {start: start,end: end}; }, updatebtns : function(nav, page) { if (page === 1) { nav.first.addclass("jp-disabled"); nav.previous.addclass("jp-disabled"); } if (page === this._numpages) { nav.next.addclass("jp-disabled"); nav.last.addclass("jp-disabled"); } if (this._currentpagenum === 1 && page > 1) { nav.first.removeclass("jp-disabled"); nav.previous.removeclass("jp-disabled"); } if (this._currentpagenum === this._numpages && page < this._numpages) { nav.next.removeclass("jp-disabled"); nav.last.removeclass("jp-disabled"); } }, updatecurrentpage : function(nav, page) { nav.currentpage.removeclass("jp-current"); nav.currentpage = nav.pages.eq(page - 1).addclass("jp-current"); }, updatepagesshowing : function(nav, interval) { var newrange = nav.pages.slice(interval.start, interval.end).not(nav.permpages); nav.pagesshowing.not(newrange).addclass("jp-hidden"); newrange.not(nav.pagesshowing).removeclass("jp-hidden"); nav.pagesshowing = newrange; }, updatebreaks : function(nav, interval) { if ( interval.start > this.options.startrange || (this.options.startrange === 0 && interval.start > 0) ) nav.fstbreak.removeclass("jp-hidden"); else nav.fstbreak.addclass("jp-hidden"); if (interval.end < this._numpages - this.options.endrange) nav.lstbreak.removeclass("jp-hidden"); else nav.lstbreak.addclass("jp-hidden"); }, callback : function(page, itemrange, pageinterval) { var pages = { current: page, interval: pageinterval, count: this._numpages }, items = { showing: this._itemsshowing, oncoming: this._items.slice(itemrange.start + this.options.perpage, itemrange.end + this.options.perpage), range: itemrange, count: this._items.length }; pages.interval.start = pages.interval.start + 1; items.range.start = items.range.start + 1; this.options.callback(pages, items); }, updatepause : function() { if (this.options.pause && this._numpages > 1) { cleartimeout(this._pause); if (this.options.clickstop && this._clicked) return; else { this._pause = settimeout(this.bind(function() { this.paginate(this._currentpagenum !== this._numpages ? this._currentpagenum + 1 : 1); }, this), this.options.pause); } } }, setminheight : function() { if (this.options.minheight && !this._container.is("table, tbody")) { settimeout(this.bind(function() { this._container.css({ "min-height": this._container.css("height") }); }, this), 1000); } }, bind : function(fn, me) { return function() { return fn.apply(me, arguments); }; }, destroy : function() { this.jqdocument.unbind("keydown.jpages"); this._container.unbind("mousewheel.jpages dommousescroll.jpages"); if (this.options.minheight) this._container.css("min-height", ""); if (this._cssanimsupport && this.options.animation.length) this._items.removeclass("animated jp-hidden jp-invisible " + this.options.animation); else this._items.removeclass("jp-hidden").fadeto(0, 1); this._holder.unbind("click.jpages").empty(); } }; $.fn[name] = function(arg) { var type = $.type(arg); if (type === "object") { if (this.length && !$.data(this, name)) { instance = new plugin(this, arg); this.each(function() { $.data(this, name, instance); }); } return this; } if (type === "string" && arg === "destroy") { instance.destroy(); this.each(function() { $.removedata(this, name); }); return this; } if (type === 'number' && arg % 1 === 0) { if (instance.validnewpage(arg)) instance.paginate(arg); return this; } return this; }; })(jquery, window, document);