/*global Arenite:true*/
/*global Arenite:true*/
Collection of utility functions wire the instances and load the configured resources.
Arenite.DI = function (arenite) {
var anonymous_id = 1;
var _resolveFunc = function (execution) {
var resolvedFunc = execution.func;
if (typeof execution.func === 'function') {
resolvedFunc = execution.func;
} else {
if (execution.extension) {
resolvedFunc = arenite[execution.instance].getInPath(execution.func);
} else {
resolvedFunc = arenite.context.get(execution.instance).getInPath(execution.func);
}
}
return resolvedFunc;
};
var _resolveArgs = function (execution, done, type, stack) {
execution.args = execution.args || [];
var failure = false;
var resolved = [];
execution.args.forEach(function (arg, idx) {
if (typeof arg.value !== 'undefined') {
resolved.push(arg.value);
} else if (typeof arg.ref !== 'undefined') {
var ref = arenite.context.get(arg.ref);
if (ref) {
resolved.push(ref);
} else {
failure = true;
if (arenite.debug) {
window.console.log('Arenite: Failed to resolve arg', arg);
}
}
} else if (typeof arg.func !== 'undefined') {
resolved.push(arg.func);
} else if (typeof arg.exec !== 'undefined') {
resolved.push(arg.exec(arenite));
} else if (typeof arg.instance !== 'undefined') {
var anonymousContext = {instances: {}};
var tempId = '__anonymous_temp_instance__' + anonymous_id++;
anonymousContext.instances[tempId] = arg.instance;
_loadContext(anonymousContext, stack);
resolved.push(arenite.context.get(tempId));
if (type === 'factory') {
arenite.context.remove(tempId);
} else {
execution.args.splice(idx, 1, {ref: tempId});
}
}
});
if (execution.wait && typeof done === 'function') {
resolved.push(done);
}
return failure ? null : resolved;
};
var _execFunction = function (execution, before, done, stack) {
var resolvedFunc = _resolveFunc(execution);
if (resolvedFunc) {
var resolvedArgs = _resolveArgs(execution, done, undefined, stack);
if (resolvedArgs) {
if (execution.wait && typeof before === 'function') {
before();
}
resolvedFunc.apply(resolvedFunc, resolvedArgs);
} else {
throw 'Unable to resolve arguments for "' + execution.func + '" of instance "' + execution.instance + '"';
}
} else {
throw 'Unknown function "' + execution.func + '" for instance "' + execution.instance + '"';
}
};
var _wire = function (instances, type, stack) {
if (!instances) {
return;
}
var unresolved = {};
instances.toKeyArray().forEach(function (instance) {
if (instances[instance].factory) {
arenite.context.add(instance, instances[instance], true);
} else {
var func = window.getInPath(instances[instance].namespace);
if (func) {
var args = _resolveArgs(instances[instance], null, type, [instance]);
if (args) {
var actualInstance = func.apply(func, args);
if (type === 'extension') {
var wrappedInstance = {};
wrappedInstance[instance] = actualInstance;
arenite = arenite.fuseWith(wrappedInstance);
}
if (arenite.debug) {
window.console.log('Arenite: Instance', instance, 'wired');
}
arenite.context.add(instance, actualInstance);
} else {
unresolved[instance] = instances[instance];
}
} else {
throw 'Unknown function "' + instances[instance].namespace + '"';
}
}
});
if (unresolved.toKeyArray().length !== instances.toKeyArray().length && unresolved.toKeyArray().length > 0) {
unresolved.forEach(function (instance, name) {
var instanceObj = {};
instanceObj[name] = instance;
_wire(instanceObj, undefined, stack.concat(name));
});
} else {
if (unresolved.toKeyArray().length !== 0) {
throw 'Make sure you don\'t have circular dependencies, Unable to resolve the following instances: ' + unresolved.toKeyArray().join(", ") + ' - [' + stack.join(', ') + ']';
}
}
};
var _init = function (instances, latch, extension) {
if (!instances) {
return;
}
instances.forEach(function (instance, instanceName) {
if (instance.init && !instance.factory) {
if (typeof instance.init === 'string') {
instance.init = {func: instance.init};
}
_execFunction(({
instance: instanceName,
extension: extension
}).fuseWith(instance.init), function () {
latch.countUp();
}, function () {
latch.countDown();
});
}
});
};
var _start = function (starts) {
if (!starts) {
return;
}
if (document.readyState !== 'complete') {
window.setTimeout(function () {
_start(starts);
}, 100);
} else {
starts.forEach(function (start) {
_execFunction(start);
});
}
};
var _loadContext = function (context, stack) {
if (arenite.config.debug) {
window.console.time('Arenite context load');
}
if (context) {
Starting must wait for the wiring
var wireLatch = arenite.async.latch(1, function () {
if (arenite.config.debug) {
window.console.timeEnd('Arenite context load');
}
_start(context.start);
}, "instances");
wiring of instances must wait for the extensions
var extensionsLatch = arenite.async.latch(1, function () {
_wire(context.instances, undefined, stack || []);
_init(context.instances, wireLatch);
wireLatch.countDown();
}, "extensions");
_wire(context.extensions, 'extension', stack || []);
_init(context.extensions, extensionsLatch, true);
extensionsLatch.countDown();
}
};
var _loadAsyncDependencies = function (dependencies) {
var latch = arenite.async.latch(dependencies.async ? dependencies.async.length : 0, function () {
_loadContext(arenite.config.context);
}, 'dependencies');
dependencies.async.forEach(function (dep) {
arenite.loader.loadScript(dep, latch.countDown);
});
};
var _loadSyncDependencies = function () {
if (!arenite.config.context || !arenite.config.context.dependencies) {
return _loadContext(arenite.config.context);
}
var dependencies;
if (!arenite.config.context.dependencies[arenite.config.mode]) {
dependencies = {sync: [], async: []};
} else {
dependencies = arenite.config.context.dependencies[arenite.config.mode];
}
var seqLatch = arenite.async.seqLatch(dependencies.sync || [], function (url) {
arenite.loader.loadScript(url, seqLatch.next);
}, function () {
_loadAsyncDependencies(dependencies);
});
seqLatch.next();
};
var _externalUrl = /^((http:\/\/)|(http:\/\/)|(\/\/)).*$/;
var _prodModuleVersion = /[\d]+\.[\d]+\.*[\d]*/;
var _devRepo = '//rawgit.com/{vendor}/{version}/{module}/';
var _prodRepo = '//cdn.rawgit.com/{vendor}/{version}/{module}/';
var _fetchModules = function (modules, callback, config) {
var results = {};
var moduleKeys = modules.toKeyArray();
if (moduleKeys.length) {
var latch = arenite.async.latch(moduleKeys.length, function () {
modules.forEach(function (module, moduleName) {
var mode = module.vendor ? 'default' : arenite.config.mode;
var newDeps = {async: [], sync: []};
(results[moduleName].module.context.dependencies[mode] || []).forEach(function (dependencies, depType) {
dependencies.forEach(function (dep) {
if (typeof dep === 'string') {
newDeps[depType].push(dep.match(_externalUrl) || !module.vendor ? dep : results[moduleName].path + dep);
} else {
newDeps[depType].push(dep.fuseWith({url: dep.url.match(_externalUrl) || !module.vendor ? dep.url : results[moduleName].path + dep.url}));
}
});
});
delete results[moduleName].module.context.dependencies;
config.context = config.context || {};
config.context.dependencies = config.context.dependencies || {
default: {
sync: [],
async: []
}
};
config.context.dependencies[config.mode] = config.context.dependencies[config.mode] || {
sync: [],
async: []
};
config.context.dependencies.forEach(function (env) {
env.fuseWith(newDeps);
});
config = config.fuseWith(results[moduleName].module);
});
callback();
}, 'modules');
modules.forEach(function (module, moduleName) {
var moduleBasePath;
if (module.vendor) {
if (arenite.config.repo) {
moduleBasePath = arenite.config.repo.replace('{vendor}', module.vendor);
} else if (module.version.match(_prodModuleVersion)) {
moduleBasePath = _prodRepo.replace('{vendor}', module.vendor);
} else {
moduleBasePath = _devRepo.replace('{vendor}', module.vendor);
}
moduleBasePath = moduleBasePath.replace('{version}', module.version);
moduleBasePath = moduleBasePath.replace('{module}', module.module);
} else {
moduleBasePath = module.module + '/';
}
arenite.loader.loadResource(moduleBasePath + 'module.json', function (xhr) {
results[moduleName] = {path: moduleBasePath, module: JSON.parse(xhr.responseText)};
if (results[moduleName].module.imports) {
_fetchModules(results[moduleName].module.imports, latch.countDown, results[moduleName].module);
} else {
latch.countDown();
}
});
});
} else {
callback();
}
};
var _loadConfig = function (config, callback) {
arenite.context.add('arenite', arenite);
arenite.config = config;
arenite.config.mode = arenite.config.mode || arenite.url.query().mode || 'default';
window.console.log('Arenite: Starting in mode', arenite.config.mode);
if (config.debug) {
if (typeof config.debug === 'function') {
config.debug = config.debug(arenite);
}
}
if (config.expose) {
var exposeName = config.expose;
if (typeof config.expose === 'function') {
exposeName = config.expose(arenite);
}
if (exposeName) {
window[exposeName] = arenite;
}
}
if (arenite.config.imports) {
window.console.log('Arenite: Fetching modules', arenite.config.imports.toKeyArray());
_fetchModules(JSON.parse(JSON.stringify(arenite.config.imports)), callback, arenite.config);
} else {
callback();
}
};
var _boot = function (config) {
if (arenite.config) {
_loadSyncDependencies();
} else {
_loadConfig(config, _loadSyncDependencies);
}
};
var _wireFactory = function (instances) {
_wire(instances, 'factory', []);
_init(instances);
};
return {
di: {
Start arenite with the given configuration
init(config)
where config is the complete configuration with imports
init: _boot,
Resolve the imports and merge them into arenite’s internal config object
loadConfig(config, callback)
where config is the partial configuration with the imports and callback is the callback after
the import has extended the config.
loadConfig: _loadConfig,
Resolve the arguments defined in an instance definition AKA execution defined in the arenite configuration format
resolveArgs(execution, done)
where execution is the object describing the execution and done is the callback after the execution.
resolveArgs: _resolveArgs,
Execute an instance definition AKA execution defined in the arenite configuration format
exec(execution, before, done)
where execution is the object describing the execution, before is an optional function to be executed
before the actual execution and done is the callback after the execution.
exec: _execFunction,
Wire a new instance at runtime (used for factories)
wire(instanceDefinitions)
where instanceDefinitions is a list of instance definition objects to be instantiated and initialized.
wire: _wireFactory
}
};
};