/** * Creates a object from a XML specification. **/ Vn.Builder = new Class ({ Extends: Vn.Object ,objectMap: {} ,tags: {} //+++++++++++++++++++++++++++++++++++++++++++ Deprecated ,loadXml: function (xmlDoc) { if (!xmlDoc) return false; var docElement = xmlDoc.documentElement; if (docElement.tagName !== 'vn') return false; this.contexts = []; var childs = docElement.childNodes; if (childs) for (var i = 0; i < childs.length; i++) this.loadNode (childs[i], null); this.resolveProperties (); delete this.contexts; return true; } ,loadXmlFromNode: function (node) { this.contexts = []; var mainNode = this.loadNode (node); this.resolveProperties (); delete this.contexts; return mainNode; } ,add: function (id, object) { this.objectMap[id] = object; } ,loadNode: function (node) { var customNode; var htmlNode = null; var tagName = null; if (node.tagName) tagName = node.tagName.toLowerCase (); if (tagName === 't') { htmlNode = document.createTextNode (_(node.firstChild.textContent)); } else if (!tagName) { htmlNode = document.importNode (node, false); } else if ((customNode = this.loadCustomNode (node, null))) { if (customNode instanceof Htk.Widget) htmlNode = customNode.getNode (); } else { htmlNode = document.createElement (tagName); var a = node.attributes; for (var i = 0; i < a.length; i++) { var nodeName = a[i].nodeName; var nodeValue = a[i].nodeValue; if (/^on-\w+/.test (nodeName)) { var method = this.getMethod (nodeValue); htmlNode.addEventListener ( nodeName.substr (3), method.bind (this.signalData)); } else if (nodeName === 'id') { this.objectMap[nodeValue] = htmlNode; } else htmlNode.setAttribute (nodeName, this.translateValue (nodeValue)); } var childs = node.childNodes; if (childs) for (var i = 0; i < childs.length; i++) { var htmlChild = this.loadNode (childs[i]); if (htmlChild) htmlNode.appendChild (htmlChild); } } return htmlNode; } ,loadCustomNode: function (node, parentContext) { if (!node.tagName) return null; var tagName = node.tagName.toLowerCase (); var klass = Vn.customTags[tagName]; if (!klass) return null; var customNode = new klass (); if (!this.tags[tagName]) this.tags[tagName] = []; this.tags[tagName].push (customNode); var context = { node: node ,parent: parentContext ,object: customNode ,klass: klass }; this.contexts.push (context); var nodeId = node.getAttribute ('id'); if (nodeId) this.objectMap[nodeId] = customNode; var childs = node.childNodes; if (childs) for (var i = 0; i < childs.length; i++) this.loadCustomNode (childs[i], context); return customNode; } ,resolveProperties: function () { for (var i = 0; i < this.contexts.length; i++) { var c = this.contexts[i]; var a = c.node.attributes; for (var j = 0; j < a.length; j++) this.setAttribute (c, a[j].nodeName, a[j].nodeValue); if (c.parent) { var parentProperty = c.node.getAttribute ('property'); if (!parentProperty) parentProperty = c.parent.klass.Child; if (parentProperty) this.setProperty (c.parent, parentProperty, c.object); if (c.klass.Parent) this.setProperty (c, c.klass.Parent, c.parent.object); } c.object.loadXml (this, c.node); } } ,setAttribute: function (c, attribute, value) { if (/^on-\w+/.test (attribute)) { var method = this.getMethod (value); if (method) c.object.on (attribute.substr (3), method, this.signalData); } else if (!/^(id|property)$/.test (attribute)) { this.setProperty (c, attribute, value) } } ,setProperty: function (c, attribute, value) { var propName = attribute.replace (/-./g, this.replaceFunc); var prop = c.klass.Properties[propName]; if (!prop) { console.warn ('Vn.Builder: Attribute \'%s\' not valid for tag \'%s\'', attribute, c.node.tagName); return; } if (!value) return; switch (prop.type) { case Boolean: value = (/^(true|1)$/i).test (value); break; case Number: value = 0 + new Number (value); break; case String: value = this.translateValue (value); break; case Function: { var method = this.getMethod (value); value = method ? method.bind (this.signalData) : null; break; } default: if (prop.type instanceof Function) { if (typeof value == 'string') value = this.get (value); if (!(value instanceof prop.type)) return; } else if (prop.enumType) value = prop.enumType[value]; } if (value !== undefined) c.object[propName] = value; else console.warn ('Vn.Builder: Empty attribute \'%s\' on tag \'%s\'', attribute, c.node.tagName); } //+++++++++++++++++++++++++++++++++++++++++++ Alpha ,load: function (thisData) { var contexts = this.contexts; var objects = new Array (contexts.length); for (var i = 0; i < contexts.length; i++) { var context = contexts[i]; objects[i] = context.func (context.template); } var links = this.links; for (var i = 0; i < links.length; i++) { var link = links[i]; objects[link.contextId][link.propName] = objects[link.valueContext]; } } ,compile: function (node, dstDocument) { this.contexts = []; this.contextMap = {}; this.pointers = []; this.links = []; this.document = dstDocument ? dstDocument : document; this.compileRec (node, null); for (var i = 0; i < this.pointers.length; i++) { var pointerId = this.pointers[i].template; var refContext = this.contextMap[pointerId]; } } ,compileRec: function (node, parentContext) { var tagName = null; if (node.tagName) tagName = node.tagName.toLowerCase (); var context = { node: node ,parent: parentContext ,template: template ,id: this.contexts.length ,func: null }; this.contexts.push (context); var template = createTextTemplate (context, node, tagName) || createPointerTemplate (context, node, tagName) || createObjectTemplate (context, node, tagName) || createHtmlTemplate (context, node, tagName); var id = node.getAttribute ('id'); if (id) this.contextMap[id] = context; if (parentContext) { var parentProperty = node.getAttribute ('property'); if (!parentProperty && parentContext.template.klass) parentProperty = parentContext.template.klass.Child; if (parentProperty) { this.links.push ({ contextId: context.id, propName: propName, valueContext: valueContext.id }); } this.registerLink (parentContext, parentProperty, context); if (klass.Parent) this.registerLink (context, klass.Parent, parentContext); } var childs = node.childNodes; if (childs) for (var i = 0; i < childs.length; i++) this.compileRec (childs[i], context); return context; } /** * Creates a text node template. **/ ,createTextTemplate: function (context, node, tagName) { if (tagName === 't') var text = _(node.firstChild.textContent); else if (!tagName) var text = node.textContent; else return null; return context.func = createTextInstance; return text; } ,createTextInstance: function (template) { return this.document.createTextNode (template); } /** * Creates a object pointer template. **/ ,createPointerTemplate: function (context, node, tagName) { if (tagName !== 'pointer') return null; this.pointers.push (context); return node.getAttribute ('object'); } ,createPointerInstance: function (template) { return this.objectMap[template]; } /** * Creates a object template. **/ ,createObjectTemplate: function (context, node, tagName) { var id = null; var handler; var props = {}; var events = null; var klass = Vn.customTags[tagName]; if (!klass) return null; var a = node.attributes; for (var i = 0; i < a.length; i++) { var attribute = a[i].nodeName; var value = a[i].nodeValue; if (attribute === 'id') { id = value; } else if ((handler = this.getEventHandler (attribute, value))) { if (!events) events = {}; events[attribute.substr (3)] = handler; } else if (attribute !== 'property') { this.createPropTemplate (context, klass, props, node, attribute, value); } } context.func = createObjectInstance; return { klass: klass, events: events, id: id }; } ,createPropTemplate: function (context, klass, props, node, attribute, value) { var newValue = null; var propName = attribute.replace (/-./g, this.replaceFunc); var propInfo = klass.Properties[propName]; if (!propInfo) { console.warn ('Vn.Builder: Attribute \'%s\' not valid for tag \'%s\'', attribute, node.tagName); return; } if (!value) { console.warn ('Vn.Builder: 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: newValue = this.getMethod (value); break; case Vn.Enum: newValue = propInfo.enumType[value]; break; } if (newValue !== null && newValue !== undefined) { props[propName] = newValue; } else if (propInfo.type instanceof Function) { this.registerLink (context, attribute, value); } else console.warn ('Vn.Builder: Attribute \'%s\' invalid for tag \'%s\'', attribute, node.tagName); } ,createObjectInstance: function (template) { var object = new template.klass (template.props); var events = template.events; for (var event in events) object.on (event, events[event].bind (this.signalData)); if (template.id) this.objectMap[id] = object; return object; } /** * Creates a HTML node template. **/ ,createHtmlTemplate: function (context, node, tagName) { var id = null; var handler; var events = null; var htmlNode = this.document.createElement (tagName); var a = node.attributes; for (var i = 0; i < a.length; i++) { var attribute = a[i].nodeName; var value = a[i].nodeValue; if (attribute === 'id') { id = value; } else if ((handler = this.getEventHandler (attribute, value))) { if (!events) events = {}; events[attribute.substr (3)] = handler; } else htmlNode.setAttribute (nodeName, this.translateValue (nodeValue)); } context.func = createHtmlInstance; return { node: htmlNode, events: events, id: id }; } ,createHtmlInstance: function (template) { var node = new template.node.cloneNode (false); var events = template.events; for (var event in events) node.addEventListener (event, events[event].bind (this.signalData)); if (template.id) this.objectMap[id] = node; return node; } //+++++++++++++++++++++++++++++++++++++++++++ Utilities ,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 = null; } if (method == null) console.warn ('Vn.Builder: Function \'%s\' not found', value); return method; } ,getEventHandler: function (attribute, value) { if (!(/^on-\w+/.test (attribute))) return null; return this.getMethod (value); } ,replaceFunc: function (token) { return token.charAt(1).toUpperCase (); } ,setParent: function (parentBuilder) { this.parentBuilder = parentBuilder; if (parentBuilder) this.signalData = parentBuilder.signalData; } ,$: function (objectId) { return this.get (objectId); } ,get: function (objectId) { var object = this.objectMap[objectId]; if (object) return object; if (this.parentBuilder) return this.parentBuilder.get (objectId); return null; } ,getObjects: function (tagName) { if (this.tags[tagName]) return this.tags[tagName]; return []; } ,_destroy: function () { for (var tag in this.tags) { var objects = this.tags[tag]; for (var i = 0; i < objects.length; i++) objects[i].unref (); } this.parent (); } });