@easydata/crud 1.4.18 → 1.4.21

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.
Files changed (78) hide show
  1. package/dist/assets/css/ed-view.css +0 -3
  2. package/dist/easydata.crud.cjs.js +1521 -0
  3. package/dist/easydata.crud.esm.js +1507 -0
  4. package/package.json +16 -51
  5. package/api-easydata.js +0 -5
  6. package/dist/browser/easydata.css +0 -2188
  7. package/dist/browser/easydata.css.map +0 -1
  8. package/dist/browser/easydata.js +0 -12208
  9. package/dist/browser/easydata.js.map +0 -1
  10. package/dist/browser/easydata.min.css +0 -3
  11. package/dist/browser/easydata.min.js +0 -3
  12. package/dist/browser/easydata.min.js.LICENSE.txt +0 -9
  13. package/dist/browser/easydata.min.js.map +0 -1
  14. package/dist/bundles/easydata.crud.js +0 -8511
  15. package/dist/bundles/easydata.crud.js.map +0 -1
  16. package/dist/bundles/easydata.crud.min.js +0 -3
  17. package/dist/bundles/easydata.crud.min.js.LICENSE.txt +0 -1
  18. package/dist/bundles/easydata.crud.min.js.map +0 -1
  19. package/dist/lib/filter/data_filter.js +0 -2
  20. package/dist/lib/filter/data_filter.js.map +0 -1
  21. package/dist/lib/filter/text_data_filter.js +0 -123
  22. package/dist/lib/filter/text_data_filter.js.map +0 -1
  23. package/dist/lib/form/entity_edit_form.js +0 -134
  24. package/dist/lib/form/entity_edit_form.js.map +0 -1
  25. package/dist/lib/form/entity_form_builder.js +0 -441
  26. package/dist/lib/form/entity_form_builder.js.map +0 -1
  27. package/dist/lib/i18n/text_resources.js +0 -32
  28. package/dist/lib/i18n/text_resources.js.map +0 -1
  29. package/dist/lib/main/data_context.js +0 -168
  30. package/dist/lib/main/data_context.js.map +0 -1
  31. package/dist/lib/main/easy_data_server_loader.js +0 -44
  32. package/dist/lib/main/easy_data_server_loader.js.map +0 -1
  33. package/dist/lib/public_api.js +0 -17
  34. package/dist/lib/public_api.js.map +0 -1
  35. package/dist/lib/utils/utils.js +0 -25
  36. package/dist/lib/utils/utils.js.map +0 -1
  37. package/dist/lib/validators/datetime_validator.js +0 -46
  38. package/dist/lib/validators/datetime_validator.js.map +0 -1
  39. package/dist/lib/validators/required_validator.js +0 -37
  40. package/dist/lib/validators/required_validator.js.map +0 -1
  41. package/dist/lib/validators/type_validator.js +0 -47
  42. package/dist/lib/validators/type_validator.js.map +0 -1
  43. package/dist/lib/validators/validator.js +0 -7
  44. package/dist/lib/validators/validator.js.map +0 -1
  45. package/dist/lib/views/easy_data_view_dispatcher.js +0 -114
  46. package/dist/lib/views/easy_data_view_dispatcher.js.map +0 -1
  47. package/dist/lib/views/entity_data_view.js +0 -225
  48. package/dist/lib/views/entity_data_view.js.map +0 -1
  49. package/dist/lib/views/options.js +0 -2
  50. package/dist/lib/views/options.js.map +0 -1
  51. package/dist/lib/views/root_data_view.js +0 -49
  52. package/dist/lib/views/root_data_view.js.map +0 -1
  53. package/dist/lib/widgets/progress_bar.js +0 -16
  54. package/dist/lib/widgets/progress_bar.js.map +0 -1
  55. package/dist/lib/widgets/text_filter_widget.js +0 -214
  56. package/dist/lib/widgets/text_filter_widget.js.map +0 -1
  57. package/styles-easydata.js +0 -4
  58. package/tsconfig.json +0 -23
  59. package/webpack.config.js +0 -137
  60. /package/dist/{lib → types}/filter/data_filter.d.ts +0 -0
  61. /package/dist/{lib → types}/filter/text_data_filter.d.ts +0 -0
  62. /package/dist/{lib → types}/form/entity_edit_form.d.ts +0 -0
  63. /package/dist/{lib → types}/form/entity_form_builder.d.ts +0 -0
  64. /package/dist/{lib → types}/i18n/text_resources.d.ts +0 -0
  65. /package/dist/{lib → types}/main/data_context.d.ts +0 -0
  66. /package/dist/{lib → types}/main/easy_data_server_loader.d.ts +0 -0
  67. /package/dist/{lib → types}/public_api.d.ts +0 -0
  68. /package/dist/{lib → types}/utils/utils.d.ts +0 -0
  69. /package/dist/{lib → types}/validators/datetime_validator.d.ts +0 -0
  70. /package/dist/{lib → types}/validators/required_validator.d.ts +0 -0
  71. /package/dist/{lib → types}/validators/type_validator.d.ts +0 -0
  72. /package/dist/{lib → types}/validators/validator.d.ts +0 -0
  73. /package/dist/{lib → types}/views/easy_data_view_dispatcher.d.ts +0 -0
  74. /package/dist/{lib → types}/views/entity_data_view.d.ts +0 -0
  75. /package/dist/{lib → types}/views/options.d.ts +0 -0
  76. /package/dist/{lib → types}/views/root_data_view.d.ts +0 -0
  77. /package/dist/{lib → types}/widgets/progress_bar.d.ts +0 -0
  78. /package/dist/{lib → types}/widgets/text_filter_widget.d.ts +0 -0
@@ -0,0 +1,1507 @@
1
+
2
+ /*
3
+ * EasyData.JS CRUD v1.4.21
4
+ * Copyright 2020-2024 Korzh.com
5
+ * Licensed under MIT
6
+ */
7
+
8
+ import { EasyDataTable, utils, DataType, i18n, EntityAttrKind, EditorTag, ValueEditor, DataRow, HttpClient, MetaData, combinePath } from '@easydata/core';
9
+ import { domel, CellRendererType, browserUtils, DFMT_REGEX, DefaultDialogService, EasyGrid, DefaultDateTimePicker } from '@easydata/ui';
10
+
11
+ class TextDataFilter {
12
+ constructor(loader, sourceTable, sourceId, isLookup = false) {
13
+ this.loader = loader;
14
+ this.sourceTable = sourceTable;
15
+ this.sourceId = sourceId;
16
+ this.isLookup = isLookup;
17
+ this.filterValue = '';
18
+ //turns off client-side search
19
+ //for test purposes
20
+ this.justServerSide = false;
21
+ }
22
+ getValue() {
23
+ return this.filterValue;
24
+ }
25
+ apply(value) {
26
+ this.filterValue = value;
27
+ if (this.filterValue) {
28
+ return this.applyCore();
29
+ }
30
+ else {
31
+ return this.clear();
32
+ }
33
+ }
34
+ clear() {
35
+ this.filterValue = '';
36
+ return Promise.resolve(this.sourceTable);
37
+ }
38
+ applyCore() {
39
+ if (this.sourceTable.getTotal() == this.sourceTable.getCachedCount() && !this.justServerSide) {
40
+ return this.applyInMemoryFilter();
41
+ }
42
+ else {
43
+ const filters = [
44
+ { class: "__substring", value: this.filterValue }
45
+ ];
46
+ return this.loader.loadChunk({
47
+ offset: 0,
48
+ limit: this.sourceTable.chunkSize,
49
+ needTotal: true,
50
+ filters: filters,
51
+ sourceId: this.sourceId,
52
+ lookup: this.isLookup
53
+ })
54
+ .then(data => {
55
+ const filteredTable = new EasyDataTable({
56
+ chunkSize: this.sourceTable.chunkSize,
57
+ loader: {
58
+ loadChunk: (params) => this.loader
59
+ .loadChunk(Object.assign(Object.assign({}, params), { filters: filters, sourceId: this.sourceId, lookup: this.isLookup }))
60
+ }
61
+ });
62
+ for (const col of this.sourceTable.columns.getItems()) {
63
+ filteredTable.columns.add(col);
64
+ }
65
+ filteredTable.setTotal(data.total);
66
+ for (const row of data.table.getCachedRows()) {
67
+ filteredTable.addRow(row);
68
+ }
69
+ return filteredTable;
70
+ });
71
+ }
72
+ }
73
+ applyInMemoryFilter() {
74
+ return new Promise((resolve, reject) => {
75
+ const filteredTable = new EasyDataTable({
76
+ chunkSize: this.sourceTable.chunkSize,
77
+ inMemory: true
78
+ });
79
+ for (const col of this.sourceTable.columns.getItems()) {
80
+ filteredTable.columns.add(col);
81
+ }
82
+ const words = this.filterValue.split('||').map(w => w.trim().toLowerCase());
83
+ const suitableColumns = this.sourceTable.columns.getItems()
84
+ .filter(col => utils.isNumericType(col.type)
85
+ || utils.getStringDataTypes().indexOf(col.type) >= 0);
86
+ const hasEnterance = (row) => {
87
+ for (const col of suitableColumns) {
88
+ const value = row.getValue(col.id);
89
+ if (value) {
90
+ const normalized = value.toString().toLowerCase();
91
+ for (const word of words) {
92
+ if (normalized.indexOf(word) >= 0) {
93
+ return true;
94
+ }
95
+ }
96
+ }
97
+ }
98
+ return false;
99
+ };
100
+ for (const row of this.sourceTable.getCachedRows()) {
101
+ if (hasEnterance(row)) {
102
+ filteredTable.addRow(row);
103
+ }
104
+ }
105
+ filteredTable.setTotal(filteredTable.getCachedCount());
106
+ resolve(filteredTable);
107
+ });
108
+ }
109
+ }
110
+
111
+ const internalDateFormat = 'yyyy-MM-dd';
112
+ const internalTimeFormat = 'HH:mm';
113
+ const getInternalDateTimeFormat = (dtype) => {
114
+ if (dtype == DataType.Date)
115
+ return internalDateFormat;
116
+ if (dtype == DataType.Time)
117
+ return internalTimeFormat;
118
+ return `${internalDateFormat}T${internalTimeFormat}`;
119
+ };
120
+ const getEditDateTimeFormat = (dtype) => {
121
+ const settings = i18n.getLocaleSettings();
122
+ if (dtype == DataType.Date)
123
+ return settings.editDateFormat;
124
+ if (dtype == DataType.Time)
125
+ return settings.editTimeFormat;
126
+ return `${settings.editDateFormat} ${settings.editTimeFormat}`;
127
+ };
128
+ const setLocation = (path) => {
129
+ const state = window.history.state;
130
+ history.pushState(state, document.title, path);
131
+ window.dispatchEvent(new Event('ed_set_location'));
132
+ };
133
+
134
+ class Validator {
135
+ }
136
+
137
+ class DateTimeValidator extends Validator {
138
+ constructor() {
139
+ super();
140
+ this.name = 'DateTime';
141
+ }
142
+ validate(attr, value) {
143
+ if (!utils.IsDefinedAndNotNull(value) || value == '')
144
+ return { successed: true };
145
+ if (utils.getDateDataTypes().indexOf(attr.dataType) >= 0) {
146
+ try {
147
+ const editFormat = getEditDateTimeFormat(attr.dataType);
148
+ const newDate = utils.strToDateTime(value, editFormat);
149
+ }
150
+ catch (_a) {
151
+ return {
152
+ successed: false,
153
+ messages: [i18n.getText('DateTimeError')]
154
+ };
155
+ }
156
+ }
157
+ return { successed: true };
158
+ }
159
+ }
160
+
161
+ class EntityEditForm {
162
+ constructor(context) {
163
+ this.context = context;
164
+ this.validators = [new DateTimeValidator()];
165
+ }
166
+ getHtml() {
167
+ return this.html;
168
+ }
169
+ setHtmlInt(html) {
170
+ this.html = html;
171
+ this.errorsDiv = this.html.querySelector('.errors-block');
172
+ }
173
+ validate() {
174
+ this.clearErrors();
175
+ const inputs = Array.from(this.html.querySelectorAll('input, select'));
176
+ let isValid = true;
177
+ for (const input of inputs) {
178
+ const attr = this.context.getMetaData().getAttributeById(input.name);
179
+ if (input.type === 'checkbox')
180
+ continue;
181
+ const result = this.validateValue(attr, input.value);
182
+ if (!result.successed) {
183
+ if (isValid) {
184
+ domel(this.errorsDiv)
185
+ .addChild('ul');
186
+ }
187
+ isValid = false;
188
+ for (const message of result.messages) {
189
+ this.errorsDiv.firstElementChild.innerHTML += `<li>${attr.caption}: ${message}</li>`;
190
+ }
191
+ }
192
+ this.markInputValid(input, result.successed);
193
+ }
194
+ return isValid;
195
+ }
196
+ getData() {
197
+ return new Promise((resolve, reject) => {
198
+ const filePromises = [];
199
+ const inputs = Array.from(this.html
200
+ .querySelectorAll('input, select, textarea'));
201
+ let obj = {};
202
+ for (const input of inputs) {
203
+ const property = input.name.substring(input.name.lastIndexOf('.') + 1);
204
+ const attr = this.context.getMetaData().getAttributeById(input.name);
205
+ if (input.type === 'checkbox') {
206
+ obj[property] = input.checked;
207
+ }
208
+ else if (input.type === 'file') {
209
+ filePromises.push(this.fileToBase64(input.files[0])
210
+ .then(content => obj[property] = content));
211
+ }
212
+ else {
213
+ obj[property] = this.mapValue(attr.dataType, input.value);
214
+ }
215
+ }
216
+ Promise.all(filePromises)
217
+ .then(() => resolve(obj))
218
+ .catch((e) => reject(e));
219
+ });
220
+ }
221
+ fileToBase64(file) {
222
+ return new Promise((resolve, reject) => {
223
+ const reader = new FileReader();
224
+ reader.readAsDataURL(file);
225
+ reader.onload = () => {
226
+ const result = reader.result.toString();
227
+ resolve(result.substring(result.indexOf(',') + 1));
228
+ };
229
+ reader.onerror = error => reject(error);
230
+ });
231
+ }
232
+ useValidator(...validator) {
233
+ this.useValidators(validator);
234
+ }
235
+ useValidators(validators) {
236
+ this.validators = this.validators.concat(validators);
237
+ }
238
+ mapValue(type, value) {
239
+ if (utils.getDateDataTypes().indexOf(type) >= 0) {
240
+ if (type !== DataType.Time && value && value.length) {
241
+ const editFormat = getEditDateTimeFormat(type);
242
+ const internalFormat = getInternalDateTimeFormat(type);
243
+ const date = utils.strToDateTime(value, editFormat);
244
+ return i18n.dateTimeToStr(date, internalFormat);
245
+ }
246
+ return value && value.length ? value : null;
247
+ }
248
+ if (utils.isIntType(type))
249
+ return parseInt(value);
250
+ if (utils.isNumericType(type))
251
+ return parseFloat(value);
252
+ return value;
253
+ }
254
+ clearErrors() {
255
+ this.errorsDiv.innerHTML = '';
256
+ this.html.querySelectorAll('input, select').forEach(el => {
257
+ el.classList.remove('is-valid');
258
+ el.classList.remove('is-invalid');
259
+ });
260
+ }
261
+ markInputValid(input, valid) {
262
+ input.classList.add(valid ? 'is-valid' : 'is-invalid');
263
+ }
264
+ validateValue(attr, value) {
265
+ const result = { successed: true, messages: [] };
266
+ for (const validator of this.validators) {
267
+ const res = validator.validate(attr, value);
268
+ if (!res.successed) {
269
+ result.successed = false;
270
+ result.messages = result.messages.concat(res.messages);
271
+ }
272
+ }
273
+ return result;
274
+ }
275
+ }
276
+
277
+ class TextFilterWidget {
278
+ constructor(slot, grid, filter, options) {
279
+ this.slot = slot;
280
+ this.grid = grid;
281
+ this.filter = filter;
282
+ this.options = {
283
+ focus: false,
284
+ instantMode: false,
285
+ instantTimeout: 1000
286
+ };
287
+ this.options = utils.assignDeep(this.options, options || {});
288
+ const stringDefRenderer = this.grid.cellRendererStore
289
+ .getDefaultRendererByType(CellRendererType.STRING);
290
+ this.grid.cellRendererStore
291
+ .setDefaultRenderer(CellRendererType.STRING, (value, column, cellElement, rowElement) => this.highlightCellRenderer(stringDefRenderer, value, column, cellElement, rowElement));
292
+ const numDefRenderer = this.grid.cellRendererStore
293
+ .getDefaultRendererByType(CellRendererType.NUMBER);
294
+ this.grid.cellRendererStore
295
+ .setDefaultRenderer(CellRendererType.NUMBER, (value, column, cellElement, rowElement) => this.highlightCellRenderer(numDefRenderer, value, column, cellElement, rowElement));
296
+ this.render();
297
+ }
298
+ render() {
299
+ const horizClass = browserUtils.IsIE()
300
+ ? 'kfrm-fields-ie is-horizontal'
301
+ : 'kfrm-fields is-horizontal';
302
+ const isEdgeOrIE = browserUtils.IsIE() || browserUtils.IsEdge();
303
+ domel(this.slot)
304
+ .addClass(horizClass)
305
+ .addChild('div', b => {
306
+ b
307
+ .addClass('control')
308
+ .addChild('input', b => {
309
+ this.filterInput = b.toDOM();
310
+ b
311
+ .attr('placeholder', i18n.getText('SearchInputPlaceholder'))
312
+ .type('text');
313
+ b.on('keydown', this.inputKeydownHandler.bind(this));
314
+ if (this.options.instantMode) {
315
+ b.on('keyup', this.inputKeyupHandler.bind(this));
316
+ }
317
+ });
318
+ if (!isEdgeOrIE) {
319
+ b
320
+ .addClass('has-icons-right')
321
+ .addChild('span', b => {
322
+ b
323
+ .addClass('icon')
324
+ .addClass('is-right')
325
+ .addClass('is-clickable')
326
+ .html('&#x1F5D9;')
327
+ .on('click', this.clearButtonClickHander.bind(this));
328
+ });
329
+ }
330
+ });
331
+ if (!this.options.instantMode) {
332
+ domel(this.slot)
333
+ .addChild('button', b => b
334
+ .addClass('kfrm-button')
335
+ .addText(i18n.getText('SearchBtn'))
336
+ .on('click', this.searchButtonClickHandler.bind(this)));
337
+ }
338
+ if (this.options.focus) {
339
+ this.filterInput.focus();
340
+ }
341
+ }
342
+ inputKeydownHandler(ev) {
343
+ if (ev.keyCode == 13) {
344
+ this.applyFilter(true);
345
+ }
346
+ }
347
+ inputKeyupHandler() {
348
+ if (this.applyFilterTimeout) {
349
+ clearTimeout(this.applyFilterTimeout);
350
+ }
351
+ this.applyFilterTimeout = setTimeout(() => {
352
+ this.applyFilter(true);
353
+ }, this.options.instantTimeout);
354
+ }
355
+ clearButtonClickHander() {
356
+ this.filterInput.value = '';
357
+ this.filterInput.focus();
358
+ this.applyFilter(true);
359
+ }
360
+ searchButtonClickHandler() {
361
+ this.applyFilter(true);
362
+ }
363
+ applyFilter(checkChange) {
364
+ if (this.applyFilterTimeout) {
365
+ clearTimeout(this.applyFilterTimeout);
366
+ }
367
+ const filterValue = this.filter.getValue();
368
+ if (!checkChange || filterValue != this.filterInput.value) {
369
+ this.filter.apply(this.filterInput.value)
370
+ .then(data => {
371
+ this.grid.setData(data);
372
+ });
373
+ return true;
374
+ }
375
+ return false;
376
+ }
377
+ highlightCellRenderer(defaultRenderer, value, column, cellElement, rowElement) {
378
+ if (utils.isNumericType(column.type)
379
+ || utils.getStringDataTypes().indexOf(column.type) >= 0) {
380
+ if (value) {
381
+ if (column.dataColumn && column.dataColumn.displayFormat
382
+ && DFMT_REGEX.test(column.dataColumn.displayFormat)) {
383
+ value = column.dataColumn.displayFormat.replace(DFMT_REGEX, (_, $1) => {
384
+ return i18n.numberToStr(value, $1);
385
+ });
386
+ }
387
+ else {
388
+ value = value.toLocaleString();
389
+ }
390
+ const result = this.highlightText(value.toString());
391
+ if (result instanceof HTMLElement) {
392
+ cellElement.title = value;
393
+ cellElement.appendChild(result);
394
+ return;
395
+ }
396
+ }
397
+ }
398
+ defaultRenderer(value, column, cellElement, rowElement);
399
+ }
400
+ highlightText(content) {
401
+ const normalizedContent = content.toLowerCase();
402
+ const filterValue = this.filter.getValue().toString();
403
+ if (filterValue && filterValue.length > 0 && content && content.length > 0) {
404
+ const indexInMas = [];
405
+ const words = filterValue.split('||').map(w => w.trim().toLowerCase());
406
+ for (let i = 0; i < words.length; i++) {
407
+ let pos = 0;
408
+ const lowerWord = words[i];
409
+ if (!lowerWord.length)
410
+ continue;
411
+ if (lowerWord === normalizedContent) {
412
+ const highlightSpan = document.createElement('span');
413
+ highlightSpan.style.backgroundColor = 'yellow';
414
+ highlightSpan.innerText = content;
415
+ return highlightSpan;
416
+ }
417
+ while (pos < content.length - 1) {
418
+ const index = normalizedContent.indexOf(lowerWord, pos);
419
+ if (index >= 0) {
420
+ indexInMas.push({ index: index, length: words[i].length });
421
+ pos = index + lowerWord.length;
422
+ }
423
+ else {
424
+ pos++;
425
+ }
426
+ }
427
+ }
428
+ if (indexInMas.length > 0) {
429
+ //sort array item by index
430
+ indexInMas.sort((item1, item2) => {
431
+ if (item1.index > item2.index) {
432
+ return 1;
433
+ }
434
+ else if (item1.index == item2.index2) {
435
+ return 0;
436
+ }
437
+ else {
438
+ return -1;
439
+ }
440
+ });
441
+ //remove intersecting gaps
442
+ for (let i = 0; i < indexInMas.length - 1;) {
443
+ const delta = indexInMas[i + 1].index - (indexInMas[i].index + indexInMas[i].length);
444
+ if (delta < 0) {
445
+ const addDelta = indexInMas[i + 1].length + delta;
446
+ if (addDelta > 0) {
447
+ indexInMas[i].length += addDelta;
448
+ }
449
+ indexInMas.splice(i + 1, 1);
450
+ }
451
+ else {
452
+ i++;
453
+ }
454
+ }
455
+ const div = document.createElement('div');
456
+ for (let i = 0; i < indexInMas.length; i++) {
457
+ if (i === 0) {
458
+ const text = document.createTextNode(content.substring(0, indexInMas[i].index));
459
+ div.appendChild(text);
460
+ }
461
+ const highlightSpan = document.createElement('span');
462
+ highlightSpan.style.backgroundColor = 'yellow';
463
+ highlightSpan.innerText = content.substring(indexInMas[i].index, indexInMas[i].index + indexInMas[i].length);
464
+ div.appendChild(highlightSpan);
465
+ const text = (i < indexInMas.length - 1)
466
+ ? document.createTextNode(content.substring(indexInMas[i].index
467
+ + indexInMas[i].length, indexInMas[i + 1].index))
468
+ : document.createTextNode(content.substring(indexInMas[i].index
469
+ + indexInMas[i].length));
470
+ div.appendChild(text);
471
+ }
472
+ return div;
473
+ }
474
+ }
475
+ return content;
476
+ }
477
+ }
478
+
479
+ const isIE = browserUtils.IsIE();
480
+ class EntityEditFormBuilder {
481
+ constructor(context, params) {
482
+ this.context = context;
483
+ this.params = params;
484
+ this.params = params || {};
485
+ this.reset();
486
+ }
487
+ reset() {
488
+ this.form = new EntityEditForm(this.context);
489
+ }
490
+ setupLookupField(parent, attr, readOnly, value) {
491
+ const lookupEntity = this.context.getMetaData().getRootEntity()
492
+ .subEntities.filter(ent => ent.id == attr.lookupEntity)[0];
493
+ const dataAttr = this.context.getMetaData().getAttributeById(attr.dataAttr);
494
+ if (!dataAttr)
495
+ return;
496
+ readOnly = readOnly || !dataAttr.isEditable;
497
+ value = this.params.values
498
+ ? this.params.values.getValue(dataAttr.id)
499
+ : undefined;
500
+ const horizClass = isIE
501
+ ? 'kfrm-fields-ie is-horizontal'
502
+ : 'kfrm-fields is-horizontal';
503
+ let inputEl;
504
+ domel(parent)
505
+ .addChild('div', b => {
506
+ b
507
+ .addClass(horizClass)
508
+ .addChild('input', b => {
509
+ inputEl = b.toDOM();
510
+ b.attr('readonly', '');
511
+ b.name(dataAttr.id);
512
+ b.type(this.resolveInputType(dataAttr.dataType));
513
+ b.value(utils.IsDefinedAndNotNull(value)
514
+ ? value.toString() : '');
515
+ });
516
+ if (!readOnly)
517
+ b.addChild('button', b => b
518
+ .addClass('kfrm-button')
519
+ .attr('title', i18n.getText('NavigationBtnTitle'))
520
+ .addText('...')
521
+ .on('click', (ev) => {
522
+ const lookupTable = new EasyDataTable({
523
+ loader: {
524
+ loadChunk: (chunkParams) => this.context.getDataLoader()
525
+ .loadChunk(Object.assign(Object.assign({}, chunkParams), { id: lookupEntity.id }))
526
+ }
527
+ });
528
+ this.context.getDataLoader()
529
+ .loadChunk({ offset: 0, limit: 1000, needTotal: true, sourceId: lookupEntity.id })
530
+ .then(data => {
531
+ for (const col of data.table.columns.getItems()) {
532
+ const attrs = lookupEntity.attributes.filter(attr => attr.id == col.id && (attr.isPrimaryKey || attr.showInLookup));
533
+ if (attrs.length) {
534
+ lookupTable.columns.add(col);
535
+ }
536
+ }
537
+ lookupTable.setTotal(data.total);
538
+ for (const row of data.table.getCachedRows()) {
539
+ lookupTable.addRow(row);
540
+ }
541
+ const ds = new DefaultDialogService();
542
+ let gridSlot = null;
543
+ let selectedSlot = null;
544
+ let widgetSlot;
545
+ const slot = domel('div')
546
+ .addClass(`kfrm-form`)
547
+ .addChild('div', b => b
548
+ .addClass(`kfrm-field`)
549
+ .addChild('label', b => b
550
+ .addText(i18n.getText('LookupSelectedItem'))
551
+ .toDOM())
552
+ .addChild('div', b => selectedSlot = b
553
+ .addText('None')
554
+ .toDOM()))
555
+ .addChild('div', b => widgetSlot = b.toDOM())
556
+ .addChild('div', b => b
557
+ .addClass('kfrm-control')
558
+ .addChild('div', b => gridSlot = b.toDOM()))
559
+ .toDOM();
560
+ let selectedValue = inputEl.value;
561
+ const getValue = (row, colId) => {
562
+ if (row instanceof DataRow) {
563
+ return row.getValue(colId);
564
+ }
565
+ const property = colId.substring(colId.lastIndexOf('.') + 1);
566
+ return row[property];
567
+ };
568
+ const updateSelectedValue = (row) => {
569
+ selectedSlot.innerHTML = lookupTable.columns
570
+ .getItems()
571
+ .map(col => {
572
+ return `<b>${col.label}:</b> ${getValue(row, col.id)}`;
573
+ })
574
+ .join(', ');
575
+ };
576
+ if (selectedValue) {
577
+ const attr = lookupEntity.getFirstPrimaryAttr();
578
+ const key = attr.id.substring(attr.id.lastIndexOf('.') + 1);
579
+ this.context.fetchRecord({ [key]: selectedValue }, lookupEntity.id)
580
+ .then(data => {
581
+ if (data.entity) {
582
+ updateSelectedValue(data.entity);
583
+ }
584
+ })
585
+ .catch(error => {
586
+ console.error(error);
587
+ });
588
+ }
589
+ const lookupGrid = new EasyGrid({
590
+ slot: gridSlot,
591
+ dataTable: lookupTable,
592
+ fixHeightOnFirstRender: true,
593
+ paging: {
594
+ pageSize: 10
595
+ },
596
+ onActiveRowChanged: (ev) => {
597
+ lookupGrid.getData().getRow(ev.rowIndex)
598
+ .then((row) => {
599
+ selectedValue = row.getValue(attr.lookupDataAttr);
600
+ updateSelectedValue(row);
601
+ });
602
+ }
603
+ });
604
+ ds.open({
605
+ title: i18n.getText('LookupDlgCaption')
606
+ .replace('{entity}', lookupEntity.caption),
607
+ body: slot,
608
+ arrangeParents: true,
609
+ beforeOpen: () => {
610
+ const dataFilter = this.context.createFilter(lookupEntity.id, lookupGrid.getData(), true);
611
+ new TextFilterWidget(widgetSlot, lookupGrid, dataFilter, { instantMode: true, focus: true });
612
+ },
613
+ onSubmit: () => {
614
+ inputEl.value = selectedValue;
615
+ return true;
616
+ },
617
+ onDestroy: () => {
618
+ lookupGrid.destroy();
619
+ // return focus on button
620
+ b.toDOM().focus();
621
+ }
622
+ });
623
+ });
624
+ }));
625
+ });
626
+ }
627
+ setupDateTimeField(parent, attr, value, readOnly, hidden) {
628
+ const horizClass = isIE
629
+ ? 'kfrm-fields-ie is-horizontal'
630
+ : 'kfrm-fields is-horizontal';
631
+ const editFormat = getEditDateTimeFormat(attr.dataType);
632
+ let inputEl;
633
+ const mask = editFormat
634
+ .replace('yyyy', '9999')
635
+ .replace('MM', '99')
636
+ .replace('dd', '99')
637
+ .replace('HH', '99')
638
+ .replace('mm', '99')
639
+ .replace('ss', '99');
640
+ domel(parent)
641
+ .addChild('div', b => {
642
+ b
643
+ .addClass(horizClass)
644
+ .addChild('input', b => {
645
+ inputEl = b.toDOM();
646
+ b.name(attr.id);
647
+ b.type(hidden ? 'hidden' : this.resolveInputType(attr.dataType));
648
+ if (readOnly) {
649
+ b.attr('readonly', '');
650
+ }
651
+ else {
652
+ b.mask(mask);
653
+ b.on('keypress', (ev) => this.applySumbit(ev))
654
+ .on('input', ev => {
655
+ b.removeClass('is-invalid');
656
+ try {
657
+ const newDate = utils.strToDateTime(inputEl.value, editFormat);
658
+ }
659
+ catch (e) {
660
+ b.addClass('is-invalid');
661
+ }
662
+ finally {
663
+ }
664
+ })
665
+ .on('blur', ev => {
666
+ if (inputEl.value === mask.replace(/[9]/g, '_')) {
667
+ inputEl.value = '';
668
+ }
669
+ });
670
+ }
671
+ b.value((utils.IsDefinedAndNotNull(value)
672
+ ? i18n.dateTimeToStr(value, editFormat)
673
+ : ''));
674
+ });
675
+ if (!readOnly)
676
+ b.addChild('button', b => b
677
+ .addClass('kfrm-button')
678
+ .attr('title', i18n.getText(attr.dataType !== DataType.Time
679
+ ? 'CalendarBtnTitle'
680
+ : 'TimerBtnTitle'))
681
+ .addChild('i', b => b.addClass(attr.dataType !== DataType.Time
682
+ ? 'ed-calendar-icon'
683
+ : 'ed-timer-icon'))
684
+ .on('click', (ev) => {
685
+ let value;
686
+ try {
687
+ value = inputEl.value.length
688
+ ? attr.dataType !== DataType.Time
689
+ ? utils.strToDateTime(inputEl.value, editFormat)
690
+ : utils.strToTime(inputEl.value)
691
+ : new Date(new Date().setSeconds(0));
692
+ }
693
+ catch (_a) {
694
+ value = new Date(new Date().setSeconds(0));
695
+ }
696
+ const pickerOptions = {
697
+ zIndex: 9999999999,
698
+ showCalendar: attr.dataType !== DataType.Time,
699
+ showTimePicker: attr.dataType !== DataType.Date,
700
+ onApply: (dateTime) => {
701
+ dateTime.setSeconds(0);
702
+ dateTime.setMilliseconds(0);
703
+ inputEl.value = i18n.dateTimeToStr(dateTime, editFormat);
704
+ }
705
+ };
706
+ const dtp = new DefaultDateTimePicker(pickerOptions);
707
+ dtp.setDateTime(value);
708
+ dtp.show(ev.target);
709
+ }).toDOM());
710
+ });
711
+ }
712
+ setupListField(parent, attr, value, values, readOnly) {
713
+ domel(parent)
714
+ .addChild('div', b => b
715
+ .addClass('kfrm-select full-width')
716
+ .addChild('select', b => {
717
+ if (readOnly)
718
+ b.attr('readonly', '');
719
+ b.attr('name', attr.id);
720
+ b.on('keypress', (ev) => this.applySumbit(ev));
721
+ if (values) {
722
+ for (let i = 0; i < values.length; i++) {
723
+ const val = values[i];
724
+ b.addOption({
725
+ value: val.id,
726
+ title: val.text,
727
+ selected: i === 0
728
+ });
729
+ }
730
+ }
731
+ b.value(value);
732
+ }));
733
+ }
734
+ setupFileField(parent, attr, readOnly, accept) {
735
+ domel(parent)
736
+ .addChild('input', b => {
737
+ if (readOnly)
738
+ b.attr('readonly', '');
739
+ b.name(attr.id)
740
+ .type(this.resolveInputType(attr.dataType));
741
+ b.attr('accept', accept);
742
+ });
743
+ }
744
+ setupTextField(parent, attr, value, readOnly, hidden) {
745
+ domel(parent)
746
+ .addChild('input', b => {
747
+ if (readOnly) {
748
+ b.attr('readonly', '');
749
+ }
750
+ b.type(hidden ? 'hidden' : this.resolveInputType(attr.dataType));
751
+ b.name(attr.id)
752
+ .type(this.resolveInputType(attr.dataType));
753
+ if (attr.dataType == DataType.Bool) {
754
+ if (value)
755
+ b.attr('checked', '');
756
+ }
757
+ else {
758
+ b.on('keypress', (ev) => this.applySumbit(ev))
759
+ .value(utils.IsDefinedAndNotNull(value)
760
+ ? value.toString()
761
+ : '');
762
+ }
763
+ });
764
+ }
765
+ setupTextArea(parent, attr, value, readOnly) {
766
+ // feature: modify size in value editor ??
767
+ domel(parent)
768
+ .addChild('textarea', b => {
769
+ if (readOnly)
770
+ b.attr('readonly', '');
771
+ b.attr('name', attr.id);
772
+ b.setStyle('height', `120px`);
773
+ b.value(utils.IsDefinedAndNotNull(value)
774
+ ? value.toString()
775
+ : '');
776
+ });
777
+ }
778
+ addFormField(parent, attr) {
779
+ const value = (this.params.values && attr.kind !== EntityAttrKind.Lookup)
780
+ ? this.params.values.getValue(attr.id)
781
+ : !this.params.isEditForm
782
+ ? attr.defaultValue
783
+ : undefined;
784
+ const editor = this.resolveEditor(attr);
785
+ const readOnly = this.params.isEditForm && (attr.isPrimaryKey || !attr.isEditable);
786
+ const required = !attr.isNullable;
787
+ if (isIE) {
788
+ parent = domel('div', parent)
789
+ .addClass('kfrm-field-ie')
790
+ .toDOM();
791
+ }
792
+ domel(parent)
793
+ .addChild('label', b => {
794
+ b.attr('for', attr.id);
795
+ b.addHtml(`${attr.caption} ${required ? '<sup style="color: red">*</sup>' : ''}: `);
796
+ if (attr.description) {
797
+ b.addChild('div', b => b
798
+ .attr('title', attr.description)
799
+ .addClass('question-mark')
800
+ .setStyle('vertical-align', 'middle')
801
+ .setStyle('display', 'inline-block'));
802
+ }
803
+ });
804
+ const hidden = attr.isPrimaryKey;
805
+ if (attr.kind === EntityAttrKind.Lookup) {
806
+ this.setupLookupField(parent, attr, readOnly, value);
807
+ return;
808
+ }
809
+ switch (editor.tag) {
810
+ case EditorTag.DateTime:
811
+ this.setupDateTimeField(parent, attr, value, readOnly, hidden);
812
+ break;
813
+ case EditorTag.List:
814
+ this.setupListField(parent, attr, value, editor.values, readOnly);
815
+ break;
816
+ case EditorTag.File:
817
+ this.setupFileField(parent, attr, readOnly, editor.accept);
818
+ break;
819
+ case EditorTag.Edit:
820
+ default:
821
+ if (editor.multiline) {
822
+ this.setupTextArea(parent, attr, value, readOnly);
823
+ }
824
+ else {
825
+ this.setupTextField(parent, attr, value, readOnly, hidden);
826
+ }
827
+ break;
828
+ }
829
+ }
830
+ resolveInputType(dataType) {
831
+ if (dataType === DataType.Bool)
832
+ return 'checkbox';
833
+ if (dataType === DataType.Blob)
834
+ return 'file';
835
+ return 'text';
836
+ }
837
+ resolveEditor(attr) {
838
+ let editor = attr.defaultEditor || new ValueEditor();
839
+ if (editor.tag == EditorTag.Unknown) {
840
+ if (utils.getDateDataTypes().indexOf(attr.dataType) >= 0) {
841
+ editor.tag = EditorTag.DateTime;
842
+ }
843
+ else {
844
+ editor.tag = EditorTag.Edit;
845
+ }
846
+ }
847
+ return editor;
848
+ }
849
+ applySumbit(ev) {
850
+ if (ev.keyCode === 13) {
851
+ this.sumbitCallback && this.sumbitCallback();
852
+ return false;
853
+ }
854
+ return false;
855
+ }
856
+ onSubmit(sumbitCallback) {
857
+ this.sumbitCallback = sumbitCallback;
858
+ return this;
859
+ }
860
+ build() {
861
+ let fb;
862
+ const formHtml = domel('div')
863
+ .addClass('kfrm-form')
864
+ .addChild('div', b => b
865
+ .addClass(`errors-block`)
866
+ .toDOM())
867
+ .addChild('div', b => {
868
+ b.addClass(`${isIE
869
+ ? 'kfrm-fields-ie col-ie-1-4 label-align-right'
870
+ : 'kfrm-fields col-a-1 label-align-right'}`);
871
+ fb = b;
872
+ })
873
+ .toDOM();
874
+ this.form['setHtmlInt'](formHtml);
875
+ for (const attr of this.context.getActiveEntity().attributes) {
876
+ if (!this.params.isEditForm && !attr.showOnCreate)
877
+ continue;
878
+ if (!attr.isPrimaryKey && this.params.isEditForm && !attr.showOnEdit) {
879
+ continue;
880
+ }
881
+ this.addFormField(fb.toDOM(), attr);
882
+ }
883
+ return this.form;
884
+ }
885
+ }
886
+
887
+ class ProgressBar {
888
+ constructor(slot) {
889
+ this.slot = slot;
890
+ this.hide();
891
+ this.slot.classList.add('ed-progress-bar');
892
+ }
893
+ show() {
894
+ this.slot.style.removeProperty('display');
895
+ }
896
+ hide() {
897
+ this.slot.style.display = 'none';
898
+ }
899
+ }
900
+
901
+ class EasyDataServerLoader {
902
+ constructor(context) {
903
+ this.context = context;
904
+ }
905
+ loadChunk(params) {
906
+ const url = this.context.resolveEndpoint('FetchDataset', { sourceId: params.sourceId || this.context.getActiveEntity().id });
907
+ delete params.sourceId;
908
+ this.context.startProcess();
909
+ const http = this.context.getHttpClient();
910
+ return http.post(url, params)
911
+ .then((result) => {
912
+ const dataTable = new EasyDataTable({
913
+ chunkSize: 1000
914
+ });
915
+ const resultSet = result.resultSet;
916
+ for (const col of resultSet.cols) {
917
+ dataTable.columns.add(col);
918
+ }
919
+ for (const row of resultSet.rows) {
920
+ dataTable.addRow(row);
921
+ }
922
+ let totalRecords = 0;
923
+ if (result.meta && result.meta.totalRecords) {
924
+ totalRecords = result.meta.totalRecords;
925
+ }
926
+ return {
927
+ table: dataTable,
928
+ total: totalRecords,
929
+ hasNext: !params.needTotal
930
+ || params.offset + params.limit < totalRecords
931
+ };
932
+ })
933
+ .finally(() => {
934
+ this.context.endProcess();
935
+ });
936
+ }
937
+ }
938
+
939
+ class DataContext {
940
+ constructor(options) {
941
+ this.endpoints = new Map();
942
+ this.endpointVarsRegex = /\{.*?\}/g;
943
+ this.options = options || {};
944
+ this.http = new HttpClient();
945
+ this.model = new MetaData();
946
+ this.model.id = options.metaDataId || '__default';
947
+ this.dataLoader = new EasyDataServerLoader(this);
948
+ const dataTableOptions = Object.assign({ loader: this.dataLoader }, options.dataTable);
949
+ this.data = new EasyDataTable(dataTableOptions);
950
+ this.setDefaultEndpoints(this.options.endpoint || '/api/easydata');
951
+ }
952
+ getActiveEntity() {
953
+ return this.activeEntity;
954
+ }
955
+ setActiveSource(entityId) {
956
+ this.activeEntity = this.model.getRootEntity().subEntities
957
+ .filter(e => e.id == entityId)[0];
958
+ }
959
+ getMetaData() {
960
+ return this.model;
961
+ }
962
+ getData() {
963
+ return this.data;
964
+ }
965
+ getDataLoader() {
966
+ return this.dataLoader;
967
+ }
968
+ createFilter(sourceId, data, isLookup) {
969
+ return new TextDataFilter(this.dataLoader, data || this.getData(), sourceId || this.activeEntity.id, isLookup);
970
+ }
971
+ loadMetaData() {
972
+ const url = this.resolveEndpoint('GetMetaData');
973
+ this.startProcess();
974
+ return this.http.get(url)
975
+ .then(result => {
976
+ if (result.model) {
977
+ this.model.loadFromData(result.model);
978
+ }
979
+ return this.model;
980
+ })
981
+ .catch(error => {
982
+ console.error(`Error: ${error.message}. Source: ${error.sourceError}`);
983
+ return null;
984
+ })
985
+ .finally(() => {
986
+ this.endProcess();
987
+ });
988
+ }
989
+ getHttpClient() {
990
+ return this.http;
991
+ }
992
+ fetchDataset() {
993
+ this.data.clear();
994
+ return this.dataLoader.loadChunk({ offset: 0, limit: this.data.chunkSize, needTotal: true })
995
+ .then(result => {
996
+ for (const col of result.table.columns.getItems()) {
997
+ this.data.columns.add(col);
998
+ }
999
+ this.data.setTotal(result.total);
1000
+ for (const row of result.table.getCachedRows()) {
1001
+ this.data.addRow(row);
1002
+ }
1003
+ return this.data;
1004
+ });
1005
+ }
1006
+ fetchRecord(keys, sourceId) {
1007
+ const url = this.resolveEndpoint('FetchRecord', { sourceId: sourceId || this.activeEntity.id });
1008
+ this.startProcess();
1009
+ return this.http.get(url, { queryParams: keys })
1010
+ .finally(() => this.endProcess());
1011
+ }
1012
+ createRecord(obj, sourceId) {
1013
+ const url = this.resolveEndpoint('CreateRecord', { sourceId: sourceId || this.activeEntity.id });
1014
+ this.startProcess();
1015
+ return this.http.post(url, obj, { dataType: 'json' })
1016
+ .finally(() => this.endProcess());
1017
+ }
1018
+ updateRecord(obj, sourceId) {
1019
+ const url = this.resolveEndpoint('UpdateRecord', { sourceId: sourceId || this.activeEntity.id });
1020
+ this.startProcess();
1021
+ return this.http.post(url, obj, { dataType: 'json' })
1022
+ .finally(() => this.endProcess());
1023
+ }
1024
+ deleteRecord(obj, sourceId) {
1025
+ const url = this.resolveEndpoint('DeleteRecord', { sourceId: sourceId || this.activeEntity.id });
1026
+ this.startProcess();
1027
+ return this.http.post(url, obj, { dataType: 'json' })
1028
+ .finally(() => this.endProcess());
1029
+ }
1030
+ setEndpoint(key, value) {
1031
+ this.endpoints.set(key, value);
1032
+ }
1033
+ setEnpointIfNotExist(key, value) {
1034
+ if (!this.endpoints.has(key))
1035
+ this.endpoints.set(key, value);
1036
+ }
1037
+ resolveEndpoint(endpointKey, options) {
1038
+ options = options || {};
1039
+ let result = this.endpoints.get(endpointKey);
1040
+ if (!result) {
1041
+ throw endpointKey + ' endpoint is not defined';
1042
+ }
1043
+ let matches = result.match(this.endpointVarsRegex);
1044
+ if (matches) {
1045
+ for (let match of matches) {
1046
+ let opt = match.substring(1, match.length - 1);
1047
+ let optVal = options[opt];
1048
+ if (!optVal) {
1049
+ if (opt == 'modelId') {
1050
+ optVal = this.model.getId();
1051
+ }
1052
+ else if (opt == 'sourceId') {
1053
+ optVal = this.activeEntity.id;
1054
+ }
1055
+ else {
1056
+ throw `Parameter [${opt}] is not defined`;
1057
+ }
1058
+ }
1059
+ result = result.replace(match, optVal);
1060
+ }
1061
+ }
1062
+ return result;
1063
+ }
1064
+ startProcess() {
1065
+ if (this.options.onProcessStart)
1066
+ this.options.onProcessStart();
1067
+ }
1068
+ endProcess() {
1069
+ if (this.options.onProcessEnd)
1070
+ this.options.onProcessEnd();
1071
+ }
1072
+ setDefaultEndpoints(endpointBase) {
1073
+ this.setEnpointIfNotExist('GetMetaData', combinePath(endpointBase, 'models/{modelId}'));
1074
+ this.setEnpointIfNotExist('FetchDataset', combinePath(endpointBase, 'models/{modelId}/sources/{sourceId}/fetch'));
1075
+ this.setEnpointIfNotExist('FetchRecord', combinePath(endpointBase, 'models/{modelId}/sources/{sourceId}/fetch'));
1076
+ this.setEnpointIfNotExist('CreateRecord', combinePath(endpointBase, 'models/{modelId}/sources/{sourceId}/create'));
1077
+ this.setEnpointIfNotExist('UpdateRecord', combinePath(endpointBase, 'models/{modelId}/sources/{sourceId}/update'));
1078
+ this.setEnpointIfNotExist('DeleteRecord', combinePath(endpointBase, 'models/{modelId}/sources/{sourceId}/delete'));
1079
+ }
1080
+ }
1081
+
1082
+ class TypeValidator extends Validator {
1083
+ constructor() {
1084
+ super();
1085
+ this.name = 'Type';
1086
+ }
1087
+ validate(attr, value) {
1088
+ if (!utils.IsDefinedAndNotNull(value) || value == '')
1089
+ return { successed: true };
1090
+ if (utils.isNumericType(attr.dataType)) {
1091
+ if (!utils.isNumeric(value))
1092
+ return {
1093
+ successed: false,
1094
+ messages: [i18n.getText('NumberError')]
1095
+ };
1096
+ if (utils.isIntType(attr.dataType)
1097
+ && !Number.isInteger(Number.parseFloat(value))) {
1098
+ return {
1099
+ successed: false,
1100
+ messages: [i18n.getText('IntNumberError')]
1101
+ };
1102
+ }
1103
+ }
1104
+ return { successed: true };
1105
+ }
1106
+ }
1107
+
1108
+ class RequiredValidator extends Validator {
1109
+ constructor() {
1110
+ super();
1111
+ this.name = 'Required';
1112
+ }
1113
+ validate(attr, value) {
1114
+ if (!attr.isNullable && (!utils.IsDefinedAndNotNull(value)
1115
+ || value === ''))
1116
+ return {
1117
+ successed: false,
1118
+ messages: [i18n.getText('RequiredError')]
1119
+ };
1120
+ return { successed: true };
1121
+ }
1122
+ }
1123
+
1124
+ class EntityDataView {
1125
+ constructor(slot, context, basePath, options) {
1126
+ this.slot = slot;
1127
+ this.context = context;
1128
+ this.basePath = basePath;
1129
+ this.options = {
1130
+ showFilterBox: true,
1131
+ showBackToEntities: true
1132
+ };
1133
+ this.defaultValidators = [new RequiredValidator(), new TypeValidator()];
1134
+ this.options = utils.assignDeep(this.options, options || {});
1135
+ this.dlg = new DefaultDialogService();
1136
+ const ent = this.context.getActiveEntity();
1137
+ if (!ent) {
1138
+ throw "Can't find active entity for " + window.location.pathname;
1139
+ }
1140
+ this.slot.innerHTML += `<h1>${ent.captionPlural || ent.caption}</h1>`;
1141
+ if (this.options.showBackToEntities) {
1142
+ domel(this.slot)
1143
+ .addChild('a', b => b
1144
+ .attr('href', 'javascript:void(0)')
1145
+ .text(`← ${i18n.getText('BackToEntities')}`)
1146
+ .on('click', (e) => {
1147
+ e.preventDefault();
1148
+ setLocation(this.basePath);
1149
+ }));
1150
+ }
1151
+ this.renderGrid();
1152
+ }
1153
+ syncGridColumnHandler(column) {
1154
+ if (column.dataColumn) {
1155
+ const attr = this.context.getMetaData().getAttributeById(column.dataColumn.id);
1156
+ if (attr) {
1157
+ column.isVisible = attr.showOnView;
1158
+ }
1159
+ }
1160
+ }
1161
+ renderGrid() {
1162
+ this.context.fetchDataset()
1163
+ .then(result => {
1164
+ const gridSlot = document.createElement('div');
1165
+ this.slot.appendChild(gridSlot);
1166
+ gridSlot.id = 'Grid';
1167
+ this.grid = new EasyGrid(utils.assignDeep({
1168
+ slot: gridSlot,
1169
+ dataTable: result,
1170
+ paging: {
1171
+ pageSize: 15,
1172
+ allowPageSizeChange: true,
1173
+ pageSizeItems: [15, 30, 50, 100, 200]
1174
+ },
1175
+ showPlusButton: this.context.getActiveEntity().isEditable,
1176
+ plusButtonTitle: i18n.getText('AddRecordBtnTitle'),
1177
+ showActiveRow: false,
1178
+ onPlusButtonClick: this.addClickHandler.bind(this),
1179
+ onGetCellRenderer: this.manageCellRenderer.bind(this),
1180
+ onRowDbClick: this.rowDbClickHandler.bind(this),
1181
+ onSyncGridColumn: this.syncGridColumnHandler.bind(this)
1182
+ }, this.options.grid || {}));
1183
+ if (this.options.showFilterBox) {
1184
+ let filterWidgetSlot;
1185
+ const filterBarDiv = domel('div')
1186
+ .addClass(`kfrm-form`)
1187
+ .setStyle('margin', '10px 0px')
1188
+ .addChild('div', b => filterWidgetSlot = b.toDOM()).toDOM();
1189
+ this.slot.insertBefore(filterBarDiv, gridSlot);
1190
+ const dataFilter = this.context.createFilter();
1191
+ this.filterWidget = new TextFilterWidget(filterWidgetSlot, this.grid, dataFilter);
1192
+ }
1193
+ });
1194
+ }
1195
+ manageCellRenderer(column, defaultRenderer) {
1196
+ if (column.isRowNum) {
1197
+ column.width = 110;
1198
+ return (value, column, cell, rowEl) => {
1199
+ const b = domel('div', cell)
1200
+ .addClass(`keg-cell-value`);
1201
+ if (this.context.getActiveEntity().isEditable) {
1202
+ b.addChild('a', b => b
1203
+ .attr('href', 'javascript:void(0)')
1204
+ .text(i18n.getText('EditBtn'))
1205
+ .on('click', (ev) => this.editClickHandler(ev, parseInt(rowEl.getAttribute('data-row-idx')))))
1206
+ .addChild('span', b => b.text(' | '))
1207
+ .addChild('a', b => b
1208
+ .attr('href', 'javascript:void(0)')
1209
+ .text(i18n.getText('DeleteBtn'))
1210
+ .on('click', (ev) => this.deleteClickHandler(ev, parseInt(rowEl.getAttribute('data-row-idx')))));
1211
+ }
1212
+ };
1213
+ }
1214
+ }
1215
+ addClickHandler() {
1216
+ const activeEntity = this.context.getActiveEntity();
1217
+ const form = new EntityEditFormBuilder(this.context)
1218
+ .onSubmit(() => dlg.submit())
1219
+ .build();
1220
+ form.useValidators(this.defaultValidators);
1221
+ const dlg = this.dlg.open({
1222
+ title: i18n.getText('AddDlgCaption')
1223
+ .replace('{entity}', activeEntity.caption),
1224
+ body: form.getHtml(),
1225
+ onSubmit: () => {
1226
+ if (!form.validate())
1227
+ return false;
1228
+ form.getData()
1229
+ .then(obj => this.context.createRecord(obj))
1230
+ .then(() => {
1231
+ return this.refreshData();
1232
+ })
1233
+ .catch((error) => {
1234
+ this.processError(error);
1235
+ });
1236
+ }
1237
+ });
1238
+ }
1239
+ editClickHandler(ev, rowIndex) {
1240
+ this.grid.getData().getRow(rowIndex)
1241
+ .then(row => {
1242
+ if (row) {
1243
+ this.showEditForm(row);
1244
+ }
1245
+ });
1246
+ }
1247
+ showEditForm(row) {
1248
+ const activeEntity = this.context.getActiveEntity();
1249
+ const form = new EntityEditFormBuilder(this.context, { isEditForm: true, values: row })
1250
+ .onSubmit(() => dlg.submit())
1251
+ .build();
1252
+ form.useValidators(this.defaultValidators);
1253
+ const dlg = this.dlg.open({
1254
+ title: i18n.getText('EditDlgCaption')
1255
+ .replace('{entity}', activeEntity.caption),
1256
+ body: form.getHtml(),
1257
+ onSubmit: () => {
1258
+ if (!form.validate())
1259
+ return false;
1260
+ form.getData()
1261
+ .then(obj => this.context.updateRecord(obj))
1262
+ .then(() => {
1263
+ return this.refreshData();
1264
+ })
1265
+ .catch((error) => {
1266
+ this.processError(error);
1267
+ });
1268
+ }
1269
+ });
1270
+ }
1271
+ rowDbClickHandler(ev) {
1272
+ if (this.context.getActiveEntity().isEditable) {
1273
+ this.showEditForm(ev.row);
1274
+ }
1275
+ }
1276
+ deleteClickHandler(ev, rowIndex) {
1277
+ this.grid.getData().getRow(rowIndex)
1278
+ .then(row => {
1279
+ if (row) {
1280
+ const activeEntity = this.context.getActiveEntity();
1281
+ const keyAttrs = activeEntity.getPrimaryAttrs();
1282
+ const keyVals = keyAttrs.map(attr => row.getValue(attr.id));
1283
+ const keys = keyAttrs.reduce((val, attr, index) => {
1284
+ const property = attr.id.substring(attr.id.lastIndexOf('.') + 1);
1285
+ val[property] = keyVals[index];
1286
+ return val;
1287
+ }, {});
1288
+ this.dlg.openConfirm(i18n.getText('DeleteDlgCaption')
1289
+ .replace('{entity}', activeEntity.caption), i18n.getText('DeleteDlgMessage')
1290
+ .replace('{recordId}', Object.keys(keys)
1291
+ .map(key => `${key}:${keys[key]}`).join(';')))
1292
+ .then((result) => {
1293
+ if (result) {
1294
+ this.context.deleteRecord(keys)
1295
+ .then(() => {
1296
+ return this.refreshData();
1297
+ })
1298
+ .catch((error) => {
1299
+ this.processError(error);
1300
+ });
1301
+ }
1302
+ });
1303
+ }
1304
+ });
1305
+ }
1306
+ processError(error) {
1307
+ this.dlg.open({
1308
+ title: 'Ooops, something went wrong',
1309
+ body: error.message,
1310
+ closable: true,
1311
+ cancelable: false
1312
+ });
1313
+ }
1314
+ refreshData() {
1315
+ return this.context.fetchDataset()
1316
+ .then(() => {
1317
+ let processed = false;
1318
+ if (this.filterWidget) {
1319
+ processed = this.filterWidget.applyFilter(false);
1320
+ }
1321
+ if (!processed) {
1322
+ this.grid.refresh();
1323
+ }
1324
+ });
1325
+ }
1326
+ }
1327
+
1328
+ class RootDataView {
1329
+ constructor(slot, context, basePath) {
1330
+ this.slot = slot;
1331
+ this.context = context;
1332
+ this.basePath = basePath;
1333
+ this.metaData = this.context.getMetaData();
1334
+ this.slot.innerHTML += `<h1>${i18n.getText('RootViewTitle')}</h1>`;
1335
+ this.renderEntitySelector();
1336
+ }
1337
+ renderEntitySelector() {
1338
+ const entities = this.metaData.getRootEntity().subEntities;
1339
+ if (this.slot) {
1340
+ domel(this.slot)
1341
+ .addChild('div', b => b
1342
+ .addClass('ed-root')
1343
+ .addChild('div', b => b
1344
+ .addClass('ed-menu-description')
1345
+ .addText(i18n.getText(!this.metaData.isEmpty() ? 'EntityMenuDesc' : 'ModelIsEmpty')))
1346
+ .addChild('ul', b => {
1347
+ b.addClass('ed-entity-menu');
1348
+ entities.forEach(ent => {
1349
+ b.addChild('li', b => {
1350
+ b.addClass('ed-entity-item')
1351
+ .on('click', () => {
1352
+ setLocation(`${this.basePath}/${decodeURIComponent(ent.id)}`);
1353
+ })
1354
+ .addChild('div', b => {
1355
+ b.addClass('ed-entity-item-caption')
1356
+ .addText(ent.captionPlural || ent.caption);
1357
+ });
1358
+ if (ent.description) {
1359
+ b.addChild('div', b => {
1360
+ b.addClass('ed-entity-item-descr')
1361
+ .addText(`${ent.description}`);
1362
+ });
1363
+ }
1364
+ });
1365
+ });
1366
+ }));
1367
+ }
1368
+ }
1369
+ }
1370
+
1371
+ class EasyDataViewDispatcher {
1372
+ constructor(options) {
1373
+ this.options = {
1374
+ container: '#EasyDataContainer',
1375
+ basePath: 'easydata'
1376
+ };
1377
+ this.onSetLocation = () => {
1378
+ this.setActiveView();
1379
+ };
1380
+ this.attach = () => {
1381
+ window.addEventListener('ed_set_location', this.onSetLocation);
1382
+ window.addEventListener('popstate', this.onSetLocation);
1383
+ };
1384
+ this.options = utils.assign(this.options, options || {});
1385
+ if (this.options.rootEntity) {
1386
+ this.options.showBackToEntities = false;
1387
+ this.basePath = '/';
1388
+ }
1389
+ else {
1390
+ this.basePath = this.normalizeBasePath(this.options.basePath);
1391
+ }
1392
+ this.setContainer(this.options.container);
1393
+ const progressBarSlot = document.createElement('div');
1394
+ const bar = new ProgressBar(progressBarSlot);
1395
+ const parent = this.container.parentElement;
1396
+ parent.insertBefore(progressBarSlot, parent.firstElementChild);
1397
+ this.context = new DataContext({
1398
+ endpoint: this.options.endpoint,
1399
+ dataTable: this.options.dataTable,
1400
+ onProcessStart: () => bar.show(),
1401
+ onProcessEnd: () => bar.hide()
1402
+ });
1403
+ }
1404
+ normalizeBasePath(basePath) {
1405
+ basePath = this.trimSlashes(basePath);
1406
+ const fullPath = decodeURIComponent(window.location.pathname);
1407
+ const idx = fullPath.toLocaleLowerCase().indexOf(basePath);
1408
+ return idx >= 0 ? fullPath.substring(0, idx + basePath.length) : '/';
1409
+ }
1410
+ trimSlashes(path) {
1411
+ return path.replace(/^\/|\/$/g, '');
1412
+ }
1413
+ setContainer(container) {
1414
+ if (!container) {
1415
+ throw 'Container is undefined';
1416
+ }
1417
+ if (typeof container === 'string') {
1418
+ if (container.length) {
1419
+ if (container[0] === '.') {
1420
+ const result = document.getElementsByClassName(container.substring(1));
1421
+ if (result.length)
1422
+ this.container = result[0];
1423
+ }
1424
+ else {
1425
+ if (container[0] === '#') {
1426
+ container = container.substring(1);
1427
+ }
1428
+ this.container = document.getElementById(container);
1429
+ }
1430
+ if (!this.container) {
1431
+ throw Error('Unrecognized `container` parameter: ' + container + '\n'
1432
+ + 'It must be an element ID, a class name (starting with .) or an HTMLElement object itself.');
1433
+ }
1434
+ }
1435
+ }
1436
+ else {
1437
+ this.container = container;
1438
+ }
1439
+ }
1440
+ getActiveSourceId() {
1441
+ if (this.options.rootEntity)
1442
+ return this.options.rootEntity;
1443
+ const path = decodeURIComponent(window.location.pathname);
1444
+ const idIndex = this.basePath.length + 1;
1445
+ return idIndex < path.length ? path.substring(idIndex) : null;
1446
+ }
1447
+ run() {
1448
+ this.attach();
1449
+ return this.context.loadMetaData()
1450
+ .then(() => {
1451
+ this.setActiveView();
1452
+ })
1453
+ .catch(error => console.error(error));
1454
+ }
1455
+ setActiveView() {
1456
+ this.clear();
1457
+ const sourceId = this.getActiveSourceId();
1458
+ if (sourceId) {
1459
+ this.context.setActiveSource(sourceId);
1460
+ window['EDView'] = new EntityDataView(this.container, this.context, this.basePath, this.options);
1461
+ }
1462
+ else {
1463
+ window['EDView'] = new RootDataView(this.container, this.context, this.basePath);
1464
+ }
1465
+ }
1466
+ clear() {
1467
+ this.container.innerHTML = '';
1468
+ this.context.getData().clear();
1469
+ }
1470
+ detach() {
1471
+ window.removeEventListener('ed_set_location', this.onSetLocation);
1472
+ window.removeEventListener('popstate', this.onSetLocation);
1473
+ }
1474
+ }
1475
+
1476
+ function addEasyDataCRUDTexts() {
1477
+ i18n.updateDefaultTexts({
1478
+ RequiredError: 'Value is required.',
1479
+ NumberError: 'Value should be a number',
1480
+ IntNumberError: 'Value should be an integer number',
1481
+ DateTimeError: 'Invalid date or time value',
1482
+ LookupSelectedItem: 'Selected item: ',
1483
+ LookupDlgCaption: 'Select {entity}',
1484
+ None: 'None',
1485
+ NavigationBtnTitle: 'Navigation values',
1486
+ CalendarBtnTitle: 'Open calendar',
1487
+ TimerBtnTitle: 'Open timer',
1488
+ AddBtnTitle: 'Add',
1489
+ AddRecordBtnTitle: 'Add record',
1490
+ EditBtn: 'Edit',
1491
+ DeleteBtn: 'Delete',
1492
+ SelectLink: '[ select ]',
1493
+ AddDlgCaption: 'Create {entity}',
1494
+ EditDlgCaption: 'Edit {entity}',
1495
+ DeleteDlgCaption: 'Delete {entity}',
1496
+ DeleteDlgMessage: 'Are you sure you want to remove this record: {{recordId}}?',
1497
+ EntityMenuDesc: 'Click on an entity to view/edit its content',
1498
+ BackToEntities: 'Back to entities',
1499
+ SearchBtn: 'Search',
1500
+ SearchInputPlaceholder: 'Search...',
1501
+ RootViewTitle: 'Entities',
1502
+ ModelIsEmpty: 'No entity was found.'
1503
+ });
1504
+ }
1505
+ addEasyDataCRUDTexts();
1506
+
1507
+ export { DataContext, EasyDataServerLoader, EasyDataViewDispatcher, EntityDataView, EntityEditForm, EntityEditFormBuilder, ProgressBar, RequiredValidator, RootDataView, TextDataFilter, TextFilterWidget, TypeValidator, Validator };