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. |