const Compiler = require('./compiler'); const Component = require('./component'); const Type = require('./type'); const kebabToCamel = require('./string-util').kebabToCamel; /** * Compiles a @Vn.Object from element tag. */ module.exports = new Class({ Extends: Compiler ,_links: [] /** * Creates a object context. */ ,compile(builder, node, tagName) { const klass = vnCustomTags[tagName]; if (!klass) return null; const context = { klass, props: {}, funcProps: {}, objectProps: {}, childs: [], events: {}, custom: null }; const a = node.attributes; for (let i = 0; i < a.length; i++) { const attribute = a[i].nodeName; const value = a[i].nodeValue; if (this.isEvent(attribute)) { const handler = this._getMethod(value) if (handler) context.events[attribute.substr(3)] = handler; } else if (!/^(id|property)$/.test(attribute)) { this.propCompile(context, node, attribute, value); } } const childNodes = node.childNodes; if (childNodes) for (let i = 0; i < childNodes.length; i++) { const child = childNodes[i]; const isElement = child.nodeType === Node.ELEMENT_NODE; const childTagName = isElement ? child.tagName.toLowerCase() : null; let childContext; if (childTagName === 'pointer') { this._addLink(context, null, child.getAttribute('object')); } else if (childTagName === 'custom') { context.custom = child; } else if (childContext = builder._compile(child)) { let prop = isElement ? child.getAttribute('property') : null; if (prop) { prop = kebabToCamel(prop); context.objectProps[prop] = childContext.id; } else context.childs.push(childContext.id); } } return context; } ,propCompile(context, node, attribute, value) { const tagName = node.tagName; const propName = kebabToCamel(attribute); const propInfo = context.klass.Properties[propName]; if (!value) { this.showError('Attribute \'%s\' empty on tag \'%s\'', attribute, tagName); return; } if (propName == 'vModel') { context.vModel = this.modelExpr(value); return; } if (!propInfo) { this.showError('Attribute \'%s\' not valid for tag \'%s\'', attribute, tagName); return; } if (this.isExpr(value)) { this.compileExpr(context, propName, value); } else { let isLink = false; let propError = false; let newValue = null; 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: context.funcProps[propName] = this._getMethod(value); break; case Type: newValue = window[value]; break; default: if (propInfo.enumType) newValue = propInfo.enumType[value]; else if (propInfo.type instanceof Function) isLink = true; else propError = true; } if (isLink) this._addLink(context, propName, value); else if (newValue !== null && newValue !== undefined) context.props[propName] = newValue; else if (propError) this.showError('Attribute \'%s\' invalid for tag \'%s\'', attribute, tagName); } } ,instantiate(doc, context, scope) { const object = new context.klass(); object.setProperties(context.props); if (context.nodeId && object instanceof Component) { const id = context.nodeId; object.htmlId = scope.getHtmlId(id); object.className = '_'+ id +' '+ (object.className || ''); } return object; } ,setProperty(object, property, value) { object[property] = value; } ,preLink(scope) { const objects = scope.objects; const links = this._links; for (let i = links.length - 1; i >= 0; i--) { const link = links[i]; const object = objects[link.context.id]; const objectRef = scope.$[link.objectId]; if (objectRef === undefined) { this.showError('Referenced unexistent object with id \'%s\'', link.objectId); continue; } if (link.prop) object[link.prop] = objectRef; else object.appendChild(objectRef); } } ,link(context, object, objects, scope) { const objectProps = context.objectProps; for (const prop in objectProps) object[prop] = objects[objectProps[prop]]; const childs = context.childs; for (let i = 0; i < childs.length; i++) object.appendChild(objects[childs[i]]); const funcProps = context.funcProps; for (const prop in funcProps) object[prop] = this.bindMethod(funcProps[prop], scope); const events = context.events; for (const event in events) { const listener = this.bindMethod(events[event], scope); if (listener) object.on(event, listener, scope.thisArg); } if (context.vModel) { object.on('change', function(lot) { context.vModel.call(scope.thisArg, scope.$, lot.$); scope.digest(); }, scope); } if (context.custom) object.loadXml(scope, context.custom); } ,_addLink(context, prop, objectId) { this._links.push({ context ,prop ,objectId: kebabToCamel(objectId) }); } ,_replaceFunc(token) { return token.charAt(1).toUpperCase(); } });