├── README.md ├── demo └── index.html └── loadmore.js /README.md: -------------------------------------------------------------------------------- 1 | loadMore.js 2 | ======= 3 | 4 | loadMore.js is a jQuery plugin for easily adding an AJAX-based "more"-link pagination to an element on ones site. 5 | 6 | ## Usage 7 | 8 | Simple: 9 | 10 | $('.list').loadmore('/path/to/more/html'); 11 | 12 | Advanced: 13 | 14 | $('.list').loadmore('/path/to/more/html', { 15 | 'className' : '', 16 | text : 'More', 17 | loadingText : 'Loading', 18 | page : 0, 19 | pageSize : false, 20 | 'pageParam' : 'page' 21 | }); 22 | 23 | ## Advanced options 24 | 25 | * **id** - a string identifier for this particular pager to make it easier for the History API integration to restore the correct pager. 26 | * **className** - extra classes to add to the more-link container 27 | * **text** - text to use in the more-link - should be either a string or a function 28 | * **loadingText** - text to use in the more-link when it is loading - should be either a string or a function 29 | * **page** - the current page in the list 30 | * **rowsPerPage** - how many elements should be expected on a new page? If less than this amount is received we've reached the end and will remove the pager 31 | * **maxPageCount** - the maximum numbers of pages to fetch at once - used by the History API integration 32 | * **pageParam** - the query parameter used to specify which page to fetch. If set to `false` no param will be specified. 33 | * **pageStartParam** - when more than one page is fetched at once this is the query parameter used to specify the page to start from. If set to `false` no param will be specified. 34 | * **complete** - a function to execute once a new page has been loaded, return false if the pager should be removed 35 | * **useHistoryAPI** - whether to use the History API in supported browsers or not 36 | * **useOffset** – whether to use offsets rather than page numbers 37 | * **useExistingButton** – rather than creating a new button, use an existing one matching this selector / element 38 | * **filterResult** – filter the received result by these selectors 39 | * **itemSelector** – the selector used to count items 40 | * **baseOffset** – an offset to add to all offsets. Will be parsed from any `pageParam` or `pageStartParam` query params on an existing button. 41 | * **processUrl** – for complex URL cases, define a method that will be sent `url` and `params` and returns either an object with a `url` and `params` key or a `url` string if no params should be used anymore. 42 | * **interpretUrl** – if one uses both `processUrl` and `useExistingButton` then one will likely have to have a custom method to extract the modfied `baseOffset` from the existing button. Receives `Location` object, `itemCount` and `options`. 43 | 44 | ### Events 45 | 46 | * **loadmore:last** - triggered on the pager when the last page has been fetched 47 | 48 | ## In action on 49 | 50 | * **Flattr.com**, eg. https://flattr.com/profile/voxpelli 51 | 52 | ## Support 53 | 54 | [![Flattr this git repo](http://api.flattr.com/button/flattr-badge-large.png)](https://flattr.com/submit/auto?user_id=voxpelli&url=https://github.com/voxpelli/jquery-loadmore&title=loadmore.js&language=en_GB&tags=github&category=software) 55 | -------------------------------------------------------------------------------- /demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | jQuery Loadmore 6 | 7 | 8 | 9 | 10 |

jQuery Loadmore

