///@INFO: BASE
/*
A component container is someone who could have components attached to it.
Mostly used for SceneNodes but it could be used for other classes (like Scene or Project).
*/
/**
* ComponentContainer class allows to add component based properties to any other class
* @class ComponentContainer
* @constructor
*/
function ComponentContainer()
{
//this function never will be called (because only the methods are attached to other classes)
//unless you instantiate this class directly, something that would be weird
this._components = [];
this._missing_components = null; //here we store info about components with missing info
//this._components_by_uid = {}; //TODO
}
/**
* Adds a component to this node.
* @method configureComponents
* @param {Object} info object containing all the info from a previous serialization
*/
ComponentContainer.prototype.configureComponents = function( info )
{
if(!info.components)
return;
var to_configure = [];
//attach first, configure later
for(var i = 0, l = info.components.length; i < l; ++i)
{
var comp_info = info.components[i];
var comp_class = comp_info[0];
var comp = null;
//special case: this is the only component that comes by default
if(comp_class == "Transform" && i == 0 && this.transform)
{
comp = this.transform;
}
else
{
//search for the class
var classObject = LS.Components[comp_class];
if(!classObject){
console.error("Unknown component found: " + comp_class);
if(!this._missing_components)
this._missing_components = [];
comp_info[2] = i; //store index
this._missing_components.push( comp_info );
continue;
}
//create component
comp = new classObject(); //comp_info[1]
//attach to node
this.addComponent(comp);
}
//what about configure the comp after adding it?
//comp.configure( comp_info[1] );
to_configure.push( comp, comp_info[1] );
//HACK very special case: due to requireScript
if( comp.constructor === LS.Components.ScriptFromFile )
comp._filename = comp_info[1].filename;
//editor stuff
if( comp_info[1].editor )
comp._editor = comp_info[1].editor;
//ensure the component uid is stored, some components may forgot about it
if( comp_info[1].uid && comp_info[1].uid !== comp.uid )
comp.uid = comp_info[1].uid;
}
//configure components now that all of them are created
//this is to avoid problems with components that check if the node has other components and if not they create it
for(var i = 0, l = to_configure.length; i < l; i+=2)
{
to_configure[i].configure( to_configure[i+1] );
}
}
/**
* Adds a component to this node.
* @method serializeComponents
* @param {Object} o container where the components will be stored
*/
ComponentContainer.prototype.serializeComponents = function( o, simplified )
{
if(!this._components)
return;
o.components = [];
for(var i = 0, l = this._components.length; i < l; ++i)
{
var comp = this._components[i];
if( !comp.serialize || comp.skip_serialize )
continue;
var obj = comp.serialize( simplified );
//check for bad stuff inside the component
/*
for(var j in obj)
{
var v = obj[j];
if( !v || v.constructor === Number || v.constructor === String || v.constructor === Boolean || v.constructor === Object || v.constructor === Array ) //regular data
continue;
obj[j] = LS.encodeObject(v);
}
*/
if(comp._editor && !simplified )
obj.editor = comp._editor;
//enforce uid storage
if(comp.hasOwnProperty("_uid") && !obj.uid)
obj.uid = comp.uid;
var object_class = LS.getObjectClassName(comp);
if(LS.debug && object_class != obj.object_class )
console.warn("Component serialize without object_class:",object_class);
if(!obj.object_class)
obj.object_class = object_class; //enforce
o.components.push([ object_class, obj ]);
}
//missing components are stored in another container and should be mergen with the rest of the components
if( this._missing_components && this._missing_components.length )
{
//try to copy in place (not perfect but this shouldnt happend very often)
for(var i = 0; i < this._missing_components.length; ++i )
{
var comp_info = this._missing_components[i];
o.components.splice( comp_info[2] || 0, 0, comp_info );
}
}
}
/**
* returns an array with all the components
* @method getComponents
* @return {Array} all the components
*/
ComponentContainer.prototype.getComponents = function( class_type )
{
if(class_type)
{
var result = [];
if(class_type.constructor === String)
class_type = LS.Components[class_type];
for(var i = 0, l = this._components.length; i < l; ++i)
{
var compo = this._components[i];
if( compo.constructor === class_type )
result.push( compo );
}
return result;
}
return this._components;
}
/**
* Adds a component to this node. (maybe attach would been a better name)
* @method addComponent
* @param {Object} component
* @return {Object} component added
*/
ComponentContainer.prototype.addComponent = function( component, index )
{
if(!component)
throw("addComponent cannot receive null");
//you may pass a component class instead of an instance
if(component.constructor === String)
{
component = LS.Components[ component ];
if(!component)
throw("component class not found: " + arguments[0] );
}
if(component.is_component)
component = new component();
//link component with container
component._root = this;
//must have uid
if( !component.uid )
component.uid = LS.generateUId("COMP-");
//not very clean, ComponetContainer shouldnt know about LS.SceneNode, but this is more simple
if( component.onAddedToNode)
component.onAddedToNode(this);
if( this._in_tree )
{
if( component.uid )
this._in_tree._components_by_uid[ component.uid ] = component;
else
console.warn("component without uid?", component);
if( component.onAddedToScene )
component.onAddedToScene( this.constructor == LS.Scene ? this : this._in_tree );
}
//link node with component
if(!this._components)
Object.defineProperty( this, "_components", { value: [], enumerable: false });
if(this._components.indexOf(component) != -1)
throw("inserting the same component twice");
if(index !== undefined && index <= this._components.length )
this._components.splice(index,0,component);
else
this._components.push( component );
LEvent.trigger( this, "componentAdded", component );
return component;
}
/**
* Removes a component from this node.
* @method removeComponent
* @param {Object} component
*/
ComponentContainer.prototype.removeComponent = function(component)
{
if(!component)
throw("removeComponent cannot receive null");
//unlink component with container
component._root = null;
//not very clean, ComponetContainer shouldnt know about LS.SceneNode, but this is more simple
if( component.onRemovedFromNode )
component.onRemovedFromNode(this);
if( this._in_tree )
{
delete this._in_tree._components_by_uid[ component.uid ];
if(component.onRemovedFromScene)
component.onRemovedFromScene( this._in_tree );
}
//remove all events
LEvent.unbindAll(this,component);
//remove from components list
var pos = this._components.indexOf(component);
if(pos != -1)
this._components.splice(pos,1);
else
console.warn("removeComponent: Component not found in node");
LEvent.trigger( this, "componentRemoved", component );
}
/**
* Removes all components from this node.
* @method removeAllComponents
* @param {Object} component
*/
ComponentContainer.prototype.removeAllComponents = function()
{
while(this._components.length)
this.removeComponent( this._components[0] );
this._missing_components = null;
}
/**
* Returns if the container has a component of this class
* @method hasComponent
* @param {String|Class} component_class the component to search for, could be a string or the class itself
* @param {Boolean} search_missing [optional] true if you want to search in the missing components too
*/
ComponentContainer.prototype.hasComponent = function( component_class, search_missing )
{
if(!this._components && !this._missing_components)
return false;
//search in missing components
if(search_missing && this._missing_components && this._missing_components.length)
{
if(component_class.constructor !== String) //weird case
component_class = LS.getClassName( component_class );
for(var i = 0, l = this._missing_components.length; i < l; ++i)
if( this._missing_components[i][0] == component_class )
return true;
}
//string
if( component_class.constructor === String )
{
component_class = LS.Components[ component_class ];
if(!component_class)
return false;
}
//search in components
for(var i = 0, l = this._components.length; i < l; ++i)
if( this._components[i].constructor === component_class )
return true;
return false;
}
/**
* Returns the first component of this container that is of the same class
* @method getComponent
* @param {Object|String} component_class the class to search a component from (could be the class or the name)
* @param {Number} index [optional] if you want the Nth component of this class
*/
ComponentContainer.prototype.getComponent = function( component_class, index )
{
if(!this._components || !component_class)
return null;
//convert string to class
if( component_class.constructor === String )
{
//special case, locator by name (the locator starts with an underscore if it is meant to be a name)
if( component_class[0] == "_" )
{
component_class = component_class.substr(1); //remove underscore
for(var i = 0, l = this._components.length; i < l; ++i)
{
if( this._components[i].name == component_class )
{
if(index !== undefined && index > 0)
{
index--;
continue;
}
return this._components[i];
}
}
return false;
}
//otherwise the string represents the class name
component_class = LS.Components[ component_class ];
if(!component_class)
return;
}
//search components
for(var i = 0, l = this._components.length; i < l; ++i)
{
if( this._components[i].constructor === component_class )
{
if(index !== undefined && index > 0)
{
index--;
continue;
}
return this._components[i];
}
}
return null;
}
/**
* Returns the component with the given uid
* @method getComponentByUId
* @param {string} uid the uid to search
*/
ComponentContainer.prototype.getComponentByUId = function(uid)
{
if(!this._components)
return null;
for(var i = 0, l = this._components.length; i < l; ++i)
if( this._components[i].uid == uid )
return this._components[i];
return null;
}
/**
* Returns the position in the components array of this component
* @method getIndexOfComponent
* @param {Number} position in the array, -1 if not found
*/
ComponentContainer.prototype.getIndexOfComponent = function(component)
{
if(!this._components)
return -1;
return this._components.indexOf( component );
}
/**
* Returns the component at index position
* @method getComponentByIndex
* @param {Object} component
*/
ComponentContainer.prototype.getComponentByIndex = function(index)
{
if(!this._components)
return null;
return this._components[index];
}
/**
* Returns a list of components matching the search, it search in the node and child nodes
* @method findComponent
* @param {Class|String} component the component class or the class name
* @return {Array} an array with all the components of the same class
*/
ComponentContainer.prototype.findComponents = function( comp_name, out )
{
out = out || [];
if(!comp_name)
return out;
if( comp_name.constructor === String )
comp_name = LS.Components[ comp_name ];
if(!comp_name)
return out;
for(var i = 0; i < this._components.length; ++i )
{
var comp = this._components[i];
if( comp && comp.constructor === comp_name )
out.push( comp );
}
if(this._children)
for(var i = 0; i < this._children.length; ++i )
this._children[i].findComponents( comp_name, out );
return out;
}
/**
* Changes the order of a component
* @method setComponentIndex
* @param {Object} component
*/
ComponentContainer.prototype.setComponentIndex = function( component, index )
{
if(!this._components)
return null;
if(index < 0)
index = 0;
var old_index = this._components.indexOf( component );
if (old_index == -1)
return;
this._components.splice( old_index, 1 );
/*
if(index >= old_index)
index--;
*/
if(index >= this._components.length)
this._components.push( component );
else
this._components.splice( index, 0, component );
}
/**
* Ensures this node has a component of the specified class, if not it creates one and attaches it
* @method requireComponent
* @param {Object|String} component_class the class to search a component from (could be the class or the name)
* @param {Object} data [optional] the object to configure the component from
* @return {Component} the component found or created
*/
ComponentContainer.prototype.requireComponent = function( component_class, data )
{
if(!component_class)
throw("no component class specified");
//convert string to class
if( component_class.constructor === String )
{
component_class = LS.Components[ component_class ];
if(!component_class)
{
console.error("component class not found:", arguments[0] );
return null;
}
}
//search component
var l = this._components.length;
for(var i = 0; i < l; ++i)
{
if( this._components[i].constructor === component_class )
return this._components[i];
}
var compo = new component_class();
this.addComponent(compo, l ); //insert before the latest scripts, to avoid situations where when partially parsed the components the component is attached but not parsed yet
if(data)
compo.configure(data);
return compo;
}
/**
* Ensures this node has a ScriptFromFile component of the specified script url, if not it creates one and attaches it
* @method requireScript
* @param {String} url the url to the script
* @return {Component} the ScriptFromFile component found or created
*/
ComponentContainer.prototype.requireScript = function( url )
{
if(!url)
throw("no url specified");
var component_class = LS.Components.ScriptFromFile;
url = LS.ResourcesManager.cleanFullpath( url ); //remove double slashes or spaces
//search component
var l = this._components.length;
for(var i = 0; i < l; ++i)
{
var comp = this._components[i];
if( comp.constructor === component_class && comp._filename == url )
return comp;
}
var compo = new component_class();
compo.filename = url;
this.addComponent( compo, l );
return compo;
}
/**
* executes the method with a given name in all the components
* @method processActionInComponents
* @param {String} method_name the name of the function to execute in all components (in string format)
* @param {Array} params array with every parameter that the function may need
* @param {Boolean} skip_scripts [optional] skip scripts
*/
ComponentContainer.prototype.processActionInComponents = function( method_name, params, skip_scripts )
{
if(this._components && this._components.length)
{
for(var i = 0, l = this._components.length; i < l; ++i)
{
var comp = this._components[i];
if( comp[method_name] && comp[method_name].constructor === Function )
{
if(!params || params.constructor !== Array)
comp[method_name].call(comp, params);
else
comp[method_name].apply(comp, params);
continue;
}
if(skip_scripts)
continue;
if(comp._script)
comp._script.callMethod( method_name, params, true );
}
}
}
/**
* executes the method with a given name in all the components and its children
* @method broadcastMessage
* @param {String} method_name the name of the function to execute in all components (in string format)
* @param {Array} params array with every parameter that the function may need
*/
ComponentContainer.prototype.broadcastMessage = function( method_name, params )
{
this.processActionInComponents( method_name, params );
if(this._children && this._children.length )
for(var i = 0, l = this._children.length; i < l; ++i)
this._children[i].broadcastMessage( method_name, params );
}