@bennerinformatics/ember-fw-table 2.0.16 → 2.0.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/addon/components/fw-cell-permission-icon.js +11 -0
- package/addon/components/fw-column-sortable.js +1 -1
- package/addon/components/fw-pagination-wrapper.js +470 -470
- package/addon/components/fw-row-toggle.js +1 -1
- package/addon/components/fw-table-sortable.js +390 -390
- package/addon/templates/components/fw-cell-action.hbs +2 -2
- package/addon/templates/components/fw-cell-permission-icon.hbs +9 -0
- package/addon/templates/components/fw-delete-modal.hbs +12 -12
- package/addon/templates/components/fw-pagination-wrapper.hbs +74 -74
- package/addon/templates/components/fw-table-expanded-row.hbs +23 -23
- package/addon/templates/components/fw-table-resort.hbs +4 -4
- package/addon/templates/components/fw-table-sortable.hbs +76 -76
- package/app/breakpoints.js +1 -0
- package/app/components/fw-cell-permission-icon.js +1 -0
- package/app/initializers/responsive.js +1 -0
- package/package.json +63 -63
|
@@ -1,470 +1,470 @@
|
|
|
1
|
-
import Component from '@ember/component';
|
|
2
|
-
import {computed} from '@ember/object';
|
|
3
|
-
import {empty, filterBy} from '@ember/object/computed';
|
|
4
|
-
import {inject} from '@ember/service';
|
|
5
|
-
import {isEmpty, isNone} from '@ember/utils';
|
|
6
|
-
import {handleAjaxError} from '@bennerinformatics/ember-fw/utils/error';
|
|
7
|
-
import exportTable from '@bennerinformatics/ember-fw-table/utils/export';
|
|
8
|
-
import Table from 'ember-light-table';
|
|
9
|
-
import RSVP from 'rsvp';
|
|
10
|
-
import layout from '../templates/components/fw-pagination-wrapper';
|
|
11
|
-
|
|
12
|
-
export default Component.extend({
|
|
13
|
-
layout,
|
|
14
|
-
ajax: inject(),
|
|
15
|
-
config: inject(),
|
|
16
|
-
media: inject(),
|
|
17
|
-
notifications: inject(),
|
|
18
|
-
store: inject(),
|
|
19
|
-
tagName: '',
|
|
20
|
-
|
|
21
|
-
/*
|
|
22
|
-
* Parameters
|
|
23
|
-
*/
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Name of the model in search
|
|
27
|
-
* @type {String}
|
|
28
|
-
* @prop modelName
|
|
29
|
-
*/
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Default sort order for the table
|
|
33
|
-
* @type {String}
|
|
34
|
-
*/
|
|
35
|
-
defaultSortKey: null,
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Number of entries per page
|
|
39
|
-
* @type {Number}
|
|
40
|
-
*/
|
|
41
|
-
entriesPerPage: 100,
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Gets the title of this table at the given time
|
|
45
|
-
* @return {String} Title of this table
|
|
46
|
-
*/
|
|
47
|
-
getTitle() {
|
|
48
|
-
return 'Table';
|
|
49
|
-
},
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Gets a list of table columns for exporting
|
|
53
|
-
* @return {Array} Array of table columns
|
|
54
|
-
*/
|
|
55
|
-
getExportColumns() {},
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Called when the table search button is pressed
|
|
59
|
-
*/
|
|
60
|
-
onSearch() {},
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Called to delete the full page of entries
|
|
64
|
-
* Should be passed in with a function
|
|
65
|
-
*/
|
|
66
|
-
deletePage: undefined,
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Determines permission for deleteTablePermission for fw-table-sortable
|
|
70
|
-
*/
|
|
71
|
-
deletePagePermission: true,
|
|
72
|
-
/**
|
|
73
|
-
* Makes a query object based on the search fields
|
|
74
|
-
* @param {Boolean} count If true, counting
|
|
75
|
-
* @param {Number} page If defined, page number for a page search
|
|
76
|
-
* @param {Boolean} export If true, exporting
|
|
77
|
-
* @return {Object} Query object
|
|
78
|
-
*/
|
|
79
|
-
makeQuery(/* {count, page, export} */) {
|
|
80
|
-
return {};
|
|
81
|
-
},
|
|
82
|
-
|
|
83
|
-
/* Table generation properties */
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* Gets a list of table columns for the results. If null, yields for table
|
|
87
|
-
* @return {Array} Array of table columns
|
|
88
|
-
*/
|
|
89
|
-
getTableColumns() {
|
|
90
|
-
return null;
|
|
91
|
-
},
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* Text to show for empty tables. If null, hides on empty
|
|
95
|
-
* @type {String}
|
|
96
|
-
*/
|
|
97
|
-
emptyText: null,
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* Actions to pass into the table
|
|
101
|
-
* @type {Object}
|
|
102
|
-
*/
|
|
103
|
-
tableActions: null,
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* Class names for the wrapper around the loading spinner and the table
|
|
107
|
-
* @type {String}
|
|
108
|
-
*/
|
|
109
|
-
tableWrapperClass: '',
|
|
110
|
-
|
|
111
|
-
/*
|
|
112
|
-
* Search properties
|
|
113
|
-
*/
|
|
114
|
-
|
|
115
|
-
/**
|
|
116
|
-
* Table title at this time
|
|
117
|
-
* @type {String}
|
|
118
|
-
*/
|
|
119
|
-
currentTitle: null,
|
|
120
|
-
|
|
121
|
-
/**
|
|
122
|
-
* If true, currently doing the main search
|
|
123
|
-
* @type {Boolean}
|
|
124
|
-
*/
|
|
125
|
-
searchingTable: false,
|
|
126
|
-
|
|
127
|
-
/**
|
|
128
|
-
* If true, show export page button
|
|
129
|
-
* @type {Boolean}
|
|
130
|
-
*/
|
|
131
|
-
showExport: true,
|
|
132
|
-
/**
|
|
133
|
-
* Number of pages being searched
|
|
134
|
-
* @type {Number}
|
|
135
|
-
*/
|
|
136
|
-
pagesSearching: 0,
|
|
137
|
-
|
|
138
|
-
/**
|
|
139
|
-
* Array of entries, indexes are the pages
|
|
140
|
-
* @type {Array}
|
|
141
|
-
*/
|
|
142
|
-
pageEntries: null,
|
|
143
|
-
|
|
144
|
-
/**
|
|
145
|
-
* Currently selected page
|
|
146
|
-
* @type {Number}
|
|
147
|
-
*/
|
|
148
|
-
page: 1,
|
|
149
|
-
|
|
150
|
-
/**
|
|
151
|
-
* Total number of entries
|
|
152
|
-
* @type {Number}
|
|
153
|
-
*/
|
|
154
|
-
count: null,
|
|
155
|
-
|
|
156
|
-
/**
|
|
157
|
-
* Query last time createQuery was called
|
|
158
|
-
* @type {[type]}
|
|
159
|
-
*/
|
|
160
|
-
lastQuery: null,
|
|
161
|
-
|
|
162
|
-
/**
|
|
163
|
-
* Current sort order for the table
|
|
164
|
-
* @type {String}
|
|
165
|
-
* @prop currentSortKey
|
|
166
|
-
*/
|
|
167
|
-
currentSortKey: null,
|
|
168
|
-
|
|
169
|
-
/*
|
|
170
|
-
* Computed properties
|
|
171
|
-
*/
|
|
172
|
-
|
|
173
|
-
/**
|
|
174
|
-
* Array index for the selected page
|
|
175
|
-
* @type {Number}
|
|
176
|
-
*/
|
|
177
|
-
index: computed('page', function() {
|
|
178
|
-
return this.get('page') - 1;
|
|
179
|
-
}),
|
|
180
|
-
|
|
181
|
-
/**
|
|
182
|
-
* Array of entries at the current page
|
|
183
|
-
* @type {Array}
|
|
184
|
-
*/
|
|
185
|
-
currentEntries: computed('pageEntries.[]', 'index', function() {
|
|
186
|
-
let entries = this.get('pageEntries');
|
|
187
|
-
if (isNone(entries)) {
|
|
188
|
-
return [];
|
|
189
|
-
}
|
|
190
|
-
return entries.objectAt(this.get('index'));
|
|
191
|
-
}),
|
|
192
|
-
|
|
193
|
-
/**
|
|
194
|
-
* Filtered entries, removing deleted entries
|
|
195
|
-
* @type {Array}
|
|
196
|
-
*/
|
|
197
|
-
filteredEntries: filterBy('currentEntries', 'isDeleted', false),
|
|
198
|
-
|
|
199
|
-
/**
|
|
200
|
-
* Current table sort key based on given properties
|
|
201
|
-
* @type {String}
|
|
202
|
-
*/
|
|
203
|
-
tableSortKey: computed('defaultSortKey', 'currentSortKey', function() {
|
|
204
|
-
let current = this.get('currentSortKey');
|
|
205
|
-
if (isEmpty(current)) {
|
|
206
|
-
return `${this.get('defaultSortKey')}:desc`;
|
|
207
|
-
}
|
|
208
|
-
return current;
|
|
209
|
-
}),
|
|
210
|
-
|
|
211
|
-
/**
|
|
212
|
-
* Number of pages available
|
|
213
|
-
* @type {Number}
|
|
214
|
-
*/
|
|
215
|
-
totalPages: computed('count', 'entriesPerPage', function() {
|
|
216
|
-
let count = this.get('count');
|
|
217
|
-
if (isNone(count)) {
|
|
218
|
-
return 0;
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
return Math.ceil(count / this.get('entriesPerPage'));
|
|
222
|
-
}),
|
|
223
|
-
|
|
224
|
-
/**
|
|
225
|
-
* True if we have multiple pages
|
|
226
|
-
* @type {Boolean}
|
|
227
|
-
*/
|
|
228
|
-
showPages: computed('totalPages', function() {
|
|
229
|
-
return this.get('totalPages') > 1;
|
|
230
|
-
}),
|
|
231
|
-
|
|
232
|
-
/**
|
|
233
|
-
* Maximum number of pages to show in the pagination component
|
|
234
|
-
* @type {Number}
|
|
235
|
-
*/
|
|
236
|
-
maxPageButtons: computed('media.{isMobile,isTablet}', function() {
|
|
237
|
-
let media = this.get('media');
|
|
238
|
-
if (media.get('isMobile')) {
|
|
239
|
-
return 3;
|
|
240
|
-
}
|
|
241
|
-
if (media.get('isTablet')) {
|
|
242
|
-
return 5;
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
return 7;
|
|
246
|
-
}),
|
|
247
|
-
|
|
248
|
-
/**
|
|
249
|
-
* Gets the serverside route to use for this model name
|
|
250
|
-
* @type {String}
|
|
251
|
-
*/
|
|
252
|
-
routeName: computed('modelName', function() {
|
|
253
|
-
let name = this.get('modelName');
|
|
254
|
-
return this.get('store').adapterFor(name).pathForType(name);
|
|
255
|
-
}),
|
|
256
|
-
|
|
257
|
-
/**
|
|
258
|
-
* Final piece of title for table
|
|
259
|
-
* @type {String}
|
|
260
|
-
*/
|
|
261
|
-
tableSuffix: computed('count', 'currentEntries', 'index', function() {
|
|
262
|
-
let count = this.get('count');
|
|
263
|
-
let entries = this.get('currentEntries');
|
|
264
|
-
if (isEmpty(entries)) {
|
|
265
|
-
return `${count} entries`;
|
|
266
|
-
}
|
|
267
|
-
return `${entries.get('length')} of ${count} entries`;
|
|
268
|
-
}),
|
|
269
|
-
|
|
270
|
-
/**
|
|
271
|
-
* Title for the table
|
|
272
|
-
* @type {String}
|
|
273
|
-
*/
|
|
274
|
-
fullTableTitle: computed('currentTitle', 'tableSuffix', function() {
|
|
275
|
-
return `${this.get('currentTitle')} - ${this.get('tableSuffix')}`;
|
|
276
|
-
}),
|
|
277
|
-
|
|
278
|
-
/** If true, hide the table when empty */
|
|
279
|
-
hideEmpty: empty('emptyText'),
|
|
280
|
-
|
|
281
|
-
/* Functions */
|
|
282
|
-
|
|
283
|
-
/**
|
|
284
|
-
* Queries the serverside to get the total record count
|
|
285
|
-
* @return {Promise} Promise that resolves to a number
|
|
286
|
-
*/
|
|
287
|
-
queryCount() {
|
|
288
|
-
// fetch standard query
|
|
289
|
-
let query = this.makeQuery({count: true});
|
|
290
|
-
query.count = true;
|
|
291
|
-
|
|
292
|
-
// make request
|
|
293
|
-
let url = this.get('config').formUrl(this.get('routeName'));
|
|
294
|
-
return this.get('ajax').request(url, {data: query}).then((({count}) => count)).catch(handleAjaxError.bind(this));
|
|
295
|
-
},
|
|
296
|
-
|
|
297
|
-
/**
|
|
298
|
-
* Query for settign a new sort order
|
|
299
|
-
* @param {Number} page Page number to start
|
|
300
|
-
* @param {String} sortKey New sort order
|
|
301
|
-
* @param {Boolean} ascending If true, sorts ascending, false descending
|
|
302
|
-
* @return {Promise} Promise that resolves to a entry array
|
|
303
|
-
*/
|
|
304
|
-
querySort(page, sortKey, ascending) {
|
|
305
|
-
let query = this.get('lastQuery');
|
|
306
|
-
|
|
307
|
-
// set sort key stuff if present
|
|
308
|
-
if (!isNone(ascending)) {
|
|
309
|
-
query.ascending = ascending;
|
|
310
|
-
}
|
|
311
|
-
if (!isEmpty(sortKey)) {
|
|
312
|
-
query.sortKey = sortKey;
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
// set limits on query
|
|
316
|
-
let entriesPerPage = this.get('entriesPerPage');
|
|
317
|
-
query.limit = entriesPerPage;
|
|
318
|
-
query.offset = (page - 1) * entriesPerPage;
|
|
319
|
-
|
|
320
|
-
// make promise
|
|
321
|
-
return RSVP.resolve(this.get('store').query(this.get('modelName'), query)).catch(handleAjaxError.bind(this));
|
|
322
|
-
},
|
|
323
|
-
|
|
324
|
-
/**
|
|
325
|
-
* Fetches the entries for the given page number
|
|
326
|
-
* @param {Number} page Page to fetch
|
|
327
|
-
* @return {Promise} Promise that resolves to an entry array
|
|
328
|
-
*/
|
|
329
|
-
queryPage(page) {
|
|
330
|
-
// same as sort, but handles the entries
|
|
331
|
-
return this.querySort(page).then((entries) => {
|
|
332
|
-
this.get('pageEntries')[page - 1] = entries;
|
|
333
|
-
return entries;
|
|
334
|
-
});
|
|
335
|
-
},
|
|
336
|
-
|
|
337
|
-
/**
|
|
338
|
-
* Gets all entries for the given query
|
|
339
|
-
* @return {Promise} Promise that resolves to entries
|
|
340
|
-
*/
|
|
341
|
-
queryAll() {
|
|
342
|
-
let query = this.makeQuery({export: true});
|
|
343
|
-
return RSVP.resolve(this.get('store').query(this.get('modelName'), query)).catch(handleAjaxError.bind(this));
|
|
344
|
-
},
|
|
345
|
-
|
|
346
|
-
actions: {
|
|
347
|
-
/* Search buttons */
|
|
348
|
-
|
|
349
|
-
/**
|
|
350
|
-
* Called when the search button is pressed
|
|
351
|
-
* @return {Promise} Promise that resolves after searching
|
|
352
|
-
*/
|
|
353
|
-
search() {
|
|
354
|
-
// TODO: canSearch?
|
|
355
|
-
// else a title getter
|
|
356
|
-
|
|
357
|
-
// start search and clean up old data
|
|
358
|
-
this.setProperties({
|
|
359
|
-
currentTitle: this.getTitle(),
|
|
360
|
-
// search data
|
|
361
|
-
lastQuery: this.makeQuery({page: 1}),
|
|
362
|
-
pageEntries: [],
|
|
363
|
-
page: 1,
|
|
364
|
-
// searching keys
|
|
365
|
-
searchingTable: true,
|
|
366
|
-
pagesSearching: 0,
|
|
367
|
-
tableColumns: this.getTableColumns()
|
|
368
|
-
});
|
|
369
|
-
|
|
370
|
-
// search callback
|
|
371
|
-
this.onSearch();
|
|
372
|
-
|
|
373
|
-
// make two requests: one for the total count and one for the first 100 entries
|
|
374
|
-
return RSVP.hash({
|
|
375
|
-
count: this.queryCount(),
|
|
376
|
-
entries: this.queryPage(1)
|
|
377
|
-
}).then(({count}) => {
|
|
378
|
-
// entries already set as part of queryPage
|
|
379
|
-
this.setProperties({
|
|
380
|
-
count,
|
|
381
|
-
searchingTable: false
|
|
382
|
-
});
|
|
383
|
-
}).catch(() => {
|
|
384
|
-
// request failed, clean up data
|
|
385
|
-
this.setProperties({
|
|
386
|
-
// search data
|
|
387
|
-
pageEntries: null,
|
|
388
|
-
count: 0,
|
|
389
|
-
// searching keys
|
|
390
|
-
searchingTable: false
|
|
391
|
-
});
|
|
392
|
-
});
|
|
393
|
-
},
|
|
394
|
-
|
|
395
|
-
/**
|
|
396
|
-
* Called when the export button is clicked to export all data
|
|
397
|
-
* @return {Promise} Promise that resolves after exporting the table
|
|
398
|
-
*/
|
|
399
|
-
export() {
|
|
400
|
-
// TODO: canSearch?
|
|
401
|
-
|
|
402
|
-
// build table for export
|
|
403
|
-
let table = new Table(this.getExportColumns());
|
|
404
|
-
return this.queryAll().then((entries) => {
|
|
405
|
-
table.setRows(entries.sortBy(this.get('defaultSortKey')).reverse());
|
|
406
|
-
exportTable(table, `${this.getTitle()} - All Entries`);
|
|
407
|
-
}).catch(handleAjaxError.bind(this));
|
|
408
|
-
},
|
|
409
|
-
|
|
410
|
-
/* Pagination */
|
|
411
|
-
|
|
412
|
-
/**
|
|
413
|
-
* Called when a page button is clicked to switch pages
|
|
414
|
-
* @param {Number} page New page number to set
|
|
415
|
-
*/
|
|
416
|
-
setPage(page) {
|
|
417
|
-
// clamp page number
|
|
418
|
-
let max = this.get('totalPages');
|
|
419
|
-
if (page < 1) {
|
|
420
|
-
page = 1;
|
|
421
|
-
} else if (page > max) {
|
|
422
|
-
page = max;
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
// if we havve entries at the page number, use those
|
|
426
|
-
// if missing, query them
|
|
427
|
-
if (isNone(this.get('pageEntries').objectAt(page - 1))) {
|
|
428
|
-
this.incrementProperty('pagesSearching');
|
|
429
|
-
this.set('page', page);
|
|
430
|
-
this.queryPage(page).then(() => {
|
|
431
|
-
// entries set in promise logic
|
|
432
|
-
this.decrementProperty('pagesSearching');
|
|
433
|
-
});
|
|
434
|
-
} else {
|
|
435
|
-
this.set('page', page);
|
|
436
|
-
}
|
|
437
|
-
},
|
|
438
|
-
|
|
439
|
-
/* Sorting */
|
|
440
|
-
|
|
441
|
-
/**
|
|
442
|
-
* Resorts the entry by the given column
|
|
443
|
-
* @param {Column} column Column to use for sorting
|
|
444
|
-
* @param {String} sortKey String to use for sorting in the column
|
|
445
|
-
* @return {Promise} Promise that resolves to entries
|
|
446
|
-
*/
|
|
447
|
-
sortColumn(column, sortKey) {
|
|
448
|
-
// if the sort key is unchanged, do nothing
|
|
449
|
-
if (sortKey === this.get('tableSortKey')) {
|
|
450
|
-
return RSVP.resolve();
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
// mark that we are sorting, column properties do not add desc
|
|
454
|
-
column.set('sorting', true);
|
|
455
|
-
|
|
456
|
-
// search for data
|
|
457
|
-
let page = this.get('page');
|
|
458
|
-
return this.querySort(page, column.searchKey || column.valuePath, column.ascending).then((entries) => {
|
|
459
|
-
// set entries to new list
|
|
460
|
-
let pageEntries = [];
|
|
461
|
-
pageEntries[page - 1] = entries;
|
|
462
|
-
this.setProperties({pageEntries, currentSortKey: sortKey});
|
|
463
|
-
|
|
464
|
-
// mark that we are done sorting
|
|
465
|
-
column.set('sorting', false);
|
|
466
|
-
return entries;
|
|
467
|
-
});
|
|
468
|
-
}
|
|
469
|
-
}
|
|
470
|
-
});
|
|
1
|
+
import Component from '@ember/component';
|
|
2
|
+
import {computed} from '@ember/object';
|
|
3
|
+
import {empty, filterBy} from '@ember/object/computed';
|
|
4
|
+
import {inject} from '@ember/service';
|
|
5
|
+
import {isEmpty, isNone} from '@ember/utils';
|
|
6
|
+
import {handleAjaxError} from '@bennerinformatics/ember-fw/utils/error';
|
|
7
|
+
import exportTable from '@bennerinformatics/ember-fw-table/utils/export';
|
|
8
|
+
import Table from 'ember-light-table';
|
|
9
|
+
import RSVP from 'rsvp';
|
|
10
|
+
import layout from '../templates/components/fw-pagination-wrapper';
|
|
11
|
+
|
|
12
|
+
export default Component.extend({
|
|
13
|
+
layout,
|
|
14
|
+
ajax: inject(),
|
|
15
|
+
config: inject(),
|
|
16
|
+
media: inject(),
|
|
17
|
+
notifications: inject(),
|
|
18
|
+
store: inject(),
|
|
19
|
+
tagName: '',
|
|
20
|
+
|
|
21
|
+
/*
|
|
22
|
+
* Parameters
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Name of the model in search
|
|
27
|
+
* @type {String}
|
|
28
|
+
* @prop modelName
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Default sort order for the table
|
|
33
|
+
* @type {String}
|
|
34
|
+
*/
|
|
35
|
+
defaultSortKey: null,
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Number of entries per page
|
|
39
|
+
* @type {Number}
|
|
40
|
+
*/
|
|
41
|
+
entriesPerPage: 100,
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Gets the title of this table at the given time
|
|
45
|
+
* @return {String} Title of this table
|
|
46
|
+
*/
|
|
47
|
+
getTitle() {
|
|
48
|
+
return 'Table';
|
|
49
|
+
},
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Gets a list of table columns for exporting
|
|
53
|
+
* @return {Array} Array of table columns
|
|
54
|
+
*/
|
|
55
|
+
getExportColumns() {},
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Called when the table search button is pressed
|
|
59
|
+
*/
|
|
60
|
+
onSearch() {},
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Called to delete the full page of entries
|
|
64
|
+
* Should be passed in with a function
|
|
65
|
+
*/
|
|
66
|
+
deletePage: undefined,
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Determines permission for deleteTablePermission for fw-table-sortable
|
|
70
|
+
*/
|
|
71
|
+
deletePagePermission: true,
|
|
72
|
+
/**
|
|
73
|
+
* Makes a query object based on the search fields
|
|
74
|
+
* @param {Boolean} count If true, counting
|
|
75
|
+
* @param {Number} page If defined, page number for a page search
|
|
76
|
+
* @param {Boolean} export If true, exporting
|
|
77
|
+
* @return {Object} Query object
|
|
78
|
+
*/
|
|
79
|
+
makeQuery(/* {count, page, export} */) {
|
|
80
|
+
return {};
|
|
81
|
+
},
|
|
82
|
+
|
|
83
|
+
/* Table generation properties */
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Gets a list of table columns for the results. If null, yields for table
|
|
87
|
+
* @return {Array} Array of table columns
|
|
88
|
+
*/
|
|
89
|
+
getTableColumns() {
|
|
90
|
+
return null;
|
|
91
|
+
},
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Text to show for empty tables. If null, hides on empty
|
|
95
|
+
* @type {String}
|
|
96
|
+
*/
|
|
97
|
+
emptyText: null,
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Actions to pass into the table
|
|
101
|
+
* @type {Object}
|
|
102
|
+
*/
|
|
103
|
+
tableActions: null,
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Class names for the wrapper around the loading spinner and the table
|
|
107
|
+
* @type {String}
|
|
108
|
+
*/
|
|
109
|
+
tableWrapperClass: '',
|
|
110
|
+
|
|
111
|
+
/*
|
|
112
|
+
* Search properties
|
|
113
|
+
*/
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Table title at this time
|
|
117
|
+
* @type {String}
|
|
118
|
+
*/
|
|
119
|
+
currentTitle: null,
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* If true, currently doing the main search
|
|
123
|
+
* @type {Boolean}
|
|
124
|
+
*/
|
|
125
|
+
searchingTable: false,
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* If true, show export page button
|
|
129
|
+
* @type {Boolean}
|
|
130
|
+
*/
|
|
131
|
+
showExport: true,
|
|
132
|
+
/**
|
|
133
|
+
* Number of pages being searched
|
|
134
|
+
* @type {Number}
|
|
135
|
+
*/
|
|
136
|
+
pagesSearching: 0,
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Array of entries, indexes are the pages
|
|
140
|
+
* @type {Array}
|
|
141
|
+
*/
|
|
142
|
+
pageEntries: null,
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Currently selected page
|
|
146
|
+
* @type {Number}
|
|
147
|
+
*/
|
|
148
|
+
page: 1,
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Total number of entries
|
|
152
|
+
* @type {Number}
|
|
153
|
+
*/
|
|
154
|
+
count: null,
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Query last time createQuery was called
|
|
158
|
+
* @type {[type]}
|
|
159
|
+
*/
|
|
160
|
+
lastQuery: null,
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Current sort order for the table
|
|
164
|
+
* @type {String}
|
|
165
|
+
* @prop currentSortKey
|
|
166
|
+
*/
|
|
167
|
+
currentSortKey: null,
|
|
168
|
+
|
|
169
|
+
/*
|
|
170
|
+
* Computed properties
|
|
171
|
+
*/
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Array index for the selected page
|
|
175
|
+
* @type {Number}
|
|
176
|
+
*/
|
|
177
|
+
index: computed('page', function() {
|
|
178
|
+
return this.get('page') - 1;
|
|
179
|
+
}),
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Array of entries at the current page
|
|
183
|
+
* @type {Array}
|
|
184
|
+
*/
|
|
185
|
+
currentEntries: computed('pageEntries.[]', 'index', function() {
|
|
186
|
+
let entries = this.get('pageEntries');
|
|
187
|
+
if (isNone(entries)) {
|
|
188
|
+
return [];
|
|
189
|
+
}
|
|
190
|
+
return entries.objectAt(this.get('index'));
|
|
191
|
+
}),
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Filtered entries, removing deleted entries
|
|
195
|
+
* @type {Array}
|
|
196
|
+
*/
|
|
197
|
+
filteredEntries: filterBy('currentEntries', 'isDeleted', false),
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Current table sort key based on given properties
|
|
201
|
+
* @type {String}
|
|
202
|
+
*/
|
|
203
|
+
tableSortKey: computed('defaultSortKey', 'currentSortKey', function() {
|
|
204
|
+
let current = this.get('currentSortKey');
|
|
205
|
+
if (isEmpty(current)) {
|
|
206
|
+
return `${this.get('defaultSortKey')}:desc`;
|
|
207
|
+
}
|
|
208
|
+
return current;
|
|
209
|
+
}),
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Number of pages available
|
|
213
|
+
* @type {Number}
|
|
214
|
+
*/
|
|
215
|
+
totalPages: computed('count', 'entriesPerPage', function() {
|
|
216
|
+
let count = this.get('count');
|
|
217
|
+
if (isNone(count)) {
|
|
218
|
+
return 0;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
return Math.ceil(count / this.get('entriesPerPage'));
|
|
222
|
+
}),
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* True if we have multiple pages
|
|
226
|
+
* @type {Boolean}
|
|
227
|
+
*/
|
|
228
|
+
showPages: computed('totalPages', function() {
|
|
229
|
+
return this.get('totalPages') > 1;
|
|
230
|
+
}),
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Maximum number of pages to show in the pagination component
|
|
234
|
+
* @type {Number}
|
|
235
|
+
*/
|
|
236
|
+
maxPageButtons: computed('media.{isMobile,isTablet}', function() {
|
|
237
|
+
let media = this.get('media');
|
|
238
|
+
if (media.get('isMobile')) {
|
|
239
|
+
return 3;
|
|
240
|
+
}
|
|
241
|
+
if (media.get('isTablet')) {
|
|
242
|
+
return 5;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
return 7;
|
|
246
|
+
}),
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Gets the serverside route to use for this model name
|
|
250
|
+
* @type {String}
|
|
251
|
+
*/
|
|
252
|
+
routeName: computed('modelName', function() {
|
|
253
|
+
let name = this.get('modelName');
|
|
254
|
+
return this.get('store').adapterFor(name).pathForType(name);
|
|
255
|
+
}),
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Final piece of title for table
|
|
259
|
+
* @type {String}
|
|
260
|
+
*/
|
|
261
|
+
tableSuffix: computed('count', 'currentEntries', 'index', function() {
|
|
262
|
+
let count = this.get('count');
|
|
263
|
+
let entries = this.get('currentEntries');
|
|
264
|
+
if (isEmpty(entries)) {
|
|
265
|
+
return `${count} entries`;
|
|
266
|
+
}
|
|
267
|
+
return `${entries.get('length')} of ${count} entries`;
|
|
268
|
+
}),
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Title for the table
|
|
272
|
+
* @type {String}
|
|
273
|
+
*/
|
|
274
|
+
fullTableTitle: computed('currentTitle', 'tableSuffix', function() {
|
|
275
|
+
return `${this.get('currentTitle')} - ${this.get('tableSuffix')}`;
|
|
276
|
+
}),
|
|
277
|
+
|
|
278
|
+
/** If true, hide the table when empty */
|
|
279
|
+
hideEmpty: empty('emptyText'),
|
|
280
|
+
|
|
281
|
+
/* Functions */
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Queries the serverside to get the total record count
|
|
285
|
+
* @return {Promise} Promise that resolves to a number
|
|
286
|
+
*/
|
|
287
|
+
queryCount() {
|
|
288
|
+
// fetch standard query
|
|
289
|
+
let query = this.makeQuery({count: true});
|
|
290
|
+
query.count = true;
|
|
291
|
+
|
|
292
|
+
// make request
|
|
293
|
+
let url = this.get('config').formUrl(this.get('routeName'));
|
|
294
|
+
return this.get('ajax').request(url, {data: query}).then((({count}) => count)).catch(handleAjaxError.bind(this));
|
|
295
|
+
},
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Query for settign a new sort order
|
|
299
|
+
* @param {Number} page Page number to start
|
|
300
|
+
* @param {String} sortKey New sort order
|
|
301
|
+
* @param {Boolean} ascending If true, sorts ascending, false descending
|
|
302
|
+
* @return {Promise} Promise that resolves to a entry array
|
|
303
|
+
*/
|
|
304
|
+
querySort(page, sortKey, ascending) {
|
|
305
|
+
let query = this.get('lastQuery');
|
|
306
|
+
|
|
307
|
+
// set sort key stuff if present
|
|
308
|
+
if (!isNone(ascending)) {
|
|
309
|
+
query.ascending = ascending;
|
|
310
|
+
}
|
|
311
|
+
if (!isEmpty(sortKey)) {
|
|
312
|
+
query.sortKey = sortKey;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// set limits on query
|
|
316
|
+
let entriesPerPage = this.get('entriesPerPage');
|
|
317
|
+
query.limit = entriesPerPage;
|
|
318
|
+
query.offset = (page - 1) * entriesPerPage;
|
|
319
|
+
|
|
320
|
+
// make promise
|
|
321
|
+
return RSVP.resolve(this.get('store').query(this.get('modelName'), query)).catch(handleAjaxError.bind(this));
|
|
322
|
+
},
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Fetches the entries for the given page number
|
|
326
|
+
* @param {Number} page Page to fetch
|
|
327
|
+
* @return {Promise} Promise that resolves to an entry array
|
|
328
|
+
*/
|
|
329
|
+
queryPage(page) {
|
|
330
|
+
// same as sort, but handles the entries
|
|
331
|
+
return this.querySort(page).then((entries) => {
|
|
332
|
+
this.get('pageEntries')[page - 1] = entries;
|
|
333
|
+
return entries;
|
|
334
|
+
});
|
|
335
|
+
},
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Gets all entries for the given query
|
|
339
|
+
* @return {Promise} Promise that resolves to entries
|
|
340
|
+
*/
|
|
341
|
+
queryAll() {
|
|
342
|
+
let query = this.makeQuery({export: true});
|
|
343
|
+
return RSVP.resolve(this.get('store').query(this.get('modelName'), query)).catch(handleAjaxError.bind(this));
|
|
344
|
+
},
|
|
345
|
+
|
|
346
|
+
actions: {
|
|
347
|
+
/* Search buttons */
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* Called when the search button is pressed
|
|
351
|
+
* @return {Promise} Promise that resolves after searching
|
|
352
|
+
*/
|
|
353
|
+
search() {
|
|
354
|
+
// TODO: canSearch?
|
|
355
|
+
// else a title getter
|
|
356
|
+
|
|
357
|
+
// start search and clean up old data
|
|
358
|
+
this.setProperties({
|
|
359
|
+
currentTitle: this.getTitle(),
|
|
360
|
+
// search data
|
|
361
|
+
lastQuery: this.makeQuery({page: 1}),
|
|
362
|
+
pageEntries: [],
|
|
363
|
+
page: 1,
|
|
364
|
+
// searching keys
|
|
365
|
+
searchingTable: true,
|
|
366
|
+
pagesSearching: 0,
|
|
367
|
+
tableColumns: this.getTableColumns()
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
// search callback
|
|
371
|
+
this.onSearch();
|
|
372
|
+
|
|
373
|
+
// make two requests: one for the total count and one for the first 100 entries
|
|
374
|
+
return RSVP.hash({
|
|
375
|
+
count: this.queryCount(),
|
|
376
|
+
entries: this.queryPage(1)
|
|
377
|
+
}).then(({count}) => {
|
|
378
|
+
// entries already set as part of queryPage
|
|
379
|
+
this.setProperties({
|
|
380
|
+
count,
|
|
381
|
+
searchingTable: false
|
|
382
|
+
});
|
|
383
|
+
}).catch(() => {
|
|
384
|
+
// request failed, clean up data
|
|
385
|
+
this.setProperties({
|
|
386
|
+
// search data
|
|
387
|
+
pageEntries: null,
|
|
388
|
+
count: 0,
|
|
389
|
+
// searching keys
|
|
390
|
+
searchingTable: false
|
|
391
|
+
});
|
|
392
|
+
});
|
|
393
|
+
},
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* Called when the export button is clicked to export all data
|
|
397
|
+
* @return {Promise} Promise that resolves after exporting the table
|
|
398
|
+
*/
|
|
399
|
+
export() {
|
|
400
|
+
// TODO: canSearch?
|
|
401
|
+
|
|
402
|
+
// build table for export
|
|
403
|
+
let table = new Table(this.getExportColumns());
|
|
404
|
+
return this.queryAll().then((entries) => {
|
|
405
|
+
table.setRows(entries.sortBy(this.get('defaultSortKey')).reverse());
|
|
406
|
+
exportTable(table, `${this.getTitle()} - All Entries`);
|
|
407
|
+
}).catch(handleAjaxError.bind(this));
|
|
408
|
+
},
|
|
409
|
+
|
|
410
|
+
/* Pagination */
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* Called when a page button is clicked to switch pages
|
|
414
|
+
* @param {Number} page New page number to set
|
|
415
|
+
*/
|
|
416
|
+
setPage(page) {
|
|
417
|
+
// clamp page number
|
|
418
|
+
let max = this.get('totalPages');
|
|
419
|
+
if (page < 1) {
|
|
420
|
+
page = 1;
|
|
421
|
+
} else if (page > max) {
|
|
422
|
+
page = max;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// if we havve entries at the page number, use those
|
|
426
|
+
// if missing, query them
|
|
427
|
+
if (isNone(this.get('pageEntries').objectAt(page - 1))) {
|
|
428
|
+
this.incrementProperty('pagesSearching');
|
|
429
|
+
this.set('page', page);
|
|
430
|
+
this.queryPage(page).then(() => {
|
|
431
|
+
// entries set in promise logic
|
|
432
|
+
this.decrementProperty('pagesSearching');
|
|
433
|
+
});
|
|
434
|
+
} else {
|
|
435
|
+
this.set('page', page);
|
|
436
|
+
}
|
|
437
|
+
},
|
|
438
|
+
|
|
439
|
+
/* Sorting */
|
|
440
|
+
|
|
441
|
+
/**
|
|
442
|
+
* Resorts the entry by the given column
|
|
443
|
+
* @param {Column} column Column to use for sorting
|
|
444
|
+
* @param {String} sortKey String to use for sorting in the column
|
|
445
|
+
* @return {Promise} Promise that resolves to entries
|
|
446
|
+
*/
|
|
447
|
+
sortColumn(column, sortKey) {
|
|
448
|
+
// if the sort key is unchanged, do nothing
|
|
449
|
+
if (sortKey === this.get('tableSortKey')) {
|
|
450
|
+
return RSVP.resolve();
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// mark that we are sorting, column properties do not add desc
|
|
454
|
+
column.set('sorting', true);
|
|
455
|
+
|
|
456
|
+
// search for data
|
|
457
|
+
let page = this.get('page');
|
|
458
|
+
return this.querySort(page, column.searchKey || column.valuePath, column.ascending).then((entries) => {
|
|
459
|
+
// set entries to new list
|
|
460
|
+
let pageEntries = [];
|
|
461
|
+
pageEntries[page - 1] = entries;
|
|
462
|
+
this.setProperties({pageEntries, currentSortKey: sortKey});
|
|
463
|
+
|
|
464
|
+
// mark that we are done sorting
|
|
465
|
+
column.set('sorting', false);
|
|
466
|
+
return entries;
|
|
467
|
+
});
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
});
|