@triptyk/ember-yeti-table 3.0.0
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/LICENSE.md +9 -0
- package/README.md +132 -0
- package/addon-main.cjs +4 -0
- package/declarations/themes/default-theme.d.ts +28 -0
- package/declarations/themes/default-theme.d.ts.map +1 -0
- package/declarations/utils/create-regex.d.ts +2 -0
- package/declarations/utils/create-regex.d.ts.map +1 -0
- package/declarations/utils/filtering-utils.d.ts +5 -0
- package/declarations/utils/filtering-utils.d.ts.map +1 -0
- package/declarations/utils/sorting-utils.d.ts +24 -0
- package/declarations/utils/sorting-utils.d.ts.map +1 -0
- package/dist/_app_/components/yeti-table/body.js +1 -0
- package/dist/_app_/components/yeti-table/header.js +1 -0
- package/dist/_app_/components/yeti-table/pagination.js +1 -0
- package/dist/_app_/components/yeti-table/table.js +1 -0
- package/dist/_app_/components/yeti-table/tbody/row/cell.js +1 -0
- package/dist/_app_/components/yeti-table/tbody/row.js +1 -0
- package/dist/_app_/components/yeti-table/tbody.js +1 -0
- package/dist/_app_/components/yeti-table/tfoot/row/cell.js +1 -0
- package/dist/_app_/components/yeti-table/tfoot/row.js +1 -0
- package/dist/_app_/components/yeti-table/tfoot.js +1 -0
- package/dist/_app_/components/yeti-table/thead/row/cell.js +1 -0
- package/dist/_app_/components/yeti-table/thead/row/column.js +1 -0
- package/dist/_app_/components/yeti-table/thead/row.js +1 -0
- package/dist/_app_/components/yeti-table/thead.js +1 -0
- package/dist/_app_/components/yeti-table.js +1 -0
- package/dist/_app_/themes/default-theme.js +1 -0
- package/dist/_app_/utils/create-regex.js +1 -0
- package/dist/_app_/utils/filtering-utils.js +1 -0
- package/dist/_app_/utils/sorting-utils.js +1 -0
- package/dist/components/yeti-table/body.js +37 -0
- package/dist/components/yeti-table/body.js.map +1 -0
- package/dist/components/yeti-table/header.js +30 -0
- package/dist/components/yeti-table/header.js.map +1 -0
- package/dist/components/yeti-table/pagination.js +86 -0
- package/dist/components/yeti-table/pagination.js.map +1 -0
- package/dist/components/yeti-table/table.js +38 -0
- package/dist/components/yeti-table/table.js.map +1 -0
- package/dist/components/yeti-table/tbody/row/cell.js +35 -0
- package/dist/components/yeti-table/tbody/row/cell.js.map +1 -0
- package/dist/components/yeti-table/tbody/row.js +47 -0
- package/dist/components/yeti-table/tbody/row.js.map +1 -0
- package/dist/components/yeti-table/tbody.js +20 -0
- package/dist/components/yeti-table/tbody.js.map +1 -0
- package/dist/components/yeti-table/tfoot/row/cell.js +31 -0
- package/dist/components/yeti-table/tfoot/row/cell.js.map +1 -0
- package/dist/components/yeti-table/tfoot/row.js +31 -0
- package/dist/components/yeti-table/tfoot/row.js.map +1 -0
- package/dist/components/yeti-table/tfoot.js +20 -0
- package/dist/components/yeti-table/tfoot.js.map +1 -0
- package/dist/components/yeti-table/thead/row/cell.js +24 -0
- package/dist/components/yeti-table/thead/row/cell.js.map +1 -0
- package/dist/components/yeti-table/thead/row/column.js +202 -0
- package/dist/components/yeti-table/thead/row/column.js.map +1 -0
- package/dist/components/yeti-table/thead/row.js +41 -0
- package/dist/components/yeti-table/thead/row.js.map +1 -0
- package/dist/components/yeti-table/thead.js +35 -0
- package/dist/components/yeti-table/thead.js.map +1 -0
- package/dist/components/yeti-table.js +618 -0
- package/dist/components/yeti-table.js.map +1 -0
- package/dist/themes/default-theme.js +51 -0
- package/dist/themes/default-theme.js.map +1 -0
- package/dist/utils/create-regex.js +34 -0
- package/dist/utils/create-regex.js.map +1 -0
- package/dist/utils/filtering-utils.js +55 -0
- package/dist/utils/filtering-utils.js.map +1 -0
- package/dist/utils/sorting-utils.js +93 -0
- package/dist/utils/sorting-utils.js.map +1 -0
- package/package.json +124 -0
- package/src/.gitkeep +0 -0
- package/src/components/yeti-table/body.gjs +94 -0
- package/src/components/yeti-table/header.gjs +54 -0
- package/src/components/yeti-table/pagination.gjs +155 -0
- package/src/components/yeti-table/table.gjs +34 -0
- package/src/components/yeti-table/tbody/row/cell.gjs +51 -0
- package/src/components/yeti-table/tbody/row.gjs +89 -0
- package/src/components/yeti-table/tbody.gjs +55 -0
- package/src/components/yeti-table/tfoot/row/cell.gjs +45 -0
- package/src/components/yeti-table/tfoot/row.gjs +44 -0
- package/src/components/yeti-table/tfoot.gjs +33 -0
- package/src/components/yeti-table/thead/row/cell.gjs +52 -0
- package/src/components/yeti-table/thead/row/column.gjs +260 -0
- package/src/components/yeti-table/thead/row.gjs +71 -0
- package/src/components/yeti-table/thead.gjs +45 -0
- package/src/components/yeti-table.gjs +780 -0
- package/src/themes/default-theme.js +53 -0
- package/src/utils/create-regex.js +65 -0
- package/src/utils/filtering-utils.js +75 -0
- package/src/utils/sorting-utils.js +108 -0
|
@@ -0,0 +1,780 @@
|
|
|
1
|
+
import { getOwner } from '@ember/application';
|
|
2
|
+
import { action, notifyPropertyChange } from '@ember/object';
|
|
3
|
+
import { later, schedule, scheduleOnce } from '@ember/runloop';
|
|
4
|
+
import { isEmpty, isPresent } from '@ember/utils';
|
|
5
|
+
import Component from '@glimmer/component';
|
|
6
|
+
import { tracked } from '@glimmer/tracking';
|
|
7
|
+
import merge from 'deepmerge';
|
|
8
|
+
import { use } from 'ember-resources';
|
|
9
|
+
import { trackedFunction } from 'reactiveweb/function';
|
|
10
|
+
import { keepLatest } from 'reactiveweb/keep-latest';
|
|
11
|
+
import { dedupeTracked, localCopy } from 'tracked-toolbox';
|
|
12
|
+
import { cached } from '@glimmer/tracking';
|
|
13
|
+
|
|
14
|
+
import DEFAULT_THEME from '../themes/default-theme.js';
|
|
15
|
+
import filterData from '../utils/filtering-utils.js';
|
|
16
|
+
import {
|
|
17
|
+
compareValues,
|
|
18
|
+
mergeSort,
|
|
19
|
+
sortMultiple,
|
|
20
|
+
} from '../utils/sorting-utils.js';
|
|
21
|
+
import Helper from '@ember/component/helper';
|
|
22
|
+
/**
|
|
23
|
+
The primary Yeti Table component. This component represents the root of the
|
|
24
|
+
table, and manages high level state of all of its subcomponents.
|
|
25
|
+
|
|
26
|
+
```hbs
|
|
27
|
+
<YetiTable @data={{this.data}} as |table|>
|
|
28
|
+
|
|
29
|
+
<table.header as |header|>
|
|
30
|
+
<header.column @prop="firstName">
|
|
31
|
+
First name
|
|
32
|
+
</header.column>
|
|
33
|
+
<header.column @prop="lastName">
|
|
34
|
+
Last name
|
|
35
|
+
</header.column>
|
|
36
|
+
<header.column @prop="points">
|
|
37
|
+
Points
|
|
38
|
+
</header.column>
|
|
39
|
+
</table.header>
|
|
40
|
+
|
|
41
|
+
<table.body/>
|
|
42
|
+
|
|
43
|
+
</YetiTable>
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
```hbs
|
|
47
|
+
<YetiTable @data={{this.data}} as |table|>
|
|
48
|
+
|
|
49
|
+
<table.thead as |thead|>
|
|
50
|
+
<thead.row as |row|>
|
|
51
|
+
<row.column @prop="firstName">
|
|
52
|
+
First name
|
|
53
|
+
</row.column>
|
|
54
|
+
<row.column @prop="lastName">
|
|
55
|
+
Last name
|
|
56
|
+
</row.column>
|
|
57
|
+
<row.column @prop="points">
|
|
58
|
+
Points
|
|
59
|
+
</row.column>
|
|
60
|
+
</thead.row>
|
|
61
|
+
</table.thead>
|
|
62
|
+
|
|
63
|
+
<table.body/>
|
|
64
|
+
|
|
65
|
+
</YetiTable>
|
|
66
|
+
```
|
|
67
|
+
@class YetiTable
|
|
68
|
+
@yield {object} table
|
|
69
|
+
@yield {Component} table.header the table header component (Single row in header)
|
|
70
|
+
@yield {Component} table.thead the table header component (Allows multiple rows in header)
|
|
71
|
+
@yield {Component} table.body the table body component
|
|
72
|
+
@yield {Component} table.tfoot the table footer component
|
|
73
|
+
@yield {Component} table.pagination the pagination controls component
|
|
74
|
+
@yield {object} table.actions an object that contains actions to interact with the table
|
|
75
|
+
@yield {object} table.paginationData object that represents the current pagination state
|
|
76
|
+
@yield {boolean} table.isLoading boolean that is `true` when data is being loaded
|
|
77
|
+
@yield {array} table.columns the columns on the table
|
|
78
|
+
@yield {array} table.visibleColumns the visible columns on the table
|
|
79
|
+
@yield {array} table.rows an array of all the rows yeti table knows of. In the sync case, it will contain the entire dataset. In the async case, it will be the same as `table.visibleRows`
|
|
80
|
+
@yield {number} table.totalRows the total number of rows on the table (regardless of pagination). Important argument in the async case to inform yeti table of the total number of rows in the dataset.
|
|
81
|
+
@yield {array} table.visibleRows the rendered rows on the table account for pagination, filtering, etc; when pagination is false, it will be the same length as totalRows
|
|
82
|
+
@yield {object} table.theme the theme being used
|
|
83
|
+
*/
|
|
84
|
+
// template imports
|
|
85
|
+
import { hash } from '@ember/helper';
|
|
86
|
+
import Table from './yeti-table/table.gjs';
|
|
87
|
+
import Header from './yeti-table/header.gjs';
|
|
88
|
+
import THead from './yeti-table/thead.gjs';
|
|
89
|
+
import Body from './yeti-table/body.gjs';
|
|
90
|
+
import TBody from './yeti-table/tbody.gjs';
|
|
91
|
+
import TFoot from './yeti-table/tfoot.gjs';
|
|
92
|
+
import Pagination from './yeti-table/pagination.gjs';
|
|
93
|
+
|
|
94
|
+
// bring ember-concurrency didCancel helper instead of
|
|
95
|
+
// including the whole dependency
|
|
96
|
+
const TASK_CANCELATION_NAME = 'TaskCancelation';
|
|
97
|
+
const didCancel = function (e) {
|
|
98
|
+
return e && e.name === TASK_CANCELATION_NAME;
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const getConfigWithDefault = function (key, defaultValue) {
|
|
102
|
+
return function () {
|
|
103
|
+
return this.config[key] ?? defaultValue;
|
|
104
|
+
};
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
// we keep `totalRows` updated manually in xwaan untracked property
|
|
108
|
+
// to allow the user to update it in a loadData call and avoid
|
|
109
|
+
// a re-run of the main tracked function
|
|
110
|
+
class UpdateTotalRows extends Helper {
|
|
111
|
+
compute(positional, { context }) {
|
|
112
|
+
context.totalRows = positional[0];
|
|
113
|
+
notifyPropertyChange(context, 'normalizedTotalRows');
|
|
114
|
+
notifyPropertyChange(context, 'paginationData');
|
|
115
|
+
return '';
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// we need some control of how we update the filter property, hence this modifier
|
|
120
|
+
// in this case, any falsy value will be considered as en empty string, which will then
|
|
121
|
+
// be deduped.
|
|
122
|
+
class UpdateFilter extends Helper {
|
|
123
|
+
compute(positional, { context }) {
|
|
124
|
+
context.filter = positional[0] || '';
|
|
125
|
+
return '';
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
class ProcessedData extends Helper {
|
|
130
|
+
compute(positional, { loadData, context }) {
|
|
131
|
+
let data = context.latestData ?? [];
|
|
132
|
+
|
|
133
|
+
if (!loadData) {
|
|
134
|
+
context.processData(data);
|
|
135
|
+
} else {
|
|
136
|
+
// This is instrumental to ignoreDataChanges working
|
|
137
|
+
context.processedData = data;
|
|
138
|
+
}
|
|
139
|
+
return '';
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export default class YetiTable extends Component {
|
|
144
|
+
<template>
|
|
145
|
+
{{#let
|
|
146
|
+
(hash
|
|
147
|
+
table=(component Table theme=this.mergedTheme parent=this)
|
|
148
|
+
header=(component
|
|
149
|
+
Header
|
|
150
|
+
columns=this.columns
|
|
151
|
+
onColumnClick=this.onColumnSort
|
|
152
|
+
sortable=this.sortable
|
|
153
|
+
sortSequence=this.sortSequence
|
|
154
|
+
parent=this
|
|
155
|
+
theme=this.mergedTheme
|
|
156
|
+
)
|
|
157
|
+
thead=(component
|
|
158
|
+
THead
|
|
159
|
+
columns=this.columns
|
|
160
|
+
onColumnClick=this.onColumnSort
|
|
161
|
+
sortable=this.sortable
|
|
162
|
+
sortSequence=this.sortSequence
|
|
163
|
+
theme=this.mergedTheme
|
|
164
|
+
parent=this
|
|
165
|
+
)
|
|
166
|
+
body=(component
|
|
167
|
+
Body
|
|
168
|
+
data=this.processedData
|
|
169
|
+
columns=this.columns
|
|
170
|
+
theme=this.mergedTheme
|
|
171
|
+
parent=this
|
|
172
|
+
)
|
|
173
|
+
tbody=(component
|
|
174
|
+
TBody
|
|
175
|
+
data=this.processedData
|
|
176
|
+
columns=this.columns
|
|
177
|
+
theme=this.mergedTheme
|
|
178
|
+
parent=this
|
|
179
|
+
)
|
|
180
|
+
tfoot=(component
|
|
181
|
+
TFoot columns=this.columns theme=this.mergedTheme parent=this
|
|
182
|
+
)
|
|
183
|
+
pagination=(component
|
|
184
|
+
Pagination
|
|
185
|
+
disabled=this.isLoading
|
|
186
|
+
theme=this.mergedTheme
|
|
187
|
+
paginationData=this.paginationData
|
|
188
|
+
paginationActions=(hash
|
|
189
|
+
previousPage=this.previousPage
|
|
190
|
+
nextPage=this.nextPage
|
|
191
|
+
goToPage=this.goToPage
|
|
192
|
+
changePageSize=this.changePageSize
|
|
193
|
+
)
|
|
194
|
+
)
|
|
195
|
+
actions=this.publicApi
|
|
196
|
+
paginationData=this.paginationData
|
|
197
|
+
isLoading=this.resolvedData.isPending
|
|
198
|
+
columns=this.columns
|
|
199
|
+
visibleColumns=this.visibleColumns
|
|
200
|
+
rows=this.normalizedRows
|
|
201
|
+
totalRows=this.normalizedTotalRows
|
|
202
|
+
visibleRows=this.processedData
|
|
203
|
+
theme=this.mergedTheme
|
|
204
|
+
)
|
|
205
|
+
as |api|
|
|
206
|
+
}}
|
|
207
|
+
{{UpdateTotalRows @totalRows context=this}}
|
|
208
|
+
{{UpdateFilter @filter context=this}}
|
|
209
|
+
{{ProcessedData loadData=@loadData context=this}}
|
|
210
|
+
|
|
211
|
+
{{#if this.renderTableElement}}
|
|
212
|
+
<Table @theme={{this.mergedTheme}} @parent={{this}} ...attributes>
|
|
213
|
+
{{yield api}}
|
|
214
|
+
</Table>
|
|
215
|
+
{{else}}
|
|
216
|
+
{{yield api}}
|
|
217
|
+
{{/if}}
|
|
218
|
+
|
|
219
|
+
{{/let}}
|
|
220
|
+
</template>
|
|
221
|
+
/**
|
|
222
|
+
* An object that contains classes for yeti table to apply when rendering its various table
|
|
223
|
+
* html elements. The theme object your pass in will be deeply merged with yeti-table's default theme
|
|
224
|
+
* and with a theme defined in your environment.js at `ENV['ember-yeti-table'].theme`.
|
|
225
|
+
*
|
|
226
|
+
* @argument theme
|
|
227
|
+
* @type {Object}
|
|
228
|
+
*/
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* The data for Yeti Table to render. It can be an array or a promise that resolves with an array.
|
|
232
|
+
* The only case when `@data` is optional is if a `@loadData` was passed in.
|
|
233
|
+
* @argument data
|
|
234
|
+
* @type {Array | Promise<Array>}
|
|
235
|
+
*/
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* The function that will be called when Yeti Table needs data. This argument is used
|
|
239
|
+
* when you don't have all the data available or loading all rows at once isn't possible,
|
|
240
|
+
* e.g the dataset is too large.
|
|
241
|
+
*
|
|
242
|
+
* By passing in a function to `@loadData` you assume the responsibility to filter, sort and
|
|
243
|
+
* paginate the data (if said features are enabled).
|
|
244
|
+
*
|
|
245
|
+
* This function must return an array or a promise that resolves with an array.
|
|
246
|
+
*
|
|
247
|
+
* This function will be called with an argument with the current state of the table.
|
|
248
|
+
* Use this object to know what data to fetch, pass it to the server, etc.
|
|
249
|
+
* Please check the "Async Data" guide to understand what that object contains and
|
|
250
|
+
* an example of its usage.
|
|
251
|
+
*
|
|
252
|
+
* @argument loadData
|
|
253
|
+
* @type {Function}
|
|
254
|
+
*/
|
|
255
|
+
|
|
256
|
+
publicApi = {
|
|
257
|
+
previousPage: this.previousPage,
|
|
258
|
+
nextPage: this.nextPage,
|
|
259
|
+
goToPage: this.goToPage,
|
|
260
|
+
changePageSize: this.changePageSize,
|
|
261
|
+
reloadData: this.reloadData,
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* This function will be called when Yeti Table initializes. It will be called with
|
|
266
|
+
* an object argument containing the functions for the possible actions you can make
|
|
267
|
+
* on a table. This object contains the following actions:
|
|
268
|
+
* - previousPage
|
|
269
|
+
* - nextPage
|
|
270
|
+
* - goToPage
|
|
271
|
+
* - changePageSize
|
|
272
|
+
* - reloadData
|
|
273
|
+
*
|
|
274
|
+
* @argument registerApi
|
|
275
|
+
* @type {Function}
|
|
276
|
+
*/
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Use this argument to enable the pagination feature. Default is `false`.
|
|
280
|
+
*
|
|
281
|
+
* @argument pagination
|
|
282
|
+
* @type {Boolean}
|
|
283
|
+
*/
|
|
284
|
+
@localCopy('args.pagination', getConfigWithDefault('pagination', false))
|
|
285
|
+
pagination;
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Controls the size of each page. Default is `15`.
|
|
289
|
+
*
|
|
290
|
+
* @argument pageSize
|
|
291
|
+
* @type {number}
|
|
292
|
+
*/
|
|
293
|
+
@localCopy('args.pageSize', getConfigWithDefault('pageSize', 15))
|
|
294
|
+
pageSize;
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Controls the current page to show. Default is `1`.
|
|
298
|
+
*
|
|
299
|
+
* @argument pageNumber
|
|
300
|
+
* @type {number}
|
|
301
|
+
*/
|
|
302
|
+
@localCopy('args.pageNumber', 1)
|
|
303
|
+
pageNumber;
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Optional argument that informs yeti table of how many rows your data has.
|
|
307
|
+
* Only needed when using a `@loadData` function and `@pagination={{true}}`.
|
|
308
|
+
* When you use `@data`, Yeti Table uses the size of that array.
|
|
309
|
+
* This information is used to calculate the pagination information that is yielded
|
|
310
|
+
* and passed to the `@loadData` function.
|
|
311
|
+
*
|
|
312
|
+
* @argument totalRows
|
|
313
|
+
* @type {number}
|
|
314
|
+
*/
|
|
315
|
+
totalRows;
|
|
316
|
+
|
|
317
|
+
// we keep `totalRows` updated manually in an untracked property
|
|
318
|
+
// to allow the user to update it in a loadData call and avoid
|
|
319
|
+
// a re-run of the main tracked function
|
|
320
|
+
// @action
|
|
321
|
+
// updateTotalRows(totalRows) {
|
|
322
|
+
// this.totalRows = totalRows;
|
|
323
|
+
// notifyPropertyChange(this, 'normalizedTotalRows');
|
|
324
|
+
// notifyPropertyChange(this, 'paginationData');
|
|
325
|
+
// }
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* The global filter. If passed in, Yeti Table will search all the rows that contain this
|
|
329
|
+
* string and show them. Defaults to `''`.
|
|
330
|
+
*
|
|
331
|
+
* @argument filter
|
|
332
|
+
* @type {String}
|
|
333
|
+
*/
|
|
334
|
+
@dedupeTracked
|
|
335
|
+
filter = '';
|
|
336
|
+
|
|
337
|
+
// we need some control of how we update the filter property, hence this modifier
|
|
338
|
+
// in this case, any falsy value will be considered as en empty string, which will then
|
|
339
|
+
// be deduped.
|
|
340
|
+
// @action
|
|
341
|
+
// updateFilter(filter) {
|
|
342
|
+
// this.filter = filter || '';
|
|
343
|
+
// }
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* An optional function to customize the filtering logic. This function should return true
|
|
347
|
+
* or false to either include or exclude the row on the resulting set. If this function depends
|
|
348
|
+
* on a value, pass that value as the `@filterUsing` argument.
|
|
349
|
+
*
|
|
350
|
+
* This function will be called with two arguments:
|
|
351
|
+
* - `row` - the current data row to use for filtering
|
|
352
|
+
* - `filterUsing` - the value you passed in as `@filterUsing`
|
|
353
|
+
*
|
|
354
|
+
* @argument filterFunction
|
|
355
|
+
* @type {Function}
|
|
356
|
+
*/
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* If you `@filterFunction` function depends on a different value (other that `@filter`)
|
|
360
|
+
* to show, pass it in this argument. Yeti Table uses this argument to know when to recalculate
|
|
361
|
+
* the fitlered rows.
|
|
362
|
+
*
|
|
363
|
+
* @argument filterUsing
|
|
364
|
+
* @type {Object}
|
|
365
|
+
*/
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Used to enable/disable sorting on all columns. You should use this to avoid passing
|
|
369
|
+
* the @sortable argument to all columns. Defaults to `true`.
|
|
370
|
+
*
|
|
371
|
+
* @argument sortable
|
|
372
|
+
* @type {Boolean}
|
|
373
|
+
*/
|
|
374
|
+
@localCopy('args.sortable', getConfigWithDefault('sortable', true))
|
|
375
|
+
sortable;
|
|
376
|
+
|
|
377
|
+
/**
|
|
378
|
+
* Use the `@sortFunction` if you want to completely customize how the row sorting is done.
|
|
379
|
+
* It will be invoked with two rows, the current sortings that are applied and the `@compareFunction`.
|
|
380
|
+
*
|
|
381
|
+
* @argument sortFunction
|
|
382
|
+
* @type {Function}
|
|
383
|
+
*/
|
|
384
|
+
@localCopy('args.sortFunction', () => sortMultiple)
|
|
385
|
+
sortFunction;
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* Use `@compareFunction` if you just want to customize how two values relate to each other (not the entire row).
|
|
389
|
+
* It will be invoked with two values and you just need to return `-1`, `0` or `1` depending on if first value is
|
|
390
|
+
* greater than the second or not. The default compare function used is the `compare` function from `@ember/utils`.
|
|
391
|
+
*
|
|
392
|
+
* @argument compareFunction
|
|
393
|
+
* @type {Function}
|
|
394
|
+
*/
|
|
395
|
+
@localCopy('args.compareFunction', () => compareValues)
|
|
396
|
+
compareFunction;
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* Use `@sortSequence` to customize the sequence in which the sorting order will cycle when
|
|
400
|
+
* clicking on the table headers. You can either pass in a comma-separated string or an array
|
|
401
|
+
* of strings. Accepted values are `'asc'`, `'desc'` and `'unsorted'`. The default value is `['asc', 'desc']`.
|
|
402
|
+
*
|
|
403
|
+
* @argument sortSequence
|
|
404
|
+
* @type {Array<string> | string}
|
|
405
|
+
*/
|
|
406
|
+
@localCopy(
|
|
407
|
+
'args.sortSequence',
|
|
408
|
+
getConfigWithDefault('sortSequence', ['asc', 'desc']),
|
|
409
|
+
)
|
|
410
|
+
sortSequence;
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* Use `@ignoreDataChanges` to prevent yeti table from observing changes to the underlying data and resorting or
|
|
414
|
+
* refiltering. Useful when doing inline editing in a table.
|
|
415
|
+
*
|
|
416
|
+
* Defaults to `false`.
|
|
417
|
+
*
|
|
418
|
+
* This is an initial render only value. Changing it after the table has been rendered will not be respected.
|
|
419
|
+
*
|
|
420
|
+
* @argument ignoreDataChanges
|
|
421
|
+
* @type {boolean}
|
|
422
|
+
*/
|
|
423
|
+
@localCopy(
|
|
424
|
+
'args.ignoreDataChanges',
|
|
425
|
+
getConfigWithDefault('ignoreDataChanges', false),
|
|
426
|
+
)
|
|
427
|
+
ignoreDataChanges;
|
|
428
|
+
|
|
429
|
+
/**
|
|
430
|
+
* Use `@renderTableElement` to prevent yeti table from rendering the topmost <table> element.
|
|
431
|
+
* Might be useful for styling purposes (e.g if you want to place your pagination controls outside
|
|
432
|
+
* of your table element). If you set this to `false`, you should render the table element yourself
|
|
433
|
+
* using the yielded `<t.table>` component.
|
|
434
|
+
*
|
|
435
|
+
* Defaults to `true`.
|
|
436
|
+
*
|
|
437
|
+
* @argument renderTableElement
|
|
438
|
+
* @type {boolean}
|
|
439
|
+
*/
|
|
440
|
+
@localCopy('args.renderTableElement', true)
|
|
441
|
+
renderTableElement;
|
|
442
|
+
|
|
443
|
+
/**
|
|
444
|
+
* The `@isColumnVisible` argument can be used to initialize the column visibility in a programmatic way.
|
|
445
|
+
* For example, let's say you store the initial column visibility in local storage, then you can
|
|
446
|
+
* use this function to initialize the `visible` column of the specific column. The given function should
|
|
447
|
+
* return a boolean which will be assigned to the `visible` property of the column. An object representing
|
|
448
|
+
* the column is passed in. Sou can use column.prop and column.name to know which column your computed
|
|
449
|
+
* the visibility for.
|
|
450
|
+
*
|
|
451
|
+
* @argument isColumnVisible
|
|
452
|
+
* @type {Function}
|
|
453
|
+
*/
|
|
454
|
+
|
|
455
|
+
// If the theme is replaced, this will invalidate, but not if any prop under theme is changed
|
|
456
|
+
@cached
|
|
457
|
+
get mergedTheme() {
|
|
458
|
+
let configTheme = this.config.theme || {};
|
|
459
|
+
let localTheme = this.args.theme || {};
|
|
460
|
+
return merge.all([DEFAULT_THEME, configTheme, localTheme]);
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
@cached
|
|
464
|
+
get visibleColumns() {
|
|
465
|
+
return this.columns.filter((c) => c.visible === true);
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
config =
|
|
469
|
+
getOwner(this).resolveRegistration('config:environment')[
|
|
470
|
+
'ember-yeti-table'
|
|
471
|
+
] || {};
|
|
472
|
+
|
|
473
|
+
get normalizedTotalRows() {
|
|
474
|
+
if (!this.args.loadData) {
|
|
475
|
+
// sync scenario using @data
|
|
476
|
+
return this.processedDataRows?.length || 0;
|
|
477
|
+
} else {
|
|
478
|
+
// async scenario. @loadData is present.
|
|
479
|
+
if (this.totalRows === undefined) {
|
|
480
|
+
// @totalRows was not passed in. Use the latest returned data set length as a fallback
|
|
481
|
+
return this.previousResolvedData.length || 0;
|
|
482
|
+
} else {
|
|
483
|
+
// @totalRows was passed in.
|
|
484
|
+
return this.totalRows;
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
get normalizedRows() {
|
|
490
|
+
if (!this.args.loadData) {
|
|
491
|
+
// sync scenario using @data
|
|
492
|
+
return this.processedDataRows;
|
|
493
|
+
} else {
|
|
494
|
+
// async scenario. @loadData is present.
|
|
495
|
+
return this.processedData;
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
get paginationData() {
|
|
500
|
+
let pageSize = this.pageSize;
|
|
501
|
+
let pageNumber = this.pageNumber;
|
|
502
|
+
let totalRows = this.normalizedTotalRows;
|
|
503
|
+
let isLastPage, totalPages;
|
|
504
|
+
|
|
505
|
+
if (totalRows) {
|
|
506
|
+
totalPages = Math.ceil(totalRows / pageSize);
|
|
507
|
+
pageNumber = Math.min(pageNumber, totalPages);
|
|
508
|
+
isLastPage = pageNumber === totalPages;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
let isFirstPage = pageNumber === 1;
|
|
512
|
+
let pageStart = (pageNumber - 1) * pageSize + 1;
|
|
513
|
+
let pageEnd = pageStart + pageSize - 1;
|
|
514
|
+
|
|
515
|
+
if (totalRows) {
|
|
516
|
+
pageEnd = Math.min(pageEnd, totalRows);
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
return {
|
|
520
|
+
pageSize,
|
|
521
|
+
pageNumber,
|
|
522
|
+
pageStart,
|
|
523
|
+
pageEnd,
|
|
524
|
+
isFirstPage,
|
|
525
|
+
isLastPage,
|
|
526
|
+
totalRows,
|
|
527
|
+
totalPages,
|
|
528
|
+
};
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
@tracked
|
|
532
|
+
columns = [];
|
|
533
|
+
|
|
534
|
+
constructor(owner, args) {
|
|
535
|
+
super(...arguments);
|
|
536
|
+
|
|
537
|
+
console.log(owner, args);
|
|
538
|
+
|
|
539
|
+
if (typeof this.args.registerApi === 'function') {
|
|
540
|
+
// eslint-disable-next-line ember/no-runloop
|
|
541
|
+
scheduleOnce('actions', null, this.args.registerApi, this.publicApi);
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
previousResolvedData = [];
|
|
546
|
+
|
|
547
|
+
resolvedData = trackedFunction(this, async () => {
|
|
548
|
+
let data = this.args.data;
|
|
549
|
+
|
|
550
|
+
if (this.columns.length == 0) {
|
|
551
|
+
return [];
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
// call loadData if exists
|
|
555
|
+
if (typeof this.args.loadData === 'function') {
|
|
556
|
+
let params = this.computeLoadDataParams();
|
|
557
|
+
|
|
558
|
+
try {
|
|
559
|
+
console.log("loadData", params);
|
|
560
|
+
|
|
561
|
+
data = await this.args.loadData(params);
|
|
562
|
+
|
|
563
|
+
console.log("loadData end");
|
|
564
|
+
|
|
565
|
+
} catch (e) {
|
|
566
|
+
console.log(e);
|
|
567
|
+
|
|
568
|
+
if (!didCancel(e)) {
|
|
569
|
+
// re-throw the non-cancellation error
|
|
570
|
+
throw e;
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
} else if (data?.then) {
|
|
574
|
+
// resolve the data promise (either from the @loadData call or @data)
|
|
575
|
+
data = await data;
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
if (this.isDestroyed) {
|
|
579
|
+
return;
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
data = data || [];
|
|
583
|
+
|
|
584
|
+
this.previousResolvedData = data;
|
|
585
|
+
|
|
586
|
+
return data;
|
|
587
|
+
});
|
|
588
|
+
|
|
589
|
+
@use latestData = keepLatest({
|
|
590
|
+
value: () => this.resolvedData.value ?? [],
|
|
591
|
+
when: () => this.resolvedData.isPending,
|
|
592
|
+
});
|
|
593
|
+
|
|
594
|
+
@tracked
|
|
595
|
+
processedData;
|
|
596
|
+
|
|
597
|
+
@tracked
|
|
598
|
+
processedDataRows;
|
|
599
|
+
|
|
600
|
+
processData(data) {
|
|
601
|
+
// only columns that have filterable = true and a prop defined will be considered
|
|
602
|
+
let columns = this.columns.filter((c) => c.filterable && isPresent(c.prop));
|
|
603
|
+
|
|
604
|
+
let sortableColumns = this.columns.filter((c) => !isEmpty(c.sort));
|
|
605
|
+
let sortings = sortableColumns.map((c) => ({
|
|
606
|
+
prop: c.prop,
|
|
607
|
+
direction: c.sort,
|
|
608
|
+
}));
|
|
609
|
+
|
|
610
|
+
let filterFunction = this.args.filterFunction;
|
|
611
|
+
let filterUsing = this.args.filterUsing;
|
|
612
|
+
let filter = this.filter;
|
|
613
|
+
|
|
614
|
+
let processTheData = () => {
|
|
615
|
+
// filter the data
|
|
616
|
+
data = filterData(data, columns, filter, filterFunction, filterUsing);
|
|
617
|
+
// Sort the data
|
|
618
|
+
if (sortings.length > 0) {
|
|
619
|
+
data = mergeSort(data, (itemA, itemB) => {
|
|
620
|
+
return this.sortFunction(
|
|
621
|
+
itemA,
|
|
622
|
+
itemB,
|
|
623
|
+
sortings,
|
|
624
|
+
this.compareFunction,
|
|
625
|
+
);
|
|
626
|
+
});
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
this.processedDataRows = data;
|
|
630
|
+
|
|
631
|
+
// Paginate the Data
|
|
632
|
+
if (this.pagination) {
|
|
633
|
+
let { pageStart, pageEnd } = this.paginationData;
|
|
634
|
+
data = data.slice(pageStart - 1, pageEnd); // slice excludes last element so we don't need to subtract 1
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
this.processedData = data;
|
|
638
|
+
};
|
|
639
|
+
|
|
640
|
+
if (this.ignoreDataChanges) {
|
|
641
|
+
// eslint-disable-next-line ember/no-runloop
|
|
642
|
+
later(processTheData, 0);
|
|
643
|
+
} else {
|
|
644
|
+
processTheData();
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
computeLoadDataParams() {
|
|
649
|
+
let params = {};
|
|
650
|
+
|
|
651
|
+
if (this.pagination) {
|
|
652
|
+
params.paginationData = this.paginationData;
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
params.sortData = this.columns
|
|
656
|
+
.filter((c) => !isEmpty(c.sort))
|
|
657
|
+
.map((c) => ({ prop: c.prop, direction: c.sort }));
|
|
658
|
+
params.filterData = {
|
|
659
|
+
filter: this.filter,
|
|
660
|
+
filterUsing: this.args.filterUsing,
|
|
661
|
+
columnFilters: this.columns.map((c) => ({
|
|
662
|
+
prop: c.prop,
|
|
663
|
+
filter: c.filter,
|
|
664
|
+
filterUsing: c.filterUsing,
|
|
665
|
+
})),
|
|
666
|
+
};
|
|
667
|
+
|
|
668
|
+
return params;
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
paginateData(data) {
|
|
672
|
+
if (this.pagination) {
|
|
673
|
+
let { pageStart, pageEnd } = this.paginationData;
|
|
674
|
+
data = data.slice(pageStart - 1, pageEnd); // slice excludes last element so we don't need to subtract 1
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
return data;
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
@action
|
|
681
|
+
async reloadData() {
|
|
682
|
+
return await this.resolvedData.retry();
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
@action
|
|
686
|
+
onColumnSort(column, e) {
|
|
687
|
+
if (column.isSorted) {
|
|
688
|
+
// if this column is already sorted, calculate the next
|
|
689
|
+
// sorting on the sequence.
|
|
690
|
+
let direction = column.sort;
|
|
691
|
+
let sortSequence = column.normalizedSortSequence;
|
|
692
|
+
direction =
|
|
693
|
+
sortSequence[
|
|
694
|
+
(sortSequence.indexOf(direction) + 1) % sortSequence.length
|
|
695
|
+
];
|
|
696
|
+
|
|
697
|
+
if (direction === 'unsorted') {
|
|
698
|
+
direction = null;
|
|
699
|
+
}
|
|
700
|
+
column.sort = direction;
|
|
701
|
+
|
|
702
|
+
if (!e.shiftKey) {
|
|
703
|
+
// if not pressed shift, reset other column sorting
|
|
704
|
+
let columns = this.columns.filter((c) => c !== column);
|
|
705
|
+
columns.forEach((c) => (c.sort = null));
|
|
706
|
+
}
|
|
707
|
+
} else {
|
|
708
|
+
// use first direction from sort sequence
|
|
709
|
+
let direction = column.normalizedSortSequence[0];
|
|
710
|
+
// create new sorting
|
|
711
|
+
column.sort = direction;
|
|
712
|
+
|
|
713
|
+
// normal click replaces all sortings with the new one
|
|
714
|
+
// shift click adds a new sorting to the existing ones
|
|
715
|
+
if (!e.shiftKey) {
|
|
716
|
+
// if not pressed shift, reset other column sortings
|
|
717
|
+
let columns = this.columns.filter((c) => c !== column);
|
|
718
|
+
columns.forEach((c) => (c.sort = null));
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
@action
|
|
724
|
+
previousPage() {
|
|
725
|
+
if (this.pagination) {
|
|
726
|
+
this.pageNumber = Math.max(this.pageNumber - 1, 1);
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
@action
|
|
731
|
+
nextPage() {
|
|
732
|
+
if (this.pagination) {
|
|
733
|
+
let { isLastPage } = this.paginationData;
|
|
734
|
+
|
|
735
|
+
if (!isLastPage) {
|
|
736
|
+
this.pageNumber = this.pageNumber + 1;
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
@action
|
|
742
|
+
goToPage(pageNumber) {
|
|
743
|
+
if (this.pagination) {
|
|
744
|
+
let { totalPages } = this.paginationData;
|
|
745
|
+
pageNumber = Math.max(pageNumber, 1);
|
|
746
|
+
|
|
747
|
+
if (totalPages) {
|
|
748
|
+
pageNumber = Math.min(pageNumber, totalPages);
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
this.pageNumber = pageNumber;
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
@action
|
|
756
|
+
changePageSize(pageSize) {
|
|
757
|
+
if (this.pagination) {
|
|
758
|
+
this.pageSize = parseInt(pageSize);
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
registerColumn(column) {
|
|
763
|
+
// eslint-disable-next-line ember/no-runloop
|
|
764
|
+
schedule('afterRender', this, function () {
|
|
765
|
+
if (typeof this.args.isColumnVisible === 'function') {
|
|
766
|
+
column.visible = this.args.isColumnVisible(column);
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
if (!this.columns.includes(column)) {
|
|
770
|
+
this.columns = [...this.columns, column];
|
|
771
|
+
}
|
|
772
|
+
});
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
unregisterColumn(column) {
|
|
776
|
+
if (this.columns.includes(column)) {
|
|
777
|
+
this.columns = this.columns.filter((c) => c !== column);
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
}
|