UNPKG

toloframework

Version:

Javascript/HTML/CSS compiler for Firefox OS or nodewebkit apps using modules in the nodejs style.

934 lines (863 loc) 31.3 kB
"use strict"; var Template = require("./boilerplate.view.template"); var Common = require("./boilerplate.view.common"); var camelCase = Common.camelCase; var CamelCase = Common.CamelCase; var isSpecial = Common.isSpecial; var RX_VIEW_ATTRIB = /^[a-z][a-z0-9]+(-[a-z0-9]+)*$/; var RX_INTEGER = /^[0-9]+$/; var RX_STD_ATT = /^[A-Za-z]+$/; var RX_TAG_NAME = /^[A-Z]+$/; var RX_CLS_NAME = /^[a-z][a-z0-9]*\.[a-z0-9.-]+$/; var RX_IDENTIFIER = /[_$a-z][_$a-z0-9]+/i; exports.generateCodeFrom = function( def, codeBehind, moduleName ) { try { var code = new Template( codeBehind, moduleName ); // Debug mode ? code.debug = def["view.debug"] ? true : false; // Define attributes. buildViewAttribs( def, code ); // Define static members. buildViewStatics( def, code ); // Look for any intialize function in code behinid. buildViewInit( def, code ); // Transform `def` to make it look like an HTML tag. def[0] = def[1]; def[1] = def[2]; delete def[2]; var rootElementName = buildElement( def, code ); code.section.elements.define.push("//-----------------------"); code.section.elements.define.push("// Declare root element."); code.section.elements.define.push( "Object.defineProperty( this, '$', {value: " + rootElementName + ".$, writable: false, enumerable: false, configurable: false } );"); // Generate full code. var key, val; code.requires.$ = "require('dom')"; if( code.pm ) { code.requires.PM = "require('tfw.binding.property-manager')"; } var out = codeBehind + "\n\n" + arrayToCodeWithNewLine([ "//===============================", "// XJS:View autogenerated code.", "try {" ]); if( code.section.comments.length > 0 ) { out += " /**\n * " + code.section.comments.join("\n * ") + "\n */\n"; } out += " module.exports = function() {\n"; out += arrayToCodeWithNewLine( code.generateRequires(), " " ); out += arrayToCodeWithNewLine( code.generateNeededBehindFunctions(), " " ); out += arrayToCodeWithNewLine( code.generateFunctions(), " " ); out += arrayToCodeWithNewLine( code.generateGlobalVariables(), " " ); out += " //-------------------\n"; out += " // Class definition.\n"; out += " var ViewClass = function( args ) {\n"; out += " try {\n"; out += " if( typeof args === 'undefined' ) args = {};\n"; out += " this.$elements = {};\n"; if( code.that ) out += " var that = this;\n"; if( code.pm ) out += " var pm = PM(this);\n"; out += generate( code.section.attribs.define, "Create attributes", " " ); out += generate( code.section.elements.define, "Create elements", " " ); out += generate( code.section.events, "Events", " " ); out += arrayToCodeWithNewLine( code.generateLinks(), " " ); out += generate( code.section.ons, "On attribute changed", " " ); out += generate( code.section.elements.init, "Initialize elements", " " ); out += generate( code.section.attribs.init, "Initialize attributes", " " ); if( code.section.init ) { out += " // Initialization.\n"; out += " CODE_BEHIND." + code.section.init + ".call( this );\n"; } out += " $.addClass(this, 'view', 'custom');\n"; out += " }\n"; out += " catch( ex ) {\n"; out += " console.error('" + moduleName + "', ex);\n"; out += " throw Error('Instantiation error in XJS of " + JSON.stringify(moduleName) + ":\\n' + ex)\n"; out += " }\n"; out += " };\n"; out += generate( code.section.statics, "Static members.", " " ); out += " return ViewClass;\n"; out += " }();\n"; out += arrayToCodeWithNewLine([ "}", "catch( ex ) {", " throw Error('Definition error in XJS of " + JSON.stringify(moduleName) + "\\n' + ex)", "}" ]); return out; } catch( ex ) { throw ex + "\n...in module " + moduleName; } }; /** * Return the code from an array and an identation. * The code is preceded by a comment. */ function generate( arr, comment, indent ) { if( typeof indent === 'undefined' ) indent = " "; if( arr.length === 0 ) return ""; var len = comment.length + 3; var out = indent + "//"; while( len --> 0 ) out += "-"; out += "\n"; out += indent + "// " + comment + ".\n"; arr.forEach(function (line) { out += indent + line + "\n"; }); return out; } /** * @example * {View DIV view.init:intialize} */ function buildViewInit( def, code ) { var init = def["view.init"]; if( typeof init === 'undefined' ) return; if( typeof init !== 'string' ) throw "The value of attribute `view.init` must be a string!"; code.addNeededBehindFunction( init ); code.section.init = init; } /** * Add static functions to the current class. * * @example * view.statics: ["show", "check"] * view.statics: "show" */ function buildViewStatics( def, code ) { var statics = def["view.statics"]; if( typeof statics === 'undefined' ) return; if( typeof statics === 'string' ) statics = [statics]; else if( !Array.isArray( statics ) ) { throw "view.statics must be a string or an array os strings!"; } statics.forEach(function (name) { code.addNeededBehindFunction( name ); code.section.statics.push( "ViewClass" + keySyntax(name) + " = CODE_BEHIND" + keySyntax(name) + ".bind(ViewClass);" ); }); } /** * @example * view.attribs: { * flat: true * type: {[default, primary, secondary]} * count: {integer} * content: {string ok behind: onContentChanged} * } */ function buildViewAttribs( def, code ) { var attribs = def["view.attribs"]; if( typeof attribs !== 'object' ) return; try { var attName, attValue; for( attName in attribs ) { if( !RX_VIEW_ATTRIB.test( attName ) ) throw "Bad syntax for attribute's name: " + JSON.stringify( attName ) + "\n" + "Example of valid syntax: action, duration-visible, ...\n" + "Example of invalid syntax: 0, durationVisible, ..."; attValue = attribs[attName]; var camelCaseAttName = camelCase( attName ); if( isSpecial( attValue ) || Array.isArray( attValue[0] ) ) { buildViewAttribsSpecial( camelCaseAttName, attValue, code ); } else { // flat: true buildViewAttribsInit( camelCaseAttName, attValue, code ); code.section.attribs.define.push( "pm.create(" + JSON.stringify(attName) + ");"); } } } catch( ex ) { throw ex + "\n...in view.attribs: " + JSON.stringify( attribs, null, " " ); } } /** * Attribute with casting. * @example * type: {[default, primary, secondary]} * count: {integer} * content: {string ok behind: onContentChanged} */ function buildViewAttribsSpecial( attName, attValue, code ) { var type = attValue[0]; var init = attValue[1]; var requireConverter = false; if( typeof attValue.behind !== 'undefined' ) buildViewAttribsSpecialCodeBehind( attName, attValue.behind, code ); if( Array.isArray( type ) ) { // Enumerate. code.addCast("enum"); code.pm = true; code.section.attribs.define.push( "pm.create(" + JSON.stringify(attName) + ", { cast: conv_enum(" + JSON.stringify(type) + ") });"); buildViewAttribsInit( attName, init, code ); } else { switch( type ) { case 'action': code.pm = true; code.section.attribs.define.push( "pm.createAction(" + JSON.stringify(attName) + ")"); break; case 'any': code.pm = true; code.section.attribs.define.push( "pm.create(" + JSON.stringify(attName) + ");"); buildViewAttribsInit( attName, init, code ); break; case 'boolean': case 'booleans': case 'color': case 'string': case 'strings': case 'array': case 'list': case 'intl': case 'unit': case 'units': case 'multilang': requireConverter = true; code.vars["conv_" + type] = "Converters.get('" + type + "')"; code.pm = true; code.section.attribs.define.push( "pm.create(" + JSON.stringify(attName) + ", { cast: conv_" + type + " });"); buildViewAttribsInit( attName, init, code ); break; case 'integer': case 'float': requireConverter = true; code.vars["conv_" + type] = "Converters.get('" + type + "')"; code.pm = true; // Is Not a number, take this default value. var nanValue = attValue.default; if( typeof nanValue !== 'number' || isNaN( nanValue ) ) { nanValue = 0; } code.section.attribs.define.push( "pm.create(" + JSON.stringify(attName) + ", { cast: conv_" + type + "(" + nanValue + ") });"); buildViewAttribsInit( attName, init, code ); break; default: throw "Unknown type \"" + type + "\" for attribute \"" + attName + "\"!"; } } if( requireConverter ) { code.requires.Converters = "require('tfw.binding.converters')"; } } /** * @example * view.attribs: { * size: {unit "32px", behind: onSizeChanged} * } */ function buildViewAttribsSpecialCodeBehind( attName, functionBehindName, code ) { if( typeof functionBehindName !== 'string' ) throw "The property \"behind\" of the attribute " + JSON.stringify( attName ) + " must be a string!"; code.that = true; code.addNeededBehindFunction( functionBehindName ); code.section.ons.push( "pm.on( " + JSON.stringify( attName ) + ", function(v) {", " try {", " CODE_BEHIND" + keySyntax( functionBehindName ) + ".call( that, v );", " }", " catch( ex ) {", " console.error('Exception in function behind " + JSON.stringify(functionBehindName) + " of module " + JSON.stringify(code.moduleName) + " for attribute " + JSON.stringify( attName ) + "! ');", " console.error( ex );", " }} );"); } /** * Initialize attribute with a value. Priority to the value set in the * contructor args. */ function buildViewAttribsInit( attName, attValue, code ) { if( attValue === undefined ) { code.section.attribs.init.push("this." + attName + " = args[" + JSON.stringify(attName) + "];"); } else { code.functions["defVal"] = "(args, attName, attValue) " + "{ return args[attName] === undefined ? attValue : args[attName]; }"; code.section.attribs.init.push("this." + attName + " = defVal(args, " + JSON.stringify(attName) + ", " + JSON.stringify(attValue) + ");"); } } /** * */ function buildElement( def, code, varName ) { varName = getVarName(def, varName); addUnique( code.elementNames, varName ); try { var type = def[0]; if( RX_TAG_NAME.test( type ) ) buildElementTag( def, code, varName ); else if ( RX_CLS_NAME.test( type ) ) buildElementCls( def, code, varName ); else throw "Unknown element name: " + JSON.stringify( type ) + "!\n" + "Names must have one of theses syntaxes: " + JSON.stringify( RX_TAG_NAME.toString() ) + " or " + JSON.stringify( RX_CLS_NAME.toString() ) + ".\n" + JSON.stringify( def ); // Children. var children = def[1]; if( typeof children === 'undefined' ) return varName; if( isSpecial( children ) ) { buildElementSpecialChild( children, code, varName ); return varName; } if( !Array.isArray( children ) ) children = [children]; var toAdd = []; children.forEach(function (child, index) { if( typeof child === 'string' || typeof child === 'number') { toAdd.push( JSON.stringify("" + child) ); } else { var childVarName = buildElement( child, code, code.id( varName, index ) ); toAdd.push( childVarName ); } }); if( toAdd.length > 0 ) { code.section.elements.define.push( "$.add( " + varName + ", " + toAdd.join(", ") + " );"); } } catch( ex ) { throw ex + "\n...in element \"" + varName + "\""; } finally { // Store the variable for use in code behind. var id = def["view.id"]; if( typeof id === 'string' ) { code.section.elements.define.push( "this.$elements." + camelCase( id ) + " = " + varName + ";"); } //code.section.elements.define.push( // "this.$elements" + keySyntax(varName) + " = " + varName + ";"); } return varName; } function buildElementTag( def, code, varName ) { var attribs = extractAttribs( def ); var arr = Object.keys( attribs.standard ); code.requires["Tag"] = "require('tfw.view').Tag"; code.section.elements.define.push( "var " + varName + " = new Tag('" + def[0] + "'" + (arr.length > 0 ? ', ' + JSON.stringify(arr) : '') + ");"); var initAttribs = buildElementTagAttribsStandard( attribs.standard, code, varName ); buildInitAttribsForTag( initAttribs, code, varName ); buildElementEvents( attribs.special, code, varName ); buildElementTagClassSwitcher( attribs.special, code, varName ); buildElementTagAttribSwitcher( attribs.special, code, varName ); buildElementTagStyle( attribs.special, code, varName ); buildElementTagChildren( attribs.special, code, varName ); buildElementTagOn( attribs.special, code, varName ); } function buildInitAttribsForTag( initAttribs, code, varName ) { initAttribs.forEach(function (initAttrib) { var attName = initAttrib[0]; var attValue = initAttrib[1]; code.section.elements.init.push( varName + keySyntax(attName) + " = " + attValue + ";"); }); } function buildElementCls( def, code, varName ) { var attribs = extractAttribs( def ); buildElementEvents( attribs.special, code, varName ); var arr = Object.keys( attribs.standard ); var viewName = CamelCase( def[0] ); code.requires[viewName] = "require('" + def[0] + "')"; var initAttribs = buildElementTagAttribsStandard( attribs.standard, code, varName ); buildInitAttribsForCls( initAttribs, code, varName, viewName ); buildElementTagClassSwitcher( attribs.special, code, varName ); buildElementTagAttribSwitcher( attribs.special, code, varName ); buildElementTagStyle( attribs.special, code, varName ); buildElementTagOn( attribs.special, code, varName ); } function buildInitAttribsForCls( initAttribs, code, varName, viewName ) { var item, attName, attValue; if( initAttribs.length === 0 ) { code.section.elements.define.push( "var " + varName + " = new " + viewName + "();"); } else if( initAttribs.length === 1 ) { item = initAttribs[0]; attName = item[0]; attValue = item[1]; code.section.elements.define.push( "var " + varName + " = new " + viewName + "({ " + attName + ": " + attValue + " });"); } else { code.section.elements.define.push( "var " + varName + " = new " + viewName + "({" ); initAttribs.forEach(function (item, index) { attName = item[0]; attValue = item[1]; code.section.elements.define.push( " " + attName + ": " + attValue + (index < initAttribs.length - 1 ? "," : "") ); }); code.section.elements.define.push( "});" ); } } /** * Generate code from attributes starting with "class.". * For instance `class.blue: {Bind focused}` means that the class * `blue` must be added if the attribute `focused` is `true` and * removed if `focused` is `false`. * On the contrary, `class.|red: {Bind focused}` means that the * class `red` must be removed if `focused` is `true` and added if * `focused` is `false`. * Finally, `class.blue|red: {Bind focused}` is the mix of both * previous syntaxes. */ function buildElementTagClassSwitcher( def, code, varName ) { var attName, attValue, classes, classesNames, className; for( attName in def ) { classesNames = getSuffix( attName, "class." ); if( !classesNames ) continue; attValue = def[attName]; if( classesNames === '*' ) { buildElementTagClassSwitcherStar( attValue, code, varName ); continue; } if( !attValue || attValue[0] !== 'Bind' ) { throw "Only bindings are accepted as values for class-switchers!\n" + attName + ": " + JSON.stringify(attValue); } classes = classesNames.split("|"); var actions = []; if( isNonEmptyString( classes[0] ) ) { code.requires.$ = "require('dom')"; code.functions["addClassIfTrue"] = "(element, className, value) {\n" + " if( value ) $.addClass(element, className);\n" + " else $.removeClass(element, className); };"; className = JSON.stringify(classes[0]); actions.push("addClassIfTrue( " + varName + ", " + className + ", v );"); } if( isNonEmptyString( classes[1] ) ) { code.requires.$ = "require('dom')"; code.functions["addClassIfFalse"] = "(element, className, value) {\n" + " if( value ) $.removeClass(element, className);\n" + " else $.addClass(element, className); };"; className = JSON.stringify(classes[1]); actions.push("addClassIfFalse( " + varName + ", " + className + ", v );"); } code.addLinkFromBind( attValue, actions ); } } /** * @example * view.children: {Bind names map:makeListItem} * view.children: {List names map:makeListItem} */ function buildElementTagChildren( def, code, varName ) { var attValue = def["view.children"]; if( typeof attValue === 'undefined' ) return; if( typeof attValue === 'string' ) { // Transformer la première ligne en la seconde: // 1) view.children: names // 2) view.children: {Bind names} attValue = {"0": 'Bind', "1": attValue }; } if( isSpecial( attValue, "bind" ) ) { attValue.action = [ "// Updating children of " + varName + ".", "$.clear(" + varName + ");", "v.forEach(function (elem) {", " $.add(" + varName + ", elem);", "});" ]; code.addLinkFromBind( attValue, attValue ); } else if( isSpecial( attValue, "list" ) ) { var codeBehindFuncName = attValue.map; code.requires.ListHandler = "require('tfw.binding.list-handler');"; if( typeof codeBehindFuncName === 'string' ) { code.addNeededBehindFunction( codeBehindFuncName ); code.section.elements.init.push( "ListHandler(this, " + varName + ", " + JSON.stringify(attValue[1]) + ", {", " map: CODE_BEHIND" + keySyntax(codeBehindFuncName) + ".bind(this)", "});" ); } else { // List handler without mapping. code.section.elements.init.push( "ListHandler(this, " + varName + ", " + JSON.stringify(attValue[1]) + ");" ); } } else { throw "Error in `view.children`: invalid syntax!\n" + JSON.stringify( attValue ); } } /** * @example * style.width: {Bind size} * style.width: "24px" */ function buildElementTagStyle( def, code, varName ) { var attName, attValue, styleName; for( attName in def ) { styleName = getSuffix( attName, "style." ); if( !styleName ) continue; attValue = def[attName]; if( typeof attValue === 'string' ) { code.section.elements.init.push( varName + ".$.style" + keySyntax(styleName) + " = " + JSON.stringify( attValue ) ); continue; } if( !attValue || attValue[0] !== 'Bind' ) { throw "Only bindings and strings are accepted as values for styles!\n" + attName + ": " + JSON.stringify(attValue); } var attributeToBindOn = attValue[1]; if( typeof attributeToBindOn !== 'string' ) throw "Binding syntax error for styles: second argument must be the name of a function behind!\n" + attName + ": " + JSON.stringify(attValue); var converter = attValue.converter; if( typeof converter === 'string' ) { code.addCast( converter ); code.section.ons.push( "pm.on(" + JSON.stringify(attValue[1]) + ", function(v) {", " " + varName + ".$.style[" + JSON.stringify(styleName) + "] = " + "conv_" + converter + "( v );", "});"); } else { code.section.ons.push( "pm.on(" + JSON.stringify(attValue[1]) + ", function(v) {", " " + varName + ".$.style[" + JSON.stringify(styleName) + "] = v;", "});"); } } } /** * @example * {tfw.view.button on.action: OnAction} */ function buildElementTagOn( def, code, varName ) { var attName, behindFunctionName, targetAttributeName; for( attName in def ) { targetAttributeName = getSuffix( attName, "on." ); if( !targetAttributeName ) continue; behindFunctionName = def[attName]; if( typeof behindFunctionName !== 'string' ) throw "The behind function name must be a string!\n" + attName + ": <" + typeof behindFunctionName + ">"; code.addNeededBehindFunction( behindFunctionName ); code.that = true; code.section.ons.push( "PM(" + varName + ").on(" + JSON.stringify(targetAttributeName) + ", function(v) {", " try { CODE_BEHIND" + keySyntax( behindFunctionName ) + ".call(that, v, " + varName + "); }", " catch( ex ) {", " console.error( ex );", " throw \"Error in behind function '" + behindFunctionName + "' for 'on." + targetAttributeName + "' of element '" + varName + "'!\";", " }", "});" ); } } /** * Generate code from attributes starting with "attrib.". For * instance `attrib.|disabled: {Bind enabled}` means that the attrib * `disabled` must be added if the attribute `enabled` is `true` and * removed if `enabled` is `false`. * The syntax is exctly the same as for class switchers. */ function buildElementTagAttribSwitcher( def, code, varName ) { var attName, attValue, attribs, attribsNames, attribName; for( attName in def ) { attribsNames = getSuffix( attName, "attrib." ); if( !attribsNames ) continue; attValue = def[attName]; if( !attValue || attValue[0] !== 'Bind' ) { throw "Only bindings are accepted as values for attrib-switchers!\n" + attName + ": " + JSON.stringify(attValue); } attribs = attribsNames.split("|"); var actions = []; if( isNonEmptyString( attribs[0] ) ) { code.requires.$ = "require('dom')"; code.functions["addAttribIfTrue"] = "(element, attribName, value) {\n" + " if( value ) $.att(element, attribName);\n" + " else $.removeAtt(element, attribName); };"; attribName = JSON.stringify(attribs[0]); actions.push("addAttribIfTrue( " + varName + ", " + attribName + ", v );"); } if( isNonEmptyString( attribs[1] ) ) { code.requires.$ = "require('dom')"; code.functions["addAttribIfFalse"] = "(element, attribName, value) {\n" + " if( value ) $.removeAtt(element, attribName);\n" + " else $.att(element, attribName); };"; attribName = JSON.stringify(attribs[1]); actions.push("addAttribIfFalse( " + varName + ", " + attribName + ", v );"); } code.addLinkFromBind( attValue, actions ); } } /** * @example * class.*: {[flat,type,pressed] getClasses} * class.*: [ {[flat,pressed] getClasses1}, {[value,pressed] getClasses2} ] */ function buildElementTagClassSwitcherStar( items, code, varName ) { if( !Array.isArray( items ) ) items = [items]; items.forEach(function (item, index) { code.that = true; if( typeof item[0] === 'string' ) item[0] = [item[0]]; var pathes = ensureArrayOfStrings( item[0], "class.*: " + JSON.stringify( items ) ); var behindFunctionName = ensureString( item[1], "The behind function must be a string!" ); pathes.forEach(function (path) { code.addLink({path: path}, {action: [ varName + ".applyClass(", " CODE_BEHIND" + keySyntax( behindFunctionName ) + ".call(that,v," + JSON.stringify(path) + "), " + index + ");" ]}); }); }); } function buildElementEvents( attribs, code, varName ) { var attName, attValue; var eventHandlers = []; for( attName in attribs ) { var eventName = getSuffix( attName, "event." ); if( !eventName ) continue; attValue = attribs[attName]; if( typeof attValue === 'string' ) { // Using a function from code behind. code.addNeededBehindFunction( attValue ); eventHandlers.push( JSON.stringify( eventName ) + ": CODE_BEHIND" + keySyntax(attValue) + ".bind( this )," ); } else { eventHandlers.push( JSON.stringify( eventName ) + ": function(v) {" ); eventHandlers = eventHandlers.concat( generateFunctionBody(attValue, code, " ") ); eventHandlers.push("},"); } } if( eventHandlers.length > 0 ) { code.requires.View = "require('tfw.view');"; code.section.events.push( "View.events(" + varName + ", {" ); // Retirer la virgule de la dernière ligne. var lastLine = eventHandlers.pop(); eventHandlers.push( lastLine.substr(0, lastLine.length - 1) ); // Indenter. code.section.events = code.section.events.concat( eventHandlers.map(x => " " + x) ); code.section.events.push( "});" ); } } /** * @return * If there are constant attribs, they are returned. * For instance, for `{tfw.view.button icon:gear wide:true flat:false content:{Bind label}}` * the result will be : [ * ["icon", "gear"], * ["wide", true], * ["flat", false] * ] * @example * {DIV class: {Bind value}} * {DIV class: hide} * {DIV textContent: {Intl title}} */ function buildElementTagAttribsStandard( attribs, code, varName ) { var attName, attValue; var initParameters = []; for( attName in attribs ) { attValue = attribs[attName]; if( isSpecial( attValue, "bind" ) ) { code.addLinkFromBind( attValue, varName + "/" + attName ); } else if( isSpecial( attValue, "intl" ) ) { initParameters.push([ attName, "_(" + JSON.stringify( attValue[1] ) + ")" ]); } else { initParameters.push([ attName, JSON.stringify( attValue ) ]); } } return initParameters; } function buildElementSpecialChild( def, code, varName ) { var type = def[0]; if( type !== 'Bind' ) throw "For tag elements, the children can be defined by an array or by `{Bind ...}`!\n" + "You provided `{" + type + " ...}`."; code.pm = true; code.section.ons.push( "pm.on('" + def[1] + "', function(v) { $.clear(" + varName + ", v); });"); } function generateFunctionBody(def, code, indent) { code.that = true; var output = []; if( !Array.isArray( def ) ) def = [def]; def.forEach(function (item) { if( typeof item === 'string' ) { code.that = true; output.push( indent + item + ".call( that, v );" ); } else if( isSpecial ) { var type = item[0].toLowerCase(); var generator = functionBodyGenerators[type]; if( typeof generator !== 'function' ) { throw "Don't know how to build a function body from " + JSON.stringify( def ) + "!\n" + "Known commands are: " + Object.keys(functionBodyGenerators).join( ", " ) + "."; } generator( output, item, code, indent ); } }); return output; } var functionBodyGenerators = { toggle: function( output, def, code, indent ) { var elemVar = getElementVarFromPath( def[1] ).join( "" ); output.push( indent + elemVar + " = " + elemVar + " ? false : true;"); }, set: function( output, def, code, indent ) { var elemVar = getElementVarFromPath( def[1] ).join( "" ); output.push( indent + elemVar + " = " + getValueCode( def[2] ) + ";" ); }, behind: function( output, def, code, indent ) { var behindFunctionName = ensureString( def[1], "In {Behind <name>}, <name> must be a string!" ); var lines = code.generateBehindCall( behindFunctionName, indent, "v" ); output.push.apply( output, lines ); } }; function getValueCode( input ) { if( isSpecial( input, "bind" ) ) { return "that" + keySyntax( input[1] ); } return JSON.stringify( input ); } /** * A path is a string which represent a variable. */ function getElementVarFromPath( path, root ) { if( typeof root === 'undefined' ) root = "that"; var items = path.split("/").map( function(item) { return camelCase( item.trim() ); }); if( items.length === 1 ) items.unshift( root ); if( items[0] === "" ) items[0] = root; var result = items.map(function(x, i) { if( i === 0 ) { if( x === root ) return x; return "element" + CamelCase(x); } if( i === items.length - 1 ) return "." + x; return keySyntax( x ) ; }); return result; } function path2BindPod( path, root ) { var items = getElementVarFromPath( path, root ); var pod = { name: items.pop().substr(1) }; pod.obj = items.join(""); return pod; } /** * An attribute is marked as _special_ as soon as it ha a dot in its name. * `view.attribs` is special, but `attribs` is not. * Attributes with a numeric key are marked as _implicit_. * @return `{ standard: {...}, special: {...}, implicit: [...] }`. */ function extractAttribs( def ) { var key, val, attribs = { standard: {}, special: {}, implicit: [] }; for( key in def ) { val = def[key]; if( RX_INTEGER.test( key ) ) { attribs.implicit.push( val ); } else if( RX_STD_ATT.test( key ) ) { attribs.standard[key] = val; } else { attribs.special[key] = val; } } return attribs; } /** * Check if an object has attributes or not. * It is empty if it has no attribute. */ function isEmptyObj( obj ) { for( var k in obj ) return false; return true; } /** * Check if an object as at least on attribute. */ function hasAttribs( obj ) { for( var k in obj ) return true; return false; } function getSuffix( text, prefix ) { if( text.substr( 0, prefix.length ) !== prefix ) return null; return text.substr( prefix.length ); } function getVarName( def, defaultVarName ) { var id = def["view.id"]; if( typeof id === 'undefined' ) { if( typeof defaultVarName === 'undefined' ) return "e_"; return defaultVarName; } return "e_" + camelCase(id); } function addUnique(arr, item) { if( arr.indexOf( item ) === -1 ) { arr.push( item ); } } function keySyntax( name ) { if( RX_IDENTIFIER.test( name ) ) return "." + name; return "[" + JSON.stringify( name ) + "]"; } function isNonEmptyString( text ) { if( typeof text !== 'string' ) return false; return text.trim().length > 0; } function clone( obj ) { return JSON.parse( JSON.stringify( obj ) ); } function arrayToCode( arr, indent ) { if( typeof indent !== 'string' ) indent = ""; return indent + arr.join("\n" + indent); } function arrayToCodeWithNewLine( arr, indent ) { if( arr.length === 0 ) return ""; return arrayToCode(arr, indent) + "\n"; } function ensureArrayOfStrings( arr, msg ) { if( typeof msg === 'undefined' ) msg = ''; if( !Array.isArray( arr ) ) throw (msg + "\n" + "Expected an array of strings!").trim(); arr.forEach(function (item, index) { if( typeof item !== 'string' ) throw (msg + "\n" + "Item #" + index + " must be a string!").trim(); }); return arr; } function ensureString( str, msg ) { if( typeof msg === 'undefined' ) msg = ''; if( typeof str !== 'string' ) throw (msg + "\n" + "Expected a string!").trim(); return str; }