Skip to content

Commit aad159e

Browse files
committed
Refactor: Move State Provider logic to component.Abstract #7100
1 parent 0031baf commit aad159e

3 files changed

Lines changed: 210 additions & 207 deletions

File tree

src/component/Abstract.mjs

Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,15 @@
11
import Base from '../core/Base.mjs';
2+
import ClassSystemUtil from '../util/ClassSystem.mjs';
23
import ComponentManager from '../manager/Component.mjs';
34
import DomEvents from '../mixin/DomEvents.mjs';
45
import Observable from '../core/Observable.mjs';
56
import VdomLifecycle from '../mixin/VdomLifecycle.mjs';
67

8+
const
9+
closestController = Symbol.for('closestController'),
10+
closestProvider = Symbol.for('closestProvider'),
11+
twoWayBindingSymbol = Symbol.for('twoWayBinding');
12+
713
/**
814
* @class Neo.component.Abstract
915
* @extends Neo.core.Base
@@ -29,12 +35,25 @@ class Abstract extends Base {
2935
* @reactive
3036
*/
3137
appName_: null,
38+
/**
39+
* Bind configs to state.Provider data properties.
40+
* @member {Object|null} bind=null
41+
*/
42+
bind: null,
3243
/**
3344
* Custom CSS selectors to apply to the root level node of this component
3445
* @member {String[]} cls_=null
3546
* @reactive
3647
*/
3748
cls_: null,
49+
/**
50+
* Convenience shortcut to access the data config of the closest state.Provider.
51+
* Read only.
52+
* @member {Object} data_=null
53+
* @protected
54+
* @reactive
55+
*/
56+
data_: null,
3857
/**
3958
* Disabled components will get the neo-disabled cls applied and won't receive DOM events
4059
* @member {Boolean} disabled_=false
@@ -45,19 +64,40 @@ class Abstract extends Base {
4564
* @member {Neo.core.Base[]} mixins=[DomEvents, Observable, VdomLifecycle]
4665
*/
4766
mixins: [DomEvents, Observable, VdomLifecycle],
67+
/**
68+
* Override specific stateProvider data properties.
69+
* This will merge the content.
70+
* @member {Object|null} modelData=null
71+
*/
72+
modelData: null,
4873
/**
4974
* True after the component render() method was called. Also fires the rendered event.
5075
* @member {Boolean} mounted_=false
5176
* @protected
5277
* @reactive
5378
*/
5479
mounted_: false,
80+
/**
81+
* If the parentId does not match a neo component id, you can manually set this value for finding
82+
* view controllers or state providers.
83+
* Use case: manually dropping components into a vdom structure
84+
* @member {Neo.component.Base|null} parentComponent_=null
85+
* @protected
86+
* @reactive
87+
*/
88+
parentComponent_: null,
5589
/**
5690
* @member {String|null} parentId_=null
5791
* @protected
5892
* @reactive
5993
*/
6094
parentId_: null,
95+
/**
96+
* Optionally add a state.Provider to share state data with child components
97+
* @member {Object|null} stateProvider_=null
98+
* @reactive
99+
*/
100+
stateProvider_: null,
61101
/**
62102
* The custom windowIs (timestamp) this component belongs to
63103
* @member {Number|null} windowId_=null
@@ -116,6 +156,30 @@ class Abstract extends Base {
116156
return me.parentComponent || (me.parentId === 'document.body' ? null : Neo.getComponent(me.parentId))
117157
}
118158

159+
/**
160+
* Triggered after any config got changed
161+
* @param {String} key
162+
* @param {*} value
163+
* @param {*} oldValue
164+
* @protected
165+
*/
166+
afterSetConfig(key, value, oldValue) {
167+
let me = this;
168+
169+
if (Neo.isUsingStateProviders && me[twoWayBindingSymbol]) {
170+
// When a component config is updated by its state provider, this flag is set to the config's key.
171+
// This prevents circular updates in two-way data bindings by skipping the push back to the state provider.
172+
if (me._skipTwoWayPush === key) {
173+
return;
174+
}
175+
let binding = me.bind?.[key];
176+
177+
if (binding?.twoWay) {
178+
this.getStateProvider()?.setData(binding.key, value)
179+
}
180+
}
181+
}
182+
119183
/**
120184
* Triggered after the id config got changed
121185
* @param {String|null} value
@@ -149,6 +213,16 @@ class Abstract extends Base {
149213
}
150214
}
151215

216+
/**
217+
* Triggered after the stateProvider config got changed
218+
* @param {Neo.state.Provider} value
219+
* @param {Object|Neo.state.Provider|null} oldValue
220+
* @protected
221+
*/
222+
afterSetStateProvider(value, oldValue) {
223+
value?.createBindings(this)
224+
}
225+
152226
/**
153227
* Triggered after the windowId config got changed
154228
* @param {Number|null} value
@@ -169,15 +243,135 @@ class Abstract extends Base {
169243
}
170244
}
171245

246+
/**
247+
* Triggered when accessing the data config
248+
* Convenience shortcut which is expensive to use, since it will generate a merged parent state providers data map.
249+
* @param {Object} value
250+
* @protected
251+
*/
252+
beforeGetData(value) {
253+
return this.getStateProvider()?.getHierarchyData()
254+
}
255+
256+
/**
257+
* Triggered before the stateProvider config gets changed.
258+
* Creates a state.Provider instance if needed.
259+
* @param {Object} value
260+
* @param {Object} oldValue
261+
* @returns {Neo.state.Provider}
262+
* @protected
263+
*/
264+
beforeSetStateProvider(value, oldValue) {
265+
oldValue?.destroy();
266+
267+
if (value) {
268+
let me = this,
269+
defaultValues = {component: me};
270+
271+
if (me.modelData) {
272+
defaultValues.data = me.modelData
273+
}
274+
275+
return ClassSystemUtil.beforeSetInstance(value, 'Neo.state.Provider', defaultValues)
276+
}
277+
278+
return null
279+
}
280+
172281
/**
173282
*
174283
*/
175284
destroy() {
176285
this.removeDomEvents();
177286
ComponentManager.unregister(this);
287+
this.stateProvider = null; // triggers destroy()
178288
super.destroy()
179289
}
180290

