11import Base from '../core/Base.mjs' ;
2+ import ClassSystemUtil from '../util/ClassSystem.mjs' ;
23import ComponentManager from '../manager/Component.mjs' ;
34import DomEvents from '../mixin/DomEvents.mjs' ;
45import Observable from '../core/Observable.mjs' ;
56import 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
218420export default Neo . setupClass ( Abstract ) ;
0 commit comments