x4js 1.4.2

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 (176) hide show
  1. package/lib/application.d.ts +95 -0
  2. package/lib/application.js +137 -0
  3. package/lib/base64.d.ts +31 -0
  4. package/lib/base64.js +135 -0
  5. package/lib/base_component.d.ts +64 -0
  6. package/lib/base_component.js +77 -0
  7. package/lib/button.d.ts +145 -0
  8. package/lib/button.js +235 -0
  9. package/lib/calendar.d.ts +77 -0
  10. package/lib/calendar.js +236 -0
  11. package/lib/canvas.d.ts +88 -0
  12. package/lib/canvas.js +354 -0
  13. package/lib/cardview.d.ts +83 -0
  14. package/lib/cardview.js +152 -0
  15. package/lib/checkbox.d.ts +72 -0
  16. package/lib/checkbox.js +126 -0
  17. package/lib/color.d.ts +144 -0
  18. package/lib/color.js +584 -0
  19. package/lib/colorpicker.d.ts +98 -0
  20. package/lib/colorpicker.js +1457 -0
  21. package/lib/combobox.d.ts +97 -0
  22. package/lib/combobox.js +246 -0
  23. package/lib/component.d.ts +572 -0
  24. package/lib/component.js +1712 -0
  25. package/lib/datastore.d.ts +392 -0
  26. package/lib/datastore.js +986 -0
  27. package/lib/dialog.d.ts +171 -0
  28. package/lib/dialog.js +468 -0
  29. package/lib/dom_events.d.ts +284 -0
  30. package/lib/dom_events.js +13 -0
  31. package/lib/drag_manager.d.ts +26 -0
  32. package/lib/drag_manager.js +118 -0
  33. package/lib/drawtext.d.ts +43 -0
  34. package/lib/drawtext.js +261 -0
  35. package/lib/fileupload.d.ts +60 -0
  36. package/lib/fileupload.js +158 -0
  37. package/lib/form.d.ts +122 -0
  38. package/lib/form.js +293 -0
  39. package/lib/formatters.d.ts +31 -0
  40. package/lib/formatters.js +75 -0
  41. package/lib/gridview.d.ts +171 -0
  42. package/lib/gridview.js +786 -0
  43. package/lib/hosts/host.d.ts +44 -0
  44. package/lib/hosts/host.js +69 -0
  45. package/lib/i18n.d.ts +67 -0
  46. package/lib/i18n.js +169 -0
  47. package/lib/icon.d.ts +56 -0
  48. package/lib/icon.js +173 -0
  49. package/lib/image.d.ts +51 -0
  50. package/lib/image.js +149 -0
  51. package/lib/index.js +1 -0
  52. package/lib/input.d.ts +86 -0
  53. package/lib/input.js +172 -0
  54. package/lib/label.d.ts +54 -0
  55. package/lib/label.js +86 -0
  56. package/lib/layout.d.ts +77 -0
  57. package/lib/layout.js +261 -0
  58. package/lib/link.d.ts +46 -0
  59. package/lib/link.js +55 -0
  60. package/lib/listview.d.ts +173 -0
  61. package/lib/listview.js +532 -0
  62. package/lib/md5.d.ts +56 -0
  63. package/lib/md5.js +397 -0
  64. package/lib/menu.d.ts +122 -0
  65. package/lib/menu.js +276 -0
  66. package/lib/messagebox.d.ts +64 -0
  67. package/lib/messagebox.js +141 -0
  68. package/lib/panel.d.ts +42 -0
  69. package/lib/panel.js +61 -0
  70. package/lib/popup.d.ts +71 -0
  71. package/lib/popup.js +373 -0
  72. package/lib/property_editor.d.ts +67 -0
  73. package/lib/property_editor.js +247 -0
  74. package/lib/radiobtn.d.ts +68 -0
  75. package/lib/radiobtn.js +131 -0
  76. package/lib/rating.d.ts +49 -0
  77. package/lib/rating.js +93 -0
  78. package/lib/request.d.ts +48 -0
  79. package/lib/request.js +220 -0
  80. package/lib/router.d.ts +13 -0
  81. package/lib/router.js +27 -0
  82. package/lib/settings.d.ts +33 -0
  83. package/lib/settings.js +63 -0
  84. package/lib/sidebarview.d.ts +44 -0
  85. package/lib/sidebarview.js +73 -0
  86. package/lib/smartedit.d.ts +103 -0
  87. package/lib/smartedit.js +381 -0
  88. package/lib/spreadsheet.d.ts +214 -0
  89. package/lib/spreadsheet.js +1073 -0
  90. package/lib/styles.d.ts +81 -0
  91. package/lib/styles.js +262 -0
  92. package/lib/svgcomponent.d.ts +165 -0
  93. package/lib/svgcomponent.js +350 -0
  94. package/lib/tabbar.d.ts +41 -0
  95. package/lib/tabbar.js +66 -0
  96. package/lib/tabview.d.ts +45 -0
  97. package/lib/tabview.js +79 -0
  98. package/lib/textarea.d.ts +59 -0
  99. package/lib/textarea.js +119 -0
  100. package/lib/textedit.d.ts +118 -0
  101. package/lib/textedit.js +406 -0
  102. package/lib/texthiliter.d.ts +56 -0
  103. package/lib/texthiliter.js +219 -0
  104. package/lib/toaster.d.ts +38 -0
  105. package/lib/toaster.js +58 -0
  106. package/lib/tools.d.ts +382 -0
  107. package/lib/tools.js +1096 -0
  108. package/lib/tooltips.d.ts +42 -0
  109. package/lib/tooltips.js +148 -0
  110. package/lib/treeview.d.ts +128 -0
  111. package/lib/treeview.js +490 -0
  112. package/lib/x4_events.d.ts +253 -0
  113. package/lib/x4_events.js +363 -0
  114. package/package.json +21 -0
  115. package/src/README.md +2 -0
  116. package/src/application.ts +191 -0
  117. package/src/base64.ts +162 -0
  118. package/src/base_component.ts +118 -0
  119. package/src/button.ts +327 -0
  120. package/src/calendar.ts +312 -0
  121. package/src/canvas.ts +501 -0
  122. package/src/cardview.ts +220 -0
  123. package/src/checkbox.ts +178 -0
  124. package/src/color.ts +748 -0
  125. package/src/colorpicker.ts +1618 -0
  126. package/src/combobox.ts +348 -0
  127. package/src/component.ts +2330 -0
  128. package/src/datastore.ts +1318 -0
  129. package/src/dialog.ts +631 -0
  130. package/src/dom_events.ts +297 -0
  131. package/src/drag_manager.ts +168 -0
  132. package/src/drawtext.ts +342 -0
  133. package/src/fileupload.ts +208 -0
  134. package/src/form.ts +362 -0
  135. package/src/formatters.ts +96 -0
  136. package/src/gridview.ts +1051 -0
  137. package/src/hosts/electron.ts +161 -0
  138. package/src/hosts/host.ts +100 -0
  139. package/src/hosts/nwjs.ts +141 -0
  140. package/src/hosts/nwjs_types.ts +339 -0
  141. package/src/i18n.ts +205 -0
  142. package/src/icon.ts +237 -0
  143. package/src/image.ts +198 -0
  144. package/src/input.ts +236 -0
  145. package/src/label.ts +124 -0
  146. package/src/layout.ts +366 -0
  147. package/src/link.ts +82 -0
  148. package/src/listview.ts +749 -0
  149. package/src/md5.ts +432 -0
  150. package/src/menu.ts +394 -0
  151. package/src/messagebox.ts +199 -0
  152. package/src/panel.ts +81 -0
  153. package/src/popup.ts +488 -0
  154. package/src/property_editor.ts +333 -0
  155. package/src/radiobtn.ts +190 -0
  156. package/src/rating.ts +131 -0
  157. package/src/request.ts +296 -0
  158. package/src/router.ts +43 -0
  159. package/src/settings.ts +75 -0
  160. package/src/sidebarview.ts +97 -0
  161. package/src/smartedit.ts +532 -0
  162. package/src/spreadsheet.ts +1423 -0
  163. package/src/styles.ts +332 -0
  164. package/src/svgcomponent.ts +440 -0
  165. package/src/tabbar.ts +105 -0
  166. package/src/tabview.ts +106 -0
  167. package/src/textarea.ts +183 -0
  168. package/src/textedit.ts +535 -0
  169. package/src/texthiliter.ts +284 -0
  170. package/src/toaster.ts +76 -0
  171. package/src/tools.ts +1391 -0
  172. package/src/tooltips.ts +185 -0
  173. package/src/treeview.ts +670 -0
  174. package/src/x4.less +1940 -0
  175. package/src/x4_events.ts +558 -0
  176. package/tsconfig.json +14 -0
@@ -0,0 +1,986 @@
1
+ /**
2
+ * ___ ___ __
3
+ * \ \/ / / _
4
+ * \ / /_| |_
5
+ * / \____ _|
6
+ * /__/\__\ |_|
7
+ *
8
+ * @file datastore.ts
9
+ * @author Etienne Cochard
10
+ * @license
11
+ * Copyright (c) 2019-2021 R-libre ingenierie
12
+ *
13
+ * This program is free software; you can redistribute it and/or modify
14
+ * it under the terms of the GNU General Public License as published by
15
+ * the Free Software Foundation; either version 3 of the License, or
16
+ * (at your option) any later version.
17
+ *
18
+ * This program is distributed in the hope that it will be useful,
19
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
20
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21
+ * GNU General Public License for more details.
22
+ *
23
+ * You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
24
+ **/
25
+ import { ajaxRequest } from './request';
26
+ import { isArray, isString } from './tools';
27
+ import { BasicEvent, EvChange, EventSource } from './x4_events';
28
+ import { BaseComponent } from './base_component';
29
+ function EvDataChange(type, id) {
30
+ return BasicEvent({ type, id });
31
+ }
32
+ /**
33
+ *
34
+ */
35
+ class MetaInfos {
36
+ name;
37
+ id; // field name holding 'id' record info
38
+ fields; // field list
39
+ constructor(name) {
40
+ this.name = name;
41
+ this.id = undefined;
42
+ this.fields = [];
43
+ }
44
+ }
45
+ const metaFields = Symbol('metaField');
46
+ function _getMetas(obj, create = true) {
47
+ let ctor = obj.constructor;
48
+ let mfld = ctor.hasOwnProperty(metaFields) ? ctor[metaFields] : undefined;
49
+ if (mfld === undefined) {
50
+ if (!create) {
51
+ console.assert(mfld !== undefined);
52
+ }
53
+ // construct our metas
54
+ mfld = new MetaInfos(ctor.name);
55
+ // merge with parent class metas
56
+ let pctor = Object.getPrototypeOf(ctor);
57
+ if (pctor != Record) {
58
+ let pmetas = pctor[metaFields];
59
+ mfld.fields = [...pmetas.fields, ...mfld.fields];
60
+ console.assert(mfld.id === undefined, 'cannot define mutiple record id');
61
+ if (!mfld.id) {
62
+ mfld.id = pmetas.id;
63
+ }
64
+ }
65
+ obj.constructor[metaFields] = mfld;
66
+ }
67
+ return mfld;
68
+ }
69
+ export var data;
70
+ (function (data_1) {
71
+ /**
72
+ * define a record id
73
+ * @example
74
+ * \@data_id()
75
+ * id: string; // this field is the record id
76
+ **/
77
+ function id() {
78
+ return (ownerCls, fldName) => {
79
+ let metas = _getMetas(ownerCls);
80
+ metas.fields.push({
81
+ name: fldName,
82
+ type: 'any',
83
+ required: true,
84
+ });
85
+ metas.id = fldName;
86
+ };
87
+ }
88
+ data_1.id = id;
89
+ /**
90
+ * @ignore
91
+ */
92
+ function field(data) {
93
+ return (ownerCls, fldName) => {
94
+ let metas = _getMetas(ownerCls);
95
+ metas.fields.push({
96
+ name: fldName,
97
+ ...data
98
+ });
99
+ };
100
+ }
101
+ data_1.field = field;
102
+ /**
103
+ * following member is a string field
104
+ * @example
105
+ * \@data_string()
106
+ * my_field: string; // this field will be seen as a string
107
+ */
108
+ function string(props) {
109
+ return field({ ...props, type: 'string' });
110
+ }
111
+ data_1.string = string;
112
+ /**
113
+ * following member is an integer field
114
+ * @example
115
+ * \@data_string()
116
+ * my_field: number; // this field will be seen as an integer
117
+ */
118
+ function int(props) {
119
+ return field({ ...props, type: 'int' });
120
+ }
121
+ data_1.int = int;
122
+ /**
123
+ * following member is a float field
124
+ * @example
125
+ * \@data_float()
126
+ * my_field: number; // this field will be seen as a float
127
+ */
128
+ function float(props) {
129
+ return field({ ...props, type: 'float' });
130
+ }
131
+ data_1.float = float;
132
+ /**
133
+ * following member is a boolean field
134
+ * @example
135
+ * \@data_bool()
136
+ * my_field: boolean; // this field will be seen as a boolean
137
+ */
138
+ function bool(props) {
139
+ return field({ ...props, type: 'bool' });
140
+ }
141
+ data_1.bool = bool;
142
+ /**
143
+ * following member is a date field
144
+ * @example
145
+ * \@data_date()
146
+ * my_field: date; // this field will be seen as a date
147
+ */
148
+ function date(props) {
149
+ return field({ ...props, type: 'date' });
150
+ }
151
+ data_1.date = date;
152
+ /**
153
+ * following member is a calculated field
154
+ * @example
155
+ * \@data_calc( )
156
+ * get my_field(): string => {
157
+ * return 'hello';
158
+ * };
159
+ */
160
+ function calc(props) {
161
+ return field({ ...props, type: 'calc' });
162
+ }
163
+ data_1.calc = calc;
164
+ /**
165
+ * following member is a record array
166
+ * @example
167
+ * \@data_array( )
168
+ * my_field(): TypedRecord[];
169
+ */
170
+ function array(ctor, props) {
171
+ return data.field({ ...props, type: 'array', model: new ctor() });
172
+ }
173
+ data_1.array = array;
174
+ })(data || (data = {}));
175
+ /**
176
+ * record model
177
+ */
178
+ export class Record {
179
+ constructor(data, id) {
180
+ if (data !== undefined) {
181
+ this.unSerialize(data, id);
182
+ }
183
+ }
184
+ clone(source) {
185
+ let rec = new this.constructor();
186
+ if (source) {
187
+ rec.unSerialize(source);
188
+ }
189
+ return rec;
190
+ }
191
+ /**
192
+ * get the record unique identifier
193
+ * by default the return value is the first field
194
+ * @return unique identifier
195
+ */
196
+ getID() {
197
+ let metas = _getMetas(this, false);
198
+ return this[metas.id];
199
+ }
200
+ /**
201
+ * MUST IMPLEMENT
202
+ * @returns fields descriptors
203
+ */
204
+ getFields() {
205
+ let metas = _getMetas(this, false);
206
+ return metas.fields;
207
+ }
208
+ /**
209
+ *
210
+ */
211
+ validate() {
212
+ let errs = null;
213
+ let fields = this.getFields();
214
+ fields.forEach((fi) => {
215
+ if (fi.required && !this.getField(fi.name)) {
216
+ if (errs) {
217
+ errs = [];
218
+ }
219
+ errs.push(new Error(`field ${fi.name} is required.`));
220
+ }
221
+ });
222
+ return errs;
223
+ }
224
+ //mapAnyFields() {
225
+ // this.getFields = ( ) => {
226
+ // return Object.keys( this ).map( (name) => {
227
+ // return <FieldInfo>{ name };
228
+ // });
229
+ // }
230
+ //}
231
+ getFieldIndex(name) {
232
+ let fields = this.getFields();
233
+ return fields.findIndex((fd) => fd.name == name);
234
+ }
235
+ /**
236
+ * default serializer
237
+ * @returns an object with known record values
238
+ */
239
+ serialize() {
240
+ let rec = {};
241
+ this.getFields().forEach((f) => {
242
+ if (f.calc === undefined) {
243
+ rec[f.name] = rec[f.name];
244
+ }
245
+ });
246
+ return rec;
247
+ }
248
+ /**
249
+ * default unserializer
250
+ * @param data - input data
251
+ * @returns a new Record
252
+ */
253
+ unSerialize(data, id) {
254
+ let fields = this.getFields();
255
+ fields.forEach((sf) => {
256
+ let value = data[sf.name];
257
+ if (value !== undefined) {
258
+ this[sf.name] = this._convertField(sf, value);
259
+ }
260
+ });
261
+ if (id !== undefined) {
262
+ this[fields[0].name] = id;
263
+ }
264
+ else {
265
+ console.assert(this.getID() !== undefined); // store do not have ID field
266
+ }
267
+ return this;
268
+ }
269
+ /**
270
+ * field conversion
271
+ * @param field - field descriptor
272
+ * @param input - value to convert
273
+ * @returns the field value in it's original form
274
+ */
275
+ _convertField(field, input) {
276
+ //TODO: boolean
277
+ switch (field.type) {
278
+ case 'float': {
279
+ let ffv = typeof (input) === 'number' ? input : parseFloat(input);
280
+ if (field.prec !== undefined) {
281
+ let mul = Math.pow(10, field.prec);
282
+ ffv = Math.round(ffv * mul) / mul;
283
+ }
284
+ return ffv;
285
+ }
286
+ case 'int': {
287
+ return typeof (input) === 'number' ? input : parseInt(input);
288
+ }
289
+ case 'date': {
290
+ return isString(input) ? new Date(input) : input;
291
+ }
292
+ case 'array': {
293
+ let result = [];
294
+ if (field.model) {
295
+ input.forEach((v) => {
296
+ result.push(field.model.clone(v));
297
+ });
298
+ return result;
299
+ }
300
+ break;
301
+ }
302
+ }
303
+ return input;
304
+ }
305
+ /**
306
+ * get raw value of a field
307
+ * @param name - field name or field index
308
+ */
309
+ getRaw(name) {
310
+ let idx;
311
+ let fields = this.getFields();
312
+ if (typeof (name) === 'string') {
313
+ idx = fields.findIndex(fi => fi.name == name);
314
+ if (idx < 0) {
315
+ console.assert(false, 'unknown field: ' + name);
316
+ return undefined;
317
+ }
318
+ }
319
+ else if (name >= 0 && name < fields.length) {
320
+ idx = name;
321
+ }
322
+ else {
323
+ console.assert(false, 'bad field name: ' + name);
324
+ return undefined;
325
+ }
326
+ let fld = fields[idx];
327
+ if (fld.calc !== undefined) {
328
+ return fld.calc.call(this);
329
+ }
330
+ return this[fld.name];
331
+ }
332
+ /**
333
+ *
334
+ * @param name
335
+ * @param data
336
+ */
337
+ setRaw(name, data) {
338
+ this[name] = data;
339
+ }
340
+ /**
341
+ * get field value (as string)
342
+ * @param name - field name
343
+ * @example
344
+ * let value = record.get('field1');
345
+ */
346
+ getField(name) {
347
+ let v = this.getRaw(name);
348
+ return (v === undefined || v === null) ? '' : '' + v;
349
+ }
350
+ /**
351
+ * set field value
352
+ * @param name - field name
353
+ * @param value - value to set
354
+ * @example
355
+ * record.set( 'field1', 7 );
356
+ */
357
+ setField(name, value) {
358
+ let fields = this.getFields();
359
+ let idx = fields.findIndex(fi => fi.name == name);
360
+ if (idx < 0) {
361
+ console.assert(false, 'unknown field: ' + name);
362
+ return;
363
+ }
364
+ let fld = fields[idx];
365
+ if (fld.calc !== undefined) {
366
+ console.assert(false, 'cannot set calc field: ' + name);
367
+ return;
368
+ }
369
+ this.setRaw(fld.name, value);
370
+ }
371
+ }
372
+ /**
373
+ * by default, the field id is rhe first member or the record
374
+ */
375
+ export class AutoRecord extends Record {
376
+ m_data;
377
+ m_fid;
378
+ constructor(data) {
379
+ super();
380
+ this.m_data = data;
381
+ }
382
+ getID() {
383
+ if (!this.m_fid) {
384
+ let fnames = Object.keys(this.m_data);
385
+ this.m_fid = fnames[0];
386
+ }
387
+ return this.m_data[this.m_fid];
388
+ }
389
+ getFields() {
390
+ let fnames = Object.keys(this.m_data);
391
+ let fields = fnames.map((n) => {
392
+ return {
393
+ name: n
394
+ };
395
+ });
396
+ return fields;
397
+ }
398
+ getRaw(name) {
399
+ return this.m_data[name];
400
+ }
401
+ setRaw(name, data) {
402
+ this.m_data[name] = data;
403
+ }
404
+ clone(data) {
405
+ return new AutoRecord({ ...data });
406
+ }
407
+ }
408
+ export class DataProxy extends BaseComponent {
409
+ constructor(props) {
410
+ super(props);
411
+ }
412
+ load() {
413
+ this._refresh();
414
+ }
415
+ save(data) {
416
+ if (this.m_props.type == 'local') {
417
+ console.assert(false); // not imp
418
+ /*
419
+ const fs = require('fs');
420
+ fs.writeFileSync( this.m_path, data );
421
+ */
422
+ }
423
+ }
424
+ _refresh(delay = 0) {
425
+ if (this.m_props.type == 'local') {
426
+ console.assert(false); // not imp
427
+ /*
428
+ const fs = require('fs');
429
+ fs.readFile( this.m_path, ( _, bdata ) => {;
430
+ let data = JSON.parse(bdata);
431
+ this.emit( 'dataready', data );
432
+ } );
433
+ */
434
+ }
435
+ else {
436
+ setTimeout(() => {
437
+ ajaxRequest({
438
+ url: this.m_props.path,
439
+ method: 'GET',
440
+ params: this.m_props.params,
441
+ success: (data) => {
442
+ this.emit('change', EvChange(data));
443
+ }
444
+ });
445
+ }, delay);
446
+ }
447
+ }
448
+ }
449
+ /**
450
+ *
451
+ */
452
+ export class DataStore extends EventSource {
453
+ m_model;
454
+ m_fields;
455
+ m_records;
456
+ m_proxy;
457
+ m_rec_index;
458
+ constructor(props) {
459
+ super();
460
+ this.m_fields = undefined;
461
+ this.m_records = [];
462
+ this.m_rec_index = null;
463
+ this.m_model = props.model;
464
+ this.m_fields = props.model.getFields();
465
+ if (props.data) {
466
+ this.setRawData(props.data);
467
+ }
468
+ else if (props.url) {
469
+ this.load(props.url);
470
+ }
471
+ }
472
+ /**
473
+ *
474
+ * @param records
475
+ */
476
+ load(url) {
477
+ //todo: that
478
+ if (url.substr(0, 7) === 'file://') {
479
+ this.m_proxy = new DataProxy({
480
+ type: 'local',
481
+ path: url.substr(7),
482
+ events: { change: (ev) => { this.setData(ev.value); } }
483
+ });
484
+ this.m_proxy.load();
485
+ }
486
+ else {
487
+ this.m_proxy = new DataProxy({
488
+ type: 'ajax',
489
+ path: url,
490
+ events: { change: (ev) => { this.setData(ev.value); } }
491
+ });
492
+ this.m_proxy.load();
493
+ }
494
+ }
495
+ reload() {
496
+ this.m_proxy.load();
497
+ }
498
+ /**
499
+ * convert raw objects to real records from model
500
+ * @param records
501
+ */
502
+ setData(records) {
503
+ let realRecords = [];
504
+ records.forEach((rec) => {
505
+ realRecords.push(this.m_model.clone(rec));
506
+ });
507
+ this.setRawData(realRecords);
508
+ }
509
+ /**
510
+ * just set the records
511
+ * @param records - must be of the same type as model
512
+ */
513
+ setRawData(records) {
514
+ this.m_records = records;
515
+ this._rebuildIndex();
516
+ this.emit('data_change', EvDataChange('change'));
517
+ }
518
+ _rebuildIndex() {
519
+ this.m_rec_index = null; // null to signal that we have to run on records instead of index
520
+ this.m_rec_index = this.createIndex(null); // prepare index (remove deleted)
521
+ this.m_rec_index = this.sortIndex(this.m_rec_index, null); // sort by id
522
+ }
523
+ /**
524
+ *
525
+ */
526
+ update(rec) {
527
+ let id = rec.getID();
528
+ let index = this.indexOfId(id);
529
+ if (index < 0) {
530
+ return false;
531
+ }
532
+ this.m_records[this.m_rec_index[index]] = rec;
533
+ this.emit('data_change', EvDataChange('update', id));
534
+ return true;
535
+ }
536
+ /**
537
+ *
538
+ * @param data
539
+ */
540
+ append(rec) {
541
+ if (!(rec instanceof Record)) {
542
+ let nrec = this.m_model.clone();
543
+ rec = nrec.unSerialize(rec);
544
+ }
545
+ console.assert(rec.getID());
546
+ this.m_records.push(rec);
547
+ this._rebuildIndex();
548
+ this.emit('data_change', EvDataChange('create', rec.getID()));
549
+ }
550
+ /**
551
+ *
552
+ */
553
+ getMaxId() {
554
+ let maxID = undefined;
555
+ this.m_records.forEach((r) => {
556
+ let rid = r.getID();
557
+ if (maxID === undefined || maxID < rid) {
558
+ maxID = rid;
559
+ }
560
+ });
561
+ return maxID;
562
+ }
563
+ /**
564
+ *
565
+ * @param id
566
+ */
567
+ delete(id) {
568
+ let idx = this.indexOfId(id);
569
+ if (idx < 0) {
570
+ return false;
571
+ }
572
+ idx = this.m_rec_index[idx];
573
+ // mark as deleted
574
+ this.m_records.splice(idx, 1);
575
+ this._rebuildIndex();
576
+ this.emit('data_change', EvDataChange('delete', id));
577
+ return true;
578
+ }
579
+ /**
580
+ * return the number of records
581
+ */
582
+ get count() {
583
+ return this.m_rec_index ? this.m_rec_index.length : this.m_records.length;
584
+ }
585
+ /**
586
+ * return the fields
587
+ */
588
+ get fields() {
589
+ return this.m_fields;
590
+ }
591
+ /**
592
+ * find the index of the element with the given id
593
+ */
594
+ indexOfId(id) {
595
+ //if( this.count<10 ) {
596
+ // this.forEach( (rec) => rec.getID() == id );
597
+ //}
598
+ for (let lim = this.count, base = 0; lim != 0; lim >>= 1) {
599
+ let p = base + (lim >> 1); // int conversion
600
+ let idx = this.m_rec_index[p];
601
+ let rid = this.m_records[idx].getID();
602
+ if (rid == id) {
603
+ return p;
604
+ }
605
+ if (rid < id) {
606
+ base = p + 1;
607
+ lim--;
608
+ }
609
+ }
610
+ return -1;
611
+ }
612
+ /**
613
+ * return the record by it's id
614
+ * @returns record or null
615
+ */
616
+ getById(id) {
617
+ let idx = this.indexOfId(id);
618
+ if (idx < 0) {
619
+ return null;
620
+ }
621
+ idx = this.m_rec_index[idx];
622
+ return this.m_records[idx];
623
+ }
624
+ /**
625
+ * return a record by it's index
626
+ * @returns record or null
627
+ */
628
+ getByIndex(index) {
629
+ let idx = this.m_rec_index[index];
630
+ return this._getRecord(idx);
631
+ }
632
+ _getRecord(index) {
633
+ return this.m_records[index] ?? null;
634
+ }
635
+ moveTo(other) {
636
+ other.setRawData(this.m_records);
637
+ }
638
+ /**
639
+ * create a new view on the DataStore
640
+ * @param opts
641
+ */
642
+ createView(opts) {
643
+ let eopts = { ...opts, store: this };
644
+ return new DataView(eopts);
645
+ }
646
+ /**
647
+ *
648
+ */
649
+ createIndex(filter) {
650
+ if (filter && filter.op === 'empty-result') {
651
+ return new Uint32Array(0);
652
+ }
653
+ let index = new Uint32Array(this.m_records.length);
654
+ let iidx = 0;
655
+ if (!filter) {
656
+ // reset filter
657
+ this.forEach((rec, idx) => {
658
+ index[iidx++] = idx;
659
+ });
660
+ }
661
+ else {
662
+ if (typeof (filter.op) === 'function') {
663
+ let fn = filter.op;
664
+ // scan all records and append only interesting ones
665
+ this.forEach((rec, idx) => {
666
+ // skip deleted
667
+ if (!rec) {
668
+ return;
669
+ }
670
+ if (fn(rec)) {
671
+ index[iidx++] = idx;
672
+ }
673
+ });
674
+ }
675
+ else {
676
+ let filterFld = this.m_model.getFieldIndex(filter.field); // field index to filter on
677
+ if (filterFld < 0) {
678
+ // unknown filter field, nothing inside
679
+ console.assert(false, 'unknown field name in filter');
680
+ return new Uint32Array(0);
681
+ }
682
+ let filterValue = filter.value;
683
+ if (isString(filterValue) && !filter.caseSensitive) {
684
+ filterValue = filterValue.toUpperCase();
685
+ }
686
+ function _lt(recval) {
687
+ return recval < filterValue;
688
+ }
689
+ function _le(recval) {
690
+ return recval <= filterValue;
691
+ }
692
+ function _eq(recval) {
693
+ return recval == filterValue;
694
+ }
695
+ function _neq(recval) {
696
+ return recval != filterValue;
697
+ }
698
+ function _ge(recval) {
699
+ return recval >= filterValue;
700
+ }
701
+ function _gt(recval) {
702
+ return recval > filterValue;
703
+ }
704
+ function _re(recval) {
705
+ filterRe.lastIndex = -1;
706
+ return filterRe.test(recval);
707
+ }
708
+ let filterFn; // filter fn
709
+ let filterRe; // if fielter is regexp
710
+ if (filterValue instanceof RegExp) {
711
+ filterRe = filterValue;
712
+ filterFn = _re;
713
+ }
714
+ else {
715
+ switch (filter.op) {
716
+ case '<': {
717
+ filterFn = _lt;
718
+ break;
719
+ }
720
+ case '<=': {
721
+ filterFn = _le;
722
+ break;
723
+ }
724
+ case '=': {
725
+ filterFn = _eq;
726
+ break;
727
+ }
728
+ case '>=': {
729
+ filterFn = _ge;
730
+ break;
731
+ }
732
+ case '>': {
733
+ filterFn = _gt;
734
+ break;
735
+ }
736
+ case '<>': {
737
+ filterFn = _neq;
738
+ break;
739
+ }
740
+ }
741
+ }
742
+ // scan all records and append only interesting ones
743
+ this.forEach((rec, idx) => {
744
+ // skip deleted
745
+ if (!rec) {
746
+ return;
747
+ }
748
+ let field = rec.getRaw(filterFld);
749
+ if (field === null || field === undefined) {
750
+ field = '';
751
+ }
752
+ else {
753
+ field = '' + field;
754
+ if (!filter.caseSensitive) {
755
+ field = field.toUpperCase();
756
+ }
757
+ }
758
+ let keep = filterFn(field);
759
+ if (keep) {
760
+ index[iidx++] = idx;
761
+ }
762
+ ;
763
+ });
764
+ }
765
+ }
766
+ return index.slice(0, iidx);
767
+ }
768
+ sortIndex(index, sort) {
769
+ let bads = 0; // unknown fields
770
+ let fidxs = []; // fields indexes
771
+ // if no fields are given, reset sort by id
772
+ if (sort === null) {
773
+ fidxs.push({ fidx: 0, asc: true });
774
+ }
775
+ else {
776
+ fidxs = sort.map((si) => {
777
+ let fi = this.m_model.getFieldIndex(si.field);
778
+ if (fi == -1) {
779
+ console.assert(false, 'unknown field name in sort');
780
+ bads++;
781
+ }
782
+ return { fidx: fi, asc: si.ascending };
783
+ });
784
+ }
785
+ // unknown field or nothing to sort on ??
786
+ if (bads || fidxs.length == 0) {
787
+ return index;
788
+ }
789
+ // sort only by one field : optimize it
790
+ if (fidxs.length == 1) {
791
+ let field = fidxs[0].fidx;
792
+ index.sort((ia, ib) => {
793
+ let va = this.getByIndex(ia).getRaw(field) ?? '';
794
+ let vb = this.getByIndex(ib).getRaw(field) ?? '';
795
+ if (va > vb) {
796
+ return 1;
797
+ }
798
+ if (va < vb) {
799
+ return -1;
800
+ }
801
+ return 0;
802
+ });
803
+ // just reverse if
804
+ if (!fidxs[0].asc) {
805
+ index.reverse();
806
+ }
807
+ }
808
+ else {
809
+ index.sort((ia, ib) => {
810
+ for (let fi = 0; fi < fidxs.length; fi++) {
811
+ let fidx = fidxs[fi].fidx;
812
+ let mul = fidxs[fi].asc ? 1 : -1;
813
+ let va = this.getByIndex(ia).getRaw(fidx) ?? '';
814
+ let vb = this.getByIndex(ib).getRaw(fidx) ?? '';
815
+ if (va > vb) {
816
+ return mul;
817
+ }
818
+ if (va < vb) {
819
+ return -mul;
820
+ }
821
+ }
822
+ return 0;
823
+ });
824
+ }
825
+ return index;
826
+ }
827
+ /**
828
+ *
829
+ */
830
+ forEach(cb) {
831
+ if (this.m_rec_index) {
832
+ this.m_rec_index.some((ri, index) => {
833
+ if (cb(this.m_records[ri], index)) {
834
+ return index;
835
+ }
836
+ });
837
+ }
838
+ else {
839
+ this.m_records.some((rec, index) => {
840
+ if (rec) {
841
+ if (cb(rec, index)) {
842
+ return index;
843
+ }
844
+ }
845
+ });
846
+ }
847
+ }
848
+ export() {
849
+ return this.m_records;
850
+ }
851
+ changed() {
852
+ this.emit('data_change', EvDataChange('change'));
853
+ }
854
+ }
855
+ export function EvViewChange(action) {
856
+ return BasicEvent({ action });
857
+ }
858
+ /**
859
+ * Dataview allow different views of the DataStore.
860
+ * You can sort the columns & filter data
861
+ * You can have multiple views for a single DataStore
862
+ */
863
+ export class DataView extends BaseComponent {
864
+ m_index;
865
+ m_store;
866
+ m_sort;
867
+ m_filter;
868
+ constructor(props) {
869
+ super(props);
870
+ this.m_store = props.store;
871
+ this.m_index = null;
872
+ this.m_filter = null;
873
+ this.m_sort = null;
874
+ this.filter(props.filter);
875
+ if (props.order) {
876
+ if (isString(props.order)) {
877
+ this.sort([{ field: props.order, ascending: true }]);
878
+ }
879
+ else if (isArray(props.order)) {
880
+ this.sort(props.order);
881
+ }
882
+ else {
883
+ this.sort([props.order]);
884
+ }
885
+ }
886
+ else {
887
+ this.sort(null);
888
+ }
889
+ this.m_store.on('data_change', (e) => this._storeChange(e));
890
+ }
891
+ _storeChange(ev) {
892
+ this._filter(this.m_filter, ev.type != 'change');
893
+ this._sort(this.m_sort, ev.type != 'change');
894
+ this.emit('view_change', EvViewChange('change'));
895
+ }
896
+ /**
897
+ *
898
+ * @param filter
899
+ */
900
+ filter(filter) {
901
+ this.m_index = null; // null to signal that we have to run on records instead of index
902
+ return this._filter(filter, true);
903
+ }
904
+ _filter(filter, notify) {
905
+ this.m_index = this.m_store.createIndex(filter);
906
+ this.m_filter = filter;
907
+ // need to sort again:
908
+ if (this.m_sort) {
909
+ this.sort(this.m_sort);
910
+ }
911
+ if (notify) {
912
+ this.emit('view_change', EvViewChange('filter'));
913
+ }
914
+ return this.m_index.length;
915
+ }
916
+ /**
917
+ *
918
+ * @param columns
919
+ * @param ascending
920
+ */
921
+ sort(props) {
922
+ this._sort(props, true);
923
+ }
924
+ _sort(props, notify) {
925
+ this.m_index = this.m_store.sortIndex(this.m_index, props);
926
+ this.m_sort = props;
927
+ if (notify) {
928
+ this.emit('view_change', EvViewChange('sort'));
929
+ }
930
+ }
931
+ /**
932
+ *
933
+ */
934
+ get store() {
935
+ return this.m_store;
936
+ }
937
+ /**
938
+ *
939
+ */
940
+ get count() {
941
+ return this.m_index.length;
942
+ }
943
+ /**
944
+ *
945
+ * @param id
946
+ */
947
+ indexOfId(id) {
948
+ let ridx = this.m_store.indexOfId(id);
949
+ return this.m_index.findIndex((rid) => rid === ridx);
950
+ }
951
+ /**
952
+ *
953
+ * @param index
954
+ */
955
+ getByIndex(index) {
956
+ if (index >= 0 && index < this.m_index.length) {
957
+ let rid = this.m_index[index];
958
+ return this.m_store.getByIndex(rid);
959
+ }
960
+ return null;
961
+ }
962
+ /**
963
+ *
964
+ * @param id
965
+ */
966
+ getById(id) {
967
+ return this.m_store.getById(id);
968
+ }
969
+ changed() {
970
+ this.emit('view_change', EvViewChange('change'));
971
+ }
972
+ /**
973
+ *
974
+ */
975
+ forEach(cb) {
976
+ debugger;
977
+ this.m_index.some((index) => {
978
+ let rec = this.m_store.getByIndex(index);
979
+ if (rec) {
980
+ if (cb(rec, index)) {
981
+ return index;
982
+ }
983
+ }
984
+ });
985
+ }
986
+ }