/*! idi.bidi.dom
*
*
* idom v0.17
*
* Key-value JSON API for template-based HTML view compositing
*
*
* ****************************************************************************
*
* Copyright (c) Marc Fawzi, Deep Thought, Inc. 2012
*
* http://javacrypt.wordpress.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*
* *****************************************************************************
*
* Some portions are derived from NattyJS, an early precursor by the same author
* "Copyright (c) Marc Fawzi, NiNu, Inc. 2011-2012" published under BSD License.
*
*/
/*
* idi.bidi.dom - Anti-Templating Framework For Javascript -- offers a new and
* different way for interacting with the DOM. In abstract terms, it takes the DOM
* and adds variables, scope, variable memoization, multiple-inheritance and
* type polymorphism (with the Node Prototype as the user defined type) In logical
* terms, it offers a flat JSON API for creating, populating, and de-populating
* predetermined DOM structures with the ability to link, directly access, populate
* and de-populate other such DOM structures at any depth within them. It gives us
* a simpler alternative to the browser's hierarchical DOM manipulation 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' idom-class='idom$teamsClass' idom-style='background-color: idom$bgColorTeams; idom$cssTransform'>
<!-- Start of Node Prototype -->
<div idom-class='idom$teamsProtoClass' idom-style='background-color: idom$instanceColorTeams;'>
<div idom-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' idom-class='idom$usersClass' idom-style='background-color: idom$bgColorUsers'>
<!-- Start of Node Prototype -->
<div idom-class='idom$usersProtoClass' idom-style='border: idom$borderStyle;'>
<div idom-onclick='idom$someHandler'>user: idom$username id: idom$id</div>
<div idom-class='idom$usersExtendedClass' idom-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
<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++) {
_$('[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: 'after',
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
_$('[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 = _$('[idom-node-id=teamInfo]').idom$clone('clone' + Number(m + 1))
//insert cloned node into body
_$('#mainDiv').appendChild(clonedEl);
// dePopulate() the linked node so we can restart with fresh node
_$('[idom-node-id=userInfo]').idom$dePopulate()
// dePopulate() the host node (so we may start with no instances in the next iteration
_$('[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]) {
_$('[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)
// since we're in the default 'replace' mode and there's a target instance
// name given we can use the same name for the new instance (unless we
// explicitly specify a new one
// 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
_$('[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)
// since we're in the default 'replace' mode and there's a target instance
// name given we can use the same name for the new instance (unless we
// explicitly specify a new one
// target instance id (for instance to be replaced)
targetInstanceName: idom.baseSelector(instanceName)
})
} else {
_$('[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)
// since we're in the default 'replace' mode and there's a target instance
// name given we can use the same name for the new instance (unless we
// explicitly specify a new one
// 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
_$('[idom-node-id="userInfo@link@teamInfo2@copy@0@clone@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)
// since we're in the default 'replace' mode and there's a target instance
// name given we can use the same name for the new instance (unless we
// explicitly specify a new one
targetInstanceName: "user1"
// we could also set 'mode' as 'proto' and only supply the targetInstanceName
// whose attributes (including inline-event handlers) we want changed
}
)
}
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
_$('[idom-node-id="userInfo@link@teamInfo2@copy@0@clone@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)
// since we're in the default 'replace' mode and there's a target instance
// name given we can use the same name for the new instance (unless we
// explicitly specify a new one
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)
_$('[idom-node-id="teamInfo@clone@clone3"]').idom$dePopulate()
}
function button4() {
// delete content of the original Linked Node (outside the clone)
if (_$('[idom-node-id="userInfo"]').idom$isPopulated())
_$('[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++) {
_$('[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: 'after',
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
_$('[idom-node-id="teamInfo@clone@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() {
_$('[idom-node-id="teamInfo@clone@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/style attribute
mode: 'node'
}
)
}
function button6() {
_$('[idom-node-id="teamInfo@clone@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: 'node'
}
)
}
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>
/******************************************************************************
*
* README:
*
* This version should work in all recent versions of the major browsers
*
* idi.bidi.dom - Anti-Templating Framework For Javascript -- offers a new and
* different way for interacting with the DOM. In abstract terms, it takes the DOM
* and adds variables, scope, variable memoization, multiple-inheritance and
* type polymorphism (with the Node Prototype as the user defined type) In logical
* terms, it offers a flat JSON API for creating, populating, and de-populating
* predetermined DOM structures with the ability to link, directly access, populate
* and de-populate other such DOM structures at any depth within them. It gives us
* a simpler alternative to the browser's hierarchical DOM manipulation 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
*
* What does it do?
*
* 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 --or 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 Node Prototype's markup.
*
* Additionally, idi.bidi.dom allows us to clone populated Nodes to create copies of the
* Node (and any Linked Nodes within it) which can be re-populated and de-populated in
* direct fashion.
*
* 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 after/before 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'|'after'|'before'|'node'|'proto', targetInstanceName:
* value, instanceName: value} ... replace is default mode
*
* if there are no populated instances of the Node then after/before/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 'node' in settings then no other settings param is expected
* and only the node's attributes are populated. Setting mode to 'node' will cause idom
* to populate only the attributes in the node itself and does not create any populated
* instances of its node prototype. idom also offers a way to populate just the attributes
* of each node instance by setting mode to 'proto' and specifying targetInstanceName or none
* for all node instances in a given node. Both 'proto' and 'node' modes may be useful when
* working with the node attributes or the node instance attributes where a jQuery plugin/
* widget is instantiated (after caching) on a given node instance.
*
* targetInstanceName: (1) idom-instance-name value for the instance of the Node to insert at
* in after and before modes. If null, insert after/before the last/first previously populated
* instance of the Node, or as the first instance 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 the
* existence of populated instance(s) of the 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 handled by idom at this time.
*
*
*********************************************************************************/