idom by idibidiart

New Way To Interact With The DOM

/*! idi.bidi.dom 
* 
* v0.11
*
* New Way To Interact With The DOM 
*
* Copyright (c) Marc Fawzi 2012
* 
* http://javacrypt.wordpress.com
*
* Apache License 2.0  
* 
* partly based on NattyJS, a BSD licensed precursor by the same author 
*
*/
 


/* * idi.bidi.dom - Anti-Templating Framework For Javascript -- offers a radically * different way for interfating with the DOM. In abstract terms, it takes the DOM * and adds variables, variable memoization, encapsulation, multiple-inheritance and * type polymorphism (with the Node Prototype as the user defined type) In logical * terms, it offers a list-based API for creating, populating, and de-populating * predetermined DOM structures with the ability to link, directly access, populate * and de-populate other predetermined DOM structures at any depth within them, thus * giving us a simple and consistent alternative to the DOM's native API while allowing * us to reduce the amount of HTML as well as separate the HTML from the presentation * logic. * */
Example:
<!--************************************************* VIEW OBJECTS ***************************************************--> <div style='display: none'> <!-- Start of Node --> <div idom-node-id='teamInfo' class='idom$teamsClass' style='background-color: idom$bgColorTeams; idom$cssTransform'> <!-- Start of Node Prototype --> <div class='idom$teamsProtoClass' style='background-color: idom$instanceColorTeams'> <div style='padding: 5px;'>idom$caption</div> <!-- @idom userInfo --> </div> <!-- End of Node Prototype --> </div> <!-- End of Node --> <!-- Start of Node --> <div idom-node-id='userInfo' class='idom$usersClass' style='background-color: idom$bgColorUsers'> <!-- Start of Node Prototype --> <div class='idom$usersProtoClass' style='border: idom$borderStyle;'> <div onclick='idom$someHandler'>user: idom$username   id: idom$id</div> <div class='idom$usersExtendedClass' style='background-color: idom$bgColorUsersExtended; display: idom$displayStatus'> <div>last name: idom$lastName<br>work: idom$work</div> </div> </div> <!-- End of Node Prototype --> </div> <!-- End of Node --> </div>

Click on any user below to see the event handler for the target element in action





Randomly colored

