{"action":"created","issue":{"url":"https://api.github.com/repos/cerebral/cerebral/issues/158","labels_url":"https://api.github.com/repos/cerebral/cerebral/issues/158/labels{/name}","comments_url":"https://api.github.com/repos/cerebral/cerebral/issues/158/comments","events_url":"https://api.github.com/repos/cerebral/cerebral/issues/158/events","html_url":"https://github.com/cerebral/cerebral/issues/158","id":124868568,"number":158,"title":"Modules draft, version 2","user":{"login":"christianalfoni","id":3956929,"avatar_url":"https://avatars.githubusercontent.com/u/3956929?v=3","gravatar_id":"","url":"https://api.github.com/users/christianalfoni","html_url":"https://github.com/christianalfoni","followers_url":"https://api.github.com/users/christianalfoni/followers","following_url":"https://api.github.com/users/christianalfoni/following{/other_user}","gists_url":"https://api.github.com/users/christianalfoni/gists{/gist_id}","starred_url":"https://api.github.com/users/christianalfoni/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/christianalfoni/subscriptions","organizations_url":"https://api.github.com/users/christianalfoni/orgs","repos_url":"https://api.github.com/users/christianalfoni/repos","events_url":"https://api.github.com/users/christianalfoni/events{/privacy}","received_events_url":"https://api.github.com/users/christianalfoni/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":null,"milestone":null,"comments":6,"created_at":"2016-01-05T00:21:30Z","updated_at":"2016-01-05T13:36:43Z","closed_at":null,"body":"Okay, so I have been thinking :-) The concept for modules has been thought of as an addition to the current API of Cerebral, but I think we have to rethink a bit more to get this nicely fit in with Cerebral.\r\n\r\n### Remove top level state, signals and services\r\nFirst of all I suggest we remove defining state as part of the Model. `controller.signal` is neither normally used, it is \"lower level\". I also want to remove passing in services to the controller. That will give us:\r\n\r\n```js\r\nimport Controller from 'cerebral';\r\nimport Model from 'cerebral-baobab';\r\n\r\nconst model = Model(); // can pass options\r\nconst controller = Controller(model);\r\n```\r\n\r\nSo no services defined and no state defined. You just connect a model to the controller.\r\n\r\n### Everything is a module\r\nSo to even get going with Cerebral you have to define a module, but instead of passing the controller, we pass an actual module where you set state, register signals and services.\r\n\r\n```js\r\nimport Controller from 'cerebral';\r\nimport Model from 'cerebral-baobab';\r\nimport Home from './modules/Home';\r\n\r\nconst model = Model();\r\nconst controller = Controller(model);\r\n\r\ncontroller.registerModules({\r\n  home: Home({foo: 'bar'})\r\n});\r\n```\r\nAnd the module is defined with a function receiving the module. On the module you have access to the name, its options, signals, registering signals and services.\r\n\r\n```js\r\nexport default (options = {}) => {\r\n  return (module, controller) => {\r\n    module.name // \"home\"\r\n  \r\n    // Add initial namespaced state\r\n    module.state({\r\n      title: 'Home'\r\n    });\r\n\r\n    // Register a namespaced signal\r\n    module.signal('buttonClicked', chain);\r\n    module.signalSync('inputChanged', chain);\r\n\r\n    // Register a namespaced service\r\n    module.service('interval', () => {});\r\n  \r\n    // Return any meta data\r\n    return {};\r\n  }\r\n}\r\n```\r\n\r\nController is exposed for granular control and ability to trigger signals on other modules.\r\n\r\n### Router becomes a module\r\n\r\n```js\r\nimport Controller from 'cerebral';\r\nimport Model from 'cerebral-baobab';\r\nimport Router from 'cerebral-router';\r\nimport Home from './modules/Home';\r\n\r\nconst model = Model();\r\nconst controller = Controller(model);\r\n\r\nconst modules = controller.modules({\r\n  home: Home({foo: 'bar'}),\r\n  router: Router({\r\n    '/': 'home.mounted'\r\n  })\r\n});\r\n```\r\n\r\n### Signals are registered on controller, but can also use module\r\n```js\r\n// By module (using React as example)\r\nrender() {\r\n  const {home} = this.props.modules;\r\n  return <button onClick={() => home.signals.buttonClicked()}>Click</button>\r\n}\r\n\r\n// By signals (using snabbdom as example)\r\nComponent(({signals}) => (\r\n  <button on-click={() => signals.home.buttonClicked()}>Click</button>\r\n));\r\n```\r\nThe `module.signals` property just points to `controller.signals.moduleName`. That means when signals are overriden by router on controller, they will also be on the module.\r\n\r\n### Sharing name of module\r\nThere are three strategies to using the name of a module, where the module should only have one instance:\r\n\r\n1. Require a specific name for your module. That way you can hardcode any pointers to `signals.myModuleName.someSignal` etc. This has worked well for Angular, which is a huge community\r\n\r\n2. Use factories inside your module. This will bring in all dependencies though, not allowing you to \"pick and choose\" using `import Input from 'cerebral-module/Input';`\r\n\r\n3. Use a proxy between the module file itself and any other files. Where you set the module when it is being instantiated, allowing any other file part of the module package to access the module itself\r\n\r\nIn situations where the module can be instantiated multiple times, you have two strategies:\r\n\r\n1. Use factories inside your module\r\n\r\n2. Specifically name the module when instantiating related components/actions etc. \r\n\r\n### Types of modules\r\nJust some pseudo code to show how we can use it.\r\n\r\n#### cerebral-ajax\r\n```js\r\nimport superagent from 'superagent';\r\n\r\nexport default (options = {}) => {\r\n  return (module) => {\r\n    module.service('get', () => return new Promise(...));\r\n  };\r\n}\r\n\r\n// Used as\r\nfunction myAction({services}) {\r\n  services.myModule.get()\r\n}\r\n```\r\n\r\n#### cerebral-ui\r\n```js\r\nimport moduleProxy from './moduleProxy';\r\nexport default (options = {}) => {\r\n  return (module) => {\r\n    moduleProxy.set(module);\r\n    module.signalSync('inputChanged', chain);\r\n  }\r\n}\r\n\r\n// Used as\r\n<Input sync={['someModule', 'somePath']}/>\r\n```\r\n\r\n#### cerebral-firebase\r\n```js\r\nimport Firebase from 'firebase';\r\n\r\nexport default (options = {}) => {\r\n  return (module) => {\r\n  \r\n    module.service('sync', (path) => {\r\n      const ref = new Firebase(options.url + path);\r\n      ref.on('value', () => module.signals.valueUpdated)\r\n    });\r\n    module.signal('valueUpdated', chain({\r\n      path: options.path\r\n    }));\r\n  };\r\n}\r\n\r\n// Used as\r\nfunction myAction({services}) {\r\n  services.firebase.sync('somepath');\r\n}\r\n```\r\n\r\n### Summary\r\nSo the goal here is to force use of modules, because they are great! But we have to clean up the API and make it more approachable, which I think this suggestion does."},"comment":{"url":"https://api.github.com/repos/cerebral/cerebral/issues/comments/168999721","html_url":"https://github.com/cerebral/cerebral/issues/158#issuecomment-168999721","issue_url":"https://api.github.com/repos/cerebral/cerebral/issues/158","id":168999721,"user":{"login":"garth","id":4643,"avatar_url":"https://avatars.githubusercontent.com/u/4643?v=3","gravatar_id":"","url":"https://api.github.com/users/garth","html_url":"https://github.com/garth","followers_url":"https://api.github.com/users/garth/followers","following_url":"https://api.github.com/users/garth/following{/other_user}","gists_url":"https://api.github.com/users/garth/gists{/gist_id}","starred_url":"https://api.github.com/users/garth/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/garth/subscriptions","organizations_url":"https://api.github.com/users/garth/orgs","repos_url":"https://api.github.com/users/garth/repos","events_url":"https://api.github.com/users/garth/events{/privacy}","received_events_url":"https://api.github.com/users/garth/received_events","type":"User","site_admin":false},"created_at":"2016-01-05T13:36:43Z","updated_at":"2016-01-05T13:36:43Z","body":"Looks good to me. Would this be for cerebral 2.0 or will you hold off on 1.0 until this stuff is ready?"}}