@easydata/crud 1.4.20 → 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.
- package/dist/assets/css/ed-view.css +104 -0
- package/dist/easydata.crud.cjs.js +1520 -21
- package/dist/easydata.crud.esm.js +1507 -0
- package/package.json +8 -8
- package/dist/easydata.crud.cjs.js.map +0 -1
- package/dist/easydata.crud.es.js +0 -22
- package/dist/easydata.crud.es.js.map +0 -1
|
@@ -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('🗙')
|
|
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 };
|