idom$caption
user: idom$username   id: idom$id
last name: idom$lastName
work: idom$work



	
/******************************************************************************
 * 
 * README:
 * 
 * This version works in Gecko and Webkit, not tested on IE
 *
 * idi.bidi.dom - Anti-Templating Framework For Javascript -- offers a radically
 * different way for interacting with the DOM. In abstract terms, it takes the DOM 
 * and adds variables, variable memoization, encapsulation, multiple-inheritance and 
 * type polymorphism (with the Node Prototype as the user defined type) In logical 
 * terms, it offers a list-based API for creating, populating, and de-populating 
 * predetermined DOM structures with the ability to link, directly access, populate
 * and de-populate other predetermined DOM structures at any depth within them, thus 
 * giving us a simple and consistent alternative to the DOM's native API while allowing 
 * us to reduce the amount of HTML as well as separate the HTML from the presentation 
 * logic.
 *  
 * Why use it?
 * 
 * idi.bidi.dom reduces HTML on a page to a minimum and places a simple and consistent
 * JSON API between presentation logic and the DOM
 * 
 * How does it work?
 * 
 * idi.bidi.dom allows the DOM to be decomposed into Nodes each having a Node Prototype 
 * with data-injected variables in their markup. Multiple instances of each Node (i.e. 
 * data populated versions, each with its own persisted data) may be created, populated 
 * with data, and inserted into --and deleted from-- the Node (with the ability to target 
 * specific, previously inserted instances of the Node or all such instances). Each Node 
 * may embed any number of other Nodes, at any depth within its markup.
 * 
 * Additionally, idi.bidi.dom allows the cloning of each Node and all the populated
 * instances within it (including any populated Linked Nodes that were embedded into the 
 * host's Node Prototype) This means that we could re-use the same host Node to create 
 * any number of differently populated Clones with nested Nodes, which allows us to
 * composite and nest DOM views.  
 *
 * Unlike other data-driven DOM rendering frameworks, idi.bidi.dom does not attempt 
 * to take the place of Javascript itself nor does it add its own boilerplate; it 
 * simply gives Javascript more power by offering a simple and consistent interface
 * to the DOM. 
 *
 * Usage:
 *
 * format: document.querySelector('#someNode').idom$(data [, settings])
 * 
 * output: creates a new instance of the Node using 'data' (json) to populate the 
 * special variables in the Node, then append/prepend to (or replace) existing 
 * instance(s) of the Node
 *
 * forClone: id of the clone the data being populated is intended for. This is omitted 
 * when operating on cloned nodes
 *
 * data: {key: value, key: value, key: value, etc} 
 * where the key must match the variable name in the data minus the idom$ prefix
 *
 * settings: {mode: 'replace'|'append'|'prepend'|'attr', targetInstanceName: 
 * value, instanceName: value} // replace is default mode
 *
 * if there no populated instances of the Node then append/prepend/replace 
 * will create a new instance of the Node (so if a targetInstanceName is supplied 
 * in this case it will throw an error, so call .$isPopulated() first to be sure before 
 * invoking this method with targetInstanceName, unless you know the node is populated)
 * 
 * If 'mode' is set to 'attr' in settings then no other settings param is expected 
 * and only the node's style and class attributes are populated (all node instance 
 * attributes, including inline event handlers, as well as the node's style and class 
 * attributes, and all idom$ vars at the node instance level can be populated in any of 
 * the other modes during instance creation and replacement. Setting mode to attr will 
 * cause idom to populate only the style and class attributes in the node itself and does 
 * not touch any of the populated instances. This is useful if the instance hosts a
 * jQuery plugin or UI widget as it will prevent the jQuery event handlers from being 
 * detached during idom updates to the node's style and/or class attributes. 
 *
 * targetInstanceName: (1) idom-instance-name value for the instance of the Node to 
 * insert _at_ when in append and prepend modes. If null, append/prepend at last/first 
 * previously populated instance of the Node, or to start of the list if none 
 * were previously populated.
 *
 * targetInstanceName: (2) dom-instance-name value for instance(s) of the Node Protoype to 
 * replace when in replace mode. If null, replace all instances.
 *
 * instanceName: idom-instance-name value for instance of Prototype Node being populated. 
 *
 *********************************************************************************
 * 
 * Other idom methods are 
 *
 * .idom$dePopulate([settings]) which can delete certain populated instances of the Node 
 * Prototype or all populated instances 
 *
 * .idom$isPopulated() may be queried before specifying targetInstanceName  
 * to verify existence of populated instance(s) of Node Prototype (the targets) 
 * 
 * idom$clone may be used to clone an entire node (including any linked nodes) after it's 
 * been populated)
 * 
 * idom.eventHandler may be used to defined inline events, e.g. onclick=idom$someHandler and
 * setting someHandler to idom.eventHandler(event, this, someFunction) during instance 
 * creation which binds 'this' context to the element the event was triggered on and passes
 * the event, parent node id for the instance, and the instanceName  
 * 
 * idom.baseSelector maybe used on node and instance id's to strip out the clone and any link
 * references from instanceName which is 
 *
 **********************************************************************************
 *
 * About Events:
 * 
 * If a handler is defined on the node it will only have access to the node id. If it's 
 * defined on or in the node prototype it will have access to the instance id
 *
 * The context of 'this' inside the handler function is the element the event is 
 * defined on
 *
 * Event handlers that are NOT defined using inline event handlers (like onclick, 
 * onmouseover, etc) are not supported by idom at this time. 
 * 
 *********************************************************************************/

<script>
/*********************************************** PRESENTER *************************************************************/

/*************************** ALL SELECTOR DRIVEN LOGIC IS CONTAINED WITHIN THE PRESENTER ***********************************/

