neo.mjs 10.0.0-beta.2 → 10.0.0-beta.4
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/.github/RELEASE_NOTES/v10.0.0-beta.4.md +41 -0
- package/ServiceWorker.mjs +2 -2
- package/apps/form/view/FormPageContainer.mjs +2 -3
- package/apps/portal/index.html +1 -1
- package/apps/portal/view/ViewportController.mjs +1 -1
- package/apps/portal/view/home/FooterContainer.mjs +1 -1
- package/apps/portal/view/learn/ContentComponent.mjs +18 -11
- package/apps/portal/view/learn/MainContainerController.mjs +6 -6
- package/learn/README.md +9 -14
- package/learn/guides/datahandling/Collections.md +436 -0
- package/learn/guides/datahandling/Grids.md +621 -0
- package/learn/guides/datahandling/Records.md +287 -0
- package/learn/guides/{StateProviders.md → datahandling/StateProviders.md} +145 -1
- package/learn/guides/fundamentals/ExtendingNeoClasses.md +359 -0
- package/learn/guides/uibuildingblocks/CustomComponents.md +287 -0
- package/learn/guides/uibuildingblocks/Layouts.md +248 -0
- package/learn/guides/userinteraction/Forms.md +449 -0
- package/learn/guides/userinteraction/form_fields/ComboBox.md +241 -0
- package/learn/tree.json +63 -52
- package/package.json +2 -2
- package/resources/scss/src/apps/portal/learn/ContentComponent.scss +9 -0
- package/src/DefaultConfig.mjs +2 -2
- package/src/Neo.mjs +37 -29
- package/src/collection/Base.mjs +29 -2
- package/src/component/Base.mjs +6 -16
- package/src/controller/Base.mjs +87 -63
- package/src/core/Base.mjs +72 -17
- package/src/core/Compare.mjs +3 -13
- package/src/core/Config.mjs +139 -0
- package/src/core/ConfigSymbols.mjs +3 -0
- package/src/core/Util.mjs +3 -18
- package/src/data/RecordFactory.mjs +22 -3
- package/src/form/field/ComboBox.mjs +6 -1
- package/src/util/Function.mjs +52 -5
- package/src/vdom/Helper.mjs +7 -5
- package/test/siesta/tests/ReactiveConfigs.mjs +112 -0
- package/learn/guides/CustomComponents.md +0 -45
- package/learn/guides/Forms.md +0 -1
- package/learn/guides/Layouts.md +0 -1
- /package/learn/guides/{Tables.md → datahandling/Tables.md} +0 -0
- /package/learn/guides/{ApplicationBootstrap.md → fundamentals/ApplicationBootstrap.md} +0 -0
- /package/learn/guides/{ConfigSystemDeepDive.md → fundamentals/ConfigSystemDeepDive.md} +0 -0
- /package/learn/guides/{DeclarativeComponentTreesVsImperativeVdom.md → fundamentals/DeclarativeComponentTreesVsImperativeVdom.md} +0 -0
- /package/learn/guides/{InstanceLifecycle.md → fundamentals/InstanceLifecycle.md} +0 -0
- /package/learn/guides/{MainThreadAddons.md → fundamentals/MainThreadAddons.md} +0 -0
- /package/learn/guides/{Mixins.md → specificfeatures/Mixins.md} +0 -0
- /package/learn/guides/{MultiWindow.md → specificfeatures/MultiWindow.md} +0 -0
- /package/learn/guides/{PortalApp.md → specificfeatures/PortalApp.md} +0 -0
- /package/learn/guides/{ComponentsAndContainers.md → uibuildingblocks/ComponentsAndContainers.md} +0 -0
- /package/learn/guides/{WorkingWithVDom.md → uibuildingblocks/WorkingWithVDom.md} +0 -0
- /package/learn/guides/{events → userinteraction/events}/CustomEvents.md +0 -0
- /package/learn/guides/{events → userinteraction/events}/DomEvents.md +0 -0
package/src/collection/Base.mjs
CHANGED
@@ -48,6 +48,11 @@ class Collection extends Base {
|
|
48
48
|
* @member {Boolean} autoSort=true
|
49
49
|
*/
|
50
50
|
autoSort: true,
|
51
|
+
/**
|
52
|
+
* Stores the items.length of the items array in use
|
53
|
+
* @member {Number} count_=0
|
54
|
+
*/
|
55
|
+
count_: 0,
|
51
56
|
/**
|
52
57
|
* Use 'primitive' for default filters, use 'advanced' for filters using a filterBy method
|
53
58
|
* which need to iterate over other collection items
|
@@ -140,6 +145,17 @@ class Collection extends Base {
|
|
140
145
|
}
|
141
146
|
|
142
147
|
/**
|
148
|
+
* Triggered after the badgePosition config got changed
|
149
|
+
* @param {Number} value
|
150
|
+
* @param {Number} oldValue
|
151
|
+
* @protected
|
152
|
+
*/
|
153
|
+
afterSetCount(value, oldValue) {
|
154
|
+
this.fire('countChange', {oldValue, value})
|
155
|
+
}
|
156
|
+
|
157
|
+
/**
|
158
|
+
* Triggered after the filters config got changed
|
143
159
|
* @param {Array} value
|
144
160
|
* @param {Array} oldValue
|
145
161
|
* @protected
|
@@ -158,6 +174,7 @@ class Collection extends Base {
|
|
158
174
|
}
|
159
175
|
|
160
176
|
/**
|
177
|
+
* Triggered after the items config got changed
|
161
178
|
* @param {Array} value
|
162
179
|
* @param {Array} oldValue
|
163
180
|
* @protected
|
@@ -174,10 +191,13 @@ class Collection extends Base {
|
|
174
191
|
item = value[i];
|
175
192
|
me.map.set(item[keyProperty], item)
|
176
193
|
}
|
194
|
+
|
195
|
+
me.count = len
|
177
196
|
}
|
178
197
|
}
|
179
198
|
|
180
199
|
/**
|
200
|
+
* Triggered after the sorters config got changed
|
181
201
|
* @param {Array} value
|
182
202
|
* @param {Array} oldValue
|
183
203
|
* @protected
|
@@ -198,6 +218,7 @@ class Collection extends Base {
|
|
198
218
|
}
|
199
219
|
|
200
220
|
/**
|
221
|
+
* Triggered after the sourceId config got changed
|
201
222
|
* @param {Number|String} value
|
202
223
|
* @param {Number|String} oldValue
|
203
224
|
* @protected
|
@@ -673,6 +694,7 @@ class Collection extends Base {
|
|
673
694
|
|
674
695
|
me.allItems = Neo.create(Collection, {
|
675
696
|
...Neo.clone(config, true, true),
|
697
|
+
id : me.id + '-all',
|
676
698
|
keyProperty: me.keyProperty,
|
677
699
|
sourceId : me.id
|
678
700
|
})
|
@@ -727,6 +749,8 @@ class Collection extends Base {
|
|
727
749
|
me.doSort(me.items, true)
|
728
750
|
}
|
729
751
|
|
752
|
+
me.count = me.items.length;
|
753
|
+
|
730
754
|
me.fire('filter', {
|
731
755
|
isFiltered: me[isFiltered],
|
732
756
|
items : me.items,
|
@@ -845,11 +869,12 @@ class Collection extends Base {
|
|
845
869
|
}
|
846
870
|
|
847
871
|
/**
|
848
|
-
* Returns the
|
872
|
+
* Returns the config value of this.count
|
849
873
|
* @returns {Number}
|
874
|
+
* @deprecated Use `this.count` directly instead.
|
850
875
|
*/
|
851
876
|
getCount() {
|
852
|
-
return this.
|
877
|
+
return this._count || 0 // skipping beforeGetCount() on purpose
|
853
878
|
}
|
854
879
|
|
855
880
|
/**
|
@@ -1238,6 +1263,8 @@ class Collection extends Base {
|
|
1238
1263
|
}
|
1239
1264
|
|
1240
1265
|
if (me[updatingIndex] === 0) {
|
1266
|
+
me.count = me._items.length;
|
1267
|
+
|
1241
1268
|
me.fire('mutate', {
|
1242
1269
|
addedItems : toAddArray,
|
1243
1270
|
preventBubbleUp: me.preventBubbleUp,
|
package/src/component/Base.mjs
CHANGED
@@ -10,6 +10,7 @@ import Rectangle from '../util/Rectangle.mjs';
|
|
10
10
|
import Style from '../util/Style.mjs';
|
11
11
|
import VDomUtil from '../util/VDom.mjs';
|
12
12
|
import VNodeUtil from '../util/VNode.mjs';
|
13
|
+
import {isDescriptor} from '../core/ConfigSymbols.mjs';
|
13
14
|
|
14
15
|
const
|
15
16
|
addUnits = value => value == null ? value : isNaN(value) ? value : `${value}px`,
|
@@ -378,7 +379,11 @@ class Component extends Base {
|
|
378
379
|
* @member {Object} vnode_=null
|
379
380
|
* @protected
|
380
381
|
*/
|
381
|
-
vnode_:
|
382
|
+
vnode_: {
|
383
|
+
[isDescriptor]: true,
|
384
|
+
value : null,
|
385
|
+
isEqual : (a, b) => a === b // vnode trees can be huge, and will get compared by the vdom worker.
|
386
|
+
},
|
382
387
|
/**
|
383
388
|
* Shortcut for style.width, defaults to px
|
384
389
|
* @member {Number|String|null} width_=null
|
@@ -646,21 +651,6 @@ class Component extends Base {
|
|
646
651
|
}
|
647
652
|
}
|
648
653
|
|
649
|
-
/**
|
650
|
-
* Triggered after the flex config got changed
|
651
|
-
* @param {Number|String|null} value
|
652
|
-
* @param {Number|String|null} oldValue
|
653
|
-
* @protected
|
654
|
-
*/
|
655
|
-
afterSetFlex(value, oldValue) {
|
656
|
-
if (!isNaN(value)) {
|
657
|
-
value = `${value} ${value} 0%`
|
658
|
-
}
|
659
|
-
|
660
|
-
this.configuredFlex = value;
|
661
|
-
this.changeVdomRootKey('flex', value)
|
662
|
-
}
|
663
|
-
|
664
654
|
/**
|
665
655
|
* Triggered after the hasUnmountedVdomChanges config got changed
|
666
656
|
* @param {Boolean} value
|
package/src/controller/Base.mjs
CHANGED
@@ -2,8 +2,11 @@ import Base from '../core/Base.mjs';
|
|
2
2
|
import HashHistory from '../util/HashHistory.mjs';
|
3
3
|
|
4
4
|
const
|
5
|
-
|
6
|
-
|
5
|
+
regexAmountSlashes = /\//g,
|
6
|
+
// Regex to extract the parameter name from a single route segment (e.g., {*itemId} -> itemId)
|
7
|
+
regexParamNameExtraction = /{(\*|\.\.\.)?([^}]+)}/,
|
8
|
+
// Regex to match route parameters like {paramName}, {*paramName}, or {...paramName}
|
9
|
+
regexRouteParam = /{(\*|\.\.\.)?([^}]+)}/g;
|
7
10
|
|
8
11
|
/**
|
9
12
|
* @class Neo.controller.Base
|
@@ -22,25 +25,33 @@ class Controller extends Base {
|
|
22
25
|
*/
|
23
26
|
ntype: 'controller',
|
24
27
|
/**
|
25
|
-
* If the URL does not contain a hash value when
|
26
|
-
*
|
28
|
+
* If the URL does not contain a hash value when this controller instance is created,
|
29
|
+
* Neo.mjs will automatically set this hash value, ensuring a default route is active.
|
27
30
|
* @member {String|null} defaultHash=null
|
28
31
|
*/
|
29
32
|
defaultHash: null,
|
30
33
|
/**
|
34
|
+
* Specifies the handler method to be invoked when no other defined route matches the URL hash.
|
35
|
+
* This acts as a fallback for unhandled routes.
|
31
36
|
* @member {String|null} defaultRoute=null
|
32
37
|
*/
|
33
38
|
defaultRoute: null,
|
34
39
|
/**
|
40
|
+
* Internal map of compiled regular expressions for each route, used for efficient hash matching.
|
41
|
+
* @protected
|
35
42
|
* @member {Object} handleRoutes={}
|
36
43
|
*/
|
37
44
|
handleRoutes: {},
|
38
45
|
/**
|
46
|
+
* Defines the routing rules for the controller. Keys are route patterns, and values are either
|
47
|
+
* handler method names (String) or objects containing `handler` and optional `preHandler` method names.
|
48
|
+
* Route patterns can include parameters like `{paramName}` and wildcards like `{*paramName}` for nested paths.
|
39
49
|
* @example
|
40
50
|
* routes: {
|
41
51
|
* '/home' : 'handleHomeRoute',
|
42
52
|
* '/users/{userId}' : {handler: 'handleUserRoute', preHandler: 'preHandleUserRoute'},
|
43
53
|
* '/users/{userId}/posts/{postId}': 'handlePostRoute',
|
54
|
+
* '/learn/{*itemId}' : 'onLearnRoute', // Captures nested paths like /learn/gettingstarted/Workspaces
|
44
55
|
* 'default' : 'handleOtherRoutes'
|
45
56
|
* }
|
46
57
|
* @member {Object} routes_={}
|
@@ -49,16 +60,18 @@ class Controller extends Base {
|
|
49
60
|
}
|
50
61
|
|
51
62
|
/**
|
63
|
+
* Creates a new Controller instance and registers its `onHashChange` method
|
64
|
+
* to listen for changes in the browser's URL hash.
|
52
65
|
* @param {Object} config
|
53
66
|
*/
|
54
67
|
construct(config) {
|
55
68
|
super.construct(config);
|
56
|
-
|
57
69
|
HashHistory.on('change', this.onHashChange, this)
|
58
70
|
}
|
59
71
|
|
60
72
|
/**
|
61
|
-
*
|
73
|
+
* Processes the defined routes configuration, compiling route patterns into regular expressions
|
74
|
+
* for efficient matching and sorting them by specificity (more slashes first).
|
62
75
|
* @param {Object} value
|
63
76
|
* @param {Object} oldValue
|
64
77
|
* @protected
|
@@ -78,7 +91,13 @@ class Controller extends Base {
|
|
78
91
|
if (key.toLowerCase() === 'default'){
|
79
92
|
me.defaultRoute = value[key]
|
80
93
|
} else {
|
81
|
-
me.handleRoutes[key] = new RegExp(key.replace(
|
94
|
+
me.handleRoutes[key] = new RegExp(key.replace(regexRouteParam, (match, isWildcard, paramName) => {
|
95
|
+
if (isWildcard || paramName.startsWith('*')) {
|
96
|
+
return '(.*)'
|
97
|
+
} else {
|
98
|
+
return '([\\w-.]+)'
|
99
|
+
}
|
100
|
+
}))
|
82
101
|
}
|
83
102
|
})
|
84
103
|
}
|
@@ -88,21 +107,19 @@ class Controller extends Base {
|
|
88
107
|
*/
|
89
108
|
destroy(...args) {
|
90
109
|
HashHistory.un('change', this.onHashChange, this);
|
91
|
-
|
92
110
|
super.destroy(...args)
|
93
111
|
}
|
94
112
|
|
95
113
|
/**
|
96
|
-
*
|
114
|
+
* @returns {Promise<void>}
|
97
115
|
*/
|
98
|
-
async
|
116
|
+
async initAsync() {
|
117
|
+
await super.initAsync();
|
118
|
+
|
99
119
|
let me = this,
|
100
120
|
{defaultHash, windowId} = me,
|
101
121
|
currentHash = HashHistory.first(windowId);
|
102
122
|
|
103
|
-
// get outside the construction chain => a related cmp & vm has to be constructed too
|
104
|
-
await me.timeout(1);
|
105
|
-
|
106
123
|
if (currentHash) {
|
107
124
|
if (currentHash.windowId === windowId) {
|
108
125
|
await me.onHashChange(currentHash, null)
|
@@ -118,9 +135,10 @@ class Controller extends Base {
|
|
118
135
|
}
|
119
136
|
|
120
137
|
/**
|
121
|
-
*
|
122
|
-
*
|
123
|
-
* @param {Object}
|
138
|
+
* Handles changes in the browser's URL hash. It identifies the most specific matching route
|
139
|
+
* and dispatches the corresponding handler, optionally executing a preHandler first.
|
140
|
+
* @param {Object} value - The new hash history entry.
|
141
|
+
* @param {Object} oldValue - The previous hash history entry.
|
124
142
|
*/
|
125
143
|
async onHashChange(value, oldValue) {
|
126
144
|
// We only want to trigger hash changes for the same browser window (SharedWorker context)
|
@@ -129,63 +147,67 @@ class Controller extends Base {
|
|
129
147
|
}
|
130
148
|
|
131
149
|
let me = this,
|
132
|
-
counter = 0,
|
133
|
-
hasRouteBeenFound = false,
|
134
150
|
{handleRoutes, routes} = me,
|
135
151
|
routeKeys = Object.keys(handleRoutes),
|
136
|
-
|
137
|
-
|
152
|
+
bestMatch = null,
|
153
|
+
bestMatchKey = null,
|
154
|
+
bestMatchParams = null;
|
138
155
|
|
139
|
-
|
140
|
-
key
|
141
|
-
|
142
|
-
preHandler = null;
|
143
|
-
responsePreHandler = null;
|
144
|
-
paramObject = {};
|
145
|
-
result = value.hashString.match(handleRoutes[key]);
|
156
|
+
for (let i = 0; i < routeKeys.length; i++) {
|
157
|
+
const key = routeKeys[i];
|
158
|
+
const result = value.hashString.match(handleRoutes[key]);
|
146
159
|
|
147
160
|
if (result) {
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
161
|
+
const
|
162
|
+
arrayParamIds = key.match(regexRouteParam),
|
163
|
+
arrayParamValues = result.splice(1, result.length - 1),
|
164
|
+
paramObject = {};
|
165
|
+
|
166
|
+
if (arrayParamIds) {
|
167
|
+
for (let j = 0; j < arrayParamIds.length; j++) {
|
168
|
+
const paramMatch = arrayParamIds[j].match(regexParamNameExtraction);
|
169
|
+
|
170
|
+
if (paramMatch) {
|
171
|
+
const paramName = paramMatch[2];
|
172
|
+
paramObject[paramName] = arrayParamValues[j];
|
173
|
+
}
|
174
|
+
}
|
153
175
|
}
|
154
176
|
|
155
|
-
|
156
|
-
|
177
|
+
// Logic to determine the best matching route:
|
178
|
+
// 1. Prioritize routes that match a longer string (more specific match).
|
179
|
+
// 2. If lengths are equal, prioritize routes with more slashes (deeper nesting).
|
180
|
+
if (!bestMatch || (result[0].length > bestMatch[0].length) ||
|
181
|
+
(result[0].length === bestMatch[0].length && (key.match(regexAmountSlashes) || []).length > (bestMatchKey.match(regexAmountSlashes) || []).length)) {
|
182
|
+
bestMatch = result;
|
183
|
+
bestMatchKey = key;
|
184
|
+
bestMatchParams = paramObject;
|
157
185
|
}
|
186
|
+
}
|
187
|
+
}
|
158
188
|
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
responsePreHandler = true
|
164
|
-
} else if (Neo.isObject(route)) {
|
165
|
-
handler = route.handler;
|
166
|
-
preHandler = route.preHandler
|
167
|
-
}
|
189
|
+
if (bestMatch) {
|
190
|
+
const route = routes[bestMatchKey];
|
191
|
+
let handler = null,
|
192
|
+
preHandler = null;
|
168
193
|
|
169
|
-
|
194
|
+
if (Neo.isString(route)) {
|
195
|
+
handler = route
|
196
|
+
} else if (Neo.isObject(route)) {
|
197
|
+
handler = route.handler;
|
198
|
+
preHandler = route.preHandler
|
170
199
|
}
|
171
200
|
|
172
|
-
|
173
|
-
}
|
201
|
+
let responsePreHandler = true;
|
174
202
|
|
175
|
-
// execute
|
176
|
-
if (hasRouteBeenFound) {
|
177
203
|
if (preHandler) {
|
178
|
-
responsePreHandler = await me[preHandler]?.call(me,
|
179
|
-
} else {
|
180
|
-
responsePreHandler = true
|
204
|
+
responsePreHandler = await me[preHandler]?.call(me, bestMatchParams, value, oldValue)
|
181
205
|
}
|
182
206
|
|
183
207
|
if (responsePreHandler) {
|
184
|
-
await me[handler]?.call(me,
|
208
|
+
await me[handler]?.call(me, bestMatchParams, value, oldValue)
|
185
209
|
}
|
186
|
-
}
|
187
|
-
|
188
|
-
if (routeKeys.length > 0 && !hasRouteBeenFound) {
|
210
|
+
} else {
|
189
211
|
if (me.defaultRoute) {
|
190
212
|
me[me.defaultRoute]?.(value, oldValue)
|
191
213
|
} else {
|
@@ -195,22 +217,24 @@ class Controller extends Base {
|
|
195
217
|
}
|
196
218
|
|
197
219
|
/**
|
198
|
-
* Placeholder method
|
199
|
-
*
|
200
|
-
* @param {Object}
|
220
|
+
* Placeholder method invoked when no matching route is found for the current URL hash.
|
221
|
+
* Controllers can override this to implement custom behavior for unhandled routes.
|
222
|
+
* @param {Object} value - The current hash history entry.
|
223
|
+
* @param {Object} oldValue - The previous hash history entry.
|
201
224
|
*/
|
202
225
|
onNoRouteFound(value, oldValue) {
|
203
226
|
|
204
227
|
}
|
205
228
|
|
206
229
|
/**
|
207
|
-
* Internal helper method to sort routes by their
|
208
|
-
*
|
209
|
-
* @param {String}
|
210
|
-
* @
|
230
|
+
* Internal helper method to sort routes by their specificity.
|
231
|
+
* Routes with more slashes are considered more specific and are prioritized.
|
232
|
+
* @param {String} route1 - The first route string to compare.
|
233
|
+
* @param {String} route2 - The second route string to compare.
|
234
|
+
* @returns {Number} A negative value if route1 is more specific, a positive value if route2 is more specific, or 0 if they have equal specificity.
|
211
235
|
*/
|
212
236
|
#sortRoutes(route1, route2) {
|
213
|
-
return (route1.match(
|
237
|
+
return (route1.match(regexAmountSlashes) || []).length - (route2.match(regexAmountSlashes)|| []).length
|
214
238
|
}
|
215
239
|
}
|
216
240
|
|
package/src/core/Base.mjs
CHANGED
@@ -1,4 +1,8 @@
|
|
1
1
|
import {buffer, debounce, intercept, resolveCallback, throttle} from '../util/Function.mjs';
|
2
|
+
import Compare from '../core/Compare.mjs';
|
3
|
+
import Util from '../core/Util.mjs';
|
4
|
+
import Config from './Config.mjs';
|
5
|
+
import {isDescriptor} from './ConfigSymbols.mjs';
|
2
6
|
import IdGenerator from './IdGenerator.mjs'
|
3
7
|
|
4
8
|
const configSymbol = Symbol.for('configSymbol'),
|
@@ -125,6 +129,12 @@ class Base {
|
|
125
129
|
remote_: null
|
126
130
|
}
|
127
131
|
|
132
|
+
/**
|
133
|
+
* A private field to store the Config controller instances.
|
134
|
+
* @member {Object} #configs={}
|
135
|
+
* @private
|
136
|
+
*/
|
137
|
+
#configs = {};
|
128
138
|
/**
|
129
139
|
* Internal cache for all timeout ids when using this.timeout()
|
130
140
|
* @member {Number[]} timeoutIds=[]
|
@@ -133,8 +143,39 @@ class Base {
|
|
133
143
|
#timeoutIds = []
|
134
144
|
|
135
145
|
/**
|
136
|
-
*
|
137
|
-
*
|
146
|
+
* The main initializer for all Neo.mjs classes, invoked by `Neo.create()`.
|
147
|
+
* NOTE: This is not the native `constructor()`, which is called without arguments by `Neo.create()` first.
|
148
|
+
*
|
149
|
+
* This method orchestrates the entire instance initialization process, including
|
150
|
+
* the setup of the powerful and flexible config system.
|
151
|
+
*
|
152
|
+
* The `config` parameter is a single object that can contain different types of properties,
|
153
|
+
* which are processed in a specific order to ensure consistency and predictability:
|
154
|
+
*
|
155
|
+
* 1. **Public Class Fields & Other Properties:** Any key in the `config` object that is NOT
|
156
|
+
* defined in the class's `static config` hierarchy is considered a public field or a
|
157
|
+
* dynamic property. These are assigned directly to the instance (`this.myField = value`)
|
158
|
+
* at the very beginning. This is crucial so that subsequent config hooks (like `afterSet*`)
|
159
|
+
* can access their latest values.
|
160
|
+
*
|
161
|
+
* 2. **Reactive Configs:** A property is considered reactive if it is defined with a trailing
|
162
|
+
* underscore (e.g., `myValue_`) in the `static config` of **any class in the inheritance
|
163
|
+
* chain**. Subclasses can provide new default values for these configs without the
|
164
|
+
* underscore, and they will still be reactive. Their values are applied via generated
|
165
|
+
* setters, triggering `beforeSet*` and `afterSet*` hooks, and they are wrapped in a
|
166
|
+
* `Neo.core.Config` instance to enable subscription-based reactivity.
|
167
|
+
*
|
168
|
+
* 3. **Non-Reactive Configs:** Properties defined in `static config` without a trailing
|
169
|
+
* underscore in their entire inheritance chain. Their default values are applied directly
|
170
|
+
* to the class **prototype**, making them shared across all instances and allowing for
|
171
|
+
* run-time modifications (prototypal inheritance). When a new value is passed to this
|
172
|
+
* method, it creates an instance-specific property that shadows the prototype value.
|
173
|
+
*
|
174
|
+
* This method also initializes the observable mixin (if applicable) and schedules asynchronous
|
175
|
+
* logic like `initAsync()` (which handles remote method access) to run after the synchronous
|
176
|
+
* construction chain is complete.
|
177
|
+
*
|
178
|
+
* @param {Object} config={} The initial configuration object for the instance.
|
138
179
|
*/
|
139
180
|
construct(config={}) {
|
140
181
|
let me = this;
|
@@ -152,13 +193,9 @@ class Base {
|
|
152
193
|
}
|
153
194
|
});
|
154
195
|
|
155
|
-
me.
|
196
|
+
me.id = config.id || IdGenerator.getId(this.getIdKey());
|
156
197
|
delete config.id;
|
157
198
|
|
158
|
-
if (me.constructor.config) {
|
159
|
-
delete me.constructor.config.id
|
160
|
-
}
|
161
|
-
|
162
199
|
me.getStaticConfig('observable') && me.initObservable(config);
|
163
200
|
|
164
201
|
// assign class field values prior to configs
|
@@ -342,16 +379,6 @@ class Base {
|
|
342
379
|
this.__proto__.constructor.overwrittenMethods[methodName].call(this, ...args)
|
343
380
|
}
|
344
381
|
|
345
|
-
/**
|
346
|
-
* Uses the IdGenerator to create an id if a static one is not explicitly set.
|
347
|
-
* Registers the instance to manager.Instance if this one is already created,
|
348
|
-
* otherwise stores it inside a tmp map.
|
349
|
-
* @param {String} id
|
350
|
-
*/
|
351
|
-
createId(id) {
|
352
|
-
this.id = id || IdGenerator.getId(this.getIdKey())
|
353
|
-
}
|
354
|
-
|
355
382
|
/**
|
356
383
|
* Unregisters this instance from Neo.manager.Instance
|
357
384
|
* and removes all object entries from this instance
|
@@ -386,6 +413,22 @@ class Base {
|
|
386
413
|
me.isDestroyed = true
|
387
414
|
}
|
388
415
|
|
416
|
+
/**
|
417
|
+
* A public method to access the underlying Config controller.
|
418
|
+
* This enables advanced interactions like subscriptions.
|
419
|
+
* @param {String} key The name of the config property (e.g., 'items').
|
420
|
+
* @returns {Config|undefined} The Config instance, or undefined if not found.
|
421
|
+
*/
|
422
|
+
getConfig(key) {
|
423
|
+
let me = this;
|
424
|
+
|
425
|
+
if (!me.#configs[key] && me.isConfig(key)) {
|
426
|
+
me.#configs[key] = new Config()
|
427
|
+
}
|
428
|
+
|
429
|
+
return me.#configs[key]
|
430
|
+
}
|
431
|
+
|
389
432
|
/**
|
390
433
|
* Used inside createId() as the default value passed to the IdGenerator.
|
391
434
|
* Override this method as needed.
|
@@ -443,6 +486,7 @@ class Base {
|
|
443
486
|
|
444
487
|
me.isConfiguring = true;
|
445
488
|
Object.assign(me[configSymbol], me.mergeConfig(config, preventOriginalConfig));
|
489
|
+
delete me[configSymbol].id;
|
446
490
|
me.processConfigs();
|
447
491
|
me.isConfiguring = false;
|
448
492
|
}
|
@@ -475,6 +519,17 @@ class Base {
|
|
475
519
|
return !this.isDestroyed
|
476
520
|
}
|
477
521
|
|
522
|
+
/**
|
523
|
+
* @param {String} key
|
524
|
+
* @returns {Boolean}
|
525
|
+
*/
|
526
|
+
isConfig(key) {
|
527
|
+
// A config is considered "reactive" if it has a generated property setter
|
528
|
+
// AND it is present as a defined config in the merged static config hierarchy.
|
529
|
+
// Neo.setupClass() removes the underscore from the static config keys.
|
530
|
+
return Neo.hasPropertySetter(this, key) && (key in this.constructor.config);
|
531
|
+
}
|
532
|
+
|
478
533
|
/**
|
479
534
|
* Override this method to change the order configs are applied to this instance.
|
480
535
|
* @param {Object} config
|
package/src/core/Compare.mjs
CHANGED
@@ -1,18 +1,7 @@
|
|
1
|
-
import Base from '../core/Base.mjs';
|
2
|
-
|
3
1
|
/**
|
4
2
|
* @class Neo.core.Compare
|
5
|
-
* @extends Neo.core.Base
|
6
3
|
*/
|
7
|
-
class Compare
|
8
|
-
static config = {
|
9
|
-
/**
|
10
|
-
* @member {String} className='Neo.core.Compare'
|
11
|
-
* @protected
|
12
|
-
*/
|
13
|
-
className: 'Neo.core.Compare'
|
14
|
-
}
|
15
|
-
|
4
|
+
class Compare {
|
16
5
|
/**
|
17
6
|
* Storing the comparison method names by data type
|
18
7
|
* @member {Object} map
|
@@ -174,7 +163,8 @@ class Compare extends Base {
|
|
174
163
|
}
|
175
164
|
}
|
176
165
|
|
177
|
-
|
166
|
+
const ns = Neo.ns('Neo.core', true);
|
167
|
+
ns.Compare = Compare;
|
178
168
|
|
179
169
|
// alias
|
180
170
|
Neo.isEqual = Compare.isEqual;
|