@titanium/turbo
Version:
🚀 Turbo is the awesome framework for turbo charging your Titanium cross-platform native mobile app development!
932 lines (828 loc) • 28.1 kB
JavaScript
/**
* @class Alloy
* Top-level module for Alloy functions.
*
* Alloy is an application framework built on top of the Titanium SDK designed to help rapidly
* develop high quality applications and reduce maintenance.
*
* Alloy uses the model-view-controller architecture to separate the application into three
* components:
*
* * **Models** provide the data of the application. Alloy utilizes **Backbone Model and Collection**
* objects for this functionality.
*
* * **Views** provide the UI components to interact with the application, written using **XML markup**
* and **Titanium Stylesheets (TSS)**, which abstracts the UI components of the Titanium API.
*
* * **Controllers** provide the glue layer between the Model and View components as well as
* additional application logic using the **Alloy API** and **Titanium API**.
*
* The API documentation provided here is used with Alloy Controllers and Widget Controllers to
* interact with the View and Model components of the application or widget.
*
* For guides on using Alloy, see
* [Alloy Framework](http://docs.appcelerator.com/platform/latest/#!/guide/Alloy_Framework).
*/
if (Ti.App.Properties.getBool('use-underscore', false)) {
var _ = require('/alloy/underscore');
} else {
var _ = require('lodash');
// include some aliases for backwards compatibility
_.all = _.every;
_.any = _.some;
_.collect = _.map;
_.compose = _.flowRight;
_.contains = _.includes;
_.detect = _.find;
_.findWhere = _.find;
_.first = _.head;
_.foldl = _.reduce;
_.foldr = _.reduceRight;
_.include = _.includes;
_.indexBy = _.keyBy;
_.inject = _.reduce;
_.object = _.fromPairs;
_.pairs = _.toPairs;
_.pluck = _.map;
_.restArguments = _.rest;
_.select = _.filter;
_.unique = _.uniq;
_.where = _.filter;
}
var Backbone = require('/alloy/backbone');
const CONST = require('/alloy/constants');
exports.version = '<%= version %>';
exports._ = _;
exports.Backbone = Backbone;
const logger = require('@geek/logger').createLogger('@titanium/turbo',{ meta: { filename: __filename }});
const DEFAULT_WIDGET = 'widget';
const TI_VERSION = Ti.version;
const IDENTITY_TRANSFORM = OS_ANDROID ? (Ti.UI.createMatrix2D ? Ti.UI.createMatrix2D() : Ti.UI.create2DMatrix()) : undefined;
let RESET = {
bottom: null,
left: null,
right: null,
top: null,
height: null,
width: null,
shadowColor: null,
shadowOffset: null,
backgroundImage: null,
backgroundRepeat: null,
center: null,
layout: null,
backgroundSelectedColor: null,
backgroundSelectedImage: null,
// non-null resets
opacity: 1.0,
touchEnabled: true,
enabled: true,
horizontalWrap: true,
zIndex: 0,
//##### DISPARITIES #####//
// Setting to "null" on android works the first time. Leaves the color
// on subsequent calls.
backgroundColor: OS_ANDROID ? 'transparent' : null,
// creates a font slightly different (smaller) than default on iOS
// https://jira.appcelerator.org/browse/TIMOB-14565
font: null,
// Throws an exception on Android if set to null. Works on other platforms.
// https://jira.appcelerator.org/browse/TIMOB-14566
visible: true,
// Setting to "null" on android makes text transparent
// https://jira.appcelerator.org/browse/TIMOB-14567
color: OS_ANDROID ? '#000' : null,
// Android will leave artifact of previous transform unless the identity matrix is
// manually reset.
// https://jira.appcelerator.org/browse/TIMOB-14568
//
// Mobileweb does not respect matrix properties set in the constructor, despite the
// documentation at docs.appcelerator.com indicating that it should.
// https://jira.appcelerator.org/browse/TIMOB-14570
transform: OS_ANDROID ? IDENTITY_TRANSFORM : null,
// Crashes if set to null on anything but Android
// https://jira.appcelerator.org/browse/TIMOB-14571
backgroundGradient: !OS_ANDROID ? {} : null,
// All supported platforms have varying behavior with border properties
// https://jira.appcelerator.org/browse/TIMOB-14573
borderColor: OS_ANDROID ? null : 'transparent',
// https://jira.appcelerator.org/browse/TIMOB-14575
borderRadius: OS_IOS ? 0 : null,
// https://jira.appcelerator.org/browse/TIMOB-14574
borderWidth: OS_IOS ? 0 : null,
};
if (OS_IOS) {
RESET = _.extend(RESET, {
backgroundLeftCap: 0,
backgroundTopCap: 0,
});
} else if (OS_ANDROID) {
RESET = _.extend(RESET, {
backgroundDisabledColor: null,
backgroundDisabledImage: null,
backgroundFocusedColor: null,
backgroundFocusedImage: null,
focusable: false,
keepScreenOn: false,
});
}
const file_registry_resource = Ti.Filesystem.getFile(Titanium.Filesystem.resourcesDirectory, '__file_registry.json');
exports.file_registry = JSON.parse(file_registry_resource.read().text);
const dependency_registry_resource = Ti.Filesystem.getFile(Titanium.Filesystem.resourcesDirectory, '__dependency_registry.json');
exports.dependency_registry = JSON.parse(dependency_registry_resource.read().text);
const widget_registry_resource = Ti.Filesystem.getFile(Titanium.Filesystem.resourcesDirectory, '__widget_registry.json');
exports.widget_registry = JSON.parse(widget_registry_resource.read().text);
function ucfirst(text) {
if (!text) {
return text;
}
return text[0].toUpperCase() + text.substr(1);
}
function addNamespace(apiName) {
return (CONST.IMPLICIT_NAMESPACES[apiName] || CONST.NAMESPACE_DEFAULT) + '.' + apiName;
}
exports.M = function(name, modelDesc, migrations) {
var config = (modelDesc || {}).config || {};
var adapter = config.adapter || {};
var extendObj = {};
var extendClass = {};
var mod;
if (adapter.type) {
mod = require('/alloy/sync/' + adapter.type);
extendObj.sync = function(method, model, opts) {
return mod.sync(method, model, opts);
};
} else {
extendObj.sync = function(method, model, opts) {
Ti.API.warn('Execution of ' + method + '#sync() function on a model that does not support persistence');
Ti.API.warn('model: ' + JSON.stringify(model.toJSON()));
};
}
extendObj.defaults = config.defaults;
// construct the model based on the current adapter type
if (migrations) {
extendClass.migrations = migrations;
}
// Run the pre model creation code, if any
if (mod && _.isFunction(mod.beforeModelCreate)) {
config = mod.beforeModelCreate(config, name) || config;
}
// Create the Model object
var Model = Backbone.Model.extend(extendObj, extendClass);
Model.prototype.config = config;
// Extend the Model with extendModel(), if defined
if (_.isFunction(modelDesc.extendModel)) {
Model = modelDesc.extendModel(Model) || Model;
}
// Run the post model creation code, if any
if (mod && _.isFunction(mod.afterModelCreate)) {
mod.afterModelCreate(Model, name);
}
return Model;
};
exports.C = function(name, modelDesc, model) {
var extendObj = {model: model};
var config = (model ? model.prototype.config : {}) || {};
var mod;
if (config.adapter && config.adapter.type) {
mod = require('/alloy/sync/' + config.adapter.type);
extendObj.sync = function(method, model, opts) {
return mod.sync(method, model, opts);
};
} else {
extendObj.sync = function(method, model, opts) {
Ti.API.warn('Execution of ' + method + '#sync() function on a collection that does not support persistence');
Ti.API.warn('model: ' + JSON.stringify(model.toJSON()));
};
}
var Collection = Backbone.Collection.extend(extendObj);
Collection.prototype.config = config;
// extend the collection object
if (_.isFunction(modelDesc.extendCollection)) {
Collection = modelDesc.extendCollection(Collection) || Collection;
}
// do any post collection creation code form the sync adapter
if (mod && _.isFunction(mod.afterCollectionCreate)) {
mod.afterCollectionCreate(Collection);
}
return Collection;
};
exports.UI = {};
exports.UI.create = function(controller, apiName, opts) {
opts = opts || {};
// Make sure we have a full api name
var baseName, ns;
var parts = apiName.split('.');
if (parts.length === 1) {
baseName = apiName;
ns = opts.ns || CONST.IMPLICIT_NAMESPACES[baseName] || CONST.NAMESPACE_DEFAULT;
} else if (parts.length > 1) {
baseName = parts[parts.length - 1];
ns = parts.slice(0, parts.length - 1).join('.');
} else {
throw 'Alloy.UI.create() failed: No API name was given in the second parameter';
}
opts.apiName = ns + '.' + baseName;
baseName = baseName[0].toUpperCase() + baseName.substr(1);
// generate the style object
var style = exports.createStyle(controller, opts);
// create the titanium proxy object
return eval(ns)['create' + baseName](style);
};
exports.createStyle = function(controller, opts, defaults) {
var classes, apiName;
// If there's no opts, there's no reason to load the style module. Just
// return an empty object.
if (!opts) {
return {};
}
// make opts.classes an array if it isn't already
if (_.isArray(opts.classes)) {
classes = opts.classes.slice(0);
} else if (_.isString(opts.classes)) {
classes = opts.classes.split(/\s+/);
} else {
classes = [];
}
// give opts.apiName a namespace if it doesn't have one already
apiName = opts.apiName;
if (apiName && apiName.indexOf('.') === -1) {
apiName = addNamespace(apiName);
}
// TODO: check cached styles based on opts and controller
// Load the runtime style for the given controller
var styleArray;
if (controller && _.isObject(controller)) {
styleArray = require('/alloy/widgets/' + controller.widgetId + '/styles/' + controller.name);
} else {
styleArray = require('/alloy/styles/' + controller);
}
var styleFinal = {};
// iterate through all styles
var i, len;
for (i = 0, len = styleArray.length; i < len; i++) {
var style = styleArray[i];
// give the apiName a namespace if necessary
var styleApi = style.key;
if (style.isApi && styleApi.indexOf('.') === -1) {
styleApi = (CONST.IMPLICIT_NAMESPACES[styleApi] || CONST.NAMESPACE_DEFAULT) + '.' + styleApi;
}
// does this style match the given opts?
if ((style.isId && opts.id && style.key === opts.id) || (style.isClass && _.contains(classes, style.key))) {
// do nothing here, keep on processing
} else if (style.isApi) {
if (style.key.indexOf('.') === -1) {
style.key = addNamespace(style.key);
}
if (style.key !== apiName) {
continue;
}
} else {
// no matches, skip this style
continue;
}
// can we clear out any form factor queries?
if (style.queries && style.queries.formFactor && !exports[style.queries.formFactor]) {
continue;
}
// process runtime custom queries
if (
style.queries &&
style.queries.if &&
(style.queries.if.trim().toLowerCase() === 'false' ||
(style.queries.if.indexOf('Alloy.Globals') !== -1 && exports.Globals[style.queries.if.split('.')[2]] === false))
) {
continue;
}
// Merge this style into the existing style object
exports.deepExtend(true, styleFinal, style.style);
}
// TODO: cache the style based on the opts and controller
// Merge remaining extra style properties from opts, if any
var extraStyle = _.omit(opts, [CONST.CLASS_PROPERTY, CONST.APINAME_PROPERTY]);
exports.deepExtend(true, styleFinal, extraStyle);
styleFinal[CONST.CLASS_PROPERTY] = classes;
styleFinal[CONST.APINAME_PROPERTY] = apiName;
return defaults ? _.defaults(styleFinal, defaults) : styleFinal;
};
function processStyle(controller, proxy, classes, opts, defaults) {
opts = opts || {};
opts.classes = classes;
if (proxy.apiName) {
opts.apiName = proxy.apiName;
}
if (proxy.id) {
opts.id = proxy.id;
}
proxy.applyProperties(exports.createStyle(controller, opts, defaults));
if (OS_ANDROID) {
proxy.classes = classes;
}
}
exports.addClass = function(controller, proxy, classes, opts) {
// make sure we actually have classes to add
if (!classes) {
if (opts) {
proxy.applyProperties(opts);
}
return;
} else {
// create a union of the existing classes with the new one(s)
var pClasses = proxy[CONST.CLASS_PROPERTY] || [];
var beforeLen = pClasses.length;
classes = _.isString(classes) ? classes.split(/\s+/) : classes;
var newClasses = _.union(pClasses, classes || []);
// make sure we actually added classes before processing styles
if (beforeLen === newClasses.length) {
if (opts) {
proxy.applyProperties(opts);
}
return;
} else {
processStyle(controller, proxy, newClasses, opts);
}
}
};
exports.removeClass = function(controller, proxy, classes, opts) {
classes = classes || [];
var pClasses = proxy[CONST.CLASS_PROPERTY] || [];
var beforeLen = pClasses.length;
// make sure there's classes to remove before processing
if (!beforeLen || !classes.length) {
if (opts) {
proxy.applyProperties(opts);
}
return;
} else {
// remove the given class(es)
classes = _.isString(classes) ? classes.split(/\s+/) : classes;
var newClasses = _.difference(pClasses, classes);
// make sure there was actually a difference before processing
if (beforeLen === newClasses.length) {
if (opts) {
proxy.applyProperties(opts);
}
return;
} else {
processStyle(controller, proxy, newClasses, opts, RESET);
}
}
};
exports.resetClass = function(controller, proxy, classes, opts) {
classes = classes || [];
classes = _.isString(classes) ? classes.split(/\s+/) : classes;
processStyle(controller, proxy, classes, opts, RESET);
};
/**
* @method createWidget
* Factory method for instantiating a widget controller. Creates and returns an instance of the
* named widget.
* @param {String} id Id of widget to instantiate.
* @param {String} [name="widget"] Name of the view within the widget to instantiate ('widget' by default)
* @param {Object} [args] Arguments to pass to the widget.
* @return {Alloy.Controller} Alloy widget controller object.
*/
exports.createWidget = function(id, name, args) {
if (typeof name !== 'undefined' && name !== null && _.isObject(name) && !_.isString(name)) {
args = name;
name = DEFAULT_WIDGET;
}
return new (require('/alloy/widgets/' + id + '/controllers/' + (name || DEFAULT_WIDGET)))(args);
};
exports.cleanUpController = function(controller) {
if( !controller ){
return;
}
if (controller.resource_name) {
// exports.Controllers[controller.resource_name] = null;
delete exports.Controllers[controller.resource_name];
}
if (controller.resource_alias) {
// exports.Controllers[controller.resource_alias] = null;
delete exports.Controllers[controller.resource_alias];
}
if (controller.__views) {
_.each(controller.__views, value => {
exports.cleanUpController(value);
});
}
if (controller.__iamalloy) {
controller.off();
controller.destroy();
}
if( exports.Controllers.current === controller){
exports.Controllers.current = null;
}
controller = null;
}
/**
* @method createController
* Factory method for instantiating a controller. Creates and returns an instance of the
* named controller.
* MODIFIED to include logic from AlloyXL
* @param {String} name Name of controller to instantiate.
* @param {Object} [args] Arguments to pass to the controller.
* @return {Alloy.Controller} Alloy controller object.
*/
exports.createController = function(name, args = {}) {
if (_.isNil(name)) {
throw new Error('Parameter "name" is required for Alloy.createController');
}
if (name.startsWith('/')) {
name = name.substring(1, name.length);
}
let controller;
if (exports.file_registry.includes(`/alloy/controllers/${name}.js`)) {
try{
controller = new (require(`/alloy/controllers/${name}`))(_.defaults({}, args, { __resource_name: name, __resource_path: `/alloy/controllers/${name}` }));
if( ! controller ){
console.error('Error creating controller: ' + name);
throw new Error('Error creating controller: ' + name);
}
} catch (error){
console.error(error);
throw new Error('Error creating controller: ' + name);
}
} else if (exports.widget_registry[name]) {
controller = new (require(exports.widget_registry[name]))(_.defaults({}, args, { __resource_name: name, __resource_path: exports.widget_registry[name] }));
if( ! controller ){
console.error('Error creating controller: ' + name);
throw new Error('Error creating controller: ' + name);
}
} else {
console.error('Alloy controller not found: ' + name);
throw new Error('Alloy controller not found: ' + name);
}
controller.resource_name = name;
exports.Controllers = exports.Controllers || {};
exports.Controllers[controller.resource_name] = controller;
if (Object.keys(controller.__views).length > 0) {
var view = controller.getViewEx({recurse: true});
if (view.alias) {
controller.resource_alias = view.alias;
exports.Controllers[controller.resource_alias] = controller;
}
controller.once = function(eventName, callback) {
controller.on(eventName, () => {
if (controller) {
controller.off(eventName);
callback();
}
});
return controller;
};
if (typeof view.addEventListener === 'function') {
if (typeof view.open === 'function') {
view.addEventListener('open', function onOpen(e) {
view.removeEventListener('open', onOpen);
controller.isOpen = true;
exports.Controllers.current = controller;
controller.trigger('open', e);
logger.verbose(`🚀 you are here → view.onOpen: ${name}`);
});
view.addEventListener('close', function onClose() {
view.removeEventListener('close', onClose);
view = null;
controller.trigger('close');
exports.cleanUpController(controller);
controller = null;
logger.verbose(`🚀 you are here → view.onClose: ${name}`);
});
view.addEventListener('postlayout', function onPostlayout(e) {
view && view.removeEventListener('postlayout', onPostlayout);
controller && controller.trigger('postlayout', e);
logger.verbose(`🚀 you are here → view.onPostlayout: ${name}`);
});
} else {
view.addEventListener('postlayout', function onPostlayout(e) {
view && view.removeEventListener('postlayout', onPostlayout);
controller && controller.trigger('postlayout', e);
logger.verbose(`🚀 you are here → view.onPostlayout: ${name}`);
});
}
}
}
return controller;
};
exports.open = function(name, params) {
logger.verbose(`🚀 you are here → Alloy.open(${name})`);
const promise = new Promise((resolve, reject) => {
let controller = exports.Controllers[name];
let view;
if (controller) {
if( controller.isOpen ){
logger.debug(`🚀 Controller is already open: ${name}`);
logger.verbose(`🚀 Resolving promise to open view: ${name}`);
return resolve();
}
view = controller.getViewEx();
} else {
controller = exports.createController(name, params);
controller.isOpen = false;
view = controller.getViewEx();
}
if (view && typeof view.open === 'function') {
if (typeof view.addEventListener === 'function') {
view.addEventListener('open', function onOpen(e) {
view.removeEventListener('open', onOpen);
controller.isOpen = true;
controller.trigger('open', e);
logger.track(`🚀 Resolving promise to open view: ${name}`);
return resolve({controller, view});
});
} else {
logger.verbose(`🚀 view.addEventListener is not a function: ${name}`);
view.open();
return resolve();
}
logger.verbose(`🚀 you are here → Alloy.open() calling view.open()`);
view.open();
return;
} else {
logger.verbose(`🚀 view.open is not a function: ${name}`);
return resolve();
}
});
return promise;
};
exports.close = async name => {
logger.verbose(`🚀 you are here → Alloy.close(${name})`);
// console.error(Promise);
// console.warn(global.Promise);
const promise = new Promise((resolve, reject) => {
const controller = exports.Controllers[name];
if (controller) {
const view = controller.getView();
if (view && typeof view.close === 'function') {
if (typeof view.addEventListener === 'function') {
view.addEventListener('close', function onClose(e) {
view.removeEventListener('close', onClose);
logger.track(`🚀 view.onClose: ${name}`);
logger.verbose(`🚀 Resolving promise to close view: ${name}`);
resolve();
});
view.close();
} else {
logger.verbose(`🚀 view.addEventListener is not a function: ${name}`);
view.close();
resolve();
}
} else {
logger.verbose(`🚀 view.close is not a function: ${name}`);
resolve();
}
} else {
logger.verbose(`🚀 view.close cannot find controller: ${name}`);
resolve();
}
});
return promise;
};
/**
* @method createModel
* Factory method for instantiating a Backbone Model object. Creates and returns an instance of the
* named model.
*
* See [Backbone.Model](http://docs.appcelerator.com/backbone/0.9.2/#Model) in the Backbone.js documentation for
* information on the methods and properties provided by the Model object.
* @param {String} name Name of model to instantiate.
* @param {Object} [args] Arguments to pass to the model.
* @return {Backbone.Model} Backbone model object.
*/
exports.createModel = function(name, args) {
return new (require('/alloy/models/' + ucfirst(name)).Model)(args);
};
/**
* @method createCollection
* Factory method for instantiating a Backbone collection of model objects. Creates and returns a
* collection for holding the named type of model objects.
*
* See [Backbone.Collection](http://docs.appcelerator.com/backbone/0.9.2/#Collection) in the Backbone.js
* documentation for information on the methods and properties provided by the
* Collection object.
* @param {String} name Name of model to hold in this collection.
* @param {Object} [args] Arguments to pass to the collection.
* @return {Backbone.Collection} Backbone collection object.
*/
exports.createCollection = function(name, args) {
return new (require('/alloy/models/' + ucfirst(name)).Collection)(args);
};
function isTabletFallback() {
return Math.min(Ti.Platform.displayCaps.platformHeight, Ti.Platform.displayCaps.platformWidth) >= 700;
}
/**
* @property {Boolean} isTablet
* `true` if the current device is a tablet.
*
*/
exports.isTablet = (function() {
if (OS_IOS) {
return Ti.Platform.osname === 'ipad';
} else if (OS_ANDROID) {
var psc = Ti.Platform.Android.physicalSizeCategory;
return (
psc === Ti.Platform.Android.PHYSICAL_SIZE_CATEGORY_LARGE ||
psc === Ti.Platform.Android.PHYSICAL_SIZE_CATEGORY_XLARGE
);
} else {
return isTabletFallback();
}
})();
/**
* @property {Boolean} isHandheld
* `true` if the current device is a handheld device (not a tablet).
*
*/
exports.isHandheld = !exports.isTablet;
/**
* @property {Object} Globals
* An object for storing globally accessible variables and functions.
* Alloy.Globals is accessible in any controller in your app:
*
* Alloy.Globals.someGlobalObject = { key: 'value' };
* Alloy.Globals.someGlobalFunction = function(){};
*
* Alloy.Globals can be accessed in other non-controller Javascript files
* like this:
*
* var theObject = require('/alloy').Globals.someGlobalObject;
*
*/
exports.Globals = {};
/**
* @property {Object} Controllers
* An object for storing instantiated controllers
* Alloy.Controllers is accessible in any controller in your app:
*
* Alloy.Controllers can be accessed in other non-controller Javascript files
* like this:
*
* var controllers = require('/alloy').Controllers;
*
*/
exports.Controllers = {};
/**
* @property {Object} Models
* An object for storing globally accessible Alloy models. Singleton models
* created via markup will be stored on this object.
*
* <Model src="myModel"/>
*
* The above markup would effectively generate the following code:
*
* Alloy.Models.myModel = Alloy.createModel('MyModel');
*
* Alloy.Models.myModel would then be accessible in any controller in your app.
*
*/
exports.Models = {};
/*
* Creates a singleton instance of a Model based on the given model, or
* returns an existing instance if one has already been created.
* Documented in docs/apidoc/model.js for docs site.
*/
exports.Models.instance = function(name) {
return exports.Models[name] || (exports.Models[name] = exports.createModel(name));
};
/**
* @property {Object} Collections
* An object for storing globally accessible Alloy collections. Singleton collections
* created via markup will be stored on this object.
*
* <Collection src="myModel"/>
*
* The above markup would effectively generate the following code:
*
* Alloy.Collections.myModel = Alloy.createCollection('MyModel');
*
* Alloy.Collections.myModel would then be accessible in any controller in your app.
*
*/
exports.Collections = {};
/*
* Creates a singleton instance of a Collection based on the given model, or
* returns an existing instance if one has already been created.
* Documented in docs/apidoc/collection.js for docs site.
*/
exports.Collections.instance = function(name) {
return exports.Collections[name] || (exports.Collections[name] = exports.createCollection(name));
};
/**
* @property {Object} CFG
* An object that stores Alloy configuration values as defined in your app's
* app/config.json file. Here's what a typical config.json file might look
* like in an Alloy app.
*
* {
* "global": { "key": "defaultValue", "anotherKey": 12345 },
* "env:development": {},
* "env:test": {},
* "env:production": {},
* "os:ios": { "key": "iosValue" },
* "os:android": { "key": "androidValue" },
* "dependencies": {}
* }
*
* If this app was compiled for iOS, the Alloy.CFG would look like this:
*
* Alloy.CFG = {
* "key": "iosValue",
* "anotherKey": 12345
* }
*
* Alloy.CFG is accessible in any controller in your app, and can be accessed
* in other non-controller Javascript files like this:
*
* var theKey = require('alloy').CFG.key;
*
*/
exports.CFG = require('/alloy/CFG');
if (OS_ANDROID) {
exports.Android = {};
exports.Android.menuItemCreateArgs = [
'itemId',
'groupId',
'title',
'order',
'actionView',
'checkable',
'checked',
'enabled',
'icon',
'showAsAction',
'titleCondensed',
'visible',
];
}
/*
* Adapted version of node.extend https://www.npmjs.org/package/node.extend
*
* Original copyright:
*
* node.extend
* Copyright 2011, John Resig
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*
* @fileoverview
* Port of jQuery.extend that actually works on node.js
*/
exports.deepExtend = function() {
var target = arguments[0] || {};
var i = 1;
var length = arguments.length;
var deep = false;
var options, name, src, copy, copy_is_array, clone;
// Handle a deep copy situation
if (typeof target === 'boolean') {
deep = target;
target = arguments[1] || {};
// skip the boolean and the target
i = 2;
}
// Handle case when target is a string or something (possible in deep copy)
if (typeof target !== 'object' && !_.isFunction(target)) {
target = {};
}
for (; i < length; i++) {
// Only deal with non-null/undefined values
options = arguments[i];
if (options != null) {
if (typeof options === 'string') {
options = options.split('');
}
// Extend the base object
for (name in options) {
src = target[name];
copy = options[name];
// Prevent never-ending loop
if (target === copy) {
continue;
}
if (
deep &&
copy &&
!_.isFunction(copy) &&
_.isObject(copy) &&
((copy_is_array = _.isArray(copy)) || !_.has(copy, 'apiName'))
) {
// Recurse if we're merging plain objects or arrays
if (copy_is_array) {
copy_is_array = false;
clone = src && _.isArray(src) ? src : [];
} else if (_.isDate(copy)) {
clone = new Date(copy.valueOf());
} else {
clone = src && _.isObject(src) ? src : {};
}
// Never move original objects, clone them
target[name] = exports.deepExtend(deep, clone, copy);
} else {
target[name] = copy;
}
}
}
}
// Return the modified object
return target;
};