//cache DOM elements with idom-node-id (i.e. all node prototypes) 
idom.cache();	

(function() {
	
	for (m = 0; m < jsonData.teams.length; m++) {
		
	//populate the Node Prototype of the Linked Node before the Host Node 
	 
	for (n = 0; n < jsonData.teams[m].members.length; n++) {
	
		document.querySelector('[idom-node-id=userInfo]').idom$(
			{
				usersClass: 'users',
				usersProtoClass: 'usersProto',
				usersExtendedClass: 'usersExtended' ,
				borderStyle: 'none',
				displayStatus: 'none',
				bgColorUsers: rndColor(),
				bgColorUsersExtended: rndColor(),
				username: jsonData.teams[m].members[n].username,
				id: jsonData.teams[m].members[n].id,
				lastName: jsonData.teams[m].members[n].otherData.lastName,
				work: jsonData.teams[m].members[n].otherData.work,
				// we can have as many event handlers on or within a node but here we're just using the 
				// same one here, on user's first name and id 
				someHandler: 'idom.eventHandler(event, this, changeInstance)' 
				// event handler can be removed by simply setting someHandler to null
			}, 
			{
				forClone: 'clone' + Number(m + 1),
				// default mode is replace...
				mode: 'append',
				instanceName: 'user' + n
			})
		
	
	}
	
	// perform replace on the Host Node (linked node gets re/linked with each idom$() call)
	// and populate again each time before making a new clone of it

	document.querySelector('[idom-node-id=teamInfo]').idom$(
		{
			teamsClass: 'teams' + Number(m + 1),
			teamsProtoClass: 'teamsProto',
			bgColorTeams: rndColor(),
			caption: jsonData.teams[m].caption,
			instanceColorTeams: rndColor() 
		}, 
		{	
			forClone: 'clone' + Number(m + 1),
			instanceName: 'teamInfo' + Number(m + 1)
		})		

	// at this point the teamInfo div is populated with the idom$ data and the content of the linked userInfo node 
	// so we may now clone it and provide a uid for the clone (e.g. 'clone1')
	var clonedEl = document.querySelector('[idom-node-id=teamInfo]').idom$clone('clone' + Number(m + 1))

	//insert cloned node into body
	document.querySelector('#mainDiv').appendChild(clonedEl);
	
	// dePopulate() the linked node so we can restart with fresh node 
	document.querySelector('[idom-node-id=userInfo]').idom$dePopulate()
		
	// dePopulate() the host node (so we may start with no instances in the next iteration
	document.querySelector('[idom-node-id=teamInfo]').idom$dePopulate()
	
	}

}())

var toggle = {};

var Instance = {};

// the event handler is defined here on each instance of the linked node that's now inside 
// the cloned node, but it can also be defined anywhere within any node (and anywhere within any linked nodes within
// it) 

// About Events:
// If a handler is defined on the node it will only have access to the node id. If it's defined on or in the
// node prototype it will have access to the instance id

// The context of 'this' inside the handler is the element the event is defined on 

// event handlers that are not defined using inline event handlers (like onclick, onmouseover, etc) are not handled by 
// idom at this time. Finding and cloning all event handlers that are attached via different means, like jQuery, 
// will be supported in the future 

