1 |
|
2 | import { Ajax } from './bunny.ajax';
|
3 | import { Template } from './bunny.template';
|
4 | import { Pagination } from './Pagination';
|
5 | import { BunnyURL } from './url';
|
6 | import { ready, addEventOnce, makeAccessible, parseTemplate} from './utils/DOM';
|
7 | import { pushCallbackToElement, callElementCallbacks, initObjectExtensions } from './utils/core';
|
8 |
|
9 | export const DataTableConfig = {
|
10 | tagName: 'datatable',
|
11 | attrUrl: 'url',
|
12 | attrTemplate: 'template',
|
13 |
|
14 | tagNamePagination: 'pagination',
|
15 | tagNameStats: 'stats',
|
16 |
|
17 | classNameAsc: 'arrow-down',
|
18 | classNameDesc: 'arrow-up',
|
19 |
|
20 | perPage: 15,
|
21 | paginationLimit: 7,
|
22 |
|
23 | ajaxHeaders: []
|
24 | };
|
25 |
|
26 | export const DataTableUI = {
|
27 |
|
28 | Config: DataTableConfig,
|
29 | Template: Template,
|
30 |
|
31 | getSearchInput(datatable, name) {
|
32 | return datatable.querySelector('[name="' + name + '"]') || false;
|
33 | },
|
34 |
|
35 | getColumn(datatable, name) {
|
36 | return this.getTable(datatable).querySelector('[pid="' + name + '"]') || false;
|
37 | },
|
38 |
|
39 | getAllSearchInputs(datatable) {
|
40 | return datatable.querySelectorAll('input, select');
|
41 | },
|
42 |
|
43 | getTable(datatable) {
|
44 | return datatable.getElementsByTagName('table')[0];
|
45 | },
|
46 |
|
47 | getOrderCells(datatable) {
|
48 | return datatable.querySelectorAll('th[pid]');
|
49 | },
|
50 |
|
51 | getPagination(datatable) {
|
52 | return datatable.getElementsByTagName(this.Config.tagNamePagination)[0];
|
53 | },
|
54 |
|
55 | getStats(datatable) {
|
56 | return datatable.getElementsByTagName(this.Config.tagNameStats)[0];
|
57 | },
|
58 |
|
59 | removeRows(datatable) {
|
60 | const table = this.getTable(datatable);
|
61 | const rowCount = table.rows.length;
|
62 | for (let i = rowCount - 1; i > 0; i--) {
|
63 | table.deleteRow(i);
|
64 | }
|
65 | },
|
66 |
|
67 | insertRows(datatable, rowsData, templateId) {
|
68 | const tpl = document.getElementById(templateId);
|
69 | if (tpl.tagName === 'TEMPLATE') {
|
70 | this.getTable(datatable).appendChild(parseTemplate(templateId, rowsData));
|
71 | } else {
|
72 | this.Template.insertAll(templateId, rowsData, this.getTable(datatable));
|
73 | }
|
74 | },
|
75 |
|
76 | clearAllColumnsOrder(thCell) {
|
77 | const thCells = thCell.parentNode.querySelectorAll('th');
|
78 | [].forEach.call(thCells, cell => {
|
79 | cell.classList.remove(this.Config.classNameAsc);
|
80 | cell.classList.remove(this.Config.classNameDesc);
|
81 | });
|
82 | },
|
83 |
|
84 | setColumnAsc(thCell) {
|
85 | this.clearAllColumnsOrder(thCell);
|
86 | thCell.classList.add(this.Config.classNameAsc);
|
87 | },
|
88 |
|
89 | setColumnDesc(thCell) {
|
90 | this.clearAllColumnsOrder(thCell);
|
91 | thCell.classList.add(this.Config.classNameDesc);
|
92 | },
|
93 |
|
94 | isColumnAsc(thCell) {
|
95 | return thCell.classList.contains(this.Config.classNameAsc);
|
96 | },
|
97 |
|
98 | isColumnDesc(thCell) {
|
99 | return thCell.classList.contains(this.Config.classNameDesc);
|
100 | },
|
101 |
|
102 | };
|
103 |
|
104 | export const DataTable = {
|
105 |
|
106 | Config: DataTableConfig,
|
107 | UI: DataTableUI,
|
108 |
|
109 | Ajax: Ajax,
|
110 | Pagination: Pagination,
|
111 |
|
112 | init(datatable) {
|
113 | if (datatable.__bunny_datatable === undefined) {
|
114 | datatable.__bunny_datatable = {};
|
115 | } else {
|
116 | return false;
|
117 | }
|
118 |
|
119 | let page = BunnyURL.getParam('page');
|
120 | if (page === undefined) {
|
121 | page = 1;
|
122 | }
|
123 |
|
124 | this.addEvents(datatable);
|
125 |
|
126 | initObjectExtensions(this, datatable);
|
127 |
|
128 | const orderData = this.getOrderDataFromURL(datatable);
|
129 | if (orderData['order_by'] !== undefined) {
|
130 | const thCell = this.UI.getColumn(datatable, orderData['order_by']);
|
131 | if (orderData['order_rule'] === 'asc') {
|
132 | this.UI.setColumnAsc(thCell);
|
133 | } else {
|
134 | this.UI.setColumnDesc(thCell);
|
135 | }
|
136 | }
|
137 |
|
138 | this.setARIA(datatable);
|
139 |
|
140 | this.changePage(datatable, page, this.getSearchAndOrderDataFromURL(datatable));
|
141 |
|
142 | return true;
|
143 | },
|
144 |
|
145 | initAll() {
|
146 | ready( () => {
|
147 | [].forEach.call(document.getElementsByTagName(this.Config.tagName), datatable => {
|
148 | this.init(datatable);
|
149 | })
|
150 | });
|
151 | },
|
152 |
|
153 | isInitiated(datatable) {
|
154 | return datatable.__bunny_datatable !== undefined;
|
155 | },
|
156 |
|
157 |
|
158 |
|
159 | getTemplateId(datatable) {
|
160 | return datatable.getAttribute(this.Config.attrTemplate);
|
161 | },
|
162 |
|
163 | getAjaxUrl(datatable) {
|
164 | return datatable.getAttribute(this.Config.attrUrl);
|
165 | },
|
166 |
|
167 | getAjaxHeaders() {
|
168 | let headers = {};
|
169 | DataTableConfig.ajaxHeaders.forEach(header => {
|
170 | const parts = header.split(': ');
|
171 | headers[parts[0]] = parts[1];
|
172 | });
|
173 | return headers;
|
174 | },
|
175 |
|
176 | addAjaxHeader(header) {
|
177 | this.Config.ajaxHeaders.push(header);
|
178 | },
|
179 |
|
180 | getSearchData(datatable) {
|
181 | const searchInputs = this.UI.getAllSearchInputs(datatable);
|
182 | const data = {};
|
183 | [].forEach.call(searchInputs, searchInput => {
|
184 | if (searchInput && searchInput.value.length > 0) {
|
185 | data[searchInput.name] = searchInput.value;
|
186 | }
|
187 | });
|
188 | return data;
|
189 | },
|
190 |
|
191 | getSearchDataFromURL(datatable, url = window.location.href) {
|
192 | const urlParams = BunnyURL.getParams(url);
|
193 | let data = {};
|
194 | for (let k in urlParams) {
|
195 | if (k !== 'page') {
|
196 | const input = this.UI.getSearchInput(datatable, k);
|
197 | if (input) {
|
198 | input.value = urlParams[k];
|
199 | data[k] = urlParams[k];
|
200 | }
|
201 | }
|
202 | }
|
203 | return data;
|
204 | },
|
205 |
|
206 |
|
207 | getOrderData(datatable) {
|
208 | const data = {};
|
209 | const thCells = this.UI.getOrderCells(datatable);
|
210 | for (let k = 0; k < thCells.length; k++) {
|
211 | const thCell = thCells[k];
|
212 | if (this.UI.isColumnAsc(thCell)) {
|
213 | data['order_by'] = thCell.getAttribute('pid');
|
214 | data['order_rule'] = 'asc';
|
215 | break;
|
216 | } else if (this.UI.isColumnDesc(thCell)) {
|
217 | data['order_by'] = thCell.getAttribute('pid');
|
218 | data['order_rule'] = 'desc';
|
219 | break;
|
220 | }
|
221 | }
|
222 | return data;
|
223 | },
|
224 |
|
225 | getOrderDataFromURL(datatable, url = window.location.href) {
|
226 | const urlParam = BunnyURL.getParam('order_by', url);
|
227 | let data = {};
|
228 | if (urlParam) {
|
229 | data['order_by'] = urlParam;
|
230 | data['order_rule'] = BunnyURL.getParam('order_rule', url);
|
231 | }
|
232 | return data;
|
233 | },
|
234 |
|
235 |
|
236 | getSearchAndOrderData(datatable) {
|
237 | return Object.assign(this.getSearchData(datatable), this.getOrderData(datatable));
|
238 | },
|
239 |
|
240 | getSearchAndOrderDataFromURL(datatable) {
|
241 | return Object.assign(this.getSearchDataFromURL(datatable), this.getOrderDataFromURL(datatable));
|
242 | },
|
243 |
|
244 |
|
245 | getDataUrl(datatable, page, urlParams = {}) {
|
246 | let url = this.Pagination.addPageParamToUrl(this.getAjaxUrl(datatable), page);
|
247 | url = BunnyURL.setParams(urlParams, url);
|
248 | return url;
|
249 | },
|
250 |
|
251 |
|
252 |
|
253 | addEvents(datatable) {
|
254 | const searchInputs = this.UI.getAllSearchInputs(datatable);
|
255 | [].forEach.call(searchInputs, searchInput => {
|
256 | const eventName = searchInput.tagName === 'INPUT' ? 'input' : 'change';
|
257 | addEventOnce(searchInput, eventName, () => {
|
258 | this.update(datatable, this.getDataUrl(datatable, 1, this.getSearchAndOrderData(datatable)));
|
259 | });
|
260 | });
|
261 |
|
262 | const thCells = this.UI.getOrderCells(datatable);
|
263 | [].forEach.call(thCells, thCell => {
|
264 | thCell.addEventListener('click', () => {
|
265 | if (this.UI.isColumnAsc(thCell)) {
|
266 | this.UI.setColumnDesc(thCell);
|
267 | this.update(datatable, this.getDataUrl(datatable, this.getPage(), this.getSearchAndOrderData(datatable)));
|
268 | } else if (this.UI.isColumnDesc(thCell)) {
|
269 | this.UI.clearAllColumnsOrder(thCell);
|
270 | this.update(datatable, this.getDataUrl(datatable, this.getPage(), this.getSearchData(datatable)));
|
271 | } else {
|
272 | this.UI.setColumnAsc(thCell);
|
273 | this.update(datatable, this.getDataUrl(datatable, this.getPage(), this.getSearchAndOrderData(datatable)));
|
274 | }
|
275 | });
|
276 | });
|
277 | },
|
278 |
|
279 | setARIA(datatable) {
|
280 | const thCells = this.UI.getOrderCells(datatable);
|
281 | [].forEach.call(thCells, thCell => {
|
282 | makeAccessible(thCell);
|
283 | });
|
284 | },
|
285 |
|
286 | attachPaginationEventHandlers(datatable) {
|
287 | const pg = this.UI.getPagination(datatable);
|
288 | this.Pagination.onItemClick(pg, (page, url) => {
|
289 | this.changePage(datatable, page);
|
290 | });
|
291 | },
|
292 |
|
293 | callHandlers(datatable, data) {
|
294 | callElementCallbacks(datatable, 'datatable_redraw', (cb) => {
|
295 | cb(data);
|
296 | })
|
297 | },
|
298 |
|
299 | callBeforeHandlers(datatable, data) {
|
300 | callElementCallbacks(datatable, 'datatable_before_redraw', (cb) => {
|
301 | cb(data);
|
302 | })
|
303 | },
|
304 |
|
305 | onBeforeRedraw(datatable, callback) {
|
306 | pushCallbackToElement(datatable, 'datatable_before_redraw', callback);
|
307 | },
|
308 |
|
309 | onRedraw(datatable, callback) {
|
310 | pushCallbackToElement(datatable, 'datatable_redraw', callback);
|
311 | },
|
312 |
|
313 |
|
314 |
|
315 | fetchData(datatable, url) {
|
316 | return new Promise(callback => {
|
317 | this.Ajax.get(url, data => {
|
318 | data = JSON.parse(data);
|
319 | callback(data);
|
320 | }, {}, this.getAjaxHeaders());
|
321 | });
|
322 | },
|
323 |
|
324 | changePage(datatable, page, data = null) {
|
325 | if (data === null) {
|
326 | data = this.getSearchAndOrderData(datatable);
|
327 | }
|
328 | this.update(datatable, this.getDataUrl(datatable, page, data));
|
329 | },
|
330 |
|
331 | getPage() {
|
332 | const page = BunnyURL.getParam('page');
|
333 | if (page) {
|
334 | return page;
|
335 | }
|
336 | return 1;
|
337 | },
|
338 |
|
339 | |
340 |
|
341 |
|
342 |
|
343 | updateURL(datatable, url) {
|
344 | let newURL = window.location.href;
|
345 |
|
346 | const page = BunnyURL.getParam('page', url);
|
347 | if (page > 1) {
|
348 | newURL = BunnyURL.setParam('page', page);
|
349 | } else if (BunnyURL.hasParam('page', newURL)) {
|
350 | newURL = BunnyURL.removeParam('page', newURL);
|
351 | }
|
352 |
|
353 | const orderBy = BunnyURL.getParam('order_by', url);
|
354 | if (orderBy) {
|
355 | newURL = BunnyURL.setParam('order_by', orderBy, newURL);
|
356 | newURL = BunnyURL.setParam('order_rule', BunnyURL.getParam('order_rule', url), newURL);
|
357 | }
|
358 |
|
359 | const searchInputs = this.UI.getAllSearchInputs(datatable);
|
360 | [].forEach.call(searchInputs, searchInput => {
|
361 | const searchParam = searchInput.name;
|
362 | const search = BunnyURL.getParam(searchParam, url);
|
363 | if (search && search !== '') {
|
364 | newURL = BunnyURL.setParam(searchParam, search, newURL);
|
365 | } else if (BunnyURL.hasParam(searchParam, newURL)) {
|
366 | newURL = BunnyURL.removeParam(searchParam, newURL);
|
367 | }
|
368 | });
|
369 |
|
370 | if (newURL !== window.location.href) {
|
371 | window.history.replaceState('', '', newURL);
|
372 | }
|
373 | },
|
374 |
|
375 | update(datatable, url) {
|
376 | this.callBeforeHandlers(datatable);
|
377 | this.UI.getTable(datatable).classList.remove('in');
|
378 | this.fetchData(datatable, url).then(data => {
|
379 | const pg = this.UI.getPagination(datatable);
|
380 | const Pagination = this.Pagination;
|
381 | Pagination.initOrUpdate(pg, data, this.getSearchAndOrderData(datatable));
|
382 | this.attachPaginationEventHandlers(datatable);
|
383 | const stats = this.UI.getStats(datatable);
|
384 | if (stats !== undefined) {
|
385 | stats.textContent = Pagination.getStats(pg);
|
386 | }
|
387 | this.UI.removeRows(datatable);
|
388 | this.UI.insertRows(datatable, data.data, this.getTemplateId(datatable));
|
389 | this.UI.getTable(datatable).classList.add('in');
|
390 | this.updateURL(datatable, url);
|
391 | this.callHandlers(datatable, data);
|
392 | });
|
393 | }
|
394 |
|
395 | };
|
396 |
|
397 | DataTable.initAll();
|