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