Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
787 lines
23 KiB
JavaScript
Executable file
787 lines
23 KiB
JavaScript
Executable file
/* global LazyLoader, DUMP */
|
|
'use strict';
|
|
|
|
(function(exports) {
|
|
/**
|
|
* Turn of this flag to debug all BaseModule based modules.
|
|
* @type {Boolean}
|
|
*/
|
|
var GLOBAL_DEBUG = false;
|
|
|
|
/**
|
|
* `_GAIA_DEVICE_TYPE_` is a placeholder and will be replaced by real
|
|
* device type in build time, `system/build/build.js` does the trick.
|
|
*/
|
|
var DEVICE_TYPE = '_GAIA_DEVICE_TYPE_';
|
|
/**
|
|
* This is used to store the constructors which are created
|
|
* via BaseModule.create().
|
|
* constructor.name => constructor
|
|
* @type {Object}
|
|
*/
|
|
var AVAILABLE_MODULES = {};
|
|
|
|
/**
|
|
* BaseModule is a class skeleton which helps you to build a module with
|
|
* * Centralized event handler
|
|
* * Centralized settings observation
|
|
* * Sub modules management including loading and starting
|
|
* * Import preload files
|
|
* * DOM rendering
|
|
* * Consistent logging function with System boot time and module name
|
|
* * Common publishing interface
|
|
* @class BaseModule
|
|
*/
|
|
var BaseModule = function() {};
|
|
|
|
/**
|
|
* The sub modules belong to this module.
|
|
* BaseModule will load and then start these sub modules automatically.
|
|
* The expressions can include path (e.g. 'path/to/ModuleName'). BaseModule
|
|
* will load them from specified subdirectory. However, the module names
|
|
* (without path) should be unique even they're under different folders.
|
|
*
|
|
* You can provide an object:
|
|
* Define a list of default type modules are able to be loaded in anytime.
|
|
* Define a list of device type modules can be loaded when running on a given
|
|
* device-type.
|
|
* {
|
|
* default: ['base_module1', 'base_module2', ...],
|
|
* device-type: ['module1', 'module2', ...]
|
|
* }
|
|
*
|
|
* Or provide an array:
|
|
* ['module1', 'module2', ...]
|
|
*
|
|
* @type {Object|Array}
|
|
*/
|
|
BaseModule.SUB_MODULES = {};
|
|
|
|
/**
|
|
* All events of need to be listened.
|
|
* BaseModule will add/remove the event listener in start/stop functions.
|
|
* The function of '_handle_' form in this module will be invoked
|
|
* when the event is caught.
|
|
* @type {Array}
|
|
*/
|
|
BaseModule.EVENTS = [];
|
|
|
|
/**
|
|
* All mozSettings need to be observed.
|
|
* BaseModule will observe and invoke the responsive
|
|
* this['_observe_' + key] function.
|
|
* @type {Array}
|
|
*/
|
|
BaseModule.SETTINGS = [];
|
|
|
|
/**
|
|
* This defines a list of file path needs to be imported
|
|
* before the real start of this module.
|
|
*
|
|
* You can provide an object:
|
|
* Define a list of default type modules are able to be loaded in anytime.
|
|
* Define a list of device type modules can be loaded when running on a given
|
|
* device-type.
|
|
* {
|
|
* default: ['base_module1', 'base_module2', ...],
|
|
* device-type: ['module1', 'module2', ...]
|
|
* }
|
|
*
|
|
* Or provide an array:
|
|
* ['module1', 'module2', ...]
|
|
*
|
|
* @type {Object|Array}
|
|
*/
|
|
BaseModule.IMPORTS = {};
|
|
|
|
/**
|
|
* This tells System the sandbox what methods you are going to
|
|
* register and let the other to request.
|
|
*
|
|
* @example
|
|
* var MyModule = function() {};
|
|
* MyModule.SERVICES = ['unlock'];
|
|
* MyModule.prototype = Object.create(BaseModule.prototype);
|
|
* MyModule.prototype.constructor = MyModule;
|
|
* MyModule.prototype.name = 'MyModule';
|
|
* var m = new MyModule();
|
|
* m.start();
|
|
* // other module
|
|
* Service.request('MyModule:unlock').then(function(result) {
|
|
* });
|
|
* Service.request('unlock').then(function(result) {
|
|
* // if the request is registered by only one module.
|
|
* });
|
|
*/
|
|
BaseModule.SERVICES = [];
|
|
|
|
/**
|
|
* The function or property exported here will be
|
|
* synchronously queried by other module in system app.
|
|
* If we are not started yet, they will get undefined.
|
|
*
|
|
* @example
|
|
* var MyModule = function() {};
|
|
* MyModule.STATES = ['isActive'];
|
|
* MyModule.prototype = Object.create(BaseModule.prototype);
|
|
* MyModule.prototype.constructor = MyModule;
|
|
* MyModule.prototype.name = 'MyModule';
|
|
* var m = new MyModule();
|
|
* m.start();
|
|
* // other module
|
|
* console.log(Service.query('MyModule.isActive'));
|
|
* // if the method name is unique.
|
|
* console.log(Service.query('isActive'));
|
|
* @type {Array}
|
|
*/
|
|
BaseModule.STATES = [];
|
|
|
|
var SubmoduleMixin = {
|
|
loadWhenIdle: function(modules) {
|
|
return this.service.request('schedule', () => {
|
|
var submodules = this._arraylizeModules(this.constructor.SUB_MODULES);
|
|
var newModules = this._arraylizeModules(modules);
|
|
this.constructor.SUB_MODULES = submodules.concat(newModules);
|
|
return this._startSubModules();
|
|
});
|
|
},
|
|
|
|
_arraylizeModules: function(submodules) {
|
|
var modules;
|
|
var defaulModules;
|
|
var deviceModules;
|
|
|
|
if (submodules && Array.isArray(submodules)) {
|
|
modules = submodules;
|
|
} else if (submodules && typeof submodules === 'object') {
|
|
defaulModules = submodules.default || [];
|
|
deviceModules = submodules[DEVICE_TYPE] || [];
|
|
modules = defaulModules.concat(deviceModules);
|
|
} else {
|
|
modules = [];
|
|
}
|
|
|
|
return modules;
|
|
},
|
|
|
|
/**
|
|
* Helper function to load and start the submodules defined in
|
|
* |this.constructor.SUB_MODULES|.
|
|
*/
|
|
_startSubModules: function() {
|
|
var submodules = this._arraylizeModules(this.constructor.SUB_MODULES);
|
|
this.constructor.SUB_MODULES = submodules;
|
|
|
|
if (submodules.length === 0) {
|
|
return Promise.resolve();
|
|
}
|
|
|
|
var unloaded = [];
|
|
submodules.forEach(function(submodule) {
|
|
if (BaseModule.defined(submodule) || window[submodule]) {
|
|
var name = BaseModule.lowerCapital(submodule);
|
|
if (!this[name]) {
|
|
this._initializeSubModule(name, submodule);
|
|
}
|
|
} else {
|
|
unloaded.push(submodule);
|
|
}
|
|
}, this);
|
|
|
|
if (unloaded.length === 0) {
|
|
this.baseSubModuleLoaded && this.baseSubModuleLoaded();
|
|
return;
|
|
}
|
|
|
|
this.debug('lazy loading submodules: ' +
|
|
unloaded.concat());
|
|
return BaseModule.lazyLoad(unloaded).then(() => {
|
|
this.debug('lazy loaded submodules: ' +
|
|
unloaded.concat());
|
|
return Promise.all(
|
|
unloaded
|
|
.map(BaseModule.parsePath)
|
|
.map(function(module) {
|
|
var moduleName = BaseModule.lowerCapital(module.name);
|
|
if (!this[moduleName]) {
|
|
return this._initializeSubModule(moduleName, module.name);
|
|
} else {
|
|
return Promise.resolve();
|
|
}
|
|
}, this));
|
|
});
|
|
},
|
|
|
|
_initializeSubModule: function(moduleName, module) {
|
|
var constructor = AVAILABLE_MODULES[module] || window[module];
|
|
if (typeof(constructor) == 'function') {
|
|
this.debug('instantiating submodule: ' + moduleName);
|
|
this[moduleName] = new constructor(this);
|
|
// If there is a custom submodule loaded handler, call it.
|
|
// Otherwise we will start the submodule right away.
|
|
if (typeof(this['_' + moduleName + '_loaded']) == 'function') {
|
|
return this['_' + moduleName + '_loaded']();
|
|
} else if (this.lifeCycleState !== 'stopped') {
|
|
return this[moduleName].start && this[moduleName].start();
|
|
}
|
|
} else {
|
|
this[moduleName] = constructor;
|
|
// For the module which does not become class yet
|
|
if (this[moduleName] && this[moduleName].start) {
|
|
return this[moduleName].start();
|
|
} else if (this[moduleName] && this[moduleName].init) {
|
|
// backward compatibility with init.
|
|
return this[moduleName].init();
|
|
} else {
|
|
return undefined;
|
|
}
|
|
}
|
|
},
|
|
|
|
_stopSubModules: function() {
|
|
if (!this.constructor.SUB_MODULES) {
|
|
return;
|
|
}
|
|
this.constructor.SUB_MODULES.map(BaseModule.parsePath)
|
|
.forEach(function(module) {
|
|
var moduleName = BaseModule.lowerCapital(module.name);
|
|
if (this[moduleName]) {
|
|
this.debug('Stopping submodule: ' + moduleName);
|
|
this[moduleName].stop && this[moduleName].stop();
|
|
}
|
|
}, this);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* SettingsMixin will provide you the ability to watch
|
|
* and store the settings in this._settings
|
|
* @type {Object}
|
|
*/
|
|
var SettingMixin = {
|
|
observe: function(name, value) {
|
|
this.debug('observing ' + name + ' : ' + value);
|
|
this._settings[name] = value;
|
|
if (typeof(this['_observe_' + name]) == 'function') {
|
|
this.debug('observer for ' + name + ' found, invoking.');
|
|
this['_observe_' + name](value);
|
|
}
|
|
},
|
|
|
|
_observeSettings: function() {
|
|
if (!this.constructor.SETTINGS) {
|
|
this.debug('No settings needed, skipping.');
|
|
return;
|
|
}
|
|
this._settings = {};
|
|
this.debug('~observing following settings: ' +
|
|
this.constructor.SETTINGS.concat());
|
|
this.constructor.SETTINGS.forEach(function(setting) {
|
|
this.service.request('SettingsCore:addObserver', setting, this);
|
|
}, this);
|
|
},
|
|
|
|
_unobserveSettings: function() {
|
|
if (!this.constructor.SETTINGS) {
|
|
return;
|
|
}
|
|
this.constructor.SETTINGS.forEach(function(setting) {
|
|
this.service.request('SettingsCore:removeObserver', setting, this);
|
|
}, this);
|
|
}
|
|
};
|
|
|
|
var EventMixin = {
|
|
/**
|
|
* Custom global event handler before the event is handled
|
|
* by a specific handler.
|
|
* Override it if necessary.
|
|
*/
|
|
_pre_handleEvent: function() {
|
|
|
|
},
|
|
|
|
/**
|
|
* Custom global event handler after the event is handled.
|
|
* Override it if necessary.
|
|
*/
|
|
_post_handleEvent: function() {
|
|
|
|
},
|
|
|
|
_subscribeEvents: function() {
|
|
if (!this.constructor.EVENTS) {
|
|
this.debug('No events wanted, skipping.');
|
|
return;
|
|
}
|
|
this.debug('event subcribing stage..');
|
|
this.constructor.EVENTS.forEach(function(event) {
|
|
this.debug('subscribing ' + event);
|
|
window.addEventListener(event, this);
|
|
}, this);
|
|
},
|
|
|
|
_unsubscribeEvents: function() {
|
|
if (!this.constructor.EVENTS) {
|
|
return;
|
|
}
|
|
this.constructor.EVENTS.forEach(function(event) {
|
|
window.removeEventListener(event, this);
|
|
}, this);
|
|
},
|
|
|
|
handleEvent: function(evt) {
|
|
if (typeof(this._pre_handleEvent) == 'function') {
|
|
var shouldContinue = this._pre_handleEvent(evt);
|
|
if (shouldContinue === false) {
|
|
return;
|
|
}
|
|
} else {
|
|
this.debug('no handle event pre found. skip');
|
|
}
|
|
if (typeof(this['_handle_' + evt.type]) == 'function') {
|
|
this.debug('handling ' + evt.type);
|
|
this['_handle_' + evt.type](evt);
|
|
}
|
|
if (typeof(this._post_handleEvent) == 'function') {
|
|
this._post_handleEvent(evt);
|
|
}
|
|
}
|
|
};
|
|
|
|
var ServiceMixin = {
|
|
_registerServices: function() {
|
|
if (!this.constructor.SERVICES) {
|
|
return;
|
|
}
|
|
this.constructor.SERVICES.forEach(function(service) {
|
|
this.service.register(service, this);
|
|
}, this);
|
|
},
|
|
|
|
_unregisterServices: function() {
|
|
if (!this.constructor.SERVICES) {
|
|
return;
|
|
}
|
|
this.constructor.SERVICES.forEach(function(service) {
|
|
this.service.unregister(service, this);
|
|
}, this);
|
|
}
|
|
};
|
|
|
|
var StateMixin = {
|
|
_registerStates: function() {
|
|
if (!this.constructor.STATES) {
|
|
return;
|
|
}
|
|
this.constructor.STATES.forEach(function(state) {
|
|
this.service.registerState(state, this);
|
|
}, this);
|
|
},
|
|
|
|
_unregisterStates: function() {
|
|
if (!this.constructor.STATES) {
|
|
return;
|
|
}
|
|
this.constructor.STATES.forEach(function(state) {
|
|
this.service.unregisterState(state, this);
|
|
}, this);
|
|
}
|
|
};
|
|
|
|
BaseModule.defined = function(name) {
|
|
return !!AVAILABLE_MODULES[name];
|
|
};
|
|
|
|
BaseModule.__clearDefined = function() {
|
|
AVAILABLE_MODULES = [];
|
|
};
|
|
|
|
/**
|
|
* Mixin the prototype with give mixin object.
|
|
* @param {Object} prototype The prototype of a class
|
|
* @param {Object} mixin An object will be mixed into the prototype
|
|
*/
|
|
BaseModule.mixin = function (prototype, mixin) {
|
|
for (var prop in mixin) {
|
|
if (mixin.hasOwnProperty(prop)) {
|
|
prototype[prop] = mixin[prop];
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Create a module based on base module and give properties.
|
|
* The constructor will be placed in AVAILABLE_MODULES if you
|
|
* specify an unique name in the prototype.
|
|
* @example
|
|
* var MyModule = function() {};
|
|
* BaseModule.create(MyModule, {
|
|
* name: 'MyModule'
|
|
* });
|
|
* var myModule = BaseModule.instantiate('MyModule');
|
|
*
|
|
* @param {Function} constructor The constructor function.
|
|
* @param {Object} prototype
|
|
* The prototype which will be injected into the class.
|
|
* @param {Object} properties
|
|
* The property object which includes getter/setter.
|
|
*/
|
|
BaseModule.create = function(constructor, prototype, properties) {
|
|
constructor.prototype = Object.create(BaseModule.prototype, properties);
|
|
constructor.prototype.constructor = constructor;
|
|
if (constructor.SETTINGS) {
|
|
BaseModule.mixin(constructor.prototype, SettingMixin);
|
|
}
|
|
if (constructor.EVENTS) {
|
|
BaseModule.mixin(constructor.prototype, EventMixin);
|
|
}
|
|
if (constructor.SERVICES) {
|
|
BaseModule.mixin(constructor.prototype, ServiceMixin);
|
|
}
|
|
if (constructor.STATES) {
|
|
BaseModule.mixin(constructor.prototype, StateMixin);
|
|
}
|
|
// Inject this anyway.
|
|
BaseModule.mixin(constructor.prototype, SubmoduleMixin);
|
|
if (prototype) {
|
|
BaseModule.mixin(constructor.prototype, prototype);
|
|
if (prototype.name) {
|
|
AVAILABLE_MODULES[prototype.name] = constructor;
|
|
} else {
|
|
console.warn('No name give, impossible to instantiate without name.');
|
|
}
|
|
}
|
|
return constructor;
|
|
};
|
|
|
|
/**
|
|
* Create a new instance based on the module name given.
|
|
* It will look up |AVAILABLE_MODULES|.
|
|
* Note: this will instante multiple instances if called more than once.
|
|
* Also it's impossible to pass arguments now.
|
|
* @param {String} moduleName The module name
|
|
* which comes from the prototype of the module.
|
|
* @return {Object} Created instance.
|
|
*/
|
|
BaseModule.instantiate = function(moduleName) {
|
|
if (moduleName in AVAILABLE_MODULES) {
|
|
var args = Array.prototype.slice.call(arguments, 1);
|
|
var constructor = function() {
|
|
AVAILABLE_MODULES[moduleName].apply(this, args);
|
|
};
|
|
constructor.prototype = AVAILABLE_MODULES[moduleName].prototype;
|
|
return new constructor();
|
|
}
|
|
return undefined;
|
|
};
|
|
|
|
/**
|
|
* Lazy load an list of modules
|
|
* @param {Array} array A list of module names
|
|
* @return {Promise} The promise of lazy loading;
|
|
* it will be invoked once lazy loading is done.
|
|
*/
|
|
BaseModule.lazyLoad = function(array) {
|
|
var self = this;
|
|
return new Promise(function(resolve) {
|
|
var fileList = [];
|
|
array.forEach(function(module) {
|
|
fileList.push(BaseModule.object2fileName(module));
|
|
}, self);
|
|
LazyLoader.load(fileList, function() {
|
|
resolve();
|
|
});
|
|
});
|
|
};
|
|
|
|
/**
|
|
* A helper function to lowercase only the capital character.
|
|
* @example
|
|
* Service.lowerCapital('AppWindowManager');
|
|
* // appWindowManager
|
|
* @param {String} str String to be lowercased on capital
|
|
* @return {String} Captital lowerred string
|
|
*/
|
|
BaseModule.lowerCapital = function(str) {
|
|
return str.charAt(0).toLowerCase() + str.slice(1);
|
|
};
|
|
|
|
/**
|
|
* A helper function to split module expressions into "path" and "name".
|
|
* @example
|
|
* Service.parsePath('path/to/ModuleName');
|
|
* // {path: 'path/to/', name: 'ModuleName'}
|
|
* @param {String} str String to be splitted
|
|
* @return {Object} The result object with members: "path" and "name".
|
|
*/
|
|
BaseModule.parsePath = function(str) {
|
|
var [, path, name] = /^(.*\/|)(.+)$/.exec(str);
|
|
return {
|
|
path: path,
|
|
name: name
|
|
};
|
|
};
|
|
|
|
/**
|
|
* A helper function to transform object name to file name
|
|
* @example
|
|
* Service.object2fileName('AppWindowManager');
|
|
* // 'js/app_window_manager.js'
|
|
* Service.object2fileName('path/to/ModuleName');
|
|
* // 'js/path/to/module_name.js'
|
|
*
|
|
* @param {String} string Module name
|
|
* @return {String} File name
|
|
*/
|
|
BaseModule.object2fileName = function(string) {
|
|
var i = 0;
|
|
var ch = '';
|
|
|
|
var module = BaseModule.parsePath(string);
|
|
var moduleName = module.name;
|
|
|
|
while (i <= moduleName.length) {
|
|
var character = moduleName.charAt(i);
|
|
if (character !== character.toLowerCase()) {
|
|
if (ch === '') {
|
|
ch += character.toLowerCase();
|
|
} else {
|
|
ch += '_' + character.toLowerCase();
|
|
}
|
|
} else {
|
|
ch += character;
|
|
}
|
|
i++;
|
|
}
|
|
return '/js/' + module.path + ch + '.js';
|
|
};
|
|
|
|
BaseModule.prototype = {
|
|
service: window.Service,
|
|
|
|
DEBUG: false,
|
|
|
|
TRACE: false,
|
|
|
|
/**
|
|
* The name of this module which is usually the constructor function name
|
|
* and could be converted to the file name.
|
|
* For example, AppWindowManager will be mapped to app_window_manager.js
|
|
* This should be unique.
|
|
* @type {String}
|
|
*/
|
|
name: '(Anonymous)',
|
|
|
|
EVENT_PREFIX: '',
|
|
|
|
/**
|
|
* We are having three states:
|
|
* * starting
|
|
* * started
|
|
* * stopped
|
|
* @type {String}
|
|
*/
|
|
lifeCycleState: 'stopped',
|
|
|
|
publish: function(event, detail, noPrefix) {
|
|
var prefix = noPrefix ? '' : this.EVENT_PREFIX;
|
|
var evt = new CustomEvent(prefix + event,
|
|
{
|
|
bubbles: true,
|
|
detail: detail || this
|
|
});
|
|
|
|
this.debug('publishing: ' + prefix + event);
|
|
|
|
window.dispatchEvent(evt);
|
|
},
|
|
|
|
/**
|
|
* Basic log.
|
|
*/
|
|
debug: function() {
|
|
if (this.DEBUG || GLOBAL_DEBUG) {
|
|
console.log('[' + this.name + ']' +
|
|
'[' + this.service.currentTime() + '] ' +
|
|
Array.slice(arguments).concat());
|
|
if (this.TRACE) {
|
|
console.trace();
|
|
}
|
|
} else if (window.DUMP) {
|
|
DUMP('[' + this.name + ']' +
|
|
'[' + this.service.currentTime() + '] ' +
|
|
Array.slice(arguments).concat());
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Log some infomation.
|
|
*/
|
|
info: function() {
|
|
if (this.DEBUG || GLOBAL_DEBUG) {
|
|
console.info('[' + this.name + ']' +
|
|
'[' + this.service.currentTime() + '] ' +
|
|
Array.slice(arguments).concat());
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Log some warning message.
|
|
*/
|
|
warn: function() {
|
|
if (this.DEBUG || GLOBAL_DEBUG) {
|
|
console.warn('[' + this.name + ']' +
|
|
'[' + this.service.currentTime() + '] ' +
|
|
Array.slice(arguments).concat());
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Log some error message.
|
|
*/
|
|
error: function() {
|
|
if (this.DEBUG || GLOBAL_DEBUG) {
|
|
console.error('[' + this.name + ']' +
|
|
'[' + this.service.currentTime() + '] ' +
|
|
Array.slice(arguments).concat());
|
|
}
|
|
},
|
|
|
|
writeSetting: function(settingObject) {
|
|
this.debug('writing ' + JSON.stringify(settingObject) +
|
|
' to settings db');
|
|
return this.service.request('SettingsCore:set', settingObject);
|
|
},
|
|
|
|
readSetting: function(name) {
|
|
if (this._settings && this._settings[name]) {
|
|
return Promise.resolve(this._settings[name]);
|
|
} else {
|
|
this.debug('reading ' + name + ' from settings db');
|
|
return this.service.request('SettingsCore:get', name);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Custom start function, override it if necessary.
|
|
* If you want to block the start process of this module,
|
|
* return a promise here.
|
|
*/
|
|
_start: function() {},
|
|
|
|
/**
|
|
* Custom stop function. Override it if necessary.
|
|
*/
|
|
_stop: function() {},
|
|
|
|
/**
|
|
* The starting progress of a module has these steps:
|
|
* * import javascript files
|
|
* * lazy load submodules and instantiate once loaded.
|
|
* * custom start function
|
|
* * attach event listeners
|
|
* * observe settings
|
|
* * register services to System
|
|
* * DOM elements rendering (not implemented)
|
|
* The import is guranteed to happen before anything else.
|
|
* The service registration is expected to happen after everything is done.
|
|
* The ordering of the remaining parts should not depends each other.
|
|
*
|
|
* The start function will return a promise to let you know
|
|
* when the module is started.
|
|
* @example
|
|
* var a = BaseModule.instantiate('A');
|
|
* a.start().then(() => {
|
|
* console.log('started');
|
|
* });
|
|
*
|
|
* Note: a module start promise will only be resolved
|
|
* after all the steps are resolved, including
|
|
* the custom start promise and the promises from all the submodules.
|
|
* So when you see a module is started, that means all of its
|
|
* submodules are started as well.
|
|
*
|
|
* @memberOf BaseModule.prototype
|
|
*/
|
|
start: function() {
|
|
if (this.lifeCycleState !== 'stopped') {
|
|
this.warn('already started');
|
|
return Promise.reject('already started');
|
|
}
|
|
this.switchLifeCycle('starting');
|
|
return this.imports();
|
|
},
|
|
|
|
__imported: function() {
|
|
// Do nothing if we are stopped.
|
|
if (this.lifeCycleState === 'stopped') {
|
|
this.warn('already stopped');
|
|
return Promise.resolve();
|
|
}
|
|
this.debug('in imported');
|
|
return Promise.all([
|
|
// Parent module needs to know the events from the submodule.
|
|
this._subscribeEvents && this._subscribeEvents(),
|
|
this._startSubModules && this._startSubModules(),
|
|
this._start(),
|
|
this._observeSettings && this._observeSettings(),
|
|
this._registerServices && this._registerServices(),
|
|
this._registerStates && this._registerStates()]).then(() => {
|
|
this.switchLifeCycle('started');
|
|
});
|
|
},
|
|
|
|
/**
|
|
* The stopping of a module has these steps:
|
|
* * unregister services to System sandbox
|
|
* * lazy load submodules and instantiate once loaded.
|
|
* * attach event listeners
|
|
* * observe settings
|
|
* * custom stop function
|
|
*/
|
|
stop: function() {
|
|
if (this.lifeCycleState === 'stopped') {
|
|
this.warn('already stopped');
|
|
return;
|
|
}
|
|
this._unregisterServices && this._unregisterServices();
|
|
this._unregisterStates && this._unregisterStates();
|
|
this._stopSubModules && this._stopSubModules();
|
|
this._unsubscribeEvents && this._unsubscribeEvents();
|
|
this._unobserveSettings && this._unobserveSettings();
|
|
this._stop();
|
|
this.switchLifeCycle('stopped');
|
|
},
|
|
|
|
switchLifeCycle: function(state) {
|
|
if (this.lifeCycleState === state) {
|
|
return;
|
|
}
|
|
|
|
this.debug('life cycle state change: ' +
|
|
this.lifeCycleState + ' -> ' + state);
|
|
this.lifeCycleState = state;
|
|
this.publish(state);
|
|
},
|
|
|
|
imports: function() {
|
|
var imports = this._arraylizeModules(this.constructor.IMPORTS);
|
|
this.constructor.IMPORTS = imports;
|
|
|
|
if (imports.length === 0) {
|
|
return this.__imported();
|
|
}
|
|
|
|
this.debug(imports);
|
|
this.debug('import loading.');
|
|
return LazyLoader.load(imports)
|
|
.then(() => {
|
|
this.debug('imported..');
|
|
return this.__imported();
|
|
});
|
|
}
|
|
};
|
|
|
|
exports.BaseModule = BaseModule;
|
|
}(window));
|