1 // $Id$ 2 3 (function ($) { 4 5 /** 6 * A pager widget for jQuery. 7 * 8 * <p>Heavily inspired by the Ruby on Rails will_paginate gem.</p> 9 * 10 * @expects this.target to be a list. 11 * @class PagerWidget 12 * @augments AjaxSolr.AbstractWidget 13 */ 14 AjaxSolr.PagerWidget = AjaxSolr.AbstractWidget.extend( 15 /** @lends AjaxSolr.PagerWidget.prototype */ 16 { 17 /** 18 * How many links are shown around the current page. 19 * 20 * @field 21 * @public 22 * @type Number 23 * @default 4 24 */ 25 innerWindow: 4, 26 27 /** 28 * How many links are around the first and the last page. 29 * 30 * @field 31 * @public 32 * @type Number 33 * @default 1 34 */ 35 outerWindow: 1, 36 37 /** 38 * The previous page link label. 39 * 40 * @field 41 * @public 42 * @type String 43 * @default "« previous" 44 */ 45 prevLabel: '« Previous', 46 47 /** 48 * The next page link label. 49 * 50 * @field 51 * @public 52 * @type String 53 * @default "next »" 54 */ 55 nextLabel: 'Next »', 56 57 /** 58 * Separator between pagination links. 59 * 60 * @field 61 * @public 62 * @type String 63 * @default "" 64 */ 65 separator: ' ', 66 67 /** 68 * The current page number. 69 * 70 * @field 71 * @private 72 * @type Number 73 */ 74 currentPage: null, 75 76 /** 77 * The total number of pages. 78 * 79 * @field 80 * @private 81 * @type Number 82 */ 83 totalPages: null, 84 85 /** 86 * @returns {String} The gap in page links, which is represented by: 87 * <span class="pager-gap">…</span> 88 */ 89 gapMarker: function () { 90 return '<span class="pager-gap">…</span>'; 91 }, 92 93 /** 94 * @returns {Array} The links for the visible page numbers. 95 */ 96 windowedLinks: function () { 97 var links = []; 98 99 var prev = null; 100 101 visible = this.visiblePageNumbers(); 102 for (var i = 0, l = visible.length; i < l; i++) { 103 if (prev && visible[i] > prev + 1) links.push(this.gapMarker()); 104 links.push(this.pageLinkOrSpan(visible[i], [ 'pager-current' ])); 105 prev = visible[i]; 106 } 107 108 return links; 109 }, 110 111 /** 112 * @returns {Array} The visible page numbers according to the window options. 113 */ 114 visiblePageNumbers: function () { 115 var windowFrom = this.currentPage - this.innerWindow; 116 var windowTo = this.currentPage + this.innerWindow; 117 118 // If the window is truncated on one side, make the other side longer 119 if (windowTo > this.totalPages) { 120 windowFrom = Math.max(0, windowFrom - (windowTo - this.totalPages)); 121 windowTo = this.totalPages; 122 } 123 if (windowFrom < 1) { 124 windowTo = Math.min(this.totalPages, windowTo + (1 - windowFrom)); 125 windowFrom = 1; 126 } 127 128 var visible = []; 129 130 // Always show the first page 131 visible.push(1); 132 // Don't add inner window pages twice 133 for (var i = 2; i <= Math.min(1 + this.outerWindow, windowFrom - 1); i++) { 134 visible.push(i); 135 } 136 // If the gap is just one page, close the gap 137 if (1 + this.outerWindow == windowFrom - 2) { 138 visible.push(windowFrom - 1); 139 } 140 // Don't add the first or last page twice 141 for (var i = Math.max(2, windowFrom); i <= Math.min(windowTo, this.totalPages - 1); i++) { 142 visible.push(i); 143 } 144 // If the gap is just one page, close the gap 145 if (this.totalPages - this.outerWindow == windowTo + 2) { 146 visible.push(windowTo + 1); 147 } 148 // Don't add inner window pages twice 149 for (var i = Math.max(this.totalPages - this.outerWindow, windowTo + 1); i < this.totalPages; i++) { 150 visible.push(i); 151 } 152 // Always show the last page, unless it's the first page 153 if (this.totalPages > 1) { 154 visible.push(this.totalPages); 155 } 156 157 return visible; 158 }, 159 160 /** 161 * @param {Number} page A page number. 162 * @param {String} classnames CSS classes to add to the page link. 163 * @param {String} text The inner HTML of the page link (optional). 164 * @returns The link or span for the given page. 165 */ 166 pageLinkOrSpan: function (page, classnames, text) { 167 text = text || page; 168 169 if (page && page != this.currentPage) { 170 return $('<a href="#"/>').html(text).attr('rel', this.relValue(page)).addClass(classnames[1]).click(this.clickHandler(page)); 171 } 172 else { 173 return $('<span/>').html(text).addClass(classnames.join(' ')); 174 } 175 }, 176 177 /** 178 * @param {Number} page A page number. 179 * @returns {Function} The click handler for the page link. 180 */ 181 clickHandler: function (page) { 182 var self = this; 183 return function () { 184 self.manager.store.get('start').val((page - 1) * (self.manager.response.responseHeader.params.rows || 10)); 185 self.manager.doRequest(); 186 return false; 187 } 188 }, 189 190 /** 191 * @param {Number} page A page number. 192 * @returns {String} The <tt>rel</tt> attribute for the page link. 193 */ 194 relValue: function (page) { 195 switch (page) { 196 case this.previousPage(): 197 return 'prev' + (page == 1 ? 'start' : ''); 198 case this.nextPage(): 199 return 'next'; 200 case 1: 201 return 'start'; 202 default: 203 return ''; 204 } 205 }, 206 207 /** 208 * @returns {Number} The page number of the previous page or null if no previous page. 209 */ 210 previousPage: function () { 211 return this.currentPage > 1 ? (this.currentPage - 1) : null; 212 }, 213 214 /** 215 * @returns {Number} The page number of the next page or null if no next page. 216 */ 217 nextPage: function () { 218 return this.currentPage < this.totalPages ? (this.currentPage + 1) : null; 219 }, 220 221 /** 222 * An abstract hook for child implementations. 223 * 224 * @param {Number} perPage The number of items shown per results page. 225 * @param {Number} offset The index in the result set of the first document to render. 226 * @param {Number} total The total number of documents in the result set. 227 */ 228 renderHeader: function (perPage, offset, total) {}, 229 230 /** 231 * Render the pagination links. 232 * 233 * @param {Array} links The links for the visible page numbers. 234 */ 235 renderLinks: function (links) { 236 if (this.totalPages) { 237 links.unshift(this.pageLinkOrSpan(this.previousPage(), [ 'pager-disabled', 'pager-prev' ], this.prevLabel)); 238 links.push(this.pageLinkOrSpan(this.nextPage(), [ 'pager-disabled', 'pager-next' ], this.nextLabel)); 239 AjaxSolr.theme('list_items', this.target, links, this.separator); 240 } 241 }, 242 243 afterRequest: function () { 244 var perPage = parseInt(this.manager.response.responseHeader.params.rows || 10); 245 var offset = parseInt(this.manager.response.responseHeader.params.start || 0); 246 var total = parseInt(this.manager.response.response.numFound); 247 248 // Normalize the offset to a multiple of perPage. 249 offset = offset - offset % perPage; 250 251 this.currentPage = Math.ceil((offset + 1) / perPage); 252 this.totalPages = Math.ceil(total / perPage); 253 254 $(this.target).empty(); 255 256 this.renderLinks(this.windowedLinks()); 257 this.renderHeader(perPage, offset, total); 258 } 259 }); 260 261 })(jQuery); 262