Prototype MVC (or MVI) client-side with RxJS and D3 to show use with various component libraries.
At present, RxJS Subjects are used like channels and not 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 like channels and not as part of an inheritance hierarchy.
A Pen by Ryan Riley on CodePen.
// channels | |
var modelChanged = new Rx.Subject(); | |
var buttonClicked = new Rx.Subject(); | |
var addNewItem = new Rx.Subject(); | |
var clearAll = new Rx.Subject(); | |
// model proxy | |
function Model() { | |
var data = []; | |
var add = | |
addNewItem.subscribe(function (e) { | |
// Push to server-side resource | |
// Receive response from server-side resource | |
var newItem = 'new item'; | |
data.push(newItem); | |
// Publish result | |
modelChanged.onNext(data); | |
}); | |
var clear = clearAll.subscribe(function () { | |
data = []; | |
modelChanged.onNext(data); | |
}); | |
return { | |
dispose: function () { | |
add.dispose(); | |
clear.dispose(); | |
} | |
}; | |
} | |
// Controller maps UI events to model events | |
function Controller() { | |
var subscription = // return the disposable | |
buttonClicked.subscribe(function (toggle) { | |
switch (toggle) { | |
case 'Add': | |
addNewItem.onNext(); | |
break; | |
case 'Clear': | |
clearAll.onNext(); | |
break; | |
} | |
}); | |
return subscription; | |
} | |
// view | |
function View(el) { | |
function init(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 | |
button.addEventListener('click', function (e) { buttonClicked.onNext('Add'); }, false); | |
reset.addEventListener('click', function (e) { buttonClicked.onNext('Clear'); }, false); | |
var ul = document.createElement('ul'); | |
frag.appendChild(header); | |
frag.appendChild(button); | |
frag.appendChild(reset); | |
frag.appendChild(ul); | |
el.appendChild(frag); | |
} | |
function render(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); | |
}); | |
} | |
init(el); | |
var subscription = modelChanged.subscribe(render.bind(el)); | |
// Return the disposable | |
return { | |
dispose: function () { | |
subscription.dispose(); | |
// TODO: unbind any DOM event listeners and destroy DOM nodes | |
} | |
}; | |
} | |
// Create model proxy | |
var model = Model(); | |
// Create intent | |
var intent = Controller(); | |
// Create view | |
var view = View(document.getElementById('content')); | |
// TODO: add D3 chart showing increase over time. | |
function dispose() { | |
model.dispose(); | |
intent.dispose(); | |
view.dispose(); | |
} |