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