Prototype MVC (or MVI) client-side with RxJS and D3 to show use with various component libraries.
At present, RxJS Subjects are used as part of an inheritance hierarchy.
A Pen by Ryan Riley on CodePen.
| <div id="content"></div> |
Prototype MVC (or MVI) client-side with RxJS and D3 to show use with various component libraries.
At present, RxJS Subjects are used as part of an inheritance hierarchy.
A Pen by Ryan Riley on CodePen.
| // Note on "channels": | |
| // If sepearate, they can easily be referenced, but then they are also disconnected from their "source" | |
| // If embedded, then they must be exposed through the objects and, as shown below, will likely | |
| // contain merged streams rather than distinct events. That could cause problems. | |
| // model proxy | |
| var Model = (function (__super__) { | |
| function Model(data) { | |
| __super__.call(this); | |
| this.data = data || []; | |
| } | |
| Model.prototype = Object.create(__super__.prototype); | |
| Model.prototype.constructor = Model; | |
| Model.prototype.observe = function (obs) { | |
| var self = this; | |
| obs.map(function (e) { | |
| switch (e) { | |
| case 'Add': | |
| // Push to server-side resource | |
| // Receive response from server-side resource | |
| var newItem = 'new item'; | |
| self.data.push(newItem); | |
| break; | |
| case 'Clear': | |
| delete self.data; | |
| self.data = []; | |
| break; | |
| } | |
| // Publish current state | |
| return self.data; | |
| }).subscribe(this); | |
| }; | |
| return Model; | |
| })(Rx.Subject); | |
| // Controller maps UI events to model events | |
| var Controller = (function (__super__) { | |
| function Controller() { | |
| __super__.call(this); | |
| } | |
| Controller.prototype = Object.create(__super__.prototype); | |
| Controller.prototype.constructor = Controller; | |
| Controller.prototype.observe = function (obs) { | |
| obs.map(function (e) { | |
| return e.target.textContent; | |
| }).subscribe(this); | |
| }; | |
| return Controller; | |
| })(Rx.Subject); | |
| // view | |
| var View = (function (__super__) { | |
| function View(opts) { | |
| __super__.call(this); | |
| this.opts = opts; | |
| } | |
| View.prototype = Object.create(__super__.prototype); | |
| View.prototype.constructor = View; | |
| View.prototype.observe = function (obs) { | |
| var el = this.opts.el; | |
| this.opts.init.call(this, el); | |
| return obs.subscribe(this.opts.render.bind(el)); | |
| }; | |
| return View; | |
| })(Rx.Subject); | |
| // Create model proxy | |
| var model = new Model(); | |
| // Create intent | |
| var intent = new Controller(); | |
| // Create view | |
| var view = new View({ | |
| el: document.getElementById('content'), | |
| init: function (el) { | |
| var frag = document.createDocumentFragment(); | |
| var header = document.createElement('h1'); | |
| header.textContent = 'Demo'; | |
| var button = document.createElement('button'); | |
| button.textContent = 'Add'; | |
| var reset = document.createElement('button'); | |
| reset.textContent = 'Clear'; | |
| // Goal: delegate event listeners to component level | |
| console.log(this); | |
| Rx.Observable.fromEvent(button, 'click') | |
| .merge(Rx.Observable.fromEvent(reset, 'click')) | |
| .subscribe(this); | |
| var ul = document.createElement('ul'); | |
| frag.appendChild(header); | |
| frag.appendChild(button); | |
| frag.appendChild(reset); | |
| frag.appendChild(ul); | |
| el.appendChild(frag); | |
| }, | |
| render: function (data) { | |
| // NOTE: this can be greatly optimized | |
| var ul = this.getElementsByTagName('ul')[0], | |
| child; | |
| // Clear | |
| while (child = ul.firstChild) { | |
| ul.removeChild(child); | |
| } | |
| // Redraw | |
| data.map(function (item) { | |
| var li = document.createElement('li'); | |
| li.textContent = item; | |
| return li; | |
| }) | |
| .forEach(function (li) { | |
| ul.appendChild(li); | |
| }); | |
| } | |
| }); | |
| // I added an `observe` member that flips the dependency chain. | |
| // This is roughly equivalent to a flipped subscribe operator, though | |
| // it allows me to call some setup functions on the view. | |
| // At present, the other objects don't require setup, but that | |
| // could easily change given a more complicated domain. | |
| view.observe(model); | |
| intent.observe(view); | |
| model.observe(intent); | |
| // TODO: add D3 chart showing increase over time. |