608 lines
12 KiB
JavaScript
Executable File
608 lines
12 KiB
JavaScript
Executable File
/**
|
|
* Creates a object from a XML specification.
|
|
**/
|
|
Vn.Builder = new Class
|
|
({
|
|
Extends: Vn.Object
|
|
,_addedMap: {}
|
|
,_contexts: null
|
|
|
|
,add: function (id, object)
|
|
{
|
|
this._addedMap[id] = object;
|
|
}
|
|
|
|
,setParent: function (parentResult)
|
|
{
|
|
this._parentResult = parentResult;
|
|
|
|
if (parentResult && !this.signalData)
|
|
this.signalData = parentResult.builder.signalData;
|
|
}
|
|
|
|
,getMain: function (result)
|
|
{
|
|
return result.objects[this._mainContext];
|
|
}
|
|
|
|
,getById: function (result, objectId)
|
|
{
|
|
var index = this._contextMap[objectId];
|
|
|
|
if (index !== undefined)
|
|
return result.objects[index];
|
|
else if (this._parentResult)
|
|
return this._parentResult.getById (objectId);
|
|
|
|
return null;
|
|
}
|
|
|
|
,getByTagName: function (result, tagName)
|
|
{
|
|
var tags = this._tags[tagName];
|
|
|
|
if (tags)
|
|
{
|
|
var arr = new Array (tags.length);
|
|
|
|
for (var i = 0; i < tags.length; i++)
|
|
arr[i] = result.objects[tags[i]];
|
|
|
|
return arr;
|
|
}
|
|
|
|
return [];
|
|
}
|
|
|
|
/**
|
|
* Compiles an XML file.
|
|
*
|
|
* @path String The XML path
|
|
* @dstDocument Document The document used to create the nodes
|
|
* @return %true on success, %false othersise
|
|
**/
|
|
,loadXml: function (path, dstDocument)
|
|
{
|
|
var xmlDoc = Vn.getXml (path);
|
|
|
|
if (!xmlDoc)
|
|
return false;
|
|
|
|
this._compileInit (dstDocument);
|
|
this._path = path;
|
|
|
|
var docElement = xmlDoc.documentElement;
|
|
|
|
if (docElement.tagName !== 'vn')
|
|
{
|
|
this._showError ('Malformed XML');
|
|
this._contexts = null;
|
|
return false;
|
|
}
|
|
|
|
var childs = docElement.childNodes;
|
|
|
|
if (childs)
|
|
for (var i = 0; i < childs.length; i++)
|
|
this._compileNode (childs[i]);
|
|
|
|
this._compileEnd ();
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Compiles a single DOM node.
|
|
*
|
|
* @path Node The DOM node
|
|
* @dstDocument Document The document used to create the nodes
|
|
* @return %true on success, %false othersise
|
|
**/
|
|
,loadXmlFromNode: function (node, dstDocument)
|
|
{
|
|
this._compileInit (dstDocument);
|
|
this._mainContext = this._compileNode (node).id;
|
|
this._compileEnd ();
|
|
return true;
|
|
}
|
|
|
|
,load: function ()
|
|
{
|
|
if (this._contexts === null)
|
|
return null;
|
|
|
|
var contexts = this._contexts;
|
|
var len = contexts.length;
|
|
var objects = new Array (len);
|
|
|
|
for (var i = 0; i < len; i++)
|
|
{
|
|
var context = contexts[i];
|
|
|
|
if (context.tagName)
|
|
objects[i] = this.elementInstantiate (context);
|
|
else if (context.klass)
|
|
objects[i] = this.objectInstantiate (context);
|
|
else
|
|
objects[i] = this.textInstantiate (context);
|
|
}
|
|
|
|
return new BuilderResult (this, objects);
|
|
}
|
|
|
|
,link: function (result)
|
|
{
|
|
var objects = result.objects;
|
|
|
|
for (var i = this._links.length - 1; i >= 0; i--)
|
|
{
|
|
var l = this._links[i];
|
|
var addedObject = this._addedMap[l.objectId];
|
|
|
|
if (addedObject)
|
|
if (l.prop)
|
|
objects[l.context.id][l.prop] = addedObject;
|
|
else
|
|
objects[l.context.id].appendChild (addedObject);
|
|
else
|
|
this._showError ('Referenced unexistent object with id \'%s\'',
|
|
l.objectId);
|
|
}
|
|
|
|
var contexts = this._contexts;
|
|
for (var i = 0; i < contexts.length; i++)
|
|
{
|
|
var context = contexts[i];
|
|
var object = objects[i];
|
|
|
|
if (context.tagName)
|
|
this.elementLink (context, object, objects, result);
|
|
else if (context.klass)
|
|
this.objectLink (context, object, objects, result);
|
|
}
|
|
}
|
|
|
|
,_compileInit: function (dstDocument)
|
|
{
|
|
this._path = null;
|
|
this._tags = {};
|
|
this._contexts = [];
|
|
this._contextMap = {};
|
|
this._links = [];
|
|
this._mainContext = null;
|
|
this._document = dstDocument ? dstDocument : document;
|
|
}
|
|
|
|
,_compileEnd: function (node)
|
|
{
|
|
for (var i = this._links.length - 1; i >= 0; i--)
|
|
{
|
|
var l = this._links[i];
|
|
var contextId = this._contextMap[l.objectId]
|
|
|
|
if (contextId != undefined)
|
|
{
|
|
if (l.prop)
|
|
l.context.objectProps[l.prop] = contextId;
|
|
else
|
|
l.context.childs.push (contextId);
|
|
|
|
this._links.splice (i, 1);
|
|
}
|
|
else
|
|
{
|
|
var object = this._addedMap[l.objectId];
|
|
|
|
if (!object && this._parentResult)
|
|
object = this._parentResult.getById (l.objectId);
|
|
|
|
if (object)
|
|
{
|
|
l.context.props[l.prop] = object;
|
|
this._links.splice (i, 1);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
,_compileNode: function (node)
|
|
{
|
|
var context = null;
|
|
var tagName = null;
|
|
|
|
if (node.nodeType === Node.ELEMENT_NODE)
|
|
tagName = node.tagName.toLowerCase ();
|
|
else if (node.nodeType !== Node.TEXT_NODE
|
|
|| /^[\n\r\t]*$/.test (node.textContent))
|
|
return null;
|
|
|
|
var context =
|
|
this.textCompile (node, tagName)
|
|
|| this.objectCompile (node, tagName)
|
|
|| this.elementCompile (node, tagName);
|
|
|
|
context.id = this._contexts.length;
|
|
|
|
if (tagName)
|
|
{
|
|
var nodeId = node.getAttribute ('id');
|
|
|
|
if (nodeId)
|
|
this._contextMap[nodeId] = context.id;
|
|
|
|
var tags = this._tags[tagName];
|
|
|
|
if (!tags)
|
|
this._tags[tagName] = tags = [];
|
|
|
|
tags.push (context.id);
|
|
}
|
|
|
|
this._contexts.push (context);
|
|
return context;
|
|
}
|
|
|
|
/**
|
|
* Creates a text node context.
|
|
**/
|
|
,textCompile: function (node, tagName)
|
|
{
|
|
if (!tagName)
|
|
var text = node.textContent;
|
|
else if (tagName === 't')
|
|
var text = _(node.firstChild.textContent);
|
|
else
|
|
return null;
|
|
|
|
return {text: text};
|
|
}
|
|
|
|
,textInstantiate: function (context)
|
|
{
|
|
return this._document.createTextNode (context.text);
|
|
}
|
|
|
|
/**
|
|
* Creates a object context.
|
|
**/
|
|
,objectCompile: function (node, tagName)
|
|
{
|
|
var klass = Vn.customTags[tagName];
|
|
|
|
if (!klass)
|
|
return null;
|
|
|
|
var props = {};
|
|
var objectProps = {};
|
|
var childs = [];
|
|
var events = {};
|
|
|
|
var context = {
|
|
klass: klass,
|
|
props: props,
|
|
objectProps: objectProps,
|
|
childs: childs,
|
|
events: events,
|
|
custom: null
|
|
};
|
|
|
|
var a = node.attributes;
|
|
|
|
for (var i = 0; i < a.length; i++)
|
|
{
|
|
var attribute = a[i].nodeName;
|
|
var value = a[i].nodeValue;
|
|
|
|
if (this._isEvent (attribute))
|
|
{
|
|
var handler = this._getMethod (value)
|
|
|
|
if (handler)
|
|
events[attribute.substr (3)] = handler;
|
|
}
|
|
else if (!/^(id|property)$/.test (attribute))
|
|
{
|
|
this.propCompile (context, klass, props,
|
|
node, attribute, value);
|
|
}
|
|
}
|
|
|
|
var childNodes = node.childNodes;
|
|
|
|
if (childNodes)
|
|
for (var i = 0; i < childNodes.length; i++)
|
|
{
|
|
var child = childNodes[i];
|
|
|
|
if (child.nodeType !== Node.ELEMENT_NODE)
|
|
continue;
|
|
|
|
var childContext;
|
|
var childTagName = child.tagName.toLowerCase ();
|
|
|
|
if (childTagName === 'pointer')
|
|
{
|
|
this._addLink (context, null, child.getAttribute ('object'));
|
|
}
|
|
else if (childTagName === 'custom')
|
|
{
|
|
context.custom = child;
|
|
}
|
|
else if (childContext = this._compileNode (child))
|
|
{
|
|
var prop = child.getAttribute ('property');
|
|
|
|
if (prop)
|
|
{
|
|
prop = prop.replace (/-./g, this._replaceFunc);
|
|
objectProps[prop] = childContext.id;
|
|
}
|
|
else
|
|
childs.push (childContext.id);
|
|
}
|
|
}
|
|
|
|
return context;
|
|
}
|
|
|
|
,propCompile: function (context, klass, props, node, attribute, value)
|
|
{
|
|
var isLink = false;
|
|
var newValue = null;
|
|
var propName = attribute.replace (/-./g, this._replaceFunc);
|
|
var propInfo = klass.Properties[propName];
|
|
|
|
if (!propInfo)
|
|
{
|
|
this._showError ('Attribute \'%s\' not valid for tag \'%s\'',
|
|
attribute, node.tagName);
|
|
return;
|
|
}
|
|
if (!value)
|
|
{
|
|
this._showError ('Attribute \'%s\' empty on tag \'%s\'',
|
|
attribute, node.tagName);
|
|
return;
|
|
}
|
|
|
|
switch (propInfo.type)
|
|
{
|
|
case Boolean:
|
|
newValue = (/^(true|1)$/i).test (value);
|
|
break;
|
|
case Number:
|
|
newValue = 0 + new Number (value);
|
|
break;
|
|
case String:
|
|
newValue = this._translateValue (value);
|
|
break;
|
|
case Function:
|
|
var method = this._getMethod (value);
|
|
newValue = method ? method.bind (this.signalData) : null;
|
|
break;
|
|
default:
|
|
if (propInfo.enumType)
|
|
newValue = propInfo.enumType[value];
|
|
else if (propInfo.type instanceof Function)
|
|
isLink = true;
|
|
}
|
|
|
|
if (isLink)
|
|
this._addLink (context, propName, value);
|
|
else if (newValue !== null && newValue !== undefined)
|
|
props[propName] = newValue;
|
|
else
|
|
this._showError ('Attribute \'%s\' invalid for tag \'%s\'',
|
|
attribute, node.tagName);
|
|
}
|
|
|
|
,objectInstantiate: function (context)
|
|
{
|
|
return new context.klass ();
|
|
}
|
|
|
|
,objectLink: function (context, object, objects, res)
|
|
{
|
|
object.setProperties (context.props);
|
|
|
|
var objectProps = context.objectProps;
|
|
for (var prop in objectProps)
|
|
object[prop] = objects[objectProps[prop]];
|
|
|
|
var childs = context.childs;
|
|
for (var i = 0; i < childs.length; i++)
|
|
object.appendChild (objects[childs[i]]);
|
|
|
|
var events = context.events;
|
|
for (var event in events)
|
|
object.on (event, events[event], this.signalData);
|
|
|
|
if (context.custom)
|
|
object.loadXml (res, context.custom);
|
|
}
|
|
|
|
/**
|
|
* Creates a HTML node context.
|
|
**/
|
|
,elementCompile: function (node, tagName)
|
|
{
|
|
var attributes = {};
|
|
var childs = [];
|
|
var events = {};
|
|
var handler;
|
|
|
|
var a = node.attributes;
|
|
|
|
for (var i = 0; i < a.length; i++)
|
|
{
|
|
var attribute = a[i].nodeName;
|
|
var value = a[i].nodeValue;
|
|
|
|
if (this._isEvent (attribute))
|
|
{
|
|
var handler = this._getMethod (value);
|
|
|
|
if (handler)
|
|
events[attribute.substr (3)] = handler;
|
|
}
|
|
else if (attribute !== 'id')
|
|
attributes[attribute] = this._translateValue (value);
|
|
}
|
|
|
|
var childContext;
|
|
var childNodes = node.childNodes;
|
|
|
|
if (childNodes)
|
|
for (var i = 0; i < childNodes.length; i++)
|
|
if (childContext = this._compileNode (childNodes[i]))
|
|
childs.push (childContext.id);
|
|
|
|
return {
|
|
tagName: tagName,
|
|
attributes: attributes,
|
|
childs: childs,
|
|
events: events
|
|
};
|
|
}
|
|
|
|
,elementInstantiate: function (context)
|
|
{
|
|
return this._document.createElement (context.tagName);
|
|
}
|
|
|
|
,elementLink: function (context, object, objects)
|
|
{
|
|
var attributes = context.attributes;
|
|
for (var attribute in attributes)
|
|
object.setAttribute (attribute, attributes[attribute]);
|
|
|
|
var childs = context.childs;
|
|
for (var i = 0; i < childs.length; i++)
|
|
{
|
|
var child = objects[childs[i]];
|
|
|
|
if (child instanceof Htk.Widget)
|
|
child = child.getNode ();
|
|
if (child instanceof Node)
|
|
object.appendChild (child);
|
|
}
|
|
|
|
var events = context.events;
|
|
for (var event in events)
|
|
object.addEventListener (event,
|
|
events[event].bind (this.signalData));
|
|
}
|
|
|
|
,_showError: function (error)
|
|
{
|
|
var path = this._path ? this._path : 'Node';
|
|
var logArgs = ['Vn.Builder: %s: '+ error, path];
|
|
|
|
for (var i = 1; i < arguments.length; i++)
|
|
logArgs.push (arguments[i]);
|
|
|
|
console.warn.apply (null, logArgs);
|
|
}
|
|
|
|
,_addLink: function (context, prop, objectId)
|
|
{
|
|
this._links.push ({
|
|
context: context
|
|
,prop: prop
|
|
,objectId: objectId
|
|
});
|
|
}
|
|
|
|
,_translateValue: function (value)
|
|
{
|
|
var chr = value.charAt (0);
|
|
|
|
if (chr === '_')
|
|
return _(value.substr (1));
|
|
else if (chr === '\\' && value.charAt (1) === '_')
|
|
return value.substr (1);
|
|
|
|
return value;
|
|
}
|
|
|
|
,_getMethod: function (value)
|
|
{
|
|
if (this.signalData)
|
|
var methodName = 'this.signalData.'+ value;
|
|
else
|
|
var methodName = value;
|
|
|
|
var method;
|
|
|
|
try {
|
|
method = eval (methodName);
|
|
}
|
|
catch (e)
|
|
{
|
|
method = undefined;
|
|
}
|
|
|
|
if (method === undefined)
|
|
this._showError ('Function \'%s\' not found', value);
|
|
|
|
return method;
|
|
}
|
|
|
|
,_isEvent: function (attribute)
|
|
{
|
|
return /^on-\w+/.test (attribute);
|
|
}
|
|
|
|
,_replaceFunc: function (token)
|
|
{
|
|
return token.charAt(1).toUpperCase ();
|
|
}
|
|
});
|
|
|
|
var BuilderResult = new Class
|
|
({
|
|
Extends: Vn.Object
|
|
|
|
,initialize: function (builder, objects)
|
|
{
|
|
this.builder = builder;
|
|
this.objects = objects;
|
|
}
|
|
|
|
,getMain: function ()
|
|
{
|
|
return this.builder.getMain (this);
|
|
}
|
|
|
|
,$: function (objectId)
|
|
{
|
|
return this.builder.getById (this, objectId);
|
|
}
|
|
|
|
,getById: function (objectId)
|
|
{
|
|
return this.builder.getById (this, objectId);
|
|
}
|
|
|
|
,getByTagName: function (tagName)
|
|
{
|
|
return this.builder.getByTagName (this, tagName);
|
|
}
|
|
|
|
,link: function ()
|
|
{
|
|
this.builder.link (this);
|
|
}
|
|
|
|
,_destroy: function ()
|
|
{
|
|
var objects = this.objects;
|
|
|
|
for (var i = 0; i < objects.length; i++)
|
|
if (objects[i] instanceof Vn.Object)
|
|
objects[i].unref ();
|
|
|
|
this.parent ();
|
|
}
|
|
});
|
|
|