Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save panesofglass/2e301b6977caeaadbeb0 to your computer and use it in GitHub Desktop.
Save panesofglass/2e301b6977caeaadbeb0 to your computer and use it in GitHub Desktop.
MVC with RxJS and D3 (inheritance)
<div id="content"></div>

MVC with RxJS and D3 (inheritance)

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.

License.

// 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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment