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(); | |
| } |