x4js 2.0.12 → 2.0.14

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