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
@@ -0,0 +1,139 @@
|
|
1
|
+
import {isDescriptor} from './ConfigSymbols.mjs';
|
2
|
+
|
3
|
+
/**
|
4
|
+
* @src/util/ClassSystem.mjs Neo.core.Config
|
5
|
+
* @private
|
6
|
+
* @internal
|
7
|
+
*
|
8
|
+
* Represents an observable container for a config property.
|
9
|
+
* This class manages the value of a config, its subscribers, and custom behaviors
|
10
|
+
* like merge strategies and equality checks defined via a descriptor object.
|
11
|
+
*
|
12
|
+
* The primary purpose of this class is to enable fine-grained reactivity and
|
13
|
+
* decoupled cross-instance state sharing within the Neo.mjs framework.
|
14
|
+
*/
|
15
|
+
class Config {
|
16
|
+
/**
|
17
|
+
* The internal value of the config property.
|
18
|
+
* @private
|
19
|
+
* @apps/portal/view/about/MemberContainer.mjs {any} #value
|
20
|
+
*/
|
21
|
+
#value;
|
22
|
+
|
23
|
+
/**
|
24
|
+
* A Set to store callback functions that subscribe to changes in this config's value.
|
25
|
+
* @private
|
26
|
+
* @apps/portal/view/about/MemberContainer.mjs {Set<Function>} #subscribers
|
27
|
+
*/
|
28
|
+
#subscribers = new Set();
|
29
|
+
|
30
|
+
/**
|
31
|
+
* The strategy to use when merging new values into this config.
|
32
|
+
* Defaults to 'deep'. Can be overridden via a descriptor.
|
33
|
+
* @apps/portal/view/about/MemberContainer.mjs {string} mergeStrategy
|
34
|
+
*/
|
35
|
+
mergeStrategy = 'deep';
|
36
|
+
|
37
|
+
/**
|
38
|
+
* The function used to compare new and old values for equality.
|
39
|
+
* Defaults to `Neo.isEqual`. Can be overridden via a descriptor.
|
40
|
+
* @apps/portal/view/about/MemberContainer.mjs {Function} isEqual
|
41
|
+
*/
|
42
|
+
isEqual = Neo.isEqual;
|
43
|
+
|
44
|
+
/**
|
45
|
+
* Creates an instance of Config.
|
46
|
+
* @param {any|Object} configObject - The initial value for the config.
|
47
|
+
*/
|
48
|
+
constructor(configObject) {
|
49
|
+
if (Neo.isObject(configObject) && configObject[isDescriptor] === true) {
|
50
|
+
this.initDescriptor(configObject)
|
51
|
+
} else {
|
52
|
+
this.#value = configObject
|
53
|
+
}
|
54
|
+
}
|
55
|
+
|
56
|
+
/**
|
57
|
+
* Gets the current value of the config property.
|
58
|
+
* @returns {any} The current value.
|
59
|
+
*/
|
60
|
+
get() {
|
61
|
+
return this.#value;
|
62
|
+
}
|
63
|
+
|
64
|
+
/**
|
65
|
+
* Initializes the `Config` instance using a descriptor object.
|
66
|
+
* Extracts `mergeStrategy` and `isEqual` from the descriptor.
|
67
|
+
* The internal `#value` is NOT set by this method.
|
68
|
+
* @param {Object} descriptor - The descriptor object for the config.
|
69
|
+
* @param {any} descriptor.value - The default value for the config (not set by this method).
|
70
|
+
* @param {string} [descriptor.merge='deep'] - The merge strategy.
|
71
|
+
* @param {Function} [descriptor.isEqual=Neo.isEqual] - The equality comparison function.
|
72
|
+
*/
|
73
|
+
initDescriptor({isEqual, merge, value}) {
|
74
|
+
let me = this;
|
75
|
+
|
76
|
+
me.#value = value
|
77
|
+
me.mergeStrategy = merge || me.mergeStrategy;
|
78
|
+
me.isEqual = isEqual || me.isEqual;
|
79
|
+
}
|
80
|
+
|
81
|
+
/**
|
82
|
+
* Notifies all subscribed callbacks about a change in the config's value.
|
83
|
+
* @param {any} newValue - The new value of the config.
|
84
|
+
* @param {any} oldValue - The old value of the config.
|
85
|
+
*/
|
86
|
+
notify(newValue, oldValue) {
|
87
|
+
for (const callback of this.#subscribers) {
|
88
|
+
callback(newValue, oldValue);
|
89
|
+
}
|
90
|
+
}
|
91
|
+
|
92
|
+
/**
|
93
|
+
* Sets a new value for the config property.
|
94
|
+
* This method performs an equality check using `this.isEqual` before updating the value.
|
95
|
+
* If the value has changed, it updates `#value` and notifies all subscribers.
|
96
|
+
* @param {any} newValue - The new value to set.
|
97
|
+
* @returns {Boolean} True if the value changed, false otherwise.
|
98
|
+
*/
|
99
|
+
set(newValue) {
|
100
|
+
if (newValue === undefined) return false; // Preserve original behavior for undefined
|
101
|
+
|
102
|
+
const
|
103
|
+
me = this,
|
104
|
+
oldValue = me.#value;
|
105
|
+
|
106
|
+
// The setter automatically uses the configured equality check
|
107
|
+
if (!me.isEqual(newValue, oldValue)) {
|
108
|
+
me.#value = newValue;
|
109
|
+
me.notify(newValue, oldValue);
|
110
|
+
return true
|
111
|
+
}
|
112
|
+
|
113
|
+
return false
|
114
|
+
}
|
115
|
+
|
116
|
+
/**
|
117
|
+
* Sets the internal value of the config property directly, without performing
|
118
|
+
* an equality check or notifying subscribers.
|
119
|
+
* This method is intended for internal framework use where direct assignment
|
120
|
+
* is necessary (e.g., during initial setup or specific internal optimizations).
|
121
|
+
* @param {any} newValue - The new value to set directly.
|
122
|
+
*/
|
123
|
+
setRaw(newValue) {
|
124
|
+
this.#value = newValue
|
125
|
+
}
|
126
|
+
|
127
|
+
/**
|
128
|
+
* Subscribes a callback function to changes in this config's value.
|
129
|
+
* The callback will be invoked with `(newValue, oldValue)` whenever the config changes.
|
130
|
+
* @param {Function} callback - The function to call when the config value changes.
|
131
|
+
* @returns {Function} A cleanup function to unsubscribe the callback.
|
132
|
+
*/
|
133
|
+
subscribe(callback) {
|
134
|
+
this.#subscribers.add(callback);
|
135
|
+
return () => this.#subscribers.delete(callback)
|
136
|
+
}
|
137
|
+
}
|
138
|
+
|
139
|
+
export default Config;
|
package/src/core/Util.mjs
CHANGED
@@ -1,10 +1,7 @@
|
|
1
|
-
import Base from './Base.mjs';
|
2
|
-
|
3
1
|
/**
|
4
2
|
* @class Neo.core.Util
|
5
|
-
* @extends Neo.core.Base
|
6
3
|
*/
|
7
|
-
class Util
|
4
|
+
class Util {
|
8
5
|
/**
|
9
6
|
* A regex to remove camel case syntax
|
10
7
|
* @member {RegExp} decamelRegEx=/([a-z])([A-Z])/g
|
@@ -13,19 +10,6 @@ class Util extends Base {
|
|
13
10
|
*/
|
14
11
|
static decamelRegEx = /([a-z])([A-Z])/g
|
15
12
|
|
16
|
-
static config = {
|
17
|
-
/**
|
18
|
-
* @member {String} className='Neo.core.Util'
|
19
|
-
* @protected
|
20
|
-
*/
|
21
|
-
className: 'Neo.core.Util',
|
22
|
-
/**
|
23
|
-
* @member {String} ntype='core-util'
|
24
|
-
* @protected
|
25
|
-
*/
|
26
|
-
ntype: 'core-util'
|
27
|
-
}
|
28
|
-
|
29
13
|
/**
|
30
14
|
* @param {Object} scope
|
31
15
|
* @param {String[]} values
|
@@ -229,7 +213,8 @@ class Util extends Base {
|
|
229
213
|
}
|
230
214
|
}
|
231
215
|
|
232
|
-
|
216
|
+
const ns = Neo.ns('Neo.core', true);
|
217
|
+
ns.Util = Util;
|
233
218
|
|
234
219
|
// aliases
|
235
220
|
Neo.applyFromNs(Neo, Util, {
|
@@ -81,7 +81,7 @@ class RecordFactory extends Base {
|
|
81
81
|
return this[dataSymbol][fieldName]
|
82
82
|
},
|
83
83
|
set(value) {
|
84
|
-
|
84
|
+
this.notifyChange({
|
85
85
|
fields: {[fieldPath]: instance.parseRecordValue({record: this, field, value})},
|
86
86
|
model,
|
87
87
|
record: this
|
@@ -193,6 +193,25 @@ class RecordFactory extends Base {
|
|
193
193
|
return null
|
194
194
|
}
|
195
195
|
|
196
|
+
/**
|
197
|
+
* The single source of truth for record field changes.
|
198
|
+
* Executes instance.setRecordFields(), and can get used via:
|
199
|
+
* - Neo.util.Function:createSequence()
|
200
|
+
* - Neo.util.Function:intercept(),
|
201
|
+
* to "listen" to field changes
|
202
|
+
* @param {Object} data
|
203
|
+
* @param {Object} data.fields
|
204
|
+
* @param {Neo.data.Model} data.model
|
205
|
+
* @param {Object} data.record
|
206
|
+
* @param {Boolean} silent=false
|
207
|
+
* @returns {Object}
|
208
|
+
*/
|
209
|
+
notifyChange(data, silent=false) {
|
210
|
+
const param = {...data, silent}
|
211
|
+
instance.setRecordFields(param);
|
212
|
+
return param
|
213
|
+
}
|
214
|
+
|
196
215
|
/**
|
197
216
|
* Bulk-update multiple record fields at once
|
198
217
|
* @param {Object} fields
|
@@ -207,7 +226,7 @@ class RecordFactory extends Base {
|
|
207
226
|
* @param {Object} fields
|
208
227
|
*/
|
209
228
|
set(fields) {
|
210
|
-
|
229
|
+
this.notifyChange({fields, model, record: this})
|
211
230
|
}
|
212
231
|
|
213
232
|
/**
|
@@ -225,7 +244,7 @@ class RecordFactory extends Base {
|
|
225
244
|
* @param {Object} fields
|
226
245
|
*/
|
227
246
|
setSilent(fields) {
|
228
|
-
|
247
|
+
this.notifyChange({fields, model, record: this}, true)
|
229
248
|
}
|
230
249
|
|
231
250
|
/**
|
@@ -118,7 +118,12 @@ class ComboBox extends Picker {
|
|
118
118
|
* which you want to submit instead
|
119
119
|
* @member {Number|String} valueField='id'
|
120
120
|
*/
|
121
|
-
valueField: 'id'
|
121
|
+
valueField: 'id',
|
122
|
+
/**
|
123
|
+
* Default width to prevent rendering issues.
|
124
|
+
* @member {Number} width=150
|
125
|
+
*/
|
126
|
+
width: 150
|
122
127
|
}
|
123
128
|
|
124
129
|
/**
|
package/src/util/Function.mjs
CHANGED
@@ -1,3 +1,6 @@
|
|
1
|
+
const originalMethodSymbol = Symbol('originalMethod');
|
2
|
+
const sequencedFnsSymbol = Symbol('sequencedFns');
|
3
|
+
|
1
4
|
/**
|
2
5
|
* Append args instead of prepending them
|
3
6
|
* @param {Function} fn
|
@@ -67,12 +70,30 @@ export function createInterceptor(target, targetMethodName, interceptFunction, s
|
|
67
70
|
* @returns {Function}
|
68
71
|
*/
|
69
72
|
export function createSequence(target, methodName, fn, scope) {
|
70
|
-
let
|
73
|
+
let currentMethod = target[methodName],
|
74
|
+
wrapper;
|
75
|
+
|
76
|
+
if (currentMethod && currentMethod[sequencedFnsSymbol]) {
|
77
|
+
// Already a sequenced method, add to its list
|
78
|
+
wrapper = currentMethod;
|
79
|
+
wrapper[sequencedFnsSymbol].push({fn, scope})
|
80
|
+
} else {
|
81
|
+
// First time sequencing this method
|
82
|
+
let originalMethod = currentMethod || Neo.emptyFn;
|
83
|
+
|
84
|
+
wrapper = function() {
|
85
|
+
originalMethod.apply(this, arguments); // Call the original method
|
86
|
+
|
87
|
+
// Call all sequenced functions
|
88
|
+
wrapper[sequencedFnsSymbol].forEach(seqFn => {
|
89
|
+
seqFn.fn.apply(seqFn.scope || this, arguments);
|
90
|
+
});
|
91
|
+
};
|
92
|
+
wrapper[sequencedFnsSymbol] = [{fn, scope}];
|
93
|
+
wrapper[originalMethodSymbol] = originalMethod; // Store original method
|
94
|
+
}
|
71
95
|
|
72
|
-
return (target[methodName] =
|
73
|
-
method.apply(this, arguments);
|
74
|
-
return fn.apply(scope || this, arguments)
|
75
|
-
})
|
96
|
+
return (target[methodName] = wrapper);
|
76
97
|
}
|
77
98
|
|
78
99
|
/**
|
@@ -178,3 +199,29 @@ export function throttle(callback, scope, delay=300) {
|
|
178
199
|
}
|
179
200
|
}
|
180
201
|
}
|
202
|
+
|
203
|
+
/**
|
204
|
+
* @param {Neo.core.Base} target
|
205
|
+
* @param {String} methodName
|
206
|
+
* @param {Function} fn
|
207
|
+
* @param {Object} scope
|
208
|
+
*/
|
209
|
+
export function unSequence(target, methodName, fn, scope) {
|
210
|
+
let currentMethod = target[methodName];
|
211
|
+
|
212
|
+
if (!currentMethod || !currentMethod[sequencedFnsSymbol]) {
|
213
|
+
return // Not a sequenced method
|
214
|
+
}
|
215
|
+
|
216
|
+
const sequencedFunctions = currentMethod[sequencedFnsSymbol];
|
217
|
+
|
218
|
+
// Filter out the function to unsequence
|
219
|
+
currentMethod[sequencedFnsSymbol] = sequencedFunctions.filter(seqFn =>
|
220
|
+
!(seqFn.fn === fn && seqFn.scope === scope)
|
221
|
+
);
|
222
|
+
|
223
|
+
if (currentMethod[sequencedFnsSymbol].length === 0) {
|
224
|
+
// If no functions left, restore the original method
|
225
|
+
target[methodName] = currentMethod[originalMethodSymbol]
|
226
|
+
}
|
227
|
+
}
|
package/src/vdom/Helper.mjs
CHANGED
@@ -508,11 +508,13 @@ class Helper extends Base {
|
|
508
508
|
|
509
509
|
let me = this;
|
510
510
|
|
511
|
-
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
-
|
511
|
+
if (!NeoConfig.unitTestMode) {
|
512
|
+
// Subscribe to global Neo.config changes for dynamic renderer switching.
|
513
|
+
Neo.currentWorker.on({
|
514
|
+
neoConfigChange: me.onNeoConfigChange,
|
515
|
+
scope : me
|
516
|
+
})
|
517
|
+
}
|
516
518
|
|
517
519
|
await me.importUtil()
|
518
520
|
}
|
@@ -0,0 +1,112 @@
|
|
1
|
+
import Neo from '../../../src/Neo.mjs';
|
2
|
+
import * as core from '../../../src/core/_export.mjs';
|
3
|
+
import {isDescriptor} from '../../../src/core/ConfigSymbols.mjs';
|
4
|
+
|
5
|
+
class MyComponent extends core.Base {
|
6
|
+
static config = {
|
7
|
+
className: 'Neo.TestComponent',
|
8
|
+
myConfig_ : 'initialValue',
|
9
|
+
arrayConfig_: {
|
10
|
+
[isDescriptor]: true,
|
11
|
+
value: [],
|
12
|
+
merge: 'replace'
|
13
|
+
},
|
14
|
+
objectConfig_: {
|
15
|
+
[isDescriptor]: true,
|
16
|
+
value: {},
|
17
|
+
merge: 'deep'
|
18
|
+
}
|
19
|
+
}
|
20
|
+
|
21
|
+
afterSetMyConfig(value, oldValue) {
|
22
|
+
// This will be called by the framework
|
23
|
+
}
|
24
|
+
}
|
25
|
+
|
26
|
+
MyComponent = Neo.setupClass(MyComponent);
|
27
|
+
|
28
|
+
StartTest(t => {
|
29
|
+
t.it('Basic reactivity with subscribe', t => {
|
30
|
+
const instance = Neo.create(MyComponent);
|
31
|
+
const configController = instance.getConfig('myConfig');
|
32
|
+
|
33
|
+
let subscriberCalled = false;
|
34
|
+
let receivedNewValue, receivedOldValue;
|
35
|
+
|
36
|
+
const cleanup = configController.subscribe((newValue, oldValue) => {
|
37
|
+
subscriberCalled = true;
|
38
|
+
receivedNewValue = newValue;
|
39
|
+
receivedOldValue = oldValue;
|
40
|
+
});
|
41
|
+
|
42
|
+
instance.myConfig = 'newValue';
|
43
|
+
|
44
|
+
t.ok(subscriberCalled, 'Subscriber callback should be called');
|
45
|
+
t.is(receivedNewValue, 'newValue', 'New value should be passed to subscriber');
|
46
|
+
t.is(receivedOldValue, 'initialValue', 'Old value should be passed to subscriber');
|
47
|
+
|
48
|
+
// Test cleanup
|
49
|
+
subscriberCalled = false;
|
50
|
+
cleanup();
|
51
|
+
instance.myConfig = 'anotherValue';
|
52
|
+
t.notOk(subscriberCalled, 'Subscriber callback should not be called after cleanup');
|
53
|
+
});
|
54
|
+
|
55
|
+
t.it('Descriptor: arrayConfig_ with merge: replace', t => {
|
56
|
+
const instance = Neo.create(MyComponent);
|
57
|
+
const configController = instance.getConfig('arrayConfig');
|
58
|
+
|
59
|
+
let subscriberCalled = 0;
|
60
|
+
configController.subscribe((newValue, oldValue) => {
|
61
|
+
subscriberCalled++;
|
62
|
+
});
|
63
|
+
|
64
|
+
const arr1 = [1, 2, 3];
|
65
|
+
instance.arrayConfig = arr1;
|
66
|
+
t.is(instance.arrayConfig, arr1, 'Array should be replaced');
|
67
|
+
t.is(subscriberCalled, 1, 'Subscriber called once for array replacement');
|
68
|
+
|
69
|
+
const arr2 = [4, 5, 6];
|
70
|
+
instance.arrayConfig = arr2;
|
71
|
+
t.is(instance.arrayConfig, arr2, 'Array should be replaced again');
|
72
|
+
t.is(subscriberCalled, 2, 'Subscriber called twice for array replacement');
|
73
|
+
|
74
|
+
// Setting the same array should not trigger a change by default isEqual
|
75
|
+
instance.arrayConfig = arr2;
|
76
|
+
t.is(subscriberCalled, 2, 'Subscriber not called when setting the same array reference');
|
77
|
+
});
|
78
|
+
|
79
|
+
t.it('Descriptor: objectConfig_ with merge: deep', t => {
|
80
|
+
const instance = Neo.create(MyComponent);
|
81
|
+
const configController = instance.getConfig('objectConfig');
|
82
|
+
|
83
|
+
let subscriberCalled = 0;
|
84
|
+
configController.subscribe((newValue, oldValue) => {
|
85
|
+
subscriberCalled++;
|
86
|
+
});
|
87
|
+
|
88
|
+
const obj1 = {a: 1, b: {c: 2}};
|
89
|
+
instance.objectConfig = obj1;
|
90
|
+
t.is(instance.objectConfig, obj1, 'Object should be set');
|
91
|
+
t.is(subscriberCalled, 1, 'Subscriber called once for object set');
|
92
|
+
|
93
|
+
// Deep merge should happen, but default isEqual will still compare references
|
94
|
+
const obj2 = {a: 1, b: {c: 3}};
|
95
|
+
instance.objectConfig = obj2;
|
96
|
+
t.is(instance.objectConfig.a, 1, 'Object property a should be 1');
|
97
|
+
t.is(instance.objectConfig.b.c, 3, 'Object property b.c should be 3');
|
98
|
+
t.is(subscriberCalled, 2, 'Subscriber called twice for object change');
|
99
|
+
|
100
|
+
// Setting the same object reference should not trigger a change
|
101
|
+
instance.objectConfig = obj2;
|
102
|
+
t.is(subscriberCalled, 2, 'Subscriber not called when setting the same object reference');
|
103
|
+
|
104
|
+
// Modifying a nested property should trigger a change if isEqual is deep
|
105
|
+
// NOTE: The current Config.mjs uses Neo.isEqual which is a deep comparison.
|
106
|
+
// If the object reference changes, it will trigger. If the object reference stays the same, but content changes, it will not trigger unless isEqual is customized.
|
107
|
+
// For now, this test relies on the fact that setting a new object reference triggers the change.
|
108
|
+
const obj3 = {a: 1, b: {c: 2}};
|
109
|
+
instance.objectConfig = obj3;
|
110
|
+
t.is(subscriberCalled, 3, 'Subscriber called for new object reference');
|
111
|
+
});
|
112
|
+
});
|
@@ -1,45 +0,0 @@
|
|
1
|
-
## Introduction
|
2
|
-
|
3
|
-
Neo.mjs is class-based, which means you're free to extend any component (or any other Neo.mjs class).
|
4
|
-
|
5
|
-
|
6
|
-
## Overriding ancestor configs
|
7
|
-
|
8
|
-
## Introducing new configs
|
9
|
-
|
10
|
-
## Lifecycle config properties
|
11
|
-
|
12
|
-
```javascript live-preview
|
13
|
-
import Button from '../button/Base.mjs';
|
14
|
-
// In practice this would be some handy reusable component
|
15
|
-
class MySpecialButton extends Button {
|
16
|
-
static config = {
|
17
|
-
className: 'Example.view.MySpecialButton',
|
18
|
-
iconCls : 'far fa-face-grin-wide',
|
19
|
-
ui : 'ghost'
|
20
|
-
}
|
21
|
-
}
|
22
|
-
|
23
|
-
MySpecialButton = Neo.setupClass(MySpecialButton);
|
24
|
-
|
25
|
-
|
26
|
-
import Container from '../container/Base.mjs';
|
27
|
-
|
28
|
-
class MainView extends Container {
|
29
|
-
static config = {
|
30
|
-
className: 'Example.view.MainView',
|
31
|
-
layout : {ntype:'vbox', align:'start'},
|
32
|
-
items : [{
|
33
|
-
module : Button,
|
34
|
-
iconCls: 'fa fa-home',
|
35
|
-
text : 'A framework button'
|
36
|
-
}, {
|
37
|
-
module : MySpecialButton,
|
38
|
-
text : 'My special button'
|
39
|
-
}]
|
40
|
-
}
|
41
|
-
}
|
42
|
-
|
43
|
-
Neo.setupClass(MainView);
|
44
|
-
```
|
45
|
-
|
package/learn/guides/Forms.md
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
## todo
|
package/learn/guides/Layouts.md
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
## todo
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
/package/learn/guides/{ComponentsAndContainers.md → uibuildingblocks/ComponentsAndContainers.md}
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|