MDL-63457 block_myoverview: Update getters for enrolled courses
[moodle.git] / lib / amd / src / paged_content_factory.js
CommitLineData
4ab09853
RW
1// This file is part of Moodle - http://moodle.org/
2//
3// Moodle is free software: you can redistribute it and/or modify
4// it under the terms of the GNU General Public License as published by
5// the Free Software Foundation, either version 3 of the License, or
6// (at your option) any later version.
7//
8// Moodle is distributed in the hope that it will be useful,
9// but WITHOUT ANY WARRANTY; without even the implied warranty of
10// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11// GNU General Public License for more details.
12//
13// You should have received a copy of the GNU General Public License
14// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
15
16/**
17 * Factory to create a paged content widget.
18 *
19 * @module core/paged_content_factory
20 * @copyright 2018 Ryan Wyllie <ryan@moodle.com>
21 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
22 */
23define(
24[
25 'jquery',
26 'core/templates',
27 'core/notification',
2c13ae01 28 'core/paged_content'
4ab09853
RW
29],
30function(
31 $,
32 Templates,
33 Notification,
34 PagedContent
35) {
36 var TEMPLATES = {
37 PAGED_CONTENT: 'core/paged_content'
38 };
39
2c13ae01
RW
40 var DEFAULT = {
41 ITEMS_PER_PAGE_SINGLE: 25,
42 ITEMS_PER_PAGE_ARRAY: [25, 50, 100, 0],
43 MAX_PAGES: 3
44 };
45
4ab09853 46 /**
2c13ae01
RW
47 * Get the default context to render the paged content mustache
48 * template.
4ab09853 49 *
2c13ae01 50 * @return {object}
4ab09853 51 */
2c13ae01
RW
52 var getDefaultTemplateContext = function() {
53 return {
54 pagingbar: false,
55 pagingdropdown: false,
56 skipjs: true,
57 ignorecontrolwhileloading: true,
58 controlplacementbottom: false
4ab09853 59 };
2c13ae01
RW
60 };
61
62 /**
63 * Get the default context to render the paging bar mustache template.
64 *
65 * @return {object}
66 */
67 var getDefaultPagingBarTemplateContext = function() {
68 return {
69 showitemsperpageselector: false,
70 itemsperpage: 35,
71 previous: true,
72 next: true,
73 activepagenumber: 1,
74 hidecontrolonsinglepage: true,
75 pages: []
76 };
77 };
78
79 /**
80 * Calculate the number of pages required for the given number of items and
81 * how many of each item should appear on a page.
82 *
83 * @param {Number} numberOfItems How many items in total.
84 * @param {Number} itemsPerPage How many items will be shown per page.
85 * @return {Number} The number of pages required.
86 */
87 var calculateNumberOfPages = function(numberOfItems, itemsPerPage) {
88 var numberOfPages = 1;
89
90 if (numberOfItems > 0) {
91 var partial = numberOfItems % itemsPerPage;
92
93 if (partial) {
94 numberOfItems -= partial;
95 numberOfPages = (numberOfItems / itemsPerPage) + 1;
96 } else {
97 numberOfPages = numberOfItems / itemsPerPage;
98 }
99 }
100
101 return numberOfPages;
102 };
103
104 /**
105 * Build the context for the paging bar template when we have a known number
106 * of items.
107 *
108 * @param {Number} numberOfItems How many items in total.
109 * @param {Number} itemsPerPage How many items will be shown per page.
110 * @return {object} Mustache template
111 */
112 var buildPagingBarTemplateContextKnownLength = function(numberOfItems, itemsPerPage) {
113 if (itemsPerPage === null) {
114 itemsPerPage = DEFAULT.ITEMS_PER_PAGE_SINGLE;
115 }
116
117 if ($.isArray(itemsPerPage)) {
118 // If we're given a total number of pages then we don't support a variable
119 // set of items per page so just use the first one.
120 itemsPerPage = itemsPerPage[0];
121 }
122
123 var context = getDefaultPagingBarTemplateContext();
124 context.itemsperpage = itemsPerPage;
125 var numberOfPages = calculateNumberOfPages(numberOfItems, itemsPerPage);
4ab09853
RW
126
127 for (var i = 1; i <= numberOfPages; i++) {
128 var page = {
129 number: i,
130 page: "" + i,
131 };
132
133 // Make the first page active by default.
134 if (i === 1) {
135 page.active = true;
136 }
137
138 context.pages.push(page);
139 }
140
141 return context;
142 };
143
144 /**
2c13ae01
RW
145 * Convert the itemsPerPage value into a format applicable for the mustache template.
146 * The given value can be either a single integer or an array of integers / objects.
147 *
148 * E.g.
149 * In: [5, 10]
150 * out: [{value: 5, active: true}, {value: 10, active: false}]
151 *
152 * In: [5, {value: 10, active: true}]
153 * Out: [{value: 5, active: false}, {value: 10, active: true}]
154 *
155 * In: [{value: 5, active: false}, {value: 10, active: true}]
156 * Out: [{value: 5, active: false}, {value: 10, active: true}]
157 *
158 * @param {int|int[]} itemsPerPage Options for number of items per page.
159 * @return {int|array}
160 */
161 var buildItemsPerPagePagingBarContext = function(itemsPerPage) {
162 if ($.isArray(itemsPerPage)) {
163 // Convert the array into a format accepted by the template.
164 var context = itemsPerPage.map(function(num) {
165 if (typeof num === 'number') {
166 // If the item is just a plain number then convert it into
167 // an object with value and active keys.
168 return {
169 value: num,
170 active: false
171 };
172 } else {
173 // Otherwise we assume the caller has specified things correctly.
174 return num;
175 }
176 });
177
178 var activeItems = context.filter(function(item) {
179 return item.active;
180 });
181
182 // Default the first item to active if one hasn't been specified.
183 if (!activeItems.length) {
184 context[0].active = true;
185 }
186
187 return context;
188 } else {
189 return itemsPerPage;
190 }
191 };
192
193 /**
194 * Build the context for the paging bar template when we have an unknown
195 * number of items.
196 *
197 * @param {Number} itemsPerPage How many items will be shown per page.
198 * @return {object} Mustache template
199 */
200 var buildPagingBarTemplateContextUnknownLength = function(itemsPerPage) {
201 if (itemsPerPage === null) {
202 itemsPerPage = DEFAULT.ITEMS_PER_PAGE_ARRAY;
203 }
204
205 var context = getDefaultPagingBarTemplateContext();
206 context.itemsperpage = buildItemsPerPagePagingBarContext(itemsPerPage);
207 context.showitemsperpageselector = $.isArray(itemsPerPage);
208
209 return context;
210 };
211
212 /**
213 * Build the context to render the paging bar template with based on the number
214 * of pages to show.
215 *
216 * @param {int|null} numberOfItems How many items are there total.
217 * @param {int|null} itemsPerPage How many items will be shown per page.
218 * @return {object} The template context.
219 */
220 var buildPagingBarTemplateContext = function(numberOfItems, itemsPerPage) {
221 if (numberOfItems) {
222 return buildPagingBarTemplateContextKnownLength(numberOfItems, itemsPerPage);
223 } else {
224 return buildPagingBarTemplateContextUnknownLength(itemsPerPage);
225 }
226 };
227
228 /**
229 * Build the context to render the paging dropdown template based on the number
4ab09853
RW
230 * of pages to show and items per page.
231 *
232 * This control is rendered with a gradual increase of the items per page to
233 * limit the number of pages in the dropdown. Each page will show twice as much
234 * as the previous page (except for the first two pages).
235 *
2c13ae01
RW
236 * By default there will only be 4 pages shown (including the "All" option) unless
237 * a different number of pages is defined using the maxPages config value.
238 *
4ab09853 239 * For example:
4ab09853
RW
240 * Items per page = 25
241 * Would render a dropdown will 4 options:
242 * 25
243 * 50
244 * 100
245 * All
246 *
2c13ae01 247 * @param {Number} itemsPerPage How many items will be shown per page.
4ab09853
RW
248 * @param {object} config Configuration options provided by the client.
249 * @return {object} The template context.
250 */
2c13ae01
RW
251 var buildPagingDropdownTemplateContext = function(itemsPerPage, config) {
252 if (itemsPerPage === null) {
253 itemsPerPage = DEFAULT.ITEMS_PER_PAGE_SINGLE;
254 }
255
256 if ($.isArray(itemsPerPage)) {
257 // If we're given an array for the items per page, rather than a number,
258 // then just use that as the options for the dropdown.
259 return {
260 options: itemsPerPage
261 };
262 }
263
4ab09853
RW
264 var context = {
265 options: []
266 };
267
268 var totalItems = 0;
269 var lastIncrease = 0;
2c13ae01 270 var maxPages = DEFAULT.MAX_PAGES;
4ab09853
RW
271
272 if (config.hasOwnProperty('maxPages')) {
273 maxPages = config.maxPages;
274 }
275
276 for (var i = 1; i <= maxPages; i++) {
277 var itemCount = 0;
278
279 if (i <= 2) {
280 itemCount = itemsPerPage;
281 lastIncrease = itemsPerPage;
282 } else {
283 lastIncrease = lastIncrease * 2;
284 itemCount = lastIncrease;
285 }
286
287 totalItems += itemCount;
288 var option = {
289 itemcount: itemCount,
290 content: totalItems
291 };
292
293 // Make the first option active by default.
294 if (i === 1) {
295 option.active = true;
296 }
297
298 context.options.push(option);
299 }
300
301 return context;
302 };
303
304 /**
305 * Build the context to render the paged content template with based on the number
306 * of pages to show, items per page, and configuration option.
307 *
308 * By default the code will render a paging bar for the paging controls unless
309 * otherwise specified in the provided config.
310 *
2c13ae01
RW
311 * @param {int|null} numberOfItems Total number of items.
312 * @param {int|null|array} itemsPerPage How many items will be shown per page.
4ab09853
RW
313 * @param {object} config Configuration options provided by the client.
314 * @return {object} The template context.
315 */
2c13ae01
RW
316 var buildTemplateContext = function(numberOfItems, itemsPerPage, config) {
317 var context = getDefaultTemplateContext();
318
319 if (config.hasOwnProperty('ignoreControlWhileLoading')) {
320 context.ignorecontrolwhileloading = config.ignoreControlWhileLoading;
321 }
322
323 if (config.hasOwnProperty('controlPlacementBottom')) {
324 context.controlplacementbottom = config.controlPlacementBottom;
325 }
326
327 if (config.hasOwnProperty('hideControlOnSinglePage')) {
328 context.hidecontrolonsinglepage = config.hideControlOnSinglePage;
329 }
330
331 if (config.hasOwnProperty('ariaLabels')) {
332 context.arialabels = config.ariaLabels;
333 }
4ab09853
RW
334
335 if (config.hasOwnProperty('dropdown') && config.dropdown) {
2c13ae01 336 context.pagingdropdown = buildPagingDropdownTemplateContext(itemsPerPage, config);
4ab09853 337 } else {
2c13ae01 338 context.pagingbar = buildPagingBarTemplateContext(numberOfItems, itemsPerPage);
4ab09853
RW
339 }
340
341 return context;
342 };
343
344 /**
2c13ae01
RW
345 * Create a paged content widget where the complete list of items is not loaded
346 * up front but will instead be loaded by an ajax request (or similar).
4ab09853 347 *
2c13ae01
RW
348 * The client code must provide a callback function which loads and renders the
349 * items for each page. See PagedContent.init for more details.
350 *
351 * The function will return a deferred that is resolved with a jQuery object
352 * for the HTML content and a string for the JavaScript.
353 *
354 * The current list of configuration options available are:
355 * dropdown {bool} True to render the page control as a dropdown (paging bar is default).
356 * maxPages {Number} The maximum number of pages to show in the dropdown (only works with dropdown option)
357 * ignoreControlWhileLoading {bool} Disable the pagination controls while loading a page (default to true)
358 * controlPlacementBottom {bool} Render controls under paged content (default to false)
359 *
360 * @param {function} renderPagesContentCallback Callback for loading and rendering the items.
361 * @param {object} config Configuration options provided by the client.
362 * @return {promise} Resolved with jQuery HTML and string JS.
4ab09853 363 */
2c13ae01
RW
364 var create = function(renderPagesContentCallback, config) {
365 return createWithTotalAndLimit(null, null, renderPagesContentCallback, config);
366 };
4ab09853 367
2c13ae01
RW
368 /**
369 * Create a paged content widget where the complete list of items is not loaded
370 * up front but will instead be loaded by an ajax request (or similar).
371 *
372 * The client code must provide a callback function which loads and renders the
373 * items for each page. See PagedContent.init for more details.
374 *
375 * The function will return a deferred that is resolved with a jQuery object
376 * for the HTML content and a string for the JavaScript.
377 *
378 * The current list of configuration options available are:
379 * dropdown {bool} True to render the page control as a dropdown (paging bar is default).
380 * maxPages {Number} The maximum number of pages to show in the dropdown (only works with dropdown option)
381 * ignoreControlWhileLoading {bool} Disable the pagination controls while loading a page (default to true)
382 * controlPlacementBottom {bool} Render controls under paged content (default to false)
383 *
384 * @param {int|array|null} itemsPerPage How many items will be shown per page.
385 * @param {function} renderPagesContentCallback Callback for loading and rendering the items.
386 * @param {object} config Configuration options provided by the client.
387 * @return {promise} Resolved with jQuery HTML and string JS.
388 */
fd68f5a9
BB
389 var createWithLimit = function(itemsPerPage, renderPagesContentCallback, config, jumpto) {
390 return createWithTotalAndLimit(null, itemsPerPage, renderPagesContentCallback, config, jumpto);
4ab09853
RW
391 };
392
393 /**
394 * Create a paged content widget where the complete list of items is not loaded
395 * up front but will instead be loaded by an ajax request (or similar).
396 *
397 * The client code must provide a callback function which loads and renders the
398 * items for each page. See PagedContent.init for more details.
399 *
400 * The function will return a deferred that is resolved with a jQuery object
401 * for the HTML content and a string for the JavaScript.
402 *
403 * The current list of configuration options available are:
404 * dropdown {bool} True to render the page control as a dropdown (paging bar is default).
2c13ae01
RW
405 * maxPages {Number} The maximum number of pages to show in the dropdown (only works with dropdown option)
406 * ignoreControlWhileLoading {bool} Disable the pagination controls while loading a page (default to true)
407 * controlPlacementBottom {bool} Render controls under paged content (default to false)
4ab09853 408 *
2c13ae01
RW
409 * @param {int|null} numberOfItems How many items are there in total.
410 * @param {int|array|null} itemsPerPage How many items will be shown per page.
4ab09853
RW
411 * @param {function} renderPagesContentCallback Callback for loading and rendering the items.
412 * @param {object} config Configuration options provided by the client.
413 * @return {promise} Resolved with jQuery HTML and string JS.
414 */
fd68f5a9 415 var createWithTotalAndLimit = function(numberOfItems, itemsPerPage, renderPagesContentCallback, config, jumpto) {
2c13ae01 416 config = config || {};
4ab09853
RW
417
418 var deferred = $.Deferred();
2c13ae01 419 var templateContext = buildTemplateContext(numberOfItems, itemsPerPage, config);
4ab09853
RW
420
421 Templates.render(TEMPLATES.PAGED_CONTENT, templateContext)
422 .then(function(html, js) {
423 html = $(html);
424
425 var container = html;
4ab09853 426
fd68f5a9 427 PagedContent.init(container, renderPagesContentCallback, jumpto);
4ab09853
RW
428
429 deferred.resolve(html, js);
430 return;
431 })
432 .fail(function(exception) {
433 deferred.reject(exception);
434 })
435 .fail(Notification.exception);
436
2c13ae01 437 return deferred.promise();
4ab09853
RW
438 };
439
440 /**
441 * Create a paged content widget where the complete list of items is loaded
442 * up front.
443 *
444 * The client code must provide a callback function which renders the
445 * items for each page. The callback will be provided with an array where each
446 * value in the array is a the list of items to render for the page.
447 *
448 * The function will return a deferred that is resolved with a jQuery object
449 * for the HTML content and a string for the JavaScript.
450 *
451 * The current list of configuration options available are:
452 * dropdown {bool} True to render the page control as a dropdown (paging bar is default).
2c13ae01
RW
453 * maxPages {Number} The maximum number of pages to show in the dropdown (only works with dropdown option)
454 * ignoreControlWhileLoading {bool} Disable the pagination controls while loading a page (default to true)
455 * controlPlacementBottom {bool} Render controls under paged content (default to false)
4ab09853
RW
456 *
457 * @param {array} contentItems The list of items to paginate.
2c13ae01 458 * @param {Number} itemsPerPage How many items will be shown per page.
4ab09853
RW
459 * @param {function} renderContentCallback Callback for rendering the items for the page.
460 * @param {object} config Configuration options provided by the client.
461 * @return {promise} Resolved with jQuery HTML and string JS.
462 */
463 var createFromStaticList = function(contentItems, itemsPerPage, renderContentCallback, config) {
464 if (typeof config == 'undefined') {
465 config = {};
466 }
467
468 var numberOfItems = contentItems.length;
2c13ae01 469 return createWithTotalAndLimit(numberOfItems, itemsPerPage, function(pagesData) {
4ab09853
RW
470 var contentToRender = [];
471 pagesData.forEach(function(pageData) {
472 var begin = pageData.offset;
473 var end = pageData.limit ? begin + pageData.limit : numberOfItems;
474 var items = contentItems.slice(begin, end);
475 contentToRender.push(items);
476 });
477
478 return renderContentCallback(contentToRender);
479 }, config);
480 };
481
482 return {
2c13ae01
RW
483 create: create,
484 createWithLimit: createWithLimit,
485 createWithTotalAndLimit: createWithTotalAndLimit,
486 createFromStaticList: createFromStaticList,
487 // Backwards compatibility just in case anyone was using this.
488 createFromAjax: createWithTotalAndLimit
4ab09853
RW
489 };
490});