simpleitjs
Version:
Simple light frontend framework
303 lines (269 loc) • 8.58 kB
text/typescript
/// <reference path="./module-scope.ts" />
const moduleCollection: Module[] = [];
const readyModules: { [id: string]: boolean } = {};
const modules: { [id: string]: ModuleScope } = {};
let ModuleScopeMain: typeof ModuleScope;
if (typeof window === "undefined") {
ModuleScopeMain = require('./module-scope');
} else {
const win: any = window;
ModuleScopeMain = win.ModuleScope;
}
enum ModuleType {
Standard,
Class
}
declare class ModuleOpts {
type: ModuleType;
}
/**
* Creates a new Module, attaches dependencies
* @param {string} Name 1st aargument, name of module to be registed.
* @param {string[]} Dependencies 2nd to 2nd last argument, dependencies of the module.
* @param {Function} ReadyFnc Last argument, resolved ready function.
* @returns {{name:string;dependencies:string[];readyFnc:Function;scope:Object}}
*/
class Module {
name: string;
dependencies: string[];
readyFnc: typeof ModuleScopeMain;
scope: ModuleScope;
moduleHolder: ModuleScope;
constructionOpts?: Object;
moduleOpts?: ModuleOpts;
public type: ModuleType = ModuleType.Standard;
public constructor(...args: any[]) {
const dependencies = [];
let allReady = true;
for (var i = 1; i < arguments.length - 1; i++) {
dependencies.push(arguments[i]);
if (!readyModules[arguments[i]]) {
allReady = false;
}
}
this.name = arguments[0];
this.dependencies = dependencies;
this.readyFnc = arguments[arguments.length - 1];
this.scope = new ModuleScopeMain();
this.moduleHolder = new ModuleScopeMain();
if (typeof arguments[arguments.length - 2] == "object") {
this.constructionOpts = arguments[arguments.length - 2];
dependencies.pop();
}
if (typeof arguments[arguments.length - 3] == "object") {
this.moduleOpts = arguments[arguments.length - 3];
dependencies.pop();
}
if (this.moduleOpts) {
this.type = this.moduleOpts.type;
}
moduleCollection.push(this);
if (dependencies.length == 0 || allReady) {
this.releaseModule(this);
this.checkNReleaseMods();
} else {
var res = this.checkInterDependency(this);
if (res) {
this.releaseModules(res);
}
}
}
/**
* Returns list of unresolved modules.
* @param {string[]} mods modules names
*/
public getUnResolvedModules(mods: string[]): string[] {
const unreadyMods = [];
for (let i = 0; i < mods.length; i++) {
if (!readyModules[mods[i]]) {
unreadyMods.push(mods[i]);
}
}
return unreadyMods;
}
/**
* Returns module by name
* @param {string} name module name
* @returns {Module}
*/
public static getModuleByName(name: string) {
for (var i = 0; i < moduleCollection.length; i++) {
if (moduleCollection[i].name == name) {
return moduleCollection[i];
}
}
return null;
}
public checkInterDependency(module: Module, overTimeReady: { [id: string]: boolean } = {}) {
const unReadyMods = this.getUnResolvedModules(module.dependencies);
let registered: boolean = false;
for (var i = 0; i < unReadyMods.length; i++) {
registered = false;
for (var j = 0; j < moduleCollection.length; j++) {
if (moduleCollection[j].name == unReadyMods[i]) {
registered = true;
break;
}
}
if (!registered) {
break;
}
}
if (registered) {
overTimeReady[module.name] = true;
var resArr = [module.name];
var allReady = true;
for (var i = 0; i < unReadyMods.length; i++) {
if (!overTimeReady[unReadyMods[i]]) {
allReady = false;
const mod = Module.getModuleByName(unReadyMods[i]);
if (!mod) {
return false;
}
var res = this.checkInterDependency(mod, overTimeReady);
if (res) {
for (var j = 0; j < res.length; j++) {
if (resArr.indexOf(res[i]) == -1) {
resArr.push(res[i]);
}
}
} else {
return false;
}
} else {
if (resArr.indexOf(unReadyMods[i]) == -1) {
resArr.push(unReadyMods[i]);
}
}
}
return resArr;
} else {
return false;
}
}
/**
* Releases all modules with support to interdependency on each other.
* @param {string[]} moduleNames module names in string list.
*/
public releaseModules(moduleNames: string[]) {
for (var i = 0; i < moduleNames.length; i++) {
var module = Module.getModuleByName(moduleNames[i]);
if (!module) {
continue;
}
readyModules[moduleNames[i]] = true;
let inst: ModuleScope;
if (typeof this.constructionOpts != "undefined") {
inst = new module.readyFnc(this.constructionOpts);
} else {
inst = new module.readyFnc();
}
module.scope = new ModuleScopeMain();
modules[module.name] = inst;
}
for (var i = 0; i < moduleNames.length; i++) {
var module = Module.getModuleByName(moduleNames[i]);
if (!module) {
continue;
}
var resolvedDeps = [];
for (var j = 0; j < module.dependencies.length; j++) {
resolvedDeps.push(modules[module.dependencies[j]]);
}
module.moduleHolder = module.readyFnc.invoke.apply(module.scope, [module.scope, ...resolvedDeps]);
if (module.moduleHolder) {
modules[module.name] = module.moduleHolder;
} else {
modules[module.name] = module.scope;
}
}
}
/**
* Checks and releases modules from main collection
*/
public checkNReleaseMods() {
for (var i = 0; i < moduleCollection.length; i++) {
var currMod = moduleCollection[i];
if (!readyModules[currMod.name] && this.areDepLoaded(currMod)) {
this.releaseModule(currMod);
}
}
};
/**
* Checks if dependencies are loaded or not.
* @param {Module} module Module to check
* @returns {boolean}
*/
public areDepLoaded(module: Module): boolean {
for (var i = 0; i < module.dependencies.length; i++) {
if (!readyModules[module.dependencies[i]]) {
return false;
}
}
return true;
}
/**
* Releases module, calls its ready function.
* @param {Module} module Module to release.
*/
public releaseModule(module: Module) {
readyModules[module.name] = true;
let inst: ModuleScope;
if (typeof this.constructionOpts != "undefined") {
inst = new module.readyFnc(this.constructionOpts);
} else {
inst = new module.readyFnc();
}
module.scope = inst;
var resolvedDeps = [];
for (var j = 0; j < module.dependencies.length; j++) {
resolvedDeps.push(modules[module.dependencies[j]]);
}
module.moduleHolder = module.readyFnc.invoke.apply(module.scope, [module.scope, ...resolvedDeps]);
if (module.moduleHolder) {
modules[module.name] = module.moduleHolder;
} else {
modules[module.name] = module.scope;
}
};
/**
* Returns module by name
* @param {string} name name of module to get.
*/
public static get(name: string) {
const mod = Module.getModuleByName(name);
if (mod && mod.type === ModuleType.Class && mod.readyFnc) {
return mod.readyFnc;
}
return modules[name];
}
/**
* Extends module by calling given function, module score is provided as arg and expected new updated scope.
* Use this to extend functionality
* @param {string} name name of module to get.
* @param {Function} fnc extnding callback function.
*/
public static extend(name: string, fnc: (model: ModuleScope) => ModuleScope) {
const scope = Module.get(name);
const out = fnc.call(scope, scope);
if (out) {
modules[name] = out;
const mod = Module.getModuleByName(name);
if (mod) {
mod.scope = out;
}
}
}
}
(function () {
if (typeof window !== "undefined") {
const win: any = window;
win.SimpleJS = win.SimpleJS || {};
win.SimpleJS.Module = Module;
win.SimpleJS.ModuleType = ModuleType;
}
})();
declare var module: any;
if (typeof module != "undefined" && typeof module.exports != "undefined") {
module.exports = Module;
}