/** * @license jQuery paging plugin v1.2.0 23/06/2014 * http://www.xarg.org/2011/09/jquery-pagination-revised/ * * Copyright (c) 2011, Robert Eisele (robert@xarg.org) * Dual licensed under the MIT or GPL Version 2 licenses. **/ (function($, window, undefined) { $["fn"]["paging"] = function(number, opts) { var self = this, Paging = { "setOptions": function(opts) { var parseFormat = function(format) { var gndx = 0, group = 0, num = 1, res = { fstack: [], // format stack asterisk: 0, // asterisk? inactive: 0, // fill empty pages with inactives up to w? blockwide: 5, // width of number block current: 3, // position of current element in number block rights: 0, // num of rights lefts: 0 // num of lefts }, tok, pattern = /[*<>pq\[\]().-]|[nc]+!?/g; var known = { "[": "first", "]": "last", "<": "prev", ">": "next", "q": "left", "p": "right", "-": "fill", ".": "leap" }, count = {}; while ((tok = pattern["exec"](format))) { tok = "" + (tok); if (undefined === known[tok]) { if ("(" === tok) { group = ++gndx; } else if (")" === tok) { group = 0; } else if (num) { if ("*" === tok) { res.asterisk = 1; res.inactive = 0; } else { // number block is the only thing left here res.asterisk = 0; res.inactive = "!" === tok.charAt(tok.length - 1); res.blockwide = tok["length"] - res.inactive; if (!(res.current = 1 + tok.indexOf("c"))) { res.current = (1 + res.blockwide) >> 1; } } res.fstack[res.fstack.length] = ({ ftype: "block", // type fgroup: 0, // group fpos: 0 // pos }); num = 0; } } else { res.fstack[res.fstack.length] = ({ ftype: known[tok], // type fgroup: group, // group fpos: undefined === count[tok] ? count[tok] = 1 : ++count[tok] // pos }); if ("q" === tok) ++res.lefts; else if ("p" === tok) ++res.rights; } } return res; }; Paging.opts = $.extend(Paging.opts || { "lapping" : 0, // number of elements overlap "perpage" : 10, // number of elements per page "page" : 1, // current page "refresh" : { "interval": 10, "url": null }, // refresh callback information "format" : "", // visual format string "lock" : false, // set to true, if you want to disable the pagination for a while. "onFormat" : function (type) { // callback for every format element /** EXAMPLE ** switch (type) { case 'block': if (!this.active) return '' + this.value + ''; else if (this.value != this.page) return '' + this.value + ''; return '' + this.value + ''; case 'right': case 'left': if (!this.active) { return ""; } return '' + this.value + ''; case 'next': if (this.active) { return ''; } return 'Next »'; case 'prev': if (this.active) { return ''; } return '« Previous'; case 'first': if (this.active) { return '|<'; } return '|<'; case 'last': if (this.active) { return ''; } return '>|'; case 'fill': if (this.active) { return "..."; } } return ""; // return nothing for missing branches **/ }, "onSelect" : function (page){ // callback for page selection /** EXAMPLE SLICE ** var data = this.slice; content.slice(prev[0], prev[1]).css('display', 'none'); content.slice(data[0], data[1]).css('display', 'block'); prev = data; **/ /** EXAMPLE AJAX ** $.ajax({ "url": '/data.php?start=' + this.slice[0] + '&end=' + this.slice[1] + '&page=' + page, "success": function(data) { // content replace } }); **/ // Return code indicates if the link of the clicked format element should be followed (otherwise only the click-event is used) return true; }, "onRefresh" : function (json) {// callback for new data of refresh api /** EXAMPLE ** if (json.number) { Paging.setNumber(json.number); } if (json.options) { Paging.setOptions(json.options); } Paging.setPage(); // Call with empty params to reload the paginator **/ } }, opts || {}); Paging.opts["lapping"]|= 0; Paging.opts["perpage"]|= 0; if (Paging.opts["page"] !== null) Paging.opts["page"] |= 0; // If the number of elements per page is less then 1, set it to default if (Paging.opts["perpage"] < 1) { Paging.opts["perpage"] = 10; } if (Paging.interval) window.clearInterval(Paging.interval); if (Paging.opts["refresh"]["url"]) { Paging.interval = window.setInterval(function() { $["ajax"]({ "url": Paging.opts["refresh"]["url"], "success": function(data) { if (typeof(data) === "string") { try { data = $["parseJSON"](data); } catch (o) { return; } } Paging.opts["onRefresh"](data); } }); }, 1000 * Paging.opts["refresh"]["interval"]); } Paging.format = parseFormat(Paging.opts["format"]); return Paging; }, "setNumber": function(number) { Paging.number = (undefined === number || number < 0) ? -1 : number; return Paging; }, "setPage": function(page) { if (Paging.opts["lock"]) { Paging.opts["onSelect"](0); return Paging; } if (undefined === page) { page = Paging.opts["page"]; if (null === page) { return Paging; } } else if (Paging.opts["page"] == page) { // Necessary to be ==, not === return Paging; } Paging.opts["page"] = (page|= 0); var number = Paging.number; var opts = Paging.opts; var rStart, rStop; var pages, buffer; var groups = 1, format = Paging.format; var data, tmp, node, lapping; var count = format.fstack["length"], i = count; // If the lapping is greater than perpage, reduce it to perpage - 1 to avoid endless loops if (opts["perpage"] <= opts["lapping"]) { opts["lapping"] = opts["perpage"] - 1; } lapping = number <= opts["lapping"] ? 0 : opts["lapping"]|0; // If the number is negative, the value doesn"t matter, we loop endlessly with a constant width if (number < 0) { number = -1; pages = -1; rStart = Math.max(1, page - format.current + 1 - lapping); rStop = rStart + format.blockwide; } else { /* Calculate the number of pages * * Variables: * - n: Number of elements * - p: Elements per page * - o: Offset (lapping) * - x: Position of last n (aka virtual number of elements) * - m: Height aka number of pages * * Condition: o < p * * Page Last element of page * ===================================== * 1 p * 2 2p - o * 3 3p - 2o * ... * k kp - (k - 1)o * k + 1 (k + 1)p - ko * * => kp - (k - 1)o < n <= (k + 1)p - ko (n is on page k+1) * <=> k(p - o) + o < n <= k(p - o) + p * <=> (n - p) / (p - o) <= k < (n - o) / (p - o) * => k = ceil((n - p) / (p - o)) * * We know that kp - ko + i = n * => i = n - k(p - o) * * => m = k + 1 * x = kp + i */ pages = 1 + Math.ceil((number - opts["perpage"]) / (opts["perpage"] - lapping)); // If current page is negative, start at the end and // Set the current page into a valid range, includes 0, which is set to 1 page = Math.max(1, Math.min(page < 0 ? 1 + pages + page : page, pages)); // Do we need to print all numbers? if (format.asterisk) { rStart = 1; rStop = 1 + pages; // Disable :first and :last for asterisk mode as we see all buttons format.current = page; format.blockwide = pages; } else { // If no, start at the best position and stop at max width or at num of pages rStart = Math.max(1, Math.min(page - format.current, pages - format.blockwide) + 1); rStop = format.inactive ? rStart + format.blockwide : Math.min(rStart + format.blockwide, 1 + pages); } } while (i--) { tmp = 0; // default everything is visible node = format.fstack[i]; switch (node.ftype) { case "left": tmp = (node.fpos < rStart); break; case "right": tmp = (rStop <= pages - format.rights + node.fpos); break; case "first": tmp = (format.current < page); break; case "last": tmp = (format.blockwide < format.current + pages - page); break; case "prev": tmp = (1 < page); break; case "next": tmp = (page < pages); break; } groups|= tmp << node.fgroup; // group visible? } data = { "number" : number, // number of elements "lapping" : lapping, // overlapping "pages" : pages, // number of pages "perpage" : opts["perpage"], // number of elements per page "page" : page, // current page "slice" : [ // two element array with bounds of the current page selection (tmp = page * (opts["perpage"] - lapping) + lapping) - opts["perpage"], // Lower bound Math.min(tmp, number) // Upper bound ] }; buffer = ""; function buffer_append(opts, data, type) { type = "" + (opts["onFormat"].call(data, type)); if (data["value"]) buffer+= type.replace(/> node.fgroup & 1); switch (node.ftype) { case "block": for (; rStart < rStop; ++rStart) { data["value"] = rStart; data["pos"] = 1 + format.blockwide - rStop + rStart; data["active"] = rStart <= pages || number < 0; // true if infinity series and rStart <= pages data["first"] = 1 === rStart; // check if it is the first page data["last"] = rStart === pages && 0 < number; // false if infinity series or rStart != pages buffer_append(opts, data, node.ftype); } continue; case "left": data["value"] = node.fpos; data["active"] = node.fpos < rStart; // Don't take group-visibility into account! break; case "right": data["value"] = pages - format.rights + node.fpos; data["active"] = rStop <= data["value"]; // Don't take group-visibility into account! break; case "first": data["value"] = 1; data["active"] = tmp && 1 < page; break; case "prev": data["value"] = Math.max(1, page - 1); data["active"] = tmp && 1 < page; break; case "last": if ((data["active"] = (number < 0))) { data["value"] = 1 + page; } else { data["value"] = pages; data["active"] = tmp && page < pages; } break; case "next": if ((data["active"] = (number < 0))) { data["value"] = 1 + page; } else { data["value"] = Math.min(1 + page, pages); data["active"] = tmp && page < pages; } break; case "leap": case "fill": data["pos"] = node.fpos; data["active"] = tmp; // tmp is true by default and changes only for group behaviour buffer_append(opts, data, node.ftype); continue; } data["pos"] = node.fpos; data["last"] = /* void */ data["first"] = undefined; buffer_append(opts, data, node.ftype); } if (self.length) { $("a", self["html"](buffer)).click(function(ev) { ev["preventDefault"](); var obj = this; do { if ('a' === obj["nodeName"].toLowerCase()) { break; } } while ((obj = obj["parentNode"])); Paging["setPage"]($(obj).data("page")); if (Paging.locate) { window.location = obj["href"]; } }); Paging.locate = opts["onSelect"].call({ "number" : number, "lapping" : lapping, "pages" : pages, "slice" : data["slice"] }, page); } return Paging; } }; return Paging ["setNumber"](number) ["setOptions"](opts) ["setPage"](); }; }(jQuery, this));