Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
787 lines
23 KiB
JavaScript
787 lines
23 KiB
JavaScript
/* 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));
|