function changeInstance(event, nodeId, instanceName, cloneId) {
	
	// normally, a unique key is the combination of nodeId + instanceName + cloneId 
	// which assumes cloneId is globally unique (user enforced), node is unique within 
	// the clone (framework enforced) and instanceName is unique within the node (user enforced)
	// but in this case the nodeId contains the cloneId so we're safe...
	
	if (Instance[nodeId]) {
		
		document.querySelector('[idom-node-id="' + nodeId + '"]').idom$(
				{
					borderStyle: 'none',
					displayStatus: 'none'
				}, 
				{
					
					// when operating on a clone, we discard the forClone argument
					
				    // instanceName in settings is the same as the original populated node 
					// without link/clone references (they're added automatically)
					instanceName: idom.baseSelector(Instance[nodeId]),
					// target instance id (for instance to be replaced) 
					targetInstanceName: idom.baseSelector(Instance[nodeId])
				})
				
				toggle[Instance[nodeId]] = !toggle[Instance[nodeId]];	
				
	} 
	
	Instance[nodeId] = instanceName;
	
	toggle[Instance[nodeId]] = !toggle[Instance[nodeId]];
	
	
	if (toggle[Instance[nodeId]]) {
		
		// notice that we're only supplying the new/changed data (the delta not the whole set) for the idom$ variables 
		// in the node prototype. The idom$ variable values are persisted for each node (shared among original, linked 
		// and cloned versions of the populated node) with the key being a combination of the base node 
		// id, instance id (without the link/clone reference) and clone id 
		
		// directly accessing the linked node inside the cloned node (the linked can be nested anywhere in the
		// node prototype of the original node the clone is copied from)
		// when operating on a clone, we discard the forClone argument
		
		document.querySelector('[idom-node-id="' + nodeId + '"]').idom$(
				{
					borderStyle: borderSelector(instanceName),
					displayStatus: 'block'
				}, 
				{
					// when operating on a clone, we discard the forClone argument
					
				    // instanceName in settings is the same as the original populated node 
					// without link/clone references (they're added automatically)
					instanceName: idom.baseSelector(instanceName),
					// target instance id (for instance to be replaced) 
					targetInstanceName: idom.baseSelector(instanceName)
				})
	} else {
		
		document.querySelector('[idom-node-id="' + nodeId + '"]').idom$(
				{
					borderStyle: 'none',
					displayStatus: 'none'
				}, 
				{
					// when operating on a clone, we discard the forClone argument
					
				    // instanceName in settings is the same as the original populated node 
					// without link/clone references (they're added automatically)
					instanceName: idom.baseSelector(instanceName),
					// target instance id (for instance to be replaced) 
					targetInstanceName: idom.baseSelector(instanceName)
				})
	}
}

function button1() {
	
	// notice that we're only supplying the new/changed data (the delta not the whole set) for the idom$ variables 
	// in the node prototype. The idom$ variable values are persisted for each node (shared among original, linked 
	// and cloned versions of the populated node) with the key being a combination of the base node 
	// id, instance id (without the link/clone reference) and clone id 	
	
	// directly accessing the linked node inside the cloned node (the linked can be nested anywhere in the
	// node prototype of the original node the clone is copied from)
	// when operating on a clone, we discard the forClone argument
	
	document.querySelector('[idom-node-id="userInfo@cloned@clone2"]').idom$( 
		{
			someHandler: "" //null value removes the handler
		},
		{
			// when operating on a clone, we discard the forClone argument
					
		    // instanceName in settings is the same as the original populated node 
			// without link/clone references (they're added automatically)		
			instanceName: "user1",
			targetInstanceName: "user1"
		}
	
	)
}

function button2() {
	
	// notice that we're only supplying the new/changed data (the delta not the whole set) for the idom$ variables 
	// in the node prototype. The idom$ variable values are persisted for each node (shared among original, linked 
	// and cloned versions of the populated node) with the key being a combination of the base node 
	// id, instance id (without the link/clone reference) and clone id 
	
	// directly accessing the linked node inside the cloned node (the linked can be nested anywhere in the
	// node prototype of the original node the clone is copied from)
	// when operating on a clone, we discard the forClone argument
	
	document.querySelector('[idom-node-id="userInfo@cloned@clone2"]').idom$( 
		{
			someHandler: "idom.eventHandler(event, this, changeInstance)" 
		},
		{
			// when operating on a clone, we discard the forClone argument
					
			// instanceName in settings is the same as the original populated node 
			// without link/clone references (they're added automatically)instanceName: "user1",
			targetInstanceName: "user1"
		}
	
	)
}

function button3() {
	
	// notice that we're only supplying the new/changed data (the delta not the whole set) for the idom$ variables 
	// in the node prototype. The idom$ variable values are persisted for each node (shared among original, linked 
	// and cloned versions of the populated node) with the key being a combination of the base node 
	// id, instance id (without the link/clone reference) and clone id 
	
	// delete content of the cloned node (including content of the cloned linked node)
	document.querySelector('[idom-node-id="teamInfo@cloned@clone3"]').idom$dePopulate()

}

function button4() {
	
	// delete content of the original Linked Node (outside the clone) 
	if (document.querySelector('[idom-node-id="userInfo"]').idom$isPopulated()) 
		document.querySelector('[idom-node-id="userInfo"]').idom$dePopulate()
		
	//Populate the Node Prototype of the original Linked Node  
	
	// notice that we're only supplying the new/changed data (the delta not the whole set) for the idom$ variables 
	// in the node prototype. The idom$ variable values are persisted for each node (shared among original, linked 
	// and cloned versions of the populated node) with the key being a combination of the base node 
	// id, instance id (without the link/clone reference) and clone id 
	
	for (n = 0; n < jsonData.teams[2].members.length; n++) {
	
		document.querySelector('[idom-node-id="userInfo"]').idom$(
			{
				// data is remembered from the last time this instance (with this clone reference) was populated
				
			}, 
			{
				forClone: 'clone3',
				// default mode is replace...
				mode: 'append',
				instanceName: 'user' + n
			})
	
	}
	
	// Perform replace on the host Node inside the clone (linked node gets re/linked with each idom$() call)
	
	// notice that we're only supplying the new/changed data (the delta not the whole set) for the idom$ variables 
	// in the node prototype. The idom$ variable values are persisted for each node (shared among original, linked 
	// and cloned versions of the populated node) with the key being a combination of the base node 
	// id, instance id (without the link/clone reference) and clone id 

	// Since we're doing a replace, no need to delete content with every invocation 
	// When operating on a clone, we discard the forClone argument
	
	document.querySelector('[idom-node-id="teamInfo@cloned@clone3"]').idom$(
		{
			// data is remembered from the last time this instance (with this clone reference) was populated
				
		}, 
		{   
			// when operating on a clone, we discard the forClone argument
					
		    // instanceName in settings is the same as the original populated node 
		    // without link/clone references (they're added automatically)
			instanceName: 'teamInfo3'
		})	
}

function button5() {
	
		document.querySelector('[idom-node-id="teamInfo@cloned@clone4"]').idom$(
			{
			 
			 // all other data is remembered for this instance and this clone
			  cssTransform: '-webkit-transform: scale(0.5) ' + 
			  				'skew(-20deg, 20deg); ' + 
			  				'-moz-transform: scale(0.5) ' +
			  				'skew(-20deg, 20deg) ; '
				
			}, 
			{
				
			// when operating on a clone, we discard the forClone argument
			// no instanceName needed since in this mode we're only populating the node's class/stle attribute
				mode: 'attr'
			}
			
		)
}

function button6() {
	
	document.querySelector('[idom-node-id="teamInfo@cloned@clone4"]').idom$(
			{
			 
			 // all other data is remembered for this instance and this clone
			  cssTransform: '-webkit-transform: scale(1) ' + 
			  				'skew(0deg, 0deg); ' + 
			  				'-moz-transform: scale(1) ' +
			  				'skew(0deg, 0deg); '
				
			}, 
			{
				// when operating on a clone, we discard the forClone argument
				// no instanceName needed since in this mode we're only populating the node's class/stle attribute
				mode: 'attr'
			}
			
		)
}

function borderSelector(instanceName) {
	
	var item = idom.baseSelector(instanceName)
	
	switch(item) {
		
		case 'user0':		
			return '1px dashed white';
		
		case 'user1':
			return '1px dotted white'
			
		case 'user2':
			return '1px solid white'
			
		default: 
			return '1px solid white'
	}	
}

function rndColor() {
    
    return (function(h){return '#000000'.substr(0,7-h.length)+h}) ((~~(Math.random()*(1<<24))).toString(16));
}

</script>