x4js 2.0.20 → 2.0.22
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/lib/cjs/x4.css +1 -1
- package/lib/cjs/x4.js +2 -2
- package/lib/esm/x4.css +1 -1
- package/lib/esm/x4.mjs +2 -2
- package/lib/styles/x4.css +1 -1
- package/lib/types/x4js.d.ts +93 -77
- package/package.json +1 -1
- package/src/components/boxes/boxes.ts +33 -1
- package/src/components/canvas/canvas.ts +9 -3
- package/src/components/combobox/combobox.module.scss +13 -0
- package/src/components/combobox/combobox.ts +7 -0
- package/src/components/form/form.ts +4 -4
- package/src/components/gridview/gridview.ts +12 -9
- package/src/components/header/header.module.scss +2 -1
- package/src/components/header/header.ts +17 -5
- package/src/components/icon/icon.module.scss +1 -0
- package/src/components/label/label.module.scss +6 -1
- package/src/components/listbox/listbox.module.scss +35 -31
- package/src/components/listbox/listbox.ts +50 -12
- package/src/components/messages/messages.ts +14 -0
- package/src/components/notification/notification.ts +1 -1
- package/src/components/popup/popup.ts +13 -2
- package/src/components/propgrid/propgrid.ts +4 -2
- package/src/components/sizers/sizer.ts +4 -4
- package/src/components/tabs/tabs.module.scss +2 -2
- package/src/components/tabs/tabs.ts +28 -4
- package/src/components/textedit/textedit.ts +1 -1
- package/src/components/treeview/treeview.module.scss +9 -1
- package/src/components/treeview/treeview.ts +52 -13
- package/src/core/component.ts +46 -9
- package/src/core/core_data.ts +8 -8
- package/src/core/core_state.ts +1 -1
- package/src/core/core_tools.ts +2 -0
- package/lib/src/components/base.scss +0 -25
- package/lib/src/components/boxes/boxes.module.scss +0 -54
- package/lib/src/components/boxes/boxes.ts +0 -370
- package/lib/src/components/breadcrumb/breadcrumb.scss +0 -56
- package/lib/src/components/breadcrumb/breadcrumb.ts +0 -93
- package/lib/src/components/breadcrumb/chevron-right.svg +0 -1
- package/lib/src/components/btngroup/btngroup.module.scss +0 -41
- package/lib/src/components/btngroup/btngroup.ts +0 -153
- package/lib/src/components/button/button.module.scss +0 -173
- package/lib/src/components/button/button.ts +0 -185
- package/lib/src/components/calendar/calendar-check-sharp-light.svg +0 -1
- package/lib/src/components/calendar/calendar.module.scss +0 -163
- package/lib/src/components/calendar/calendar.ts +0 -327
- package/lib/src/components/calendar/chevron-left-sharp-light.svg +0 -1
- package/lib/src/components/calendar/chevron-right-sharp-light.svg +0 -1
- package/lib/src/components/canvas/canvas.module.scss +0 -25
- package/lib/src/components/canvas/canvas.ts +0 -189
- package/lib/src/components/canvas/canvas_ex.ts +0 -276
- package/lib/src/components/checkbox/check.svg +0 -4
- package/lib/src/components/checkbox/checkbox.module.scss +0 -142
- package/lib/src/components/checkbox/checkbox.ts +0 -140
- package/lib/src/components/colorinput/colorinput.module.scss +0 -65
- package/lib/src/components/colorinput/colorinput.ts +0 -91
- package/lib/src/components/colorinput/crosshairs-simple-sharp-light.svg +0 -1
- package/lib/src/components/colorpicker/colorpicker.module.scss +0 -133
- package/lib/src/components/colorpicker/colorpicker.ts +0 -482
- package/lib/src/components/combobox/combobox.module.scss +0 -133
- package/lib/src/components/combobox/combobox.ts +0 -275
- package/lib/src/components/combobox/updown.svg +0 -4
- package/lib/src/components/components.ts +0 -42
- package/lib/src/components/dialog/dialog.module.scss +0 -104
- package/lib/src/components/dialog/dialog.ts +0 -229
- package/lib/src/components/dialog/xmark-sharp-light.svg +0 -1
- package/lib/src/components/filedrop/cloud-arrow-up.svg +0 -1
- package/lib/src/components/filedrop/filedrop.module.scss +0 -70
- package/lib/src/components/filedrop/filedrop.ts +0 -131
- package/lib/src/components/form/form.module.scss +0 -38
- package/lib/src/components/form/form.ts +0 -172
- package/lib/src/components/gridview/arrow-down-light.svg +0 -1
- package/lib/src/components/gridview/arrow-up-light.svg +0 -1
- package/lib/src/components/gridview/gridview.module.scss +0 -324
- package/lib/src/components/gridview/gridview.ts +0 -1175
- package/lib/src/components/header/header.module.scss +0 -40
- package/lib/src/components/header/header.ts +0 -130
- package/lib/src/components/icon/icon.module.scss +0 -31
- package/lib/src/components/icon/icon.ts +0 -137
- package/lib/src/components/image/image.module.scss +0 -28
- package/lib/src/components/image/image.ts +0 -168
- package/lib/src/components/input/input.module.scss +0 -74
- package/lib/src/components/input/input.ts +0 -422
- package/lib/src/components/keyboard/arrow-up.svg +0 -1
- package/lib/src/components/keyboard/delete-left.svg +0 -1
- package/lib/src/components/keyboard/eye-slash.svg +0 -1
- package/lib/src/components/keyboard/keyboard.module.scss +0 -134
- package/lib/src/components/keyboard/keyboard.ts +0 -526
- package/lib/src/components/label/label.module.scss +0 -76
- package/lib/src/components/label/label.ts +0 -97
- package/lib/src/components/link/link.ts +0 -81
- package/lib/src/components/listbox/listbox.module.scss +0 -161
- package/lib/src/components/listbox/listbox.ts +0 -539
- package/lib/src/components/menu/caret-right-solid.svg +0 -1
- package/lib/src/components/menu/menu.module.scss +0 -117
- package/lib/src/components/menu/menu.ts +0 -174
- package/lib/src/components/messages/circle-exclamation.svg +0 -1
- package/lib/src/components/messages/messages.module.scss +0 -92
- package/lib/src/components/messages/messages.ts +0 -215
- package/lib/src/components/messages/pen-field.svg +0 -1
- package/lib/src/components/normalize.scss +0 -391
- package/lib/src/components/notification/circle-check-solid.svg +0 -1
- package/lib/src/components/notification/circle-exclamation-solid.svg +0 -1
- package/lib/src/components/notification/circle-notch-light.svg +0 -1
- package/lib/src/components/notification/notification.module.scss +0 -84
- package/lib/src/components/notification/notification.ts +0 -107
- package/lib/src/components/notification/xmark-sharp-light.svg +0 -1
- package/lib/src/components/panel/panel.module.scss +0 -60
- package/lib/src/components/panel/panel.ts +0 -58
- package/lib/src/components/popup/popup.module.scss +0 -51
- package/lib/src/components/popup/popup.ts +0 -442
- package/lib/src/components/progress/progress.module.scss +0 -57
- package/lib/src/components/progress/progress.ts +0 -44
- package/lib/src/components/propgrid/folder-closed.svg +0 -1
- package/lib/src/components/propgrid/folder-open.svg +0 -1
- package/lib/src/components/propgrid/progrid.module.scss +0 -112
- package/lib/src/components/propgrid/propgrid.ts +0 -288
- package/lib/src/components/propgrid/updown.svg +0 -4
- package/lib/src/components/radio/radio.module.scss +0 -147
- package/lib/src/components/radio/radio.svg +0 -4
- package/lib/src/components/radio/radio.ts +0 -142
- package/lib/src/components/rating/rating.module.scss +0 -23
- package/lib/src/components/rating/rating.ts +0 -131
- package/lib/src/components/rating/star-sharp-light.svg +0 -1
- package/lib/src/components/rating/star-sharp-solid.svg +0 -1
- package/lib/src/components/select/select.module.scss +0 -9
- package/lib/src/components/select/select.ts +0 -134
- package/lib/src/components/shared.scss +0 -137
- package/lib/src/components/sizers/sizer.module.scss +0 -90
- package/lib/src/components/sizers/sizer.ts +0 -132
- package/lib/src/components/slider/slider.module.scss +0 -118
- package/lib/src/components/slider/slider.ts +0 -198
- package/lib/src/components/switch/switch.module.scss +0 -127
- package/lib/src/components/switch/switch.ts +0 -62
- package/lib/src/components/tabs/tabs.module.scss +0 -45
- package/lib/src/components/tabs/tabs.ts +0 -205
- package/lib/src/components/textarea/textarea.module.scss +0 -63
- package/lib/src/components/textarea/textarea.ts +0 -125
- package/lib/src/components/textedit/textedit.module.scss +0 -116
- package/lib/src/components/textedit/textedit.ts +0 -115
- package/lib/src/components/themes.scss +0 -88
- package/lib/src/components/tickline/tickline.module.scss +0 -26
- package/lib/src/components/tickline/tickline.ts +0 -82
- package/lib/src/components/tooltips/circle-info-sharp-light.svg +0 -1
- package/lib/src/components/tooltips/comments-question.svg +0 -1
- package/lib/src/components/tooltips/tooltips.scss +0 -72
- package/lib/src/components/tooltips/tooltips.ts +0 -109
- package/lib/src/components/treeview/chevron-down-light.svg +0 -1
- package/lib/src/components/treeview/treeview.module.scss +0 -185
- package/lib/src/components/treeview/treeview.ts +0 -445
- package/lib/src/components/viewport/viewport.module.scss +0 -32
- package/lib/src/components/viewport/viewport.ts +0 -41
- package/lib/src/core/component.ts +0 -1066
- package/lib/src/core/core_application.ts +0 -265
- package/lib/src/core/core_colors.ts +0 -250
- package/lib/src/core/core_data.ts +0 -1310
- package/lib/src/core/core_dom.ts +0 -471
- package/lib/src/core/core_dragdrop.ts +0 -201
- package/lib/src/core/core_element.ts +0 -115
- package/lib/src/core/core_events.ts +0 -177
- package/lib/src/core/core_i18n.ts +0 -393
- package/lib/src/core/core_react.ts +0 -79
- package/lib/src/core/core_router.ts +0 -237
- package/lib/src/core/core_state.ts +0 -62
- package/lib/src/core/core_styles.ts +0 -214
- package/lib/src/core/core_svg.ts +0 -712
- package/lib/src/core/core_tools.ts +0 -906
- package/lib/src/types/scss.d.ts +0 -4
- package/lib/src/types/svg.d.ts +0 -1
- package/lib/src/types/x4react.d.ts +0 -9
- package/lib/src/x4.scss +0 -19
- package/lib/src/x4.ts +0 -35
- package/lib/src/x4tsx.d.ts +0 -25
|
@@ -1,1310 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ___ ___ __
|
|
3
|
-
* \ \/ / / _
|
|
4
|
-
* \ / /_| |_
|
|
5
|
-
* / \____ _|
|
|
6
|
-
* /__/\__\ |_|
|
|
7
|
-
*
|
|
8
|
-
* @file core_data.ts
|
|
9
|
-
* @author Etienne Cochard
|
|
10
|
-
*
|
|
11
|
-
* @copyright (c) 2024 R-libre ingenierie
|
|
12
|
-
*
|
|
13
|
-
* Use of this source code is governed by an MIT-style license
|
|
14
|
-
* that can be found in the LICENSE file or at https://opensource.org/licenses/MIT.
|
|
15
|
-
**/
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
import { EvChange } from './component';
|
|
19
|
-
import { CoreElement } from './core_element';
|
|
20
|
-
import { CoreEvent, EventMap, EventSource } from './core_events';
|
|
21
|
-
import { isArray, isString } from './core_tools';
|
|
22
|
-
|
|
23
|
-
export type DataRecordID = any;
|
|
24
|
-
export type DataFieldValue = string | Date | number | boolean;
|
|
25
|
-
|
|
26
|
-
export type ChangeCallback = (type: string, id?: DataRecordID) => void;
|
|
27
|
-
export type CalcCallback = () => string;
|
|
28
|
-
|
|
29
|
-
export type FieldType = 'string' | 'int' | 'float' | 'date' | 'bool' | 'array' | 'object' | 'any' | 'calc';
|
|
30
|
-
export type DataIndex = Uint32Array;
|
|
31
|
-
|
|
32
|
-
export interface EvDataChange extends CoreEvent {
|
|
33
|
-
change_type: 'create' | 'update' | 'delete' | 'data' | 'change';
|
|
34
|
-
id?: DataRecordID;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* fields definition
|
|
44
|
-
* field with index=0 is record id
|
|
45
|
-
*/
|
|
46
|
-
|
|
47
|
-
export interface MetaData {
|
|
48
|
-
type?: FieldType;
|
|
49
|
-
prec?: number;
|
|
50
|
-
required?: boolean;
|
|
51
|
-
calc?: (rec: DataRecord) => any;
|
|
52
|
-
model?: DataModel; // in case of array of subtypes, the model
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
export interface FieldInfo extends MetaData {
|
|
56
|
-
name: string;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
*
|
|
61
|
-
*/
|
|
62
|
-
|
|
63
|
-
class MetaInfos {
|
|
64
|
-
name: string;
|
|
65
|
-
id: string; // field name holding 'id' record info
|
|
66
|
-
fields: FieldInfo[]; // field list
|
|
67
|
-
|
|
68
|
-
constructor( name: string ) {
|
|
69
|
-
this.name = name;
|
|
70
|
-
this.id = undefined;
|
|
71
|
-
this.fields = [];
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
const metaFields = Symbol( 'metaField' );
|
|
76
|
-
|
|
77
|
-
function _getMetas( obj: object, create = true ) : MetaInfos {
|
|
78
|
-
|
|
79
|
-
let ctor = obj.constructor as any;
|
|
80
|
-
let mfld = Object.prototype.hasOwnProperty.call(ctor,metaFields) ? ctor[metaFields] : undefined;
|
|
81
|
-
|
|
82
|
-
if( mfld===undefined ) {
|
|
83
|
-
if( !create ) {
|
|
84
|
-
console.assert( mfld!==undefined );
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// construct our metas
|
|
88
|
-
mfld = new MetaInfos( ctor.name );
|
|
89
|
-
|
|
90
|
-
// merge with parent class metas
|
|
91
|
-
let pctor = Object.getPrototypeOf(ctor);
|
|
92
|
-
if( pctor!=DataModel ) {
|
|
93
|
-
let pmetas = pctor[metaFields];
|
|
94
|
-
mfld.fields = [...pmetas.fields, ...mfld.fields ]
|
|
95
|
-
|
|
96
|
-
console.assert( mfld.id===undefined, 'cannot define mutiple record id' );
|
|
97
|
-
if( !mfld.id ) {
|
|
98
|
-
mfld.id = pmetas.id;
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
(obj.constructor as any)[metaFields] = mfld;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
return mfld;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
// eslint-disable-next-line @typescript-eslint/no-namespace
|
|
109
|
-
export namespace data {
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* define a model id
|
|
113
|
-
* @example
|
|
114
|
-
* \@data_id()
|
|
115
|
-
* id: string; // this field is the record id
|
|
116
|
-
**/
|
|
117
|
-
|
|
118
|
-
export function id( ) {
|
|
119
|
-
return ( ownerCls: any, fldName: string ) => {
|
|
120
|
-
let metas = _getMetas( ownerCls );
|
|
121
|
-
metas.fields.push( {
|
|
122
|
-
name: fldName,
|
|
123
|
-
type: 'any',
|
|
124
|
-
required: true,
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
metas.id = fldName;
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
/**
|
|
132
|
-
* @ignore
|
|
133
|
-
*/
|
|
134
|
-
|
|
135
|
-
export function field( data: MetaData ) {
|
|
136
|
-
|
|
137
|
-
return ( ownerCls: any, fldName: string ) => {
|
|
138
|
-
let metas = _getMetas( ownerCls );
|
|
139
|
-
metas.fields.push( {
|
|
140
|
-
name: fldName,
|
|
141
|
-
...data
|
|
142
|
-
} );
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
/**
|
|
147
|
-
* following member is a string field
|
|
148
|
-
* @example
|
|
149
|
-
* \@data_string()
|
|
150
|
-
* my_field: string; // this field will be seen as a string
|
|
151
|
-
*/
|
|
152
|
-
|
|
153
|
-
export function string( props?: MetaData ) {
|
|
154
|
-
return field( { ...props, type: 'string' } );
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
/**
|
|
158
|
-
* following member is an integer field
|
|
159
|
-
* @example
|
|
160
|
-
* \@data_string()
|
|
161
|
-
* my_field: number; // this field will be seen as an integer
|
|
162
|
-
*/
|
|
163
|
-
|
|
164
|
-
export function int( props?: MetaData ) {
|
|
165
|
-
return field( { ...props, type: 'int' } );
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
/**
|
|
169
|
-
* following member is a float field
|
|
170
|
-
* @example
|
|
171
|
-
* \@data_float()
|
|
172
|
-
* my_field: number; // this field will be seen as a float
|
|
173
|
-
*/
|
|
174
|
-
|
|
175
|
-
export function float( props?: MetaData ) {
|
|
176
|
-
return field( { ...props, type: 'float' } );
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
/**
|
|
180
|
-
* following member is a boolean field
|
|
181
|
-
* @example
|
|
182
|
-
* \@data_bool()
|
|
183
|
-
* my_field: boolean; // this field will be seen as a boolean
|
|
184
|
-
*/
|
|
185
|
-
|
|
186
|
-
export function bool( props?: MetaData ) {
|
|
187
|
-
return field( { ...props, type: 'bool' } );
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
/**
|
|
191
|
-
* following member is a date field
|
|
192
|
-
* @example
|
|
193
|
-
* \@data_date()
|
|
194
|
-
* my_field: date; // this field will be seen as a date
|
|
195
|
-
*/
|
|
196
|
-
|
|
197
|
-
export function date( props?: MetaData ) {
|
|
198
|
-
return field( { ...props, type: 'date' } );
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
/**
|
|
202
|
-
* following member is a calculated field
|
|
203
|
-
* @example
|
|
204
|
-
* \@data_calc( )
|
|
205
|
-
* get my_field(): string => {
|
|
206
|
-
* return 'hello';
|
|
207
|
-
* };
|
|
208
|
-
*/
|
|
209
|
-
|
|
210
|
-
export function calc( props?: MetaData ) {
|
|
211
|
-
return field( { ...props, type: 'calc'} )
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
/**
|
|
215
|
-
*
|
|
216
|
-
*/
|
|
217
|
-
|
|
218
|
-
interface ModelConstructor {
|
|
219
|
-
new ( data?: any, id?: any ): DataModel;
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
/**
|
|
223
|
-
* following member is a record array
|
|
224
|
-
* @example
|
|
225
|
-
* \@data_array( )
|
|
226
|
-
* my_field(): TypedRecord[];
|
|
227
|
-
*/
|
|
228
|
-
|
|
229
|
-
export function array( ctor: ModelConstructor, props?: MetaData ) {
|
|
230
|
-
return data.field( { ...props, type: 'array', model: ctor ? new ctor() : null } )
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
/**
|
|
234
|
-
* following member is unknown
|
|
235
|
-
* @example
|
|
236
|
-
* \@data.any( )
|
|
237
|
-
* my_field: TypedRecord[];
|
|
238
|
-
*/
|
|
239
|
-
|
|
240
|
-
export function any( props?: MetaData ) {
|
|
241
|
-
return field( { ...props, type: 'any' } );
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
/**
|
|
249
|
-
* record model
|
|
250
|
-
*/
|
|
251
|
-
|
|
252
|
-
export class DataModel {
|
|
253
|
-
|
|
254
|
-
/**
|
|
255
|
-
* MUST IMPLEMENT
|
|
256
|
-
* @returns fields descriptors
|
|
257
|
-
*/
|
|
258
|
-
|
|
259
|
-
getFields(): FieldInfo[] {
|
|
260
|
-
let metas = _getMetas( this, false );
|
|
261
|
-
return metas.fields;
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
/**
|
|
265
|
-
*
|
|
266
|
-
*/
|
|
267
|
-
|
|
268
|
-
validate( record: DataRecord ) : Error[] {
|
|
269
|
-
|
|
270
|
-
let errs: Error[] = null;
|
|
271
|
-
|
|
272
|
-
let fields = this.getFields( );
|
|
273
|
-
|
|
274
|
-
fields.forEach( (fi) => {
|
|
275
|
-
if( fi.required && !this.getField(fi.name,record) ) {
|
|
276
|
-
if( errs ) {
|
|
277
|
-
errs = [];
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
errs.push( new Error( `field ${fi.name} is required.` ) );
|
|
281
|
-
}
|
|
282
|
-
})
|
|
283
|
-
|
|
284
|
-
return errs;
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
/**
|
|
288
|
-
* return the field index by name
|
|
289
|
-
*/
|
|
290
|
-
|
|
291
|
-
getFieldIndex( name: string ) : number {
|
|
292
|
-
let fields = this.getFields( );
|
|
293
|
-
return fields.findIndex( (fd) => fd.name == name );
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
/**
|
|
297
|
-
* default serializer
|
|
298
|
-
* @returns an object with known record values
|
|
299
|
-
*/
|
|
300
|
-
|
|
301
|
-
serialize<T = any>( input: DataRecord ): T {
|
|
302
|
-
let rec: any = {};
|
|
303
|
-
|
|
304
|
-
this.getFields().forEach((f) => {
|
|
305
|
-
if( f.calc === undefined ) {
|
|
306
|
-
rec[f.name] = input[f.name];
|
|
307
|
-
}
|
|
308
|
-
});
|
|
309
|
-
|
|
310
|
-
return rec as T;
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
/**
|
|
316
|
-
* default unserializer
|
|
317
|
-
* @param data - input data
|
|
318
|
-
* @returns a new Record
|
|
319
|
-
*/
|
|
320
|
-
|
|
321
|
-
unSerialize(data: any, id?: DataRecordID ) : DataRecord {
|
|
322
|
-
|
|
323
|
-
const fields = this.getFields();
|
|
324
|
-
const rec = new DataRecord( );
|
|
325
|
-
|
|
326
|
-
fields.forEach( (sf) => {
|
|
327
|
-
let value = data[sf.name];
|
|
328
|
-
if (value !== undefined) {
|
|
329
|
-
rec[sf.name] = this._convertField( sf, value );
|
|
330
|
-
}
|
|
331
|
-
});
|
|
332
|
-
|
|
333
|
-
if( id!==undefined ) {
|
|
334
|
-
rec[fields[0].name] = id;
|
|
335
|
-
}
|
|
336
|
-
else {
|
|
337
|
-
console.assert( this.getID(rec)!==undefined ); // store do not have ID field
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
return rec;
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
/**
|
|
344
|
-
* field conversion
|
|
345
|
-
* @param field - field descriptor
|
|
346
|
-
* @param input - value to convert
|
|
347
|
-
* @returns the field value in it's original form
|
|
348
|
-
*/
|
|
349
|
-
|
|
350
|
-
protected _convertField( field: FieldInfo, input: any ) : any {
|
|
351
|
-
|
|
352
|
-
//TODO: boolean
|
|
353
|
-
|
|
354
|
-
switch( field.type ) {
|
|
355
|
-
case 'float': {
|
|
356
|
-
let ffv: number = typeof (input) === 'number' ? input : parseFloat(input);
|
|
357
|
-
|
|
358
|
-
if (field.prec !== undefined) {
|
|
359
|
-
let mul = Math.pow(10, field.prec);
|
|
360
|
-
ffv = Math.round(ffv * mul) / mul;
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
return ffv;
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
case 'int': {
|
|
367
|
-
return typeof (input) === 'number' ? input : parseInt(input);
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
case 'date': {
|
|
371
|
-
return isString(input) ? new Date(input) : input;
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
case 'array': {
|
|
375
|
-
debugger;
|
|
376
|
-
/*
|
|
377
|
-
let result: any[] = [];
|
|
378
|
-
|
|
379
|
-
if( field.model ) {
|
|
380
|
-
input.forEach( ( v: any ) => {
|
|
381
|
-
result.push( field.model.clone( v ) );
|
|
382
|
-
})
|
|
383
|
-
|
|
384
|
-
return result;
|
|
385
|
-
}
|
|
386
|
-
*/
|
|
387
|
-
break;
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
return input;
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
/**
|
|
395
|
-
* get the record unique identifier
|
|
396
|
-
* by default the return value is the first field
|
|
397
|
-
* @return unique identifier
|
|
398
|
-
*/
|
|
399
|
-
|
|
400
|
-
getID( rec: DataRecord ): any {
|
|
401
|
-
if( !rec ) return null;
|
|
402
|
-
let metas = _getMetas( this, false );
|
|
403
|
-
return rec[metas.id];
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
/**
|
|
407
|
-
* get raw value of a field
|
|
408
|
-
* @param name - field name or field index
|
|
409
|
-
*/
|
|
410
|
-
|
|
411
|
-
getRaw( name: string | number, rec: DataRecord ) : any {
|
|
412
|
-
|
|
413
|
-
let idx;
|
|
414
|
-
let fields = this.getFields( );
|
|
415
|
-
|
|
416
|
-
if( typeof(name) === 'string' ) {
|
|
417
|
-
idx = fields.findIndex( ( fi: FieldInfo) => fi.name == name );
|
|
418
|
-
if( idx < 0 ) {
|
|
419
|
-
console.assert( false, 'unknown field: '+name);
|
|
420
|
-
return undefined;
|
|
421
|
-
}
|
|
422
|
-
}
|
|
423
|
-
else if( name<fields.length ) {
|
|
424
|
-
if( name<0 ) {
|
|
425
|
-
return undefined
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
idx = name;
|
|
429
|
-
}
|
|
430
|
-
else {
|
|
431
|
-
console.assert( false, 'bad field name: '+name);
|
|
432
|
-
return undefined;
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
let fld = fields[idx];
|
|
436
|
-
if( fld.calc!==undefined ) {
|
|
437
|
-
return fld.calc( rec );
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
return rec[fld.name];
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
/**
|
|
444
|
-
* get field value (as string)
|
|
445
|
-
* @param name - field name
|
|
446
|
-
* @example
|
|
447
|
-
* let value = record.get('field1');
|
|
448
|
-
*/
|
|
449
|
-
|
|
450
|
-
getField( name: string, rec: DataRecord ): string {
|
|
451
|
-
let v = this.getRaw( name, rec );
|
|
452
|
-
return (v===undefined || v===null) ? '' : ''+v;
|
|
453
|
-
}
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
/**
|
|
457
|
-
*
|
|
458
|
-
*/
|
|
459
|
-
|
|
460
|
-
export class DataRecord {
|
|
461
|
-
[ key: string ]: DataFieldValue;
|
|
462
|
-
|
|
463
|
-
/*
|
|
464
|
-
/ **
|
|
465
|
-
* @returns fields descriptors
|
|
466
|
-
* /
|
|
467
|
-
|
|
468
|
-
getFields(): FieldInfo[] {
|
|
469
|
-
let metas = _getMetas( this, false );
|
|
470
|
-
return metas.fields;
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
/ **
|
|
476
|
-
*
|
|
477
|
-
* @param name
|
|
478
|
-
* @param data
|
|
479
|
-
* /
|
|
480
|
-
|
|
481
|
-
setRaw( name: string, data: string ) {
|
|
482
|
-
this[name] = data;
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
/ **
|
|
488
|
-
* set field value
|
|
489
|
-
* @param name - field name
|
|
490
|
-
* @param value - value to set
|
|
491
|
-
* @example
|
|
492
|
-
* record.set( 'field1', 7 );
|
|
493
|
-
* /
|
|
494
|
-
|
|
495
|
-
setField(name: string, value: any) {
|
|
496
|
-
let fields = this.getFields( );
|
|
497
|
-
let idx = fields.findIndex( fi => fi.name == name );
|
|
498
|
-
|
|
499
|
-
if( idx < 0 ) {
|
|
500
|
-
console.assert( false, 'unknown field: '+name);
|
|
501
|
-
return;
|
|
502
|
-
}
|
|
503
|
-
|
|
504
|
-
let fld = fields[idx];
|
|
505
|
-
if( fld.calc!==undefined ) {
|
|
506
|
-
console.assert( false, 'cannot set calc field: '+name);
|
|
507
|
-
return;
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
this.setRaw( fld.name, value );
|
|
511
|
-
}
|
|
512
|
-
*/
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
}
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
/**
|
|
519
|
-
*
|
|
520
|
-
*/
|
|
521
|
-
|
|
522
|
-
interface DataEventMap extends EventMap {
|
|
523
|
-
change?: EvChange;
|
|
524
|
-
}
|
|
525
|
-
|
|
526
|
-
type DataSolver = ( data: any ) => DataRecord[];
|
|
527
|
-
|
|
528
|
-
export interface DataProxyProps {
|
|
529
|
-
url: string;
|
|
530
|
-
params?: string[];
|
|
531
|
-
solver?: DataSolver;
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
export class DataProxy extends CoreElement<DataEventMap> {
|
|
535
|
-
|
|
536
|
-
protected m_props: DataProxyProps;
|
|
537
|
-
|
|
538
|
-
constructor( props: DataProxyProps ) {
|
|
539
|
-
super( );
|
|
540
|
-
|
|
541
|
-
this.m_props = props;
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
async load( url?: string ) {
|
|
545
|
-
if( url ) {
|
|
546
|
-
this.m_props.url = url;
|
|
547
|
-
}
|
|
548
|
-
else {
|
|
549
|
-
url = this.m_props.url;
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
if( this.m_props.params ) {
|
|
553
|
-
url += '?' + this.m_props.params.join( '&' );
|
|
554
|
-
}
|
|
555
|
-
|
|
556
|
-
const r = await fetch( url );
|
|
557
|
-
if( r.ok ) {
|
|
558
|
-
const raw = await r.json( );
|
|
559
|
-
|
|
560
|
-
let json = raw;
|
|
561
|
-
if( this.m_props.solver ) {
|
|
562
|
-
json = this.m_props.solver( json );
|
|
563
|
-
}
|
|
564
|
-
|
|
565
|
-
this.fire( 'change', {value:json,context:raw} );
|
|
566
|
-
}
|
|
567
|
-
}
|
|
568
|
-
}
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
/**
|
|
572
|
-
*
|
|
573
|
-
*/
|
|
574
|
-
|
|
575
|
-
interface DataStoreProps {
|
|
576
|
-
model: DataModel;
|
|
577
|
-
data?: any[];
|
|
578
|
-
url?: string;
|
|
579
|
-
autoload?: false;
|
|
580
|
-
solver?: DataSolver;
|
|
581
|
-
}
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
interface DataStoreEventMap extends EventMap {
|
|
585
|
-
data_change: EvDataChange;
|
|
586
|
-
}
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
/**
|
|
591
|
-
*
|
|
592
|
-
*/
|
|
593
|
-
|
|
594
|
-
export class DataStore extends EventSource<DataStoreEventMap> {
|
|
595
|
-
|
|
596
|
-
protected m_model: DataModel;
|
|
597
|
-
protected m_fields: FieldInfo[];
|
|
598
|
-
protected m_records: DataRecord[];
|
|
599
|
-
|
|
600
|
-
protected m_proxy: DataProxy;
|
|
601
|
-
protected m_rec_index: DataIndex;
|
|
602
|
-
|
|
603
|
-
constructor(props: DataStoreProps ) {
|
|
604
|
-
super( );
|
|
605
|
-
|
|
606
|
-
this.m_fields = undefined;
|
|
607
|
-
this.m_records = [];
|
|
608
|
-
this.m_rec_index = null;
|
|
609
|
-
this.m_model = props.model;
|
|
610
|
-
this.m_fields = props.model.getFields();
|
|
611
|
-
|
|
612
|
-
if (props.data) {
|
|
613
|
-
this.setRawData( props.data );
|
|
614
|
-
}
|
|
615
|
-
else if( props.url ) {
|
|
616
|
-
this.m_proxy = new DataProxy( {
|
|
617
|
-
url: props.url,
|
|
618
|
-
solver: props.solver,
|
|
619
|
-
});
|
|
620
|
-
|
|
621
|
-
this.m_proxy.on( 'change', ( ev: EvChange) => {
|
|
622
|
-
this.setData( ev.value );
|
|
623
|
-
});
|
|
624
|
-
|
|
625
|
-
if( props.autoload!=false ) {
|
|
626
|
-
this.m_proxy.load( );
|
|
627
|
-
}
|
|
628
|
-
}
|
|
629
|
-
}
|
|
630
|
-
|
|
631
|
-
/**
|
|
632
|
-
*
|
|
633
|
-
* @param records
|
|
634
|
-
*/
|
|
635
|
-
|
|
636
|
-
async load( url?: string ) {
|
|
637
|
-
return this.m_proxy.load( url );
|
|
638
|
-
}
|
|
639
|
-
|
|
640
|
-
async reload( ) {
|
|
641
|
-
return this.m_proxy.load( );
|
|
642
|
-
}
|
|
643
|
-
|
|
644
|
-
/**
|
|
645
|
-
* convert raw objects to real records from model
|
|
646
|
-
* @param records
|
|
647
|
-
*/
|
|
648
|
-
|
|
649
|
-
public setData( records: any[] ) {
|
|
650
|
-
|
|
651
|
-
const realRecords: DataRecord[] = new Array( records.length );
|
|
652
|
-
|
|
653
|
-
records.forEach( (rec,idx) => {
|
|
654
|
-
realRecords[idx] = this.m_model.unSerialize(rec);
|
|
655
|
-
});
|
|
656
|
-
|
|
657
|
-
this.setRawData( realRecords );
|
|
658
|
-
}
|
|
659
|
-
|
|
660
|
-
/**
|
|
661
|
-
* just set the records
|
|
662
|
-
* @param records - must be of the same type as model
|
|
663
|
-
*/
|
|
664
|
-
|
|
665
|
-
public setRawData(records: DataRecord[]) {
|
|
666
|
-
|
|
667
|
-
this.m_records = records;
|
|
668
|
-
this._rebuildIndex( );
|
|
669
|
-
this.fire( 'data_change', { change_type: 'change'} );
|
|
670
|
-
}
|
|
671
|
-
|
|
672
|
-
private _rebuildIndex( ) {
|
|
673
|
-
this.m_rec_index = null; // null to signal that we have to run on records instead of index
|
|
674
|
-
this.m_rec_index = this.createIndex( null ); // prepare index (remove deleted)
|
|
675
|
-
this.m_rec_index = this.sortIndex( this.m_rec_index, null ); // sort by id
|
|
676
|
-
}
|
|
677
|
-
|
|
678
|
-
/**
|
|
679
|
-
*
|
|
680
|
-
*/
|
|
681
|
-
|
|
682
|
-
public update( rec: DataRecord ) {
|
|
683
|
-
|
|
684
|
-
let id = this.m_model.getID( rec );
|
|
685
|
-
let index = this.indexOfId(id);
|
|
686
|
-
if (index < 0) {
|
|
687
|
-
return false;
|
|
688
|
-
}
|
|
689
|
-
|
|
690
|
-
this.m_records[this.m_rec_index[index]] = rec;
|
|
691
|
-
this.fire( 'data_change', {change_type: 'update', id } );
|
|
692
|
-
return true;
|
|
693
|
-
}
|
|
694
|
-
|
|
695
|
-
/**
|
|
696
|
-
*
|
|
697
|
-
* @param data
|
|
698
|
-
*/
|
|
699
|
-
|
|
700
|
-
public append( rec: DataRecord | any ) {
|
|
701
|
-
|
|
702
|
-
if( !(rec instanceof DataRecord) ) {
|
|
703
|
-
rec = this.m_model.unSerialize( rec );
|
|
704
|
-
}
|
|
705
|
-
|
|
706
|
-
const id = this.m_model.getID(rec);
|
|
707
|
-
console.assert( id!==undefined );
|
|
708
|
-
|
|
709
|
-
this.m_records.push( rec );
|
|
710
|
-
this._rebuildIndex( );
|
|
711
|
-
this.fire( 'data_change', {change_type: 'create', id } );
|
|
712
|
-
}
|
|
713
|
-
|
|
714
|
-
/**
|
|
715
|
-
*
|
|
716
|
-
*/
|
|
717
|
-
|
|
718
|
-
getMaxId( ) {
|
|
719
|
-
let maxID: number = undefined;
|
|
720
|
-
const m = this.m_model;
|
|
721
|
-
|
|
722
|
-
this.m_records.forEach( (r) => {
|
|
723
|
-
let rid = m.getID( r );
|
|
724
|
-
if( maxID===undefined || maxID<rid ) {
|
|
725
|
-
maxID = rid;
|
|
726
|
-
}
|
|
727
|
-
});
|
|
728
|
-
|
|
729
|
-
return maxID;
|
|
730
|
-
}
|
|
731
|
-
|
|
732
|
-
/**
|
|
733
|
-
*
|
|
734
|
-
* @param id
|
|
735
|
-
*/
|
|
736
|
-
|
|
737
|
-
public delete(id: DataRecordID ): boolean {
|
|
738
|
-
|
|
739
|
-
let idx = this.indexOfId( id );
|
|
740
|
-
if( idx<0 ) {
|
|
741
|
-
return false;
|
|
742
|
-
}
|
|
743
|
-
|
|
744
|
-
idx = this.m_rec_index[idx];
|
|
745
|
-
|
|
746
|
-
// mark as deleted
|
|
747
|
-
this.m_records.splice( idx, 1 );
|
|
748
|
-
this._rebuildIndex( );
|
|
749
|
-
this.fire( 'data_change', { change_type: 'delete', id } );
|
|
750
|
-
return true;
|
|
751
|
-
}
|
|
752
|
-
|
|
753
|
-
/**
|
|
754
|
-
* return the number of records
|
|
755
|
-
*/
|
|
756
|
-
|
|
757
|
-
get count( ) : number {
|
|
758
|
-
return this.m_rec_index ? this.m_rec_index.length : this.m_records.length;
|
|
759
|
-
}
|
|
760
|
-
|
|
761
|
-
/**
|
|
762
|
-
* return the fields
|
|
763
|
-
*/
|
|
764
|
-
|
|
765
|
-
get fields( ) : FieldInfo [] {
|
|
766
|
-
return this.m_fields;
|
|
767
|
-
}
|
|
768
|
-
|
|
769
|
-
/**
|
|
770
|
-
* find the index of the element with the given id
|
|
771
|
-
*/
|
|
772
|
-
|
|
773
|
-
public indexOfId(id: DataRecordID ): number {
|
|
774
|
-
|
|
775
|
-
//if( this.count<10 ) {
|
|
776
|
-
// this.forEach( (rec) => rec.getID() == id );
|
|
777
|
-
//}
|
|
778
|
-
|
|
779
|
-
const m = this.m_model;
|
|
780
|
-
|
|
781
|
-
for( let lim = this.count, base = 0; lim != 0; lim >>= 1 ) {
|
|
782
|
-
|
|
783
|
-
const p = base + (lim >> 1); // int conversion
|
|
784
|
-
const idx = this.m_rec_index[p];
|
|
785
|
-
const rid = m.getID( this.m_records[idx] );
|
|
786
|
-
|
|
787
|
-
if( rid==id ) {
|
|
788
|
-
return p;
|
|
789
|
-
}
|
|
790
|
-
|
|
791
|
-
if( rid<id ) {
|
|
792
|
-
base = p+1;
|
|
793
|
-
lim--;
|
|
794
|
-
}
|
|
795
|
-
}
|
|
796
|
-
|
|
797
|
-
return -1;
|
|
798
|
-
}
|
|
799
|
-
|
|
800
|
-
/**
|
|
801
|
-
* return the record by it's id
|
|
802
|
-
* @returns record or null
|
|
803
|
-
*/
|
|
804
|
-
|
|
805
|
-
public getById(id: DataRecordID): DataRecord {
|
|
806
|
-
let idx = this.indexOfId( id );
|
|
807
|
-
if( idx<0 ) {
|
|
808
|
-
return null;
|
|
809
|
-
}
|
|
810
|
-
|
|
811
|
-
idx = this.m_rec_index[idx];
|
|
812
|
-
return this.m_records[idx];
|
|
813
|
-
}
|
|
814
|
-
|
|
815
|
-
/**
|
|
816
|
-
* return a record by it's index
|
|
817
|
-
* @returns record or null
|
|
818
|
-
*/
|
|
819
|
-
|
|
820
|
-
public getByIndex( index: number ): DataRecord {
|
|
821
|
-
let idx = this.m_rec_index[index];
|
|
822
|
-
return this._getRecord( idx );
|
|
823
|
-
}
|
|
824
|
-
|
|
825
|
-
private _getRecord( index: number ) : DataRecord {
|
|
826
|
-
return this.m_records[index] ?? null;
|
|
827
|
-
}
|
|
828
|
-
|
|
829
|
-
public moveTo( other: DataStore ) {
|
|
830
|
-
other.setRawData( this.m_records );
|
|
831
|
-
}
|
|
832
|
-
|
|
833
|
-
/**
|
|
834
|
-
* create a new view on the DataStore
|
|
835
|
-
* @param opts
|
|
836
|
-
*/
|
|
837
|
-
|
|
838
|
-
createView( opts?: DataViewProps ) : DataView {
|
|
839
|
-
let eopts = { ...opts, store: this };
|
|
840
|
-
return new DataView( eopts );
|
|
841
|
-
}
|
|
842
|
-
|
|
843
|
-
/**
|
|
844
|
-
*
|
|
845
|
-
*/
|
|
846
|
-
|
|
847
|
-
createIndex( filter: FilterInfo ) : DataIndex {
|
|
848
|
-
|
|
849
|
-
if( filter && filter.op==='empty-result' ) {
|
|
850
|
-
return new Uint32Array(0);
|
|
851
|
-
}
|
|
852
|
-
|
|
853
|
-
let index = new Uint32Array( this.m_records.length );
|
|
854
|
-
let iidx = 0;
|
|
855
|
-
|
|
856
|
-
if( !filter ) {
|
|
857
|
-
// reset filter
|
|
858
|
-
this.forEach( (rec, idx) => {
|
|
859
|
-
index[iidx++] = idx;
|
|
860
|
-
} );
|
|
861
|
-
}
|
|
862
|
-
else {
|
|
863
|
-
if( typeof(filter.op)==='function' ) {
|
|
864
|
-
|
|
865
|
-
let fn = filter.op as FilterFunc;
|
|
866
|
-
|
|
867
|
-
// scan all records and append only interesting ones
|
|
868
|
-
this.forEach( (rec, idx) => {
|
|
869
|
-
// skip deleted
|
|
870
|
-
if( !rec ) {
|
|
871
|
-
return;
|
|
872
|
-
}
|
|
873
|
-
|
|
874
|
-
if( fn(rec) ) {
|
|
875
|
-
index[iidx++] = idx;
|
|
876
|
-
}
|
|
877
|
-
} );
|
|
878
|
-
}
|
|
879
|
-
else {
|
|
880
|
-
let filterFld = this.m_model.getFieldIndex( filter.field ); // field index to filter on
|
|
881
|
-
if( filterFld<0 ) {
|
|
882
|
-
// unknown filter field, nothing inside
|
|
883
|
-
console.assert( false, 'unknown field name in filter' )
|
|
884
|
-
return new Uint32Array(0);
|
|
885
|
-
}
|
|
886
|
-
|
|
887
|
-
let filterValue = filter.value;
|
|
888
|
-
if( isString(filterValue) && !filter.caseSensitive ) {
|
|
889
|
-
filterValue = filterValue.toUpperCase( );
|
|
890
|
-
}
|
|
891
|
-
|
|
892
|
-
function _lt( recval: string ) : boolean {
|
|
893
|
-
return recval < filterValue;
|
|
894
|
-
}
|
|
895
|
-
|
|
896
|
-
function _le( recval: string ) : boolean {
|
|
897
|
-
return recval <= filterValue;
|
|
898
|
-
}
|
|
899
|
-
|
|
900
|
-
function _eq( recval: string ) : boolean {
|
|
901
|
-
return recval == filterValue;
|
|
902
|
-
}
|
|
903
|
-
|
|
904
|
-
function _neq( recval: string ) : boolean {
|
|
905
|
-
return recval != filterValue;
|
|
906
|
-
}
|
|
907
|
-
|
|
908
|
-
function _ge( recval: string ) : boolean {
|
|
909
|
-
return recval >= filterValue;
|
|
910
|
-
}
|
|
911
|
-
|
|
912
|
-
function _gt( recval: string ) : boolean {
|
|
913
|
-
return recval > filterValue;
|
|
914
|
-
}
|
|
915
|
-
|
|
916
|
-
function _re( recval: string ) : boolean {
|
|
917
|
-
filterRe.lastIndex = -1;
|
|
918
|
-
return filterRe.test( recval );
|
|
919
|
-
}
|
|
920
|
-
|
|
921
|
-
let filterFn: ( rec: string ) => boolean; // filter fn
|
|
922
|
-
let filterRe: RegExp; // if fielter is regexp
|
|
923
|
-
if( filterValue instanceof RegExp ) {
|
|
924
|
-
filterRe = filterValue;
|
|
925
|
-
filterFn = _re;
|
|
926
|
-
}
|
|
927
|
-
else {
|
|
928
|
-
switch( filter.op ) {
|
|
929
|
-
case '<': { filterFn = _lt; break; }
|
|
930
|
-
case '<=': { filterFn = _le; break; }
|
|
931
|
-
case '=': { filterFn = _eq; break; }
|
|
932
|
-
case '>=': { filterFn = _ge; break; }
|
|
933
|
-
case '>': { filterFn = _gt; break; }
|
|
934
|
-
case '<>': { filterFn = _neq; break; }
|
|
935
|
-
}
|
|
936
|
-
}
|
|
937
|
-
|
|
938
|
-
// scan all records and append only interesting ones
|
|
939
|
-
const m = this.m_model;
|
|
940
|
-
|
|
941
|
-
this.forEach( (rec, idx) => {
|
|
942
|
-
|
|
943
|
-
// skip deleted
|
|
944
|
-
if( !rec ) {
|
|
945
|
-
return;
|
|
946
|
-
}
|
|
947
|
-
|
|
948
|
-
let field = m.getRaw( filterFld, rec );
|
|
949
|
-
if( field===null || field===undefined ) {
|
|
950
|
-
field = '';
|
|
951
|
-
}
|
|
952
|
-
else {
|
|
953
|
-
field = ''+field;
|
|
954
|
-
if( !filter.caseSensitive ) {
|
|
955
|
-
field = field.toUpperCase( );
|
|
956
|
-
}
|
|
957
|
-
}
|
|
958
|
-
|
|
959
|
-
let keep = filterFn( field );
|
|
960
|
-
if( keep ) {
|
|
961
|
-
index[iidx++] = idx;
|
|
962
|
-
};
|
|
963
|
-
});
|
|
964
|
-
}
|
|
965
|
-
}
|
|
966
|
-
|
|
967
|
-
return index.slice( 0, iidx );
|
|
968
|
-
}
|
|
969
|
-
|
|
970
|
-
sortIndex( index: DataIndex, sort: SortProp[] ) {
|
|
971
|
-
|
|
972
|
-
interface sort_info {
|
|
973
|
-
fidx: number,
|
|
974
|
-
asc: boolean
|
|
975
|
-
}
|
|
976
|
-
|
|
977
|
-
let bads = 0; // unknown fields
|
|
978
|
-
let fidxs: sort_info[] = []; // fields indexes
|
|
979
|
-
|
|
980
|
-
// if no fields are given, reset sort by id
|
|
981
|
-
if ( sort===null ) {
|
|
982
|
-
fidxs.push( { fidx: 0, asc: true } );
|
|
983
|
-
}
|
|
984
|
-
else {
|
|
985
|
-
fidxs = sort.map( (si) => {
|
|
986
|
-
|
|
987
|
-
let fi = this.m_model.getFieldIndex( si.field );
|
|
988
|
-
if (fi == -1) {
|
|
989
|
-
console.assert( false, 'unknown field name in sort' )
|
|
990
|
-
bads++;
|
|
991
|
-
}
|
|
992
|
-
|
|
993
|
-
return { fidx: fi, asc: si.ascending };
|
|
994
|
-
});
|
|
995
|
-
}
|
|
996
|
-
|
|
997
|
-
// unknown field or nothing to sort on ??
|
|
998
|
-
if( bads || fidxs.length==0 ) {
|
|
999
|
-
return index;
|
|
1000
|
-
}
|
|
1001
|
-
|
|
1002
|
-
// sort only by one field : optimize it
|
|
1003
|
-
const m = this.m_model;
|
|
1004
|
-
|
|
1005
|
-
if( fidxs.length==1 ) {
|
|
1006
|
-
|
|
1007
|
-
const field = fidxs[0].fidx;
|
|
1008
|
-
|
|
1009
|
-
index.sort( ( ia, ib ) => {
|
|
1010
|
-
|
|
1011
|
-
let va = m.getRaw( field, this.getByIndex(ia) ) ?? '';
|
|
1012
|
-
let vb = m.getRaw( field, this.getByIndex(ib) ) ?? '';
|
|
1013
|
-
if (va > vb) { return 1; }
|
|
1014
|
-
if (va < vb) { return -1; }
|
|
1015
|
-
return 0;
|
|
1016
|
-
} );
|
|
1017
|
-
|
|
1018
|
-
// just reverse if
|
|
1019
|
-
if( !fidxs[0].asc ) {
|
|
1020
|
-
index.reverse( );
|
|
1021
|
-
}
|
|
1022
|
-
}
|
|
1023
|
-
else {
|
|
1024
|
-
index.sort( ( ia, ib ) => {
|
|
1025
|
-
|
|
1026
|
-
for( let fi=0; fi<fidxs.length; fi++ ) {
|
|
1027
|
-
|
|
1028
|
-
let fidx = fidxs[fi].fidx;
|
|
1029
|
-
let mul = fidxs[fi].asc ? 1 : -1;
|
|
1030
|
-
|
|
1031
|
-
let va = m.getRaw( fidx, this.getByIndex(ia) ) ?? '';
|
|
1032
|
-
let vb = m.getRaw( fidx, this.getByIndex(ib) ) ?? '';
|
|
1033
|
-
if (va > vb) { return mul; }
|
|
1034
|
-
if (va < vb) { return -mul; }
|
|
1035
|
-
}
|
|
1036
|
-
|
|
1037
|
-
return 0;
|
|
1038
|
-
} );
|
|
1039
|
-
}
|
|
1040
|
-
|
|
1041
|
-
return index
|
|
1042
|
-
}
|
|
1043
|
-
|
|
1044
|
-
/**
|
|
1045
|
-
*
|
|
1046
|
-
*/
|
|
1047
|
-
|
|
1048
|
-
forEach( cb: ( rec: DataRecord, index: number ) => any ) {
|
|
1049
|
-
|
|
1050
|
-
if( this.m_rec_index ) {
|
|
1051
|
-
this.m_rec_index.some( (ri,index) => {
|
|
1052
|
-
if( cb( this.m_records[ri], index ) ) {
|
|
1053
|
-
return index;
|
|
1054
|
-
}
|
|
1055
|
-
});
|
|
1056
|
-
}
|
|
1057
|
-
else {
|
|
1058
|
-
this.m_records.some( ( rec, index ) => {
|
|
1059
|
-
if( rec ) {
|
|
1060
|
-
if( cb( rec, index ) ) {
|
|
1061
|
-
return index;
|
|
1062
|
-
}
|
|
1063
|
-
}
|
|
1064
|
-
} );
|
|
1065
|
-
}
|
|
1066
|
-
}
|
|
1067
|
-
|
|
1068
|
-
export( ) {
|
|
1069
|
-
return this.m_records;
|
|
1070
|
-
}
|
|
1071
|
-
|
|
1072
|
-
changed( ) {
|
|
1073
|
-
this.fire( 'data_change', { change_type: 'change'} );
|
|
1074
|
-
}
|
|
1075
|
-
|
|
1076
|
-
getModel( ) {
|
|
1077
|
-
return this.m_model;
|
|
1078
|
-
}
|
|
1079
|
-
}
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
// :: VIEWS ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
|
1083
|
-
|
|
1084
|
-
export interface EvViewChange extends CoreEvent {
|
|
1085
|
-
change_type: "change" | "filter" | "sort";
|
|
1086
|
-
}
|
|
1087
|
-
|
|
1088
|
-
interface DataViewEventMap extends EventMap {
|
|
1089
|
-
view_change: EvViewChange;
|
|
1090
|
-
}
|
|
1091
|
-
|
|
1092
|
-
interface DataViewProps {
|
|
1093
|
-
store?: DataStore;
|
|
1094
|
-
filter?: FilterInfo;
|
|
1095
|
-
order?: string | SortProp[] | SortProp;
|
|
1096
|
-
}
|
|
1097
|
-
|
|
1098
|
-
export type FilterFunc = ( rec: DataRecord ) => boolean;
|
|
1099
|
-
|
|
1100
|
-
export interface FilterInfo {
|
|
1101
|
-
op: '<' | '<=' | '=' | '>=' | '>' | '<>' | 'empty-result' | FilterFunc, // emptydb mean return an empty result always
|
|
1102
|
-
field?: string;
|
|
1103
|
-
value?: string | RegExp; // if regexp then operator is =
|
|
1104
|
-
caseSensitive?: boolean;
|
|
1105
|
-
}
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
export interface SortProp {
|
|
1109
|
-
field: string; //
|
|
1110
|
-
ascending: boolean; //
|
|
1111
|
-
numeric?: boolean; // numeric sort
|
|
1112
|
-
}
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
/**
|
|
1117
|
-
* Dataview allow different views of the DataStore.
|
|
1118
|
-
* You can sort the columns & filter data
|
|
1119
|
-
* You can have multiple views for a single DataStore
|
|
1120
|
-
*/
|
|
1121
|
-
|
|
1122
|
-
export class DataView extends CoreElement<DataViewEventMap>
|
|
1123
|
-
{
|
|
1124
|
-
protected m_index: DataIndex;
|
|
1125
|
-
protected m_store: DataStore;
|
|
1126
|
-
protected m_model: DataModel;
|
|
1127
|
-
|
|
1128
|
-
protected m_sort: SortProp[];
|
|
1129
|
-
protected m_filter: FilterInfo;
|
|
1130
|
-
|
|
1131
|
-
protected m_props: DataViewProps;
|
|
1132
|
-
|
|
1133
|
-
constructor( props: DataViewProps ) {
|
|
1134
|
-
super( );
|
|
1135
|
-
|
|
1136
|
-
this.m_props = props;
|
|
1137
|
-
this.m_store = props.store;
|
|
1138
|
-
this.m_index = null;
|
|
1139
|
-
this.m_filter = null;
|
|
1140
|
-
this.m_sort = null;
|
|
1141
|
-
this.m_model = this.m_store.getModel();
|
|
1142
|
-
|
|
1143
|
-
this.filter( props.filter );
|
|
1144
|
-
|
|
1145
|
-
if( props.order ) {
|
|
1146
|
-
if( isString(props.order) ) {
|
|
1147
|
-
this.sort( [ { field: props.order, ascending: true } ] );
|
|
1148
|
-
}
|
|
1149
|
-
else if( isArray(props.order) ) {
|
|
1150
|
-
this.sort( props.order );
|
|
1151
|
-
}
|
|
1152
|
-
else {
|
|
1153
|
-
this.sort( [props.order] );
|
|
1154
|
-
}
|
|
1155
|
-
}
|
|
1156
|
-
else {
|
|
1157
|
-
this.sort( null );
|
|
1158
|
-
}
|
|
1159
|
-
|
|
1160
|
-
this.m_store.addListener( 'data_change', ( e ) => this._storeChange(e) );
|
|
1161
|
-
}
|
|
1162
|
-
|
|
1163
|
-
private _storeChange( ev: EvDataChange ) {
|
|
1164
|
-
|
|
1165
|
-
this._filter( this.m_filter, ev.type!='change' );
|
|
1166
|
-
this._sort( this.m_sort, ev.type!='change' );
|
|
1167
|
-
|
|
1168
|
-
this.fire( 'view_change', { change_type: 'change' } );
|
|
1169
|
-
}
|
|
1170
|
-
|
|
1171
|
-
/**
|
|
1172
|
-
*
|
|
1173
|
-
* @param filter
|
|
1174
|
-
*/
|
|
1175
|
-
|
|
1176
|
-
public filter( filter?: FilterInfo ) : number {
|
|
1177
|
-
|
|
1178
|
-
this.m_index = null; // null to signal that we have to run on records instead of index
|
|
1179
|
-
return this._filter( filter, true );
|
|
1180
|
-
}
|
|
1181
|
-
|
|
1182
|
-
private _filter( filter: FilterInfo, notify: boolean) : number {
|
|
1183
|
-
|
|
1184
|
-
this.m_index = this.m_store.createIndex( filter );
|
|
1185
|
-
this.m_filter = filter;
|
|
1186
|
-
|
|
1187
|
-
// need to sort again:
|
|
1188
|
-
if( this.m_sort ) {
|
|
1189
|
-
this.sort( this.m_sort );
|
|
1190
|
-
}
|
|
1191
|
-
|
|
1192
|
-
if( notify ) {
|
|
1193
|
-
this.fire( 'view_change', { change_type: 'filter' } );
|
|
1194
|
-
}
|
|
1195
|
-
|
|
1196
|
-
return this.m_index.length;
|
|
1197
|
-
}
|
|
1198
|
-
|
|
1199
|
-
/**
|
|
1200
|
-
*
|
|
1201
|
-
* @param columns
|
|
1202
|
-
* @param ascending
|
|
1203
|
-
*/
|
|
1204
|
-
|
|
1205
|
-
public sort( props: SortProp[] ) {
|
|
1206
|
-
this._sort( props, true );
|
|
1207
|
-
}
|
|
1208
|
-
|
|
1209
|
-
private _sort( props: SortProp[], notify: boolean ) {
|
|
1210
|
-
this.m_index = this.m_store.sortIndex( this.m_index, props );
|
|
1211
|
-
this.m_sort = props;
|
|
1212
|
-
|
|
1213
|
-
if( notify ) {
|
|
1214
|
-
this.fire( 'view_change', { change_type: 'sort' } );
|
|
1215
|
-
}
|
|
1216
|
-
}
|
|
1217
|
-
|
|
1218
|
-
/**
|
|
1219
|
-
*
|
|
1220
|
-
*/
|
|
1221
|
-
|
|
1222
|
-
getStore ( ) {
|
|
1223
|
-
return this.m_store;
|
|
1224
|
-
}
|
|
1225
|
-
|
|
1226
|
-
/**
|
|
1227
|
-
*
|
|
1228
|
-
*/
|
|
1229
|
-
|
|
1230
|
-
public getCount() {
|
|
1231
|
-
return this.m_index.length;
|
|
1232
|
-
}
|
|
1233
|
-
|
|
1234
|
-
/**
|
|
1235
|
-
*
|
|
1236
|
-
* @param id
|
|
1237
|
-
*/
|
|
1238
|
-
|
|
1239
|
-
public indexOfId(id: DataRecordID): number {
|
|
1240
|
-
let ridx = this.m_store.indexOfId( id );
|
|
1241
|
-
return this.m_index.findIndex( (rid) => rid === ridx );
|
|
1242
|
-
}
|
|
1243
|
-
|
|
1244
|
-
/**
|
|
1245
|
-
*
|
|
1246
|
-
* @param index
|
|
1247
|
-
*/
|
|
1248
|
-
|
|
1249
|
-
public getByIndex(index: number): DataRecord {
|
|
1250
|
-
|
|
1251
|
-
if (index >= 0 && index < this.m_index.length) {
|
|
1252
|
-
let rid = this.m_index[index];
|
|
1253
|
-
return this.m_store.getByIndex( rid );
|
|
1254
|
-
}
|
|
1255
|
-
|
|
1256
|
-
return null;
|
|
1257
|
-
}
|
|
1258
|
-
|
|
1259
|
-
public getIdByIndex( index: number ) : DataRecordID {
|
|
1260
|
-
const rec = this.getByIndex( index );
|
|
1261
|
-
return this.m_model.getID( rec );
|
|
1262
|
-
}
|
|
1263
|
-
|
|
1264
|
-
public getRecId( rec: DataRecord ): DataRecordID {
|
|
1265
|
-
return this.m_model.getID( rec );
|
|
1266
|
-
}
|
|
1267
|
-
|
|
1268
|
-
/**
|
|
1269
|
-
*
|
|
1270
|
-
* @param id
|
|
1271
|
-
*/
|
|
1272
|
-
|
|
1273
|
-
public getById( id: DataRecordID): DataRecord {
|
|
1274
|
-
return this.m_store.getById( id );
|
|
1275
|
-
}
|
|
1276
|
-
|
|
1277
|
-
/**
|
|
1278
|
-
*
|
|
1279
|
-
*/
|
|
1280
|
-
|
|
1281
|
-
getModel( ) {
|
|
1282
|
-
return this.m_model;
|
|
1283
|
-
}
|
|
1284
|
-
|
|
1285
|
-
/**
|
|
1286
|
-
*
|
|
1287
|
-
*/
|
|
1288
|
-
|
|
1289
|
-
changed( ) {
|
|
1290
|
-
this.fire( 'view_change', {change_type:'change'} );
|
|
1291
|
-
}
|
|
1292
|
-
|
|
1293
|
-
/**
|
|
1294
|
-
*
|
|
1295
|
-
*/
|
|
1296
|
-
|
|
1297
|
-
forEach( cb: ( rec: DataRecord, index: number ) => any ) {
|
|
1298
|
-
this.m_index.some( ( index ) => {
|
|
1299
|
-
let rec = this.m_store.getByIndex( index );
|
|
1300
|
-
if( rec ) {
|
|
1301
|
-
if( cb( rec, index ) ) {
|
|
1302
|
-
return index;
|
|
1303
|
-
}
|
|
1304
|
-
}
|
|
1305
|
-
} );
|
|
1306
|
-
}
|
|
1307
|
-
}
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|