DOMatic is a lightweight javascript client-side MVC framework that uses JsonML syntax and virtual DOM diffing. Its goal is to create fast UI components with minimal hard syntax. Source is available for download at https://github.com/danristea/DOMatic/tree/master
Components
A component is essentially an object with a controller/view pair of function properties.
The controller function should return an object, and the view function should return JsonML.
// simple component var app = { controller: function(options) { return {time: options.time} }, view: function(ctrl) { return ["div", "component mounted at : " + ctrl.time] } }
Mounting the base component can be achieved calling UI.mount like so:
UI.mount(app, document.body, initobj)
where “app” is the component object, “document.body” is the DOM element where the view is to be mounted, and “initobj” is an optional object passed during instantiation.
Nested components
Components can be nested by including another component object into the view, with the controller referencing an already instantiated controller.
// child component var child = { controller: function () { return this; }, view: function(ctrl) { return ["div", " child component "] } } // parent component var parent = { controller: function () { this.child = new child.controller() //instantiation of the child controller return this }, view: function(ctrl) { return ["div", {controller: ctrl.child, view: child.view}, " parent component "] //using ctrl.child reference to the instantiated child controller } }
Using unique references of previously instantiated controllers allows for component identification in the event of identical components (widgets) and allows for sorting with no unnecessary hard syntax.
Rendering
Rendering is automatically performed during the initial mount. Nested components are also automatically rendered the first time, as long as their corresponding cache does not yet exist.
Subsequent rendering can be achieved by passing the references to the controllers coupled with the corresponding views that need updating.
This provides full control over rendering as you decide when and which components should update their corresponding views.
Examples:
UI.render() //renders all components
UI.render(obj) //renders the component associated with the instantiated controller obj
UI.render([obj1, obj2, obj3]) //renders the 3 objects of the passed array
Full example
var data = ["project A", "project B", "project C"]; var storage = { data: "empty" } var table = {} table.controller = function() { this.thead = "thead" this.tbody = "tbody" this.getData = function(fn) { setTimeout(function() { storage.data = data fn() }, 2000) } return this } table.view = function(ctrl) { var tbody = function() { if (typeof storage.data === "string") return ["tr", ["td", storage.data]] else return ["tr", storage.data.map(function(val) { return ["td", val] })] } return [ ["thead", ["tr", ["th", "Priority 1"], [["th", "Priority 2"], ["th", "Priority 3"]] ]], ["tbody", tbody()] ] } var app = {} app.controller = function(arg) { var _this = this this.table = new table.controller() this.status = arg.status this.date = arg.date this.loadedAt = "N/A" this.swap = function() { if (Array.isArray(storage.data)) { var last = storage.data.pop() storage.data.push(storage.data.shift()) storage.data.unshift(last) UI.redraw(_this.table) } else alert("Nothing to swap, load data first.") } this.load = function() { _this.status = "loading... " _this.table.getData(function() { _this.status = "loaded" _this.loadedAt = new Date().toString() UI.redraw([_this, _this.table]) //UI.redraw() }) UI.redraw(_this) } return this } app.view = function(ctrl) { return [ ["h1", "Main App"], ["div", "Started At : ", ctrl.date], ["hr"], ["div", ["button", {"onclick": ctrl.load}, "Load"], ["div", "Status : ", ctrl.status], ["div", "Last loaded : ", ctrl.loadedAt], ["button", {"onclick": ctrl.swap}, "Swap"], ["table", {controller: ctrl.table, view: table.view }] ] ] } var initobj = { status: "ready", date: new Date().toString() } window.onload = function () { UI.mount(app, document.body, initobj) }