11 | 12 |
13 |
14 | 19 | 23 |
24 |
25 | 26 | Next 27 | 28 | 41 | 42 | -------------------------------------------------------------------------------- /loadmore.js: -------------------------------------------------------------------------------- 1 | /*jslint browser: true, maxerr: 50, indent: 2 */ 2 | /*global jQuery: false */ 3 | (function ($) { 4 | "use strict"; 5 | 6 | var maybeCall, update, getItemCount, moreClick, supportsHistory, idCount = 0; 7 | 8 | // Below taken from tipsy.js 9 | maybeCall = function (value, context) { 10 | return (typeof value === 'function') ? (value.call(context)) : value; 11 | }; 12 | 13 | update = function (pageTarget) { 14 | var $this = $(this), 15 | options = $this.data('loadmore-options'), 16 | $text = $this.children('span.text'), 17 | currentPage = (options.useOffset ? getItemCount($this, options) : $this.data('loadmore-page')), 18 | url = options.url, 19 | params = {}; 20 | 21 | if ($this.hasClass('loading') || pageTarget <= currentPage) { 22 | return false; 23 | } 24 | 25 | $this.addClass('loading'); 26 | $text.text(maybeCall(options.loadingText, $text[0])); 27 | 28 | if (pageTarget - currentPage > 1 && options.pageStartParam) { 29 | params[options.pageStartParam] = currentPage + 1 + options.baseOffset; 30 | if (options.maxPageCount !== false && (options.maxPageCount * (options.useOffset ? options.rowsPerPage : 1)) < pageTarget - currentPage) { 31 | pageTarget = currentPage + options.maxPageCount; 32 | } 33 | } 34 | if (options.pageParam) { 35 | params[options.pageParam] = pageTarget + options.baseOffset; 36 | } 37 | 38 | if (options.processUrl) { 39 | url = options.processUrl(url, params); 40 | params = url.params || {}; 41 | url = url.url || url; 42 | } 43 | 44 | $.get(url, params) 45 | .fail(function () { 46 | //TODO: Do the fail dance 47 | }) 48 | .done(function (data) { 49 | if (!options.useOffset && currentPage !== $this.data('loadmore-page')) { 50 | return; 51 | } 52 | 53 | var historyState = {}, $newData, itemCount; 54 | 55 | if (!options.useOffset) { 56 | $this.data('loadmore-page', pageTarget); 57 | } 58 | $this.removeClass('loading'); 59 | $text.text(maybeCall(options.text, $text[0])); 60 | 61 | if (supportsHistory && options.useHistoryAPI) { 62 | historyState[$this.attr('id')] = pageTarget; 63 | window.history.replaceState(jQuery.extend(true, window.history.state, {loadmore : historyState}), document.title); 64 | } 65 | 66 | $newData = $(data).filter('*'); 67 | if (options.filterResult) { 68 | $newData = $newData.find(options.filterResult).add($newData.filter(options.filterResult)); 69 | } 70 | 71 | $newData = $('
').append($newData); 72 | 73 | itemCount = (options.itemSelector ? $(options.itemSelector, $newData) : $newData).length; 74 | 75 | if (options.useExistingButton) { 76 | $newData.children().appendTo($this.data('loadmore-container')); 77 | } else { 78 | $newData.children().insertBefore($this); 79 | } 80 | 81 | if (options.rowsPerPage !== false && itemCount < (options.useOffset ? 1 : options.rowsPerPage) * (pageTarget - currentPage)) { 82 | $this.trigger('loadmore:last').remove(); 83 | } 84 | 85 | if (options.complete && options.complete.call($newData) === false && $this.parent().length) { 86 | $this.trigger('loadmore:last').remove(); 87 | } 88 | }); 89 | return false; 90 | }; 91 | 92 | getItemCount = function ($this, options) { 93 | return (options.itemSelector ? $(options.itemSelector, $this.data('loadmore-container')) : $this.siblings()).length; 94 | }; 95 | 96 | moreClick = function (e) { 97 | var page, $this = $(this), options = $this.data('loadmore-options'); 98 | if (options.useOffset) { 99 | page = getItemCount($this, options) + options.rowsPerPage; 100 | } 101 | else 102 | { 103 | page = $this.data('loadmore-page') + 1; 104 | } 105 | return update.call(this, page); 106 | }; 107 | 108 | $.fn.loadmore = function (url, options) { 109 | if (typeof url === 'object') { 110 | options = $.extend({}, $.fn.loadmore.defaults, url); 111 | } else { 112 | options = $.extend({}, $.fn.loadmore.defaults, options); 113 | options.url = url; 114 | } 115 | 116 | this.each(function () { 117 | var $more, $text, id, itemCount, moreBaseOffset, idDuplicates = 0; 118 | 119 | itemCount = (options.itemSelector ? $(options.itemSelector, this) : $(this).children()).length; 120 | 121 | if (options.id) { 122 | id = options.id; 123 | while ($('#' + id).length) { 124 | idDuplicates += 1; 125 | id = options.id + '-' + idDuplicates; 126 | } 127 | } else { 128 | idCount += 1; 129 | id = 'loadmore-' + idCount; 130 | } 131 | 132 | if (options.useExistingButton) { 133 | $more = $(options.useExistingButton); 134 | 135 | options.text = $more.text(); 136 | 137 | moreBaseOffset = options.interpretUrl ? options.interpretUrl($more.get(0), itemCount, options): undefined; 138 | 139 | if (moreBaseOffset) { 140 | options.baseOffset = moreBaseOffset; 141 | } 142 | } else { 143 | $more = $('', { 144 | 'id' : id, 145 | 'class' : options.className, 146 | 'href' : '#' 147 | }); 148 | 149 | $text = $('', {'class' : 'text'}); 150 | $text.appendTo($more) 151 | .text(maybeCall(options.text, $text[0])); 152 | } 153 | 154 | $more.data('loadmore-options', options); 155 | $more.data('loadmore-container', this); 156 | 157 | if (!options.useOffset) { 158 | $more.data('loadmore-page', options.page); 159 | } 160 | 161 | if (!options.useExistingButton) { 162 | if (options.rowsPerPage !== false && itemCount < options.rowsPerPage) { 163 | return; 164 | } 165 | 166 | $more.appendTo(this); 167 | } 168 | 169 | $more.click(moreClick); 170 | 171 | if (supportsHistory && options.useHistoryAPI && window.history.state && window.history.state.loadmore && window.history.state.loadmore[id]) { 172 | update.call($more[0], window.history.state.loadmore[id]); 173 | } 174 | }); 175 | 176 | return this; 177 | }; 178 | 179 | $.fn.loadmore.defaults = { 180 | id : null, 181 | className : 'more', 182 | useExistingButton: false, 183 | text : 'More', 184 | loadingText : 'Loading', 185 | page : 0, 186 | rowsPerPage : false, 187 | maxPageCount : false, 188 | pageParam : 'page', 189 | pageStartParam : 'start', 190 | filterResult: '*', 191 | complete : false, 192 | useHistoryAPI : true, 193 | useOffset : false, 194 | baseOffset: 0, 195 | processUrl: false, 196 | interpretUrl: function (loc, itemCount, options) { 197 | var result; 198 | 199 | loc.search.substr(1).split('&').some(function (pair) { 200 | pair = pair.split('='); 201 | if (pair[0] === options.pageParam) { 202 | result = parseInt(pair[1]) + options.baseOffset; 203 | return true; 204 | } else if (pair[0] === options.pageStartParam) { 205 | result = parseInt(pair[1]) + options.baseOffset - itemCount; 206 | return true; 207 | } 208 | }); 209 | 210 | return result; 211 | } 212 | }; 213 | 214 | // Below partly taken from jquery.pjax.js 215 | supportsHistory = 216 | window.history && window.history.pushState && window.history.replaceState 217 | // History API isn't reliable on iOS yet and Safari and Chrome doesn't implement history.state 218 | && !navigator.userAgent.match(/(iPod|iPhone|iPad|Safari\/|Chrome\/|WebApps\/.+CFNetwork)/); 219 | 220 | if (supportsHistory) { 221 | // Below taken from jquery.pjax.js 222 | // Add the state property to jQuery's event object so we can use it in 223 | // $(window).bind('popstate') 224 | if ($.inArray('state', $.event.props) < 0) { 225 | $.event.props.push('state'); 226 | } 227 | 228 | $(window).on('popstate', function (event) { 229 | var id, $elem, state = (event.state || {}).loadmore || {}; 230 | for (id in state) { 231 | if (state.hasOwnProperty(id)) { 232 | $elem = $('#' + id); 233 | if ($elem.length) { 234 | update.call($elem[0], state[id]); 235 | } 236 | } 237 | } 238 | }); 239 | } 240 | 241 | $.loadmore = { 242 | supportsHistory : supportsHistory, 243 | removeHistoryState : function (id) { 244 | if (supportsHistory && window.history.state && window.history.state.loadmore && window.history.state.loadmore[id]) { 245 | var state = window.history.state; 246 | delete state.loadmore[id]; 247 | window.history.replaceState(state, document.title); 248 | } 249 | } 250 | }; 251 | }(jQuery)); 252 | --------------------------------------------------------------------------------