///@INFO: BASE
/**
* CompositePattern implements the Composite Pattern, which allows to one class to contain instances of its own class
* creating a tree-like structure.
* @class CompositePattern
* @constructor
*/
function CompositePattern()
{
//WARNING! do not add anything here, it will never be called
}
CompositePattern.prototype.compositeCtor = function()
{
//this method is not called by SceneNode
this._parentNode = null;
this._children = null;
this._in_tree = null;
}
/* use .scene instead
CompositePattern.prototype.getScene = function()
{
this._in_tree;
}
*/
/**
* Adds one child to this instance
* @method addChild
* @param {*} child
* @param {number} index [optional] in which position you want to insert it, if not specified it goes to the last position
* @param {*} options [optional] data to be passed when adding it, used for special cases when moving nodes around
**/
CompositePattern.prototype.addChild = function(node, index, options)
{
if( !node )
throw("cannot addChild of null");
if( node.constructor !== this.constructor )
throw("added child must be of the same type");
//be careful with weird recursions...
var aux = this;
while( aux._parentNode )
{
if(aux == node)
{
console.error("addChild: Cannot insert a node as his own child");
return false;
}
aux = aux._parentNode;
}
//has a parent
if(node._parentNode)
node._parentNode.removeChild(node, options);
/*
var moved = false;
if(node._parentNode)
{
moved = true;
node._onChangeParent(this, options);
//remove from parent children
var pos = node._parentNode._children.indexOf(node);
if(pos != -1)
node._parentNode._children.splice(pos,1);
}
*/
//attach to this
node._parentNode = this;
if( !this._children )
this._children = [node];
else if(index == undefined)
this._children.push(node);
else
this._children.splice(index,0,node);
//the same as scene but we called tree to make it more generic
var tree = this._in_tree;
//this would never fire but just in case
if(tree && node._in_tree && node._in_tree != tree)
throw("Cannot add a node that belongs to another scene tree");
//Same tree
node._in_tree = tree;
//overwritten from SceneNode
if(this._onChildAdded)
this._onChildAdded(node, options);
LEvent.trigger(this,"childAdded", node);
if(tree)
{
//added to scene tree
LEvent.trigger(tree, "treeItemAdded", node);
inner_recursive(node);
}
//recursive action
function inner_recursive(item)
{
if(!item._children) return;
for(var i in item._children)
{
var child = item._children[i];
if(!child._in_tree)
{
//added to scene tree
LEvent.trigger( tree, "treeItemAdded", child );
child._in_tree = tree;
}
inner_recursive( child );
}
}
}
/**
* Removes the node from its parent (and from the scene tree)
*
* @method removeChild
* @param {Node} node this child to remove
* @param {*} param1 data passed to onChildRemoved
* @param {*} param2 data passed to onChildRemoved as second parameter
* @return {Boolean} returns true if it was found and removed
*/
CompositePattern.prototype.removeChild = function(node, param1, param2)
{
if(!node)
throw("cannot removeChild of null");
if(!this._children || node._parentNode != this)
return false;
if( node._parentNode != this)
return false; //not his son
var pos = this._children.indexOf(node);
if(pos == -1)
return false; //not his son �?
this._children.splice(pos,1);
if(this._onChildRemoved)
this._onChildRemoved(node, param1, param2);
LEvent.trigger(this,"childRemoved", node);
if(node._in_tree)
{
LEvent.trigger(node._in_tree, "treeItemRemoved", node);
//propagate to childs
inner_recursive(node);
}
node._in_tree = null;
//recursive action to remove tree
function inner_recursive(item)
{
if(!item._children)
return;
for(var i = 0, l = item._children.length; i < l; ++i)
{
var child = item._children[i];
if(child._in_tree)
{
LEvent.trigger( child._in_tree, "treeItemRemoved", child );
child._in_tree = null;
}
inner_recursive( child );
}
}
return true;
}
/**
* Remove every child node
*
* @method removeAllChildren
*/
CompositePattern.prototype.removeAllChildren = function( param1, param2 )
{
if(this._children)
while( this._children.length )
this.removeChild( this._children[0], param1, param2 );
}
/**
* Serialize the data from all the children
*
* @method serializeChildren
* @return {Array} array containing all serialized data from every children
*/
CompositePattern.prototype.serializeChildren = function( simplified )
{
var r = [];
if(this._children)
for(var i in this._children)
r.push( this._children[i].serialize( false, simplified ) ); //serialize calls serializeChildren
return r;
}
/**
* Configure every children with the data
*
* @method configureChildren
* @return {Array} o array containing all serialized data
*/
CompositePattern.prototype.configureChildren = function(o)
{
if(!o.children)
return;
for(var i = 0; i < o.children.length; ++i)
{
var c = o.children[i];
//create instance
var node = new this.constructor(c.id); //id is hardcoded...
//we do this before because otherwise the event fired by addChild wont have all the info which is crucial in some cases in the editor
if(c.uid)
node.uid = c.uid;
if(c.editor)
node._editor = c.editor;
//add before configure, so every child has a scene tree
this.addChild(node);
//we configure afterwards otherwise children wouldnt have a scene tree to bind anything
node.configure(c);
}
}
/**
* Returns parent node
*
* @method getParent
* @return {SceneNode} parent node
*/
CompositePattern.prototype.getParent = function()
{
return this._parentNode;
}
/**
* returns a list with all direct children (if you want below that use getDescendants)
* @method getChildren
* @param {Array} Original array containing the children
**/
CompositePattern.prototype.getChildren = function()
{
return this._children || [];
}
/**
* returns the index of a child in the children array
* @method getChildIndex
* @param {SceneNode} child the child to search for
* @return {number} the index of this child in the array, if it is not inside returns -1
**/
CompositePattern.prototype.getChildIndex = function( child )
{
return this._children ? this._children.indexOf( child ) : -1;
}
/**
* Returns the child in the index position
* @method getChildByIndex
* @param {number} index the index in the array
* @return {SceneNode} the child in that position
**/
CompositePattern.prototype.getChildByIndex = function( index )
{
return this._children && this._children.length > index ? this._children[ index ] : null;
}
/**
* Returns the child that matches that name
* @method getChildByName
* @param {String} name
* @return {SceneNode} the child with that name otherwise returns null;
**/
CompositePattern.prototype.getChildByName = function( name )
{
if(!this._children)
return null;
for(var i = 0; i < this._children.length; ++i)
if(this._children[i].name == name )
return this._children[i];
return null;
}
/**
* Returns the path name of the node (a path name is a concatenation of the name of the nodes an its ancestors: "root|parent|child"
* @method getPathName
* @return {String} the pathname
**/
CompositePattern.prototype.getPathName = function()
{
if(!this._in_tree)
return null;
if(this === this._in_tree.root )
return "";
var path = this.name;
var parent = this._parentNode;
while(parent)
{
if(parent === this._in_tree.root )
return path;
path = parent.name + "|" + path;
parent = parent._parentNode;
}
return null;
}
//DOM style
Object.defineProperty( CompositePattern.prototype, "childNodes", {
enumerable: true,
get: function() {
return this._children || [];
},
set: function(v) {
//TODO
}
});
Object.defineProperty( CompositePattern.prototype, "parentNode", {
enumerable: true,
get: function() {
return this._parentNode;
},
set: function(v) {
//TODO
}
});
Object.defineProperty( CompositePattern.prototype, "scene", {
enumerable: true,
get: function() {
return this._in_tree;
},
set: function(v) {
throw("Scene cannot be set, you must use addChild in parent");
}
});
/**
* get all nodes below this in the hierarchy (children and children of children)
*
* @method getDescendants
* @return {Array} array containing all descendants
*/
CompositePattern.prototype.getDescendants = function()
{
if(!this._children || this._children.length == 0)
return [];
var r = this._children.concat();
for(var i = 0; i < this._children.length; ++i)
r = r.concat( this._children[i].getDescendants() );
return r;
}
/**
* Swaps the index in the children array so it is before
* @method moveBefore
* @param {SceneNode} sibling [optional] allows to put before given node, otherwise it will be moved one position before of current position
* @return {number} new index
**/
CompositePattern.prototype.moveBefore = function( sibling )
{
if(!this._parentNode || (sibling && this._parentNode !== sibling._parentNode) )
return -1;
var parent_children = this._parentNode._children;
var index = parent_children.indexOf( this );
if(index == -1)
throw("moveBefore node not found in parent, this is impossible");
var new_index = index - 1;
if(sibling)
{
new_index = parent_children.indexOf( sibling );
if(new_index == -1)
return -1;
new_index = new_index - 1; //before
}
if(index == new_index || new_index < 0)
return new_index; //nothing to do
parent_children.splice( index, 1 ); //remove
if(new_index > index) //sibling is after
new_index -= 1;
parent_children.splice( new_index, 0, this); //insert
LEvent.trigger(this._in_tree,"node_rearranged", this );
return new_index;
}
/**
* Swaps the index in the children array so it is before
* @method moveAfter
* @param {SceneNode} sibling [optional] allows to put after given node, otherwise it will be moved one position after current position
* @return {number} new index
**/
CompositePattern.prototype.moveAfter = function( sibling )
{
if(!this._parentNode || (sibling && this._parentNode !== sibling._parentNode) )
return -1;
var parent_children = this._parentNode._children;
var index = parent_children.indexOf( this );
if(index == -1)
throw("moveBefore node not found in parent, this is impossible");
var new_index = index + 1;
if(sibling)
{
new_index = parent_children.indexOf( sibling );
if(new_index == -1)
return -1;
new_index = new_index + 1; //before
}
if( index == new_index || new_index >= parent_children.length )
return new_index; //nothing to do
parent_children.splice( index, 1 ); //remove
if(new_index > index) //sibling is after
new_index -= 1;
parent_children.splice( new_index, 0, this); //insert
LEvent.trigger(this._in_tree,"node_rearranged", this );
return new_index;
}
/**
* Search for a node using a string that could be a name, a fullname or a uid
* @method findNode
* @param {String} name_or_uid
* @return {SceneNode} the node or null
**/
CompositePattern.prototype.findNode = function( name_or_uid )
{
if(name_or_uid == "")
return this;
if(!name_or_uid)
return null;
if(name_or_uid.charAt(0) != LS._uid_prefix)
return this.findNodeByName( name_or_uid );
return this.findNodeByUId( name_or_uid );
}
/**
* search a node by its name
* this function gets called a lot when using animations
* @method findNodeByName
* @param {String} name
* @return {SceneNode} the node or null
**/
CompositePattern.prototype.findNodeByName = function( name )
{
if(!name)
return null;
if(this.name == name)
return this;
var children = this._children;
if(children)
{
for(var i = 0, l = children.length; i < l; ++i)
{
var node = children[i];
if( node.name == name )
return node;
if(node._children)
{
var r = node.findNodeByName( name );
if(r)
return r;
}
}
}
return null;
}
/**
* search a node by its uid
* @method findNodeByUId
* @param {String} id
* @return {SceneNode} the node or null
**/
CompositePattern.prototype.findNodeByUId = function( uid )
{
if(!uid)
return null;
if(this.uid == uid)
return this;
var children = this._children;
if(children)
for(var i = 0; i < children.length; ++i)
{
var node = children[i];
if( node.uid == uid )
return node;
if(node._children)
{
var r = node.findNodeByUId(uid);
if(r)
return r;
}
}
return null;
}
/**
* returns how many levels deep is the node in the hierarchy
* @method getHierarchyLevel
* @return {Number} the level, 0 if it is the root
**/
CompositePattern.prototype.getHierarchyLevel = function()
{
if(!this._parentNode)
return 0;
return this._parentNode.getHierarchyLevel() + 1;
}