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
JavaScript
;
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;
}