alloy
Version:
Appcelerator Titanium MVC Framework
970 lines (843 loc) • 30.7 kB
JavaScript
var U = require('../../utils'),
colors = require('colors'),
path = require('path'),
os = require('os'),
fs = require('fs'),
wrench = require('wrench'),
jsonlint = require('jsonlint'),
logger = require('../../logger'),
astController = require('./ast/controller'),
_ = require('../../lib/alloy/underscore')._,
styler = require('./styler'),
XMLSerializer = require("xmldom").XMLSerializer,
CONST = require('../../common/constants');
///////////////////////////////////////
////////// private variables //////////
///////////////////////////////////////
var alloyRoot = path.join(__dirname,'..','..'),
platformsDir = path.join(alloyRoot,'..','platforms'),
alloyUniqueIdPrefix = '__alloyId',
alloyUniqueIdCounter = 0,
JSON_NULL = JSON.parse('null'),
compilerConfig;
///////////////////////////////
////////// constants //////////
///////////////////////////////
var RESERVED_ATTRIBUTES = [
'platform',
'formFactor',
'if',
CONST.BIND_COLLECTION,
CONST.BIND_WHERE,
CONST.AUTOSTYLE_PROPERTY,
'ns',
'method',
'module'
],
RESERVED_ATTRIBUTES_REQ_INC = [
'platform',
'type',
'src',
'formFactor',
'if',
CONST.BIND_COLLECTION,
CONST.BIND_WHERE,
CONST.AUTOSTYLE_PROPERTY,
'ns',
'method',
'module'
],
RESERVED_EVENT_REGEX = /^on([A-Z].+)/;
// load CONDITION_MAP with platforms
exports.CONDITION_MAP = {
handheld: {
runtime: "!Alloy.isTablet"
},
tablet: {
runtime: "Alloy.isTablet"
}
};
_.each(CONST.PLATFORMS, function(p) {
exports.CONDITION_MAP[p] = require(path.join(platformsDir,p,'index'))['condition'];
});
exports.bindingsMap = {};
exports.destroyCode = '';
exports.postCode = '';
exports.models = [];
exports.dataFunctionNames = {};
//////////////////////////////////////
////////// public interface //////////
//////////////////////////////////////
exports.getCompilerConfig = function() {
return compilerConfig;
};
exports.generateVarName = function(id, name) {
if (_.contains(CONST.JS_RESERVED_ALL,id)) {
U.die([
'Invalid ID "' + id + '" for <' + name + '>.',
'Can\'t use reserved Javascript words as IDs.',
'Reserved words: [' + CONST.JS_RESERVED_ALL.sort().join(',') + ']'
]);
}
return '$.__views.' + id;
};
exports.generateUniqueId = function() {
return alloyUniqueIdPrefix + alloyUniqueIdCounter++;
};
exports.getNodeFullname = function(node) {
var name = node.nodeName,
ns = node.getAttribute('ns') || CONST.IMPLICIT_NAMESPACES[name] || CONST.NAMESPACE_DEFAULT,
fullname = ns + '.' + name;
return fullname;
};
exports.isNodeForCurrentPlatform = function(node) {
var isForCurrentPlatform = !node.hasAttribute('platform') || !compilerConfig || !compilerConfig.alloyConfig;
_.each(node.getAttribute('platform').split(','), function(p) {
// need to account for multiple platforms and negation, such as
// platform=ios,android or platform=!ios or platform="android,!mobileweb"
p = p.trim();
if(p === compilerConfig.alloyConfig.platform || (p.indexOf('!') === 0 && p.slice(1) !== compilerConfig.alloyConfig.platform)) {
isForCurrentPlatform = true;
}
});
return isForCurrentPlatform;
};
exports.getParserArgs = function(node, state, opts) {
state = state || {};
opts = opts || {};
var defaultId = opts.defaultId || undefined,
doSetId = opts.doSetId === false ? false : true,
name = node.nodeName,
ns = node.getAttribute('ns') || CONST.IMPLICIT_NAMESPACES[name] || CONST.NAMESPACE_DEFAULT,
fullname = ns + '.' + name,
id = node.getAttribute('id') || defaultId || exports.generateUniqueId(),
platform = node.getAttribute('platform'),
formFactor = node.getAttribute('formFactor'),
tssIf = node.getAttribute('if'),
platformObj;
// make sure we're not reusing the default ID for the first top level element
if (id === exports.currentDefaultId &&
(node.parentNode && node.parentNode.nodeName !== 'Alloy') &&
!node.__idWarningHandled) {
logger.warn([
'<' + name + '> at line ' + node.lineNumber +
' is using this view\'s default ID "' + id + '". ' +
'Only a top-level element in a view should use the default ID'
]);
node.__idWarningHandled = true;
}
// handle binding arguments
var bindObj = {};
bindObj[CONST.BIND_COLLECTION] = node.getAttribute(CONST.BIND_COLLECTION);
bindObj[CONST.BIND_WHERE] = node.getAttribute(CONST.BIND_WHERE);
bindObj[CONST.BIND_TRANSFORM] = node.getAttribute(CONST.BIND_TRANSFORM);
bindObj[CONST.BIND_FUNCTION] = node.getAttribute(CONST.BIND_FUNCTION);
// cleanup namespaces and nodes
ns = ns.replace(/^Titanium\./, 'Ti.');
if (doSetId && !_.contains(CONST.MODEL_ELEMENTS, fullname)) {
node.setAttribute('id', id);
}
// process the platform attribute
if (platform) {
platformObj = {};
_.each((platform).split(','), function(p) {
var matches = U.trim(p).match(/^(\!{0,1})(.+)/);
if (matches !== null) {
var negate = matches[1];
var name = matches[2];
if (_.contains(CONST.PLATFORMS, name)) {
if (negate === '!') {
_.each(_.without(CONST.PLATFORMS, name), function(n) {
platformObj[n] = true;
});
} else {
platformObj[name] = true;
}
return;
}
}
U.die('Invalid platform type found: ' + p);
});
}
// get create arguments and events from attributes
var createArgs = {},
events = [];
var attrs = _.contains(['Alloy.Require'], fullname) ?
RESERVED_ATTRIBUTES_REQ_INC :
RESERVED_ATTRIBUTES;
// determine whether to autoStyle this component
// 1. autoStyle attribute
// 2. autoStyle from <Alloy>
// 3. autoStyle from config.json
var autoStyle = (function() {
var prop = CONST.AUTOSTYLE_PROPERTY;
if (node.hasAttribute(prop)) {
return node.getAttribute(prop) === 'true';
} else {
return exports[prop];
}
})();
// TODO: Add the apiName until TIMOB-12553 is resolved
if (autoStyle) {
createArgs[CONST.APINAME_PROPERTY] = fullname;
}
_.each(node.attributes, function(attr) {
var attrName = attr.nodeName;
if (_.contains(attrs, attrName)) { return; }
var matches = attrName.match(RESERVED_EVENT_REGEX);
if (matches !== null && exports.isNodeForCurrentPlatform(node) && !_.contains(CONST.SPECIAL_PROPERTY_NAMES, attrName)) {
events.push({
name: U.lcfirst(matches[1]),
value: node.getAttribute(attrName)
});
} else {
var theValue = node.getAttribute(attrName);
if (/^\s*(?:(?:Ti|Titanium|Alloy.Globals|Alloy.CFG)\.|L\(.+\)\s*$)/.test(theValue)) {
var match = theValue.match(/^\s*L\([^'"]+\)\s*$/);
if (match !== null) {
theValue = theValue.replace(/\(/g, '("').replace(/\)/g, '")');
}
theValue = styler.STYLE_EXPR_PREFIX + theValue;
}
if (attrName === 'class') {
if (autoStyle) {
createArgs[CONST.CLASS_PROPERTY] = theValue.split(/\s+/) || [];
}
} else {
createArgs[attrName] = theValue;
}
}
});
if (autoStyle && !createArgs[CONST.CLASS_PROPERTY]) {
createArgs[CONST.CLASS_PROPERTY] = [];
}
return _.extend({
ns: ns,
name: name,
id: id,
fullname: fullname,
formFactor: node.getAttribute('formFactor'),
symbol: exports.generateVarName(id, name),
classes: node.getAttribute('class').split(' ') || [],
tssIf: node.getAttribute('if').split(',') || [],
parent: state.parent || {},
platform: platformObj,
createArgs: createArgs,
events: events
}, bindObj);
};
exports.generateNodeExtended = function(node, state, newState) {
return exports.generateNode(node, _.extend(_.clone(state), newState));
};
exports.generateNode = function(node, state, defaultId, isTopLevel, isModelOrCollection) {
if (node.nodeType != 1) return '';
if(!exports.isNodeForCurrentPlatform(node)) {
return '';
}
var args = exports.getParserArgs(node, state, { defaultId: defaultId }),
codeTemplate = "if (<%= condition %>) {\n<%= content %>}\n",
code = {
content: '',
pre: ''
};
// Check for platform specific considerations
var conditionType = compilerConfig && compilerConfig.alloyConfig && compilerConfig.alloyConfig.platform ? 'compile' : 'runtime';
if (args.platform) {
var conditionArray = [];
_.each(args.platform, function(v,k) {
conditionArray.push(exports.CONDITION_MAP[k][conditionType]);
});
code.condition = '(' + conditionArray.join(' || ') + ')';
}
//Add form factor condition, if application form-factor specific runtime check
if (args.formFactor && exports.CONDITION_MAP[args.formFactor]) {
var check = exports.CONDITION_MAP[args.formFactor].runtime;
code.condition = (code.condition) ? code.condition += ' && ' + check : check;
}
// ALOY-871: add the if condition check
args.tssIf = _.compact(args.tssIf);
if(args.tssIf.length >0) {
if(code.condition) {
code.condition += (' && (' + args.tssIf.join(' || ') + ')');
} else {
code.condition = args.tssIf.join(' || ');
}
}
// pass relevant conditional information in state
if (code.condition) {
if (state.condition) {
state.condition += '&&' + code.condition;
} else {
state.condition = code.condition;
}
}
// Determine which parser to use for this node
var parsersDir = path.join(alloyRoot,'commands','compile','parsers');
var parserRequire = 'default';
if (_.contains(fs.readdirSync(parsersDir), args.fullname+'.js')) {
parserRequire = args.fullname+'.js';
}
// Execute the appropriate tag parser and append code
var isLocal = state.local;
// [ALOY-787] keeping track of widget id
var widgetId = state.widgetId;
state = require('./parsers/' + parserRequire).parse(node, state) || { parent: {} };
code.content += state.code;
state.widgetId = widgetId;
// Use local variable if given
if (isLocal && state.parent) { args.symbol = state.parent.symbol || args.symbol; }
// Use manually given args.symbol if present
if (state.args) { args.symbol = state.args.symbol || args.symbol; }
// add to list of top level views, if its top level
if (isTopLevel) {
if (state.isProxyProperty) {
delete state.isProxyProperty;
code.content += state.parent.symbol + ' && $.addProxyProperty("' + state.propertyName +
'", ' + state.parent.symbol + ');';
} else {
code.content += args.symbol + ' && $.addTopLevelView(' + args.symbol + ');';
}
}
// handle any model/collection code
if (state.modelCode) {
code.pre += state.modelCode;
delete state.modelCode;
}
// handle any events from markup
if (args.events && args.events.length > 0 &&
!_.contains(CONST.SKIP_EVENT_HANDLING, args.fullname) &&
!state.isViewTemplate) {
// determine which function name to use for event handling:
// * addEventListener() for Titanium proxies
// * on() for everything else (controllers, models, collections)
var eventFunc = /^Alloy\.(?:Collection|Model|Require|Widget)/.test(args.fullname) ?
'on' : 'addEventListener';
_.each(args.events, function(ev) {
var eventObj = {
obj: isModelOrCollection ? state.args.symbol : args.symbol,
ev: ev.name,
cb: ev.value,
escapedCb: ev.value.replace(/'/g, "\\'"),
func: eventFunc
}, postCode;
// create templates for immediate and deferred event handler creation
var theDefer = _.template("__defers['<%= obj %>!<%= ev %>!<%= escapedCb %>']", eventObj);
var theEvent;
if (eventFunc === 'addEventListener') {
theEvent = _.template("$.addListener(<%= obj %>,'<%= ev %>',<%= cb %>)", eventObj);
} else {
theEvent = _.template("<%= obj %>.<%= func %>('<%= ev %>',<%= cb %>)", eventObj);
}
var deferTemplate = theDefer + " && " + theEvent + ";";
var immediateTemplate;
if (/[\.\[]/.test(eventObj.cb)) {
immediateTemplate = "try{" + theEvent + ";}catch(e){" + theDefer + "=true;}";
} else {
immediateTemplate = "<%= cb %>?" + theEvent + ":" + theDefer + "=true;";
}
// add the generated code to the view code and post-controller code respectively
code.content += _.template(immediateTemplate, eventObj);
postCode = _.template(deferTemplate, eventObj);
exports.postCode += state.condition ? _.template(codeTemplate, {
condition: state.condition,
content: postCode
}) : postCode;
});
}
// Continue parsing if necessary
if (state.parent) {
var states = _.isArray(state.parent) ? state.parent : [state.parent];
_.each(states, function(p) {
var parent = p.node;
if (!parent) { return; }
for (var i = 0, l = parent.childNodes.length; i < l; i++) {
var newState = _.defaults({ parent: p }, state);
if(node.hasAttribute('formFactor') || state.parentFormFactor) {
// propagate the form factor down through the hierarchy
newState.parentFormFactor = (node.getAttribute('formFactor') || state.parentFormFactor);
}
code.content += exports.generateNode(parent.childNodes.item(i), newState);
}
});
}
if (!isModelOrCollection) {
return code.condition ? _.template(codeTemplate, code) : code.content;
} else {
return {
content: code.condition ? _.template(codeTemplate, code) : code.content,
pre: code.condition ? _.template(codeTemplate, {content:code.pre}) : code.pre
};
}
};
exports.expandRequireNode = function(requireNode, doRecursive) {
var cloneNode = requireNode.cloneNode(true);
function getViewRequirePath(node) {
var regex = new RegExp('\\.' + CONST.FILE_EXT.VIEW + '$'),
src = node.getAttribute('src'),
fullname = exports.getNodeFullname(node),
name = node.getAttribute('name') || CONST.NAME_WIDGET_DEFAULT,
type = fullname === 'Alloy.Widget' ? 'widget' : node.getAttribute('type') || CONST.REQUIRE_TYPE_DEFAULT,
fullpaths = [];
var platform;
if (compilerConfig && compilerConfig.alloyConfig && compilerConfig.alloyConfig.platform) {
platform = compilerConfig.alloyConfig.platform;
}
// Must be a view, with a valid src, in a <Require> element
if (!src) {
return null;
} else if (fullname === 'Alloy.Require' && type === 'view') {
if (platform) { fullpaths.push(path.join(compilerConfig.dir.views,platform,src)); }
fullpaths.push(path.join(compilerConfig.dir.views,src));
} else if (fullname === 'Alloy.Widget' ||
(fullname === 'Alloy.Require' && type === 'widget')) {
if (platform) {
fullpaths.push(path.join(compilerConfig.dir.widgets,src,'views',platform,name));
}
fullpaths.push(path.join(compilerConfig.dir.widgets,src,'views',name));
if (platform) {
fullpaths.push(path.join(alloyRoot,'..','widgets',src,'views',platform,name));
}
fullpaths.push(path.join(alloyRoot,'..','widgets',src,'views',name));
} else {
return null;
}
// check the extensions on the paths to check
var found = false;
var fullpath;
for (var i = 0; i < fullpaths.length; i++) {
fullpath = fullpaths[i];
fullpath += regex.test(fullpath) ? '' : '.' + CONST.FILE_EXT.VIEW;
if (fs.existsSync(fullpath)) {
found = true;
break;
}
}
// abort if there's no view to be found
if (!found) {
U.die([
type + ' "' + src + '" ' + (type === 'widget' ? 'view "' + name + '" ' : '') +
'does not exist.',
'The following paths were inspected:'
].concat(fullpaths));
}
return fullpath;
}
//create function, it expects 2 values.
function insertAfter(newElement,targetElement) {
//target is what you want it to go after. Look for this elements parent.
var parent = targetElement.parentNode;
//if the parents lastchild is the targetElement...
if(parent.lastchild == targetElement) {
//add the newElement after the target element.
parent.appendChild(newElement);
} else {
// else the target has siblings, insert the new element between the target and it's next sibling.
parent.insertBefore(newElement, targetElement.nextSibling);
}
}
function processRequire(node, isFirst) {
// make sure we have a valid required view and get its path
var fullpath = getViewRequirePath(node);
if (fullpath === null) {
return;
}
// re-assemble XML with required elements
if (isFirst) {
cloneNode = U.XML.getAlloyFromFile(fullpath);
} else {
var newDocRoot = U.XML.getAlloyFromFile(fullpath);
_.each(U.XML.getElementsFromNodes(newDocRoot.childNodes), function(n) {
insertAfter(n, node);
});
node.parentNode.removeChild(node);
}
}
// Expand the <Require>, recursively if specified
if (getViewRequirePath(cloneNode) !== null) {
processRequire(cloneNode, true);
while (doRecursive) {
var reqs = cloneNode.getElementsByTagName('Require');
var widgets = cloneNode.getElementsByTagName('Widget');
var all = [];
// condense node lists into a single array
_.each(reqs, function(req) {
all.push(req);
});
_.each(widgets, function(widget) {
all.push(widget);
});
// find all the valid widgets/requires
var viewRequires = _.filter(reqs, function(req) {
return getViewRequirePath(req) !== null;
});
if (viewRequires.length === 0) {
break;
}
// TODO: https://jira.appcelerator.org/browse/ALOY-256
//_.each(viewRequires, processRequire);
processRequire(viewRequires[0]);
}
}
return cloneNode;
};
exports.inspectRequireNode = function(node) {
var newNode = exports.expandRequireNode(node, true);
var children = U.XML.getElementsFromNodes(newNode.childNodes);
var names = [];
_.each(children, function(c) {
var args = exports.getParserArgs(c);
// skip model elements when inspecting nodes for <Require>
if (_.contains(CONST.MODEL_ELEMENTS, args.fullname)) {
newNode.removeChild(c);
return;
}
names.push(args.fullname);
});
return {
children: U.XML.getElementsFromNodes(newNode.childNodes),
length: names.length,
names: names
};
};
exports.copyWidgetResources = function(resources, resourceDir, widgetId, opts) {
opts = opts || {};
var platform;
if (compilerConfig && compilerConfig.alloyConfig && compilerConfig.alloyConfig.platform) {
platform = compilerConfig.alloyConfig.platform;
}
_.each(resources, function(dir) {
if (!path.existsSync(dir)) { return; }
logger.trace('WIDGET_SRC=' + path.relative(compilerConfig.dir.project, dir));
var files = wrench.readdirSyncRecursive(dir);
_.each(files, function(file) {
var source = path.join(dir, file);
// make sure the file exists and that it is not filtered
if (!fs.existsSync(source) ||
(opts.filter && opts.filter.test(file)) ||
(opts.exceptions && _.contains(opts.exceptions, file))) {
return;
}
if (fs.statSync(source).isFile()) {
var dirname = path.dirname(file);
var parts = dirname.split(/[\/\\]/);
if (opts.titaniumFolder && parts[0] === opts.titaniumFolder) {
dirname = parts.slice(1).join('/');
}
var destDir = path.join(resourceDir, dirname, widgetId);
var dest = path.join(destDir, path.basename(file));
if (!path.existsSync(destDir)) {
wrench.mkdirSyncRecursive(destDir, 0755);
}
logger.trace('Copying ' + file.yellow + ' --> ' +
path.relative(compilerConfig.dir.project, dest).yellow + '...');
U.copyFileSync(source, dest);
}
});
// [ALOY-1002] Remove ios folder copied from widget
var iosDir = path.join(resourceDir, 'ios');
if (fs.existsSync(iosDir)) {
wrench.rmdirSyncRecursive(iosDir);
}
logger.trace(' ');
});
if(opts.theme) {
// if this widget has been themed, copy its theme assets atop the stock ones
var widgetThemeDir = path.join(compilerConfig.dir.project, 'app', 'themes', opts.theme, 'widgets', widgetId);
if(fs.existsSync(widgetThemeDir)) {
logger.trace('Processing themed widgets');
var widgetAssetSourceDir = path.join(widgetThemeDir, 'assets');
var widgetAssetTargetDir = path.join(resourceDir, widgetId);
if(fs.existsSync(widgetAssetSourceDir)) {
wrench.copyDirSyncRecursive(widgetAssetSourceDir, widgetAssetTargetDir, {preserve: true});
}
// platform-specific assets from the widget must override those of the theme
if(platform && path.existsSync(path.join(resources[0], platform))) {
wrench.copyDirSyncRecursive(path.join(resources[0], platform), widgetAssetTargetDir, {preserve: true});
}
// however platform-specific theme assets must override the platform assets from the widget
if(platform && path.existsSync(path.join(widgetAssetSourceDir, platform))) {
logger.trace('Processing platform-specific theme assets for the ' + widgetId + ' widget');
widgetAssetSourceDir = path.join(widgetAssetSourceDir, platform);
wrench.copyDirSyncRecursive(widgetAssetSourceDir, widgetAssetTargetDir, {preserve: true});
}
// [ALOY-1002] Remove platform-specific folders copied from theme
if (fs.existsSync(widgetAssetTargetDir)) {
var files = wrench.readdirSyncRecursive(widgetAssetTargetDir);
_.each(files, function(file) {
var source = path.join(widgetAssetTargetDir, file);
if (path.existsSync(source) && fs.statSync(source).isDirectory()) {
wrench.rmdirSyncRecursive(source);
}
});
}
}
}
};
exports.mergeI18n = function(srcI18nDir, compileConfigDir, opts) {
logger.info(' i18n: "' + srcI18nDir + '"');
var buildI18nDir = path.join(compileConfigDir.project, CONST.DIR.BUILD_I18N),
appI18nDir = path.join(compileConfigDir.project, CONST.DIR.I18N),
files = wrench.readdirSyncRecursive(srcI18nDir),
serializer = new XMLSerializer();
if (!fs.existsSync(buildI18nDir)) {
wrench.mkdirSyncRecursive(buildI18nDir, 0755);
fs.existsSync(appI18nDir) && wrench.copyDirSyncRecursive(appI18nDir, buildI18nDir, {preserve: false});
}
_.each(files, function(file) {
var source = path.join(srcI18nDir, file),
outputPath = path.join(buildI18nDir, file);
if (!path.existsSync(outputPath)) {
if (fs.statSync(source).isDirectory()) {
wrench.mkdirSyncRecursive(outputPath, 0755);
} else {
U.copyFileSync(source, outputPath);
}
} else {
if (fs.statSync(source).isFile()) {
var outxml = U.XML.parseFromFile(outputPath),
root = outxml.documentElement,
sourcexml = U.XML.parseFromFile(source),
obj = {};
_.each(outxml.getElementsByTagName('string'), function(node) {
var name = node.getAttribute('name');
obj[name] = node;
});
_.each(sourcexml.getElementsByTagName('string'), function(node){
var name = node.getAttribute('name'),
value = node.childNodes[0].nodeValue;
if (name in obj) {
if (opts.override) {
root.replaceChild(node, obj[name]);
}
} else {
root.appendChild(outxml.createTextNode('\t'));
root.appendChild(node);
root.appendChild(outxml.createTextNode('\n'));
}
});
fs.writeFileSync(outputPath, serializer.serializeToString(outxml), 'utf8');
}
}
});
};
function updateImplicitNamspaces(platform) {
switch(platform) {
case 'android':
break;
case 'ios':
break;
case 'mobileweb':
CONST.IMPLICIT_NAMESPACES.NavigationGroup = 'Ti.UI.MobileWeb';
break;
}
}
exports.createCompileConfig = function(inputPath, outputPath, alloyConfig, buildLog) {
var dirs = ['assets','config','controllers','lib','migrations','models','styles','themes','vendor','views','widgets'];
var libDirs = ['builtins','template'];
var resources = path.resolve(path.join(outputPath,'Resources'));
var obj = {
alloyConfig: alloyConfig,
dir: {
home: path.resolve(inputPath),
project: path.resolve(outputPath),
resources: resources,
resourcesAlloy: path.join(resources,'alloy')
},
buildLog: buildLog
};
// create list of dirs
_.each(dirs, function(dir) {
obj.dir[dir] = path.resolve(path.join(inputPath,dir));
});
_.each(libDirs, function(dir) {
obj.dir[dir] = path.resolve(path.join(alloyRoot,dir));
});
// ensure the generated directories exist
U.ensureDir(obj.dir.resources);
// process and normalize the config.json file
var configs = _.defaults(generateConfig(obj), {
// sets the theme
theme: undefined,
// are we going to generate sourcemaps?
sourcemap: true,
// are we enabling dynamic styling for all generated components?
autoStyle: false,
// the list of widget dependencies
dependencies: {},
// TODO: Include no adapters by default
adapters: CONST.ADAPTERS
});
// normalize adapters
if (!configs.adapters) {
configs.adapters = [];
} else if (!_.isArray(configs.adapters)) {
configs.adapters = [configs.adapters];
}
logger.debug(JSON.stringify(configs, null, ' ').split(os.EOL));
// update implicit namespaces, if possible
updateImplicitNamspaces(alloyConfig.platform);
// keep a copy of the config for this module
compilerConfig = _.extend(obj, configs);
return obj;
};
function generateConfig(obj) {
var buildLog = obj.buildLog;
var o = {};
var alloyConfig = obj.alloyConfig;
var platform = require('../../../platforms/'+alloyConfig.platform+'/index').titaniumFolder;
//var defaultCfg = 'module.exports=' + JSON.stringify(o) + ';';
// get the app and resources locations
var appCfg = path.join(obj.dir.home,'config.'+CONST.FILE_EXT.CONFIG);
var resourcesBase = (function() {
var base = obj.dir.resources;
if (platform) { base = path.join(base,platform); }
return path.join(base,'alloy');
})();
var resourcesCfg = path.join(resourcesBase,'CFG.js');
// parse config.json, if it exists
if (path.existsSync(appCfg)) {
o = exports.parseConfig(appCfg, alloyConfig, o);
if (o.theme) {
var themeCfg = path.join(obj.dir.home,'themes',o.theme,'config.'+CONST.FILE_EXT.CONFIG);
// parse theme config.json, if it exists
if (path.existsSync(themeCfg)) {
o = exports.parseConfig(themeCfg, alloyConfig, o);
}
}
}
// only regenerate the CFG.js when necessary
var hash = U.createHashFromString(JSON.stringify(o));
if(buildLog.data.cfgHash && buildLog.data.cfgHash === hash && fs.existsSync(path.join(obj.dir.resources, 'alloy', 'CFG.js'))) {
// use cached CFG.js file
logger.info(' [config.json] config.json unchanged, using cached config.json...');
} else {
// cached CFG.js is out of sync with config.json, regenerate and save
logger.info(' [config.json] regenerating CFG.js from config.json...');
buildLog.data.cfgHash = hash;
// write out the config runtime module
wrench.mkdirSyncRecursive(resourcesBase, 0755);
//logger.debug('Writing "Resources/' + (platform ? platform + '/' : '') + 'alloy/CFG.js"...');
var output = "module.exports=" + JSON.stringify(o) + ";";
fs.writeFileSync(resourcesCfg, output);
// TODO: deal with TIMOB-14884
var baseFolder = path.join(obj.dir.resources, 'alloy');
if (!fs.existsSync(baseFolder)) {
wrench.mkdirSyncRecursive(baseFolder, 0755);
}
fs.writeFileSync(path.join(baseFolder, 'CFG.js'), output);
}
return o;
}
exports.parseConfig = function(file, alloyConfig, o) {
var j;
try {
j = jsonlint.parse(fs.readFileSync(file,'utf8'));
} catch (e) {
U.die('Error processing "config.' + CONST.FILE_EXT.CONFIG + '"', e);
}
_.each(j, function(v,k) {
if (!/^(?:env\:|os\:)/.test(k) && k !== 'global') {
logger.debug(k + ' = ' + JSON.stringify(v));
o[k] = v;
}
});
if (alloyConfig) {
o = _.extend(o, j['global']);
o = _.extend(o, j['env:'+alloyConfig.deploytype]);
o = _.extend(o, j['os:'+alloyConfig.platform]);
o = _.extend(o, j['env:'+alloyConfig.deploytype + ' os:'+alloyConfig.platform]);
o = _.extend(o, j['os:'+alloyConfig.platform + ' env:'+alloyConfig.deploytype]);
}
return o;
};
exports.loadController = function(file) {
var code = {
parentControllerName: '',
controller: '',
pre: ''
}, contents;
// Read the controller file
try {
if (!path.existsSync(file)) {
return code;
}
contents = fs.readFileSync(file,'utf8');
} catch (e) {
U.die('Error reading controller file "' + file + '".', e);
}
// get the base controller for this controller
code.controller = contents;
code.parentControllerName = astController.getBaseController(contents, file);
return code;
};
exports.validateNodeName = function(node, names) {
var fullname = exports.getNodeFullname(node);
var ret = null;
if (!_.isArray(names)) { names = [names]; }
// Is the node name in the given list of valid names?
ret = _.find(names, function(name) { return name === fullname; });
if (ret) { return ret; }
// Is it an Alloy.Require?
if (fullname === 'Alloy.Require' || fullname === 'Alloy.Widget') {
var inspect = exports.inspectRequireNode(node);
ret = _.find(inspect.children, function(n) {
return _.contains(names, exports.getNodeFullname(n));
});
if (ret) {
return exports.getNodeFullname(ret);
}
}
return null;
};
exports.generateCollectionBindingTemplate = function(args) {
var code = '';
// Determine the collection variable to use
var obj = { name: args[CONST.BIND_COLLECTION] };
var col = _.template((exports.currentManifest ? CONST.WIDGET_OBJECT : 'Alloy') + ".Collections['<%= name %>'] || <%= name %>", obj);
var colVar = exports.generateUniqueId();
// Create the code for the filter and transform functions
var where = args[CONST.BIND_WHERE];
var transform = args[CONST.BIND_TRANSFORM];
var whereCode = where ? where + "(" + colVar + ")" : colVar + ".models";
var transformCode = transform ? transform + "(<%= localModel %>)" : "{}";
var handlerFunc = args[CONST.BIND_FUNCTION] || exports.generateUniqueId();
if(args.parentFormFactor) {
if(!exports.dataFunctionNames[handlerFunc]) {
exports.dataFunctionNames[handlerFunc] = [];
}
exports.dataFunctionNames[handlerFunc].push(args.parentFormFactor);
// append the form factor for the code below
handlerFunc += U.ucfirst(args.parentFormFactor);
}
// construct code template
code += "var " + colVar + "=" + col + ";";
code += "function " + handlerFunc + "(e) {";
code += " if (e && e.fromAdapter) { return; }";
code += " var opts = " + handlerFunc + ".opts || {};";
code += " var models = " + whereCode + ";";
code += " var len = models.length;";
code += "<%= pre %>";
code += " for (var i = 0; i < len; i++) {";
code += " var <%= localModel %> = models[i];";
if(!args.isDataBoundMap) {
code += " <%= localModel %>.__transform = " + transformCode + ";";
} else {
// because (ti.map).annotations[] doesn't accept an array of anonymous objects
// we convert them to actual Annotations before pushing them to the array
if(transform) {
// createAnnotation() requires JSON. While localModel is a Model,
// transformed models are JSON
code += " <%= annotationArray %>.push(require('ti.map').createAnnotation(" + transformCode + "));";
} else {
code += " <%= annotationArray %>.push(require('ti.map').createAnnotation((<%= localModel %>).toJSON()));";
}
}
code += "<%= items %>";
code += " }";
code += "<%= post %>";
code += "};";
code += colVar + ".on('" + CONST.COLLECTION_BINDING_EVENTS + "'," + handlerFunc + ");";
exports.destroyCode += ((args.parentFormFactor) ? 'Alloy.is' + U.ucfirst(args.parentFormFactor) + ' && ' : '' ) +
colVar + ".off('" + CONST.COLLECTION_BINDING_EVENTS + "'," + handlerFunc + ");";
return code;
};