My JavaScript book is out! Don't miss the opportunity to upgrade your beginner or average dev skills.
Showing posts with label beta. Show all posts
Showing posts with label beta. Show all posts

Sunday, August 02, 2009

PyramiDOM - from Alpha to Beta

As promised, I have commented and improved my last experiment: PyramiDOM, (IE version) now with some new feature:

  • One click from bookmark to add PyramiDOM, another one to remove it

  • In a single session, colors are preserved even if PyramiDOM is added and removed more than once

  • Good browsers with W3C DOM events support and/or a console, will experience a better PyramiDOM thanks to automatic update feature and related dom nodes logged in the console

  • last part of the bookmark is the size of each brick. Right now it is 2 but you can easily change it to 4, 6, 8, whatever multiple of 2 you prefer



The PyramiDOM Source Code


It is really simple and the main logic is behind the Brick constructor and every brick instance.

(function(size){

var // shortcut to attach/detach events
add = document.addEventListener ?
function(node, name, callback){
node.addEventListener(name, callback, false);
}:
function(node, name, callback){
node.attachEvent("on" + name, callback);
}
,
del = document.removeEventListener ?
function(node, name, callback){
node.removeEventListener(name, callback, false);
}:
function(node, name, callback){
node.detachEvent("on" + name, callback);
}
,
// scope shortcut for document.body
body = document.body,
// scope shortcut for parseInt
i = parseInt,
// render timeout varible
t = 0,
// events filter (enable/disable)
exec = true,
// internal shortcuts
node, style
;

// main function, just a loop over each node to create a new Brick stack
function PyramiDOM(/* Brick */ parent, /* HTMLElement */ dom){
for(var child, node, childNodes = dom.childNodes, i = 0, length = childNodes.length; i < length; ++i){
if((node = childNodes[i]).nodeType === 1)
parent.add(PyramiDOM(new Brick(node, parent), node));
};
return parent;
};

// via brick info create a link (for alt/title easy tooltip)
// and append into the dom node
PyramiDOM.render = function render(/* Brick */ parent, /* HTMLElement */ dom){
var a = document.createElement("a"),
style = a.style,
alt = parent.dom.nodeName
;
if(parent.dom.id)
alt += " #" + parent.dom.id;
if(parent.dom.className)
alt += " " + parent.dom.className;

// the id contains this indexOf related brick in the Brick.register stack
a.id = parent.id + "-pyramidom";
// using parseInt will guarantee a quick integer transformation

a.title = a.alt = alt;
style.position = "absolute";
style.margin = style.padding = 0;
style.top = parent.top + "px";
style.left = parent.left + "px";
style.height = parent.height + "px";
style.width = parent.width + "px";
style.background = parent.color;
dom.appendChild(a);
// recursion: for each brick children render a link
for(var children = parent.children, i = 0, length = children.length; i < length; ++i)
render(children[i], dom);
};

// check if the node was already there
// in order to be able to remove PyramiDOM with another click
// in the bookmark menu
PyramiDOM.node = document.getElementById("pyramidom");

// if present (re-clicked) ...
if(node = PyramiDOM.node){
// ... remove node
del(document, "DOMNodeInserted", document.PyramiDOM);
del(document, "DOMNodeRemoved", document.PyramiDOM);
body.removeChild(node);
node.innerHTML = "";
PyramiDOM.node = node = null;
return;
} else {
// create the node
node = PyramiDOM.node = body.appendChild(document.createElement("div"));
style = node.style;
node.id = "pyramidom";
style.top = "0px";
style.left = "0px";
style.position = "absolute";
style.zIndex = 2147483647;
// attach events
add(node, "mousedown", function(e){
var target = (e || event).target;
if(target.nodeName === "A"){
// try to show the related node in the console
try{console.log(Brick.register[i(target.id)].dom)}catch(e){};
}
});
add(node, "mouseover", function(e){
var target = (e || event).target;
if(target.nodeName === "A"){
target = Brick.register[i(target.id)];
if(target.dom.style){
target.background = target.dom.style.background || "";
target.dom.style.background = "yellow";
}
}
});
add(node, "mouseout", function(e){
var target = (e || event).target;
if(target.nodeName === "A"){
target = Brick.register[i(target.id)];
if(target.dom.style)
target.dom.style.background = target.background;
}
});
add(document, "DOMNodeInserted", document.PyramiDOM = function(){
// if modification is NOT PyramiDOM itself
if(exec){
// avoid greedy computations
if(0 < t)
clearTimeout(t);
// be sure last timeout will represent the current DOM
t = setTimeout(function(){
// disable insert/remove events
exec = false;
body.removeChild(node);
PyramiDOM.render(PyramiDOM(new Brick(document), document), node);
body.appendChild(node);
// enable insert/remove events
exec = true;
}, 250);
};
});
add(document, "DOMNodeRemoved", document.PyramiDOM);
};

// each brick is an instance with some computated property
function Brick(dom, parent){
this.dom = dom;
// register this instance (reference via link id)
this.id = Brick.register.push(this) - 1;
// set color via nodeName
this.color = Brick.color(dom.nodeName);
// level means how nested is related dom element
this.level = (this.parent = parent) ? parent.level + 1 : 0;
// calculate the left position of this brick
this.left = this.level * this.width;
// set an empty children stack
this.children = [];
// if this is not the root
if(this.level){
// if the parent has other children
if(parent.children.length){
// get latest child position via top, height, plus 1 pixel as space
var child = parent.children[parent.children.length - 1];
this.top = child.top + child.height + 1;
} else
// this brick is the first for this level
// let's put it a bit more down than its parent
// to obtain the Pyramid effect
this.top = parent.top + (this.width / 2) << 0;
} else
// root, top is 0
this.top = 0;
};

// generate random unique colors based on a generic string, nodeName in this case
// the reason it is cached is to avoid epileptic effect each time the pyramid
// is regenerated (new in this beta version)
Brick.color = document.PyramiDOMColor || (document.PyramiDOMColor = (function(){
function color(hex){return new Array(7 - hex.length).join("0") + hex};
function random(nodeName){
// quickly avoid duplicated
do{var c = (Math.random()*0x1000000)<<0}while(cache[c]);
cache[c] = true;
// cache result to avoid this operation for same nodeName
return hash[nodeName] = "#" + color(c.toString(16));
};
var cache = {}, hash = {};
return function(nodeName){return hash[nodeName] || random(nodeName)};
})());

// every brick object is registered in a global public stack
Brick.register = [];

// every brick has a default size
Brick.prototype.width =
Brick.prototype.height = size;

// every brick has a children stack
Brick.prototype.add = function(child){
this.children.push(child);
this.height += child.height + 1;
};

// create PyramiDOM
document.PyramiDOM();

// size of each brick (2, 4, 6 up to whatever % 2 === 0)
})(2);


Have fun with PyramiDOM :)