MDL-61138 javascript: add paged content widget
[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',
28 'core/paged_content_pages'
29],
30function(
31 $,
32 Templates,
33 Notification,
34 PagedContent
35) {
36 var TEMPLATES = {
37 PAGED_CONTENT: 'core/paged_content'
38 };
39
40 /**
41 * Build the context to render the paging bar template with based on the number
42 * of pages to show.
43 *
44 * @param {int} numberOfPages How many pages to have in the paging bar.
45 * @param {int} itemsPerPage How many items will be shown per page.
46 * @return {object} The template context.
47 */
48 var buildPagingBarTemplateContext = function(numberOfPages, itemsPerPage) {
49 var context = {
50 "itemsperpage": itemsPerPage,
51 "previous": {},
52 "next": {},
53 "pages": []
54 };
55
56 for (var i = 1; i <= numberOfPages; i++) {
57 var page = {
58 number: i,
59 page: "" + i,
60 };
61
62 // Make the first page active by default.
63 if (i === 1) {
64 page.active = true;
65 }
66
67 context.pages.push(page);
68 }
69
70 return context;
71 };
72
73 /**
74 * Build the context to render the paging dropdown template with based on the number
75 * of pages to show and items per page.
76 *
77 * This control is rendered with a gradual increase of the items per page to
78 * limit the number of pages in the dropdown. Each page will show twice as much
79 * as the previous page (except for the first two pages).
80 *
81 * For example:
82 * Number of pages = 3
83 * Items per page = 25
84 * Would render a dropdown will 4 options:
85 * 25
86 * 50
87 * 100
88 * All
89 *
90 * @param {int} numberOfPages How many options to have in the dropdown.
91 * @param {int} itemsPerPage How many items will be shown per page.
92 * @param {object} config Configuration options provided by the client.
93 * @return {object} The template context.
94 */
95 var buildPagingDropdownTemplateContext = function(numberOfPages, itemsPerPage, config) {
96 var context = {
97 options: []
98 };
99
100 var totalItems = 0;
101 var lastIncrease = 0;
102 var maxPages = numberOfPages;
103
104 if (config.hasOwnProperty('maxPages')) {
105 maxPages = config.maxPages;
106 }
107
108 for (var i = 1; i <= maxPages; i++) {
109 var itemCount = 0;
110
111 if (i <= 2) {
112 itemCount = itemsPerPage;
113 lastIncrease = itemsPerPage;
114 } else {
115 lastIncrease = lastIncrease * 2;
116 itemCount = lastIncrease;
117 }
118
119 totalItems += itemCount;
120 var option = {
121 itemcount: itemCount,
122 content: totalItems
123 };
124
125 // Make the first option active by default.
126 if (i === 1) {
127 option.active = true;
128 }
129
130 context.options.push(option);
131 }
132
133 return context;
134 };
135
136 /**
137 * Build the context to render the paged content template with based on the number
138 * of pages to show, items per page, and configuration option.
139 *
140 * By default the code will render a paging bar for the paging controls unless
141 * otherwise specified in the provided config.
142 *
143 * @param {int} numberOfPages How many pages to have.
144 * @param {int} itemsPerPage How many items will be shown per page.
145 * @param {object} config Configuration options provided by the client.
146 * @return {object} The template context.
147 */
148 var buildTemplateContext = function(numberOfPages, itemsPerPage, config) {
149 var context = {
150 pagingbar: false,
151 pagingdropdown: false,
152 skipjs: true
153 };
154
155 if (config.hasOwnProperty('dropdown') && config.dropdown) {
156 context.pagingdropdown = buildPagingDropdownTemplateContext(numberOfPages, itemsPerPage, config);
157 } else {
158 context.pagingbar = buildPagingBarTemplateContext(numberOfPages, itemsPerPage);
159 }
160
161 return context;
162 };
163
164 /**
165 * Calculate the number of pages required for the given number of items and
166 * how many of each item should appear on a page.
167 *
168 * @param {int} numberOfItems How many items in total.
169 * @param {int} itemsPerPage How many items will be shown per page.
170 * @return {int} The number of pages required.
171 */
172 var calculateNumberOfPages = function(numberOfItems, itemsPerPage) {
173 var numberOfPages = 1;
174
175 if (numberOfItems > 0) {
176 var partial = numberOfItems % itemsPerPage;
177
178 if (partial) {
179 numberOfItems -= partial;
180 numberOfPages = (numberOfItems / itemsPerPage) + 1;
181 } else {
182 numberOfPages = numberOfItems / itemsPerPage;
183 }
184 }
185
186 return numberOfPages;
187 };
188
189 /**
190 * Create a paged content widget where the complete list of items is not loaded
191 * up front but will instead be loaded by an ajax request (or similar).
192 *
193 * The client code must provide a callback function which loads and renders the
194 * items for each page. See PagedContent.init for more details.
195 *
196 * The function will return a deferred that is resolved with a jQuery object
197 * for the HTML content and a string for the JavaScript.
198 *
199 * The current list of configuration options available are:
200 * dropdown {bool} True to render the page control as a dropdown (paging bar is default).
201 *
202 * @param {int} numberOfItems How many items are there in total.
203 * @param {int} itemsPerPage How many items will be shown per page.
204 * @param {function} renderPagesContentCallback Callback for loading and rendering the items.
205 * @param {object} config Configuration options provided by the client.
206 * @return {promise} Resolved with jQuery HTML and string JS.
207 */
208 var createFromAjax = function(numberOfItems, itemsPerPage, renderPagesContentCallback, config) {
209 if (typeof config == 'undefined') {
210 config = {};
211 }
212
213 var deferred = $.Deferred();
214 var numberOfPages = calculateNumberOfPages(numberOfItems, itemsPerPage);
215 var templateContext = buildTemplateContext(numberOfPages, itemsPerPage, config);
216
217 Templates.render(TEMPLATES.PAGED_CONTENT, templateContext)
218 .then(function(html, js) {
219 html = $(html);
220
221 var container = html;
222 var pagedContent = html.find(PagedContent.rootSelector);
223
224 PagedContent.init(pagedContent, container, renderPagesContentCallback);
225
226 deferred.resolve(html, js);
227 return;
228 })
229 .fail(function(exception) {
230 deferred.reject(exception);
231 })
232 .fail(Notification.exception);
233
234 return deferred;
235 };
236
237 /**
238 * Create a paged content widget where the complete list of items is loaded
239 * up front.
240 *
241 * The client code must provide a callback function which renders the
242 * items for each page. The callback will be provided with an array where each
243 * value in the array is a the list of items to render for the page.
244 *
245 * The function will return a deferred that is resolved with a jQuery object
246 * for the HTML content and a string for the JavaScript.
247 *
248 * The current list of configuration options available are:
249 * dropdown {bool} True to render the page control as a dropdown (paging bar is default).
250 *
251 * @param {array} contentItems The list of items to paginate.
252 * @param {int} itemsPerPage How many items will be shown per page.
253 * @param {function} renderContentCallback Callback for rendering the items for the page.
254 * @param {object} config Configuration options provided by the client.
255 * @return {promise} Resolved with jQuery HTML and string JS.
256 */
257 var createFromStaticList = function(contentItems, itemsPerPage, renderContentCallback, config) {
258 if (typeof config == 'undefined') {
259 config = {};
260 }
261
262 var numberOfItems = contentItems.length;
263 return createFromAjax(numberOfItems, itemsPerPage, function(pagesData) {
264 var contentToRender = [];
265 pagesData.forEach(function(pageData) {
266 var begin = pageData.offset;
267 var end = pageData.limit ? begin + pageData.limit : numberOfItems;
268 var items = contentItems.slice(begin, end);
269 contentToRender.push(items);
270 });
271
272 return renderContentCallback(contentToRender);
273 }, config);
274 };
275
276 return {
277 createFromAjax: createFromAjax,
278 createFromStaticList: createFromStaticList
279 };
280});