Skip to content

Instantly share code, notes, and snippets.

@dhinesh03
Last active August 29, 2015 14:20
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save dhinesh03/4408820e78d14738f979 to your computer and use it in GitHub Desktop.
Save dhinesh03/4408820e78d14738f979 to your computer and use it in GitHub Desktop.
multi-tenancy workaround for mithril.js
var z = (function (){
//helpers
var cache = {};
var target;
var type = {}.toString;
var noop = function() {};
var retainer = { subtree : 'retain' };
var treeNode = (function() {
treeNode=function(parent,componentName){
this.cName=componentName;
this.parent=parent;
this.children={};
return this;
};
treeNode.prototype.addChild=function(componentName){
var newChild = new treeNode(this,componentName);
this.children[componentName] = newChild;
return newChild;
};
function _traverseUp (context,existingPath) {
var node;
var path = existingPath.slice();
while (context.parent) {
if(!(path.indexOf(context.cName) > -1)) {
path.push(context.cName);
}
context = context.parent;
}
return path;
}
function _traverseDown (context,existingPath) {
var path =existingPath.slice();
(function walkDown (node) {
var newContext;
if(!(path.indexOf(node.cName) > -1)) {
path.push(node.cName);
}
var obj = node.children
for (var o in obj) {
if(obj.hasOwnProperty(o)){
newContext = obj[o];
walkDown(newContext);
}
}
})(context);
return path
}
treeNode.prototype.getPath = function (existingPath) {
var path;
if (type.call(existingPath) === '[object Array]') {
path=existingPath.slice();
} else {
path = [];
}
path = _traverseDown(this,path);
return _traverseUp(this,path);
};
return treeNode
} )();
var tenant=function(newCompTree,componentName, component,args) {
var controller = function() {
this.compTree=newCompTree;
var ctrl = (component.controller || noop).apply(this, args) || this
var originalOnunload = ctrl.onunload;
ctrl.onunload = function() {
//console.log(componentName+" cache removed")
if(cache[componentName]) delete cache[componentName]
if(originalOnunload) originalOnunload.apply(this, arguments)
}
return ctrl
}
var view = function(ctrl) {
if (arguments.length > 1) args = args.concat([].slice.call(arguments, 1))
if(target === undefined || (type.call(target) === '[object Array]' && target.indexOf(componentName) > -1) || target === componentName || target === "all")
return component.view.apply(component, args.length ? [ctrl].concat(args) : [ctrl])
else
return retainer
}
return {controller: controller, view: view}
}
return {
/* targetComponents may be an array of compTree elements / a compTree / all
(all/undefined) will trigger the full redraw
*/
withTarget:function(targetComponents, callback) {
return function(e) {
//recursively construct the path
if(type.call(targetComponents) === '[object Array]') {
target = [];
for (var i = 0; i < targetComponents.length; i++) {
if(type.call(targetComponents[i]) === '[object Object]') {
target=targetComponents[i].getPath(target);
}
}
}
else if(type.call(targetComponents) === '[object Object]') {
target = targetComponents.getPath();
} else {
target = targetComponents
}
callback.call(this, e)
}
},
/* z.component takes 3 mandatory args and after any no of optional args same as m.component
1st arg => caller ctrl ,ie parent controller of the component
2nd arg => must be unique string that will refer the component instance
3rd arg => component
From 4th arg refer the m.component doc
*/
component:function(parentCtrl,componentName,component){
parentCtrl.compTree = parentCtrl.compTree || new treeNode(undefined,"root");
var newCompTree = parentCtrl.compTree.addChild(componentName);
var args=[];
if (arguments.length > 3) args = args.concat([].slice.call(arguments, 3))
var mcomponent = m.component.apply(undefined,[tenant(newCompTree,componentName,component,args)].concat(args));
mcomponent.view.$original = component.view;
return mcomponent;
},
/* Accept only string (i.e., component name) / array of strings (i.e., array of component names) / all / undefined */
setTarget:function(targets){
target = targets;
},
bindOnce:function(componentName,viewName,view) {
if(cache[componentName] === undefined) {
cache[componentName] = {};
}
if (cache[componentName][viewName] === undefined) {
cache[componentName][viewName] = true
return view()
}
else return retainer
}
}
})();
@dhinesh03
Copy link
Author

Here's an example for the multi-tenancy gist

var model = [{key:1,name:"component1",value:""}, {key:2,name:"component2",value:""}, {key:3,name:"component3",value:""}]
var App = {
    controller: function() {
            var ctrl= this;
            this.data= model;
            this.addOneMoreElement =function() {
                var newKey= model.length+1
                model.push({key:newKey,name:"component"+newKey,value:""})
                /* z.setTarget will accept only component instance name/all/undefined not component treenode */
                z.setTarget("component"+newKey)
            };
            this.removeElement=function() {
                z.setTarget("component"+(model.length))
                model.pop()                
            };
    },
    view: function(ctrl) {
        return m(".app", [
            m("button[type=button]", {onclick: ctrl.addOneMoreElement}, "Add One"),
            m("button[type=button]", {onclick: ctrl.removeElement}, "Remove One"),
            ctrl.data.map(function(item) {
                /* z.component takes 3 mandatory args and after any no of optional args same as m.component
                1st arg => caller ctrl ,ie parent controller of the component
                2nd arg => must be unique string that will refer the component instance
                3rd arg => component
                From 4th arg refer the m.component doc
                */
                return z.component(ctrl,item.name,MyComponent, item)
            })
        ])
    }
}

var MyComponent = {
    controller: function(args) {
        var ctrl = this;        
        ctrl.count=0;
        /* ctrl.compTree is a treenode that represent the current component , 
        same as ctrl.compTree.parent refer the parent component
        */
        ctrl.onClick= z.withTarget([ctrl.compTree,ctrl.compTree.parent.children["component3"]],function(ev) {
            ctrl.count++;
            args.value=this.children[0].value;
        }) 
        ctrl.onunload=function(){
            console.log(args.name +" has been unloded")
        }
        return ctrl;
    },
    view: function(ctrl,args) {
        return m("div",[" No of click(s):"+ctrl.count,
                m("h2", 'Option'+args.key),
                m("div.radio-container", [          
                     m('div',{className:'.radioYes',onclick:ctrl.onClick},[
                            m("input", {type:"radio", name:args.name,checked: args.value === "Y" ? true : false, value:"Y"})
                            ,"Yes"]
                        ),
                     m('div',{className:'.radioNo',onclick:ctrl.onClick},[
                            m("input", {type:"radio", name:args.name,checked: args.value === "N" ? true : false, value:"N"})
                            ,"No"]
                        )
                ]),
                z.component(ctrl,"textBox"+args.key,MyNestedComponent,{placeholder:"Type Here .."}),
               z.bindOnce(args.name,"lineBreak",function () {return m("div","----------------------------")})
            ]);
    }
}

var MyNestedComponent ={
    view: function(ctrl,args) {
        return m("input[type=text]",{placeholder:args.placeholder})
    }
}
z.setTarget("all");
m.mount(document.body, App)
//@ sourceURL=dynamicScript.js

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment