diff --git a/packages/cx/src/data/View.js b/packages/cx/src/data/View.js index 70e1fe712..388978b3b 100644 --- a/packages/cx/src/data/View.js +++ b/packages/cx/src/data/View.js @@ -170,6 +170,17 @@ export class View { this.store = store; this.meta = store.getMeta(); } + + getMethods() { + return { + getData: ::this.getData, + set: ::this.set, + get: ::this.get, + update: ::this.update, + delete: ::this.delete, + toggle: ::this.toggle + } + } } View.prototype.sealed = false; //indicate that data should be copied before virtual items are added \ No newline at end of file diff --git a/packages/cx/src/ui/Component.d.ts b/packages/cx/src/ui/Component.d.ts index d14fd5fe1..d273288ff 100644 --- a/packages/cx/src/ui/Component.d.ts +++ b/packages/cx/src/ui/Component.d.ts @@ -6,6 +6,8 @@ interface DecoratorFactory { export class Component { + constructor(config?: Cx.Config); + init(); /** diff --git a/packages/cx/src/ui/Controller.js b/packages/cx/src/ui/Controller.js index acf862b48..9d976407d 100644 --- a/packages/cx/src/ui/Controller.js +++ b/packages/cx/src/ui/Controller.js @@ -1,6 +1,8 @@ import {computable} from '../data/computable'; import {Component} from './Component'; import {isArray} from '../util/isArray'; +import {isFunction} from '../util/isFunction'; +import {View} from "../data/View"; const computablePrefix = 'computable-'; const triggerPrefix = 'trigger-'; @@ -87,3 +89,49 @@ export class Controller extends Component { Controller.namespace = 'ui.controller.'; Controller.lazyInit = true; + +Controller.factory = function(alias, config, more) { + if (isFunction(alias)) { + let cfg = { + ...config, + ...more + }; + + if (cfg.instance) { + //in rare cases instance.store may change, so we cannot rely on the store passed through configuration + cfg.store = new InstanceStoreProxy(cfg.instance); + Object.assign(cfg, cfg.store.getMethods()); + } + + let result = alias(cfg); + if (result instanceof Controller) + return result; + + return new Controller({ + ...config, + ...more, + ...result + }); + } + + return new Controller({ + ...config, + ...more + }); +}; + +class InstanceStoreProxy extends View { + constructor(instance) { + super({ + store: instance.store + }); + + Object.defineProperty(this, "store", { + get: () => instance.store + }); + } + + getData() { + return this.store.getData(); + } +} diff --git a/packages/cx/src/ui/Controller.spec.js b/packages/cx/src/ui/Controller.spec.js index c069800c7..2136c63d7 100644 --- a/packages/cx/src/ui/Controller.spec.js +++ b/packages/cx/src/ui/Controller.spec.js @@ -35,13 +35,13 @@ describe('Controller', () => { } let widget = -
+
; let store = new Store(); const component = renderer.create( - + ); let tree = component.toJSON(); @@ -75,7 +75,7 @@ describe('Controller', () => { const component = renderer.create( - + ); @@ -115,7 +115,7 @@ describe('Controller', () => { const component = renderer.create(
- +
); @@ -146,7 +146,7 @@ describe('Controller', () => { const component = renderer.create(
-
+
); @@ -176,7 +176,7 @@ describe('Controller', () => { const component = renderer.create(
-
+
); @@ -187,6 +187,7 @@ describe('Controller', () => { it('invokes triggers and computables in order of definition', () => { let log = []; + class TestController extends Controller { onInit() { this.addTrigger('t1', [], () => { @@ -205,13 +206,13 @@ describe('Controller', () => { } let widget = -
+
; let store = new Store(); const component = renderer.create( - + ); let tree = component.toJSON(); @@ -220,6 +221,7 @@ describe('Controller', () => { it('is recreated if a component is hidden and shown', () => { let initCount = 0; + class TestController extends Controller { onInit() { initCount++; @@ -231,7 +233,7 @@ describe('Controller', () => { const component = renderer.create( -
+
); @@ -246,5 +248,113 @@ describe('Controller', () => { let tree3 = component.toJSON(); assert.equal(initCount, 2); }); + + it('allows creation through a factory', () => { + let store = new Store({ data: { x: 0}}); + + const controllerFactory = ({store}) => { + return { + increment() { + store.update("x", x => x + 1); + } + } + }; + + let c = Controller.create(controllerFactory, {store}); + + c.increment(); + assert.equal(store.get("x"), 1); + }); + + it('lifetime methods of a functional controller are properly invoked', () => { + let initCount = 0, + destroyCount = 0; + + const testController = () => ({ + onInit() { + initCount++; + }, + + onDestroy() { + destroyCount++; + } + }); + + let store = new Store(); + store.set('visible', true); + + const component = renderer.create( + +
+ + ); + + let tree1 = component.toJSON(); + assert.equal(initCount, 1); + + store.set('visible', false); + let tree2 = component.toJSON(); + assert.equal(destroyCount, 1); + + store.set('visible', true); + let tree3 = component.toJSON(); + assert.equal(initCount, 2); + }); + + it('widgets can easily define controller methods', () => { + let store = new Store({ data: { x: 0}}); + + const component = renderer.create( + +
x + count); + } + }} + > +
+
+ + + + ); + + let tree1 = component.toJSON(); + assert.equal(store.get("x"), 1); + }); + + it('functional controllers get store methods through configuration', () => { + let store = new Store({ data: { x: 0}}); + + const component = renderer.create( + +
({ + increment(count) { + update("x", x => x + count); + } + })} + > +
+
+ + ); + + let tree1 = component.toJSON(); + assert.equal(store.get("x"), 1); + }); });