291+
/**
292+
* Find an instance stored inside a config via optionally passing a ntype.
293+
* Returns this[configName] or the closest parent component with a match.
294+
* Used by getController() & getStateProvider()
295+
* @param {String} configName
296+
* @param {String} [ntype]
297+
* @returns {Neo.core.Base|null}
298+
*/
299+
getConfigInstanceByNtype(configName, ntype) {
300+
let me = this,
301+
config = me[configName],
302+
{parentComponent} = me;
303+
304+
if (config && (!ntype || ntype === config.ntype)) {
305+
return config
306+
}
307+
308+
if (!parentComponent && me.parentId) {
309+
parentComponent = me.parent || Neo.get(me.parentId);
310+
}
311+
312+
if (parentComponent) {
313+
// todo: We need ?. until functional.component.Base supports controllers
314+
return parentComponent.getConfigInstanceByNtype?.(configName, ntype)
315+
}
316+
317+
return null
318+
}
319+
320+
/**
321+
* Convenience shortcut
322+
* @param args
323+
* @returns {*}
324+
*/
325+
getState(...args) {
326+
return this.getStateProvider().getData(...args)
327+
}
328+
329+
/**
330+
* Returns this.stateProvider or the closest parent stateProvider
331+
* @param {String} [ntype]
332+
* @returns {Neo.state.Provider|null}
333+
*/
334+
getStateProvider(ntype) {
335+
if (!Neo.isUsingStateProviders) {
336+
return null
337+
}
338+
339+
let me = this,
340+
provider;
341+
342+
if (!ntype) {
343+
provider = me[closestProvider];
344+
345+
if (provider) {
346+
return provider
347+
}
348+
}
349+
350+
provider = me.getConfigInstanceByNtype('stateProvider', ntype);
351+
352+
if (!ntype) {
353+
me[closestProvider] = provider
354+
}
355+
356+
return provider
357+
}
358+
359+
/**
360+
* @param args
361+
*/
362+
initConfig(...args) {
363+
super.initConfig(...args);
364+
this.getStateProvider()?.createBindings(this)
365+
}
366+
367+
/**
368+
*
369+
*/
370+
onConstructed() {
371+
super.onConstructed();
372+
this.getStateProvider()?.createBindings(this)
373+
}
374+
181375
/**
182376
* Change multiple configs at once, ensuring that all afterSet methods get all new assigned values
183377
* @param {Object} values={}
@@ -213,6 +407,14 @@ class Abstract extends Base {
213407
super.set(values);
214408
this.silentVdomUpdate = false
215409
}
410+
411+
/**
412+
* Convenience shortcut
413+
* @param args
414+
*/
415+
setState(...args) {
416+
this.getStateProvider().setData(...args)
417+
}
216418
}
217419

218420
export default Neo.setupClass(Abstract);

0 commit comments

Comments
 (0)