Click on any user below to see the event handler for the target element in action
Randomly colored
/*! 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
/****************************************************************************** * * 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>