mojito
Version:
Mojito provides an architecture, components and tools for developers to build complex web applications faster.
245 lines (202 loc) • 9.13 kB
JavaScript
/*
* Copyright (c) 2011-2013, Yahoo! Inc. All rights reserved.
* Copyrights licensed under the New BSD License.
* See the accompanying LICENSE file for terms.
*/
/*jslint anon:true, nomen:true*/
/*global YUI*/
/**
* This object is responsible for running mojits.
* @class MojitoDispatcher
* @static
* @public
*/
YUI.add('mojito-dispatcher', function (Y, NAME) {
'use strict';
// on the server side, controllers are stateless, but on
// the client, things are different, we can cache them by
// instanceId to re-use them when possible.
var _cacheInstances = {},
_cacheControllers = {};
Y.namespace('mojito').Dispatcher = {
/**
* Initializes the dispatcher instance.
* @method init
* @public
* @param {Y.mojito.ResourceStore} resourceStore the store to use.
* @param {Y.mojito.TunnelClient} rpcTunnel optional tunnel client for RPC calls
* @return {Y.mojito.Dispatcher}
*/
init: function (resourceStore, rpcTunnel) {
if (!resourceStore) {
throw new Error(
'Mojito cannot instantiate without a resource store.'
);
}
// Cache parameters as instance variables for the dispatch() call to
// reference.
this.store = resourceStore;
this.tunnel = rpcTunnel;
Y.log('Dispatcher created', 'debug', NAME);
return this;
},
/**
* Attaches requirements to dispatch the current mojit when
* position. This is usually needed when running in the
* client side and loading mojits on demand.
* @method _useController
* @protected
* @param {object} command the command to dispatch
* @param {OutputAdapter} adapter the output adapter
*/
_useController: function (command, adapter) {
var my = this,
controllers = Y.mojito.controllers,
instance = command.instance;
// TODO: part of the optimization here can be to
// avoid calling use when the controller already exists.
// use controller's yui module name to attach
// the controller to Y ondemand
Y.use(instance.controller, function () {
if (controllers[instance.controller]) {
// continue with the workflow
my._createActionContext(command, adapter);
} else {
// the controller was not found, we should halt
adapter.error(new Error('Invalid controller name [' +
instance.controller + '] for mojit [' +
instance.type + '].'));
}
});
},
/**
* Create AC object for a particular controller.
* @method _createActionContext
* @protected
* @param {object} command the command to dispatch
* @param {OutputAdapter} adapter the output adapter
*/
_createActionContext: function (command, adapter) {
var ac;
// HookSystem::StartBlock
Y.mojito.hooks.hook('dispatchCreateAction', adapter.hook, 'start', command);
// HookSystem::EndBlock
// the controller is not stateless on the client, we
// store it for re-use.
// TODO: we need to find a way to clean this for apps
// that attent to create and destroy mojits from the page
// but maybe we can just wait for the YAF refactor.
if (!_cacheControllers[command.instance.instanceId]) {
_cacheControllers[command.instance.instanceId] =
Y.mojito.util.heir(Y.mojito.controllers[command.instance.controller]);
}
// Note that creation of an ActionContext current causes
// immediate invocation of the dispatch() call.
try {
ac = new Y.mojito.ActionContext({
command: command,
controller: _cacheControllers[command.instance.instanceId],
dispatcher: this, // NOTE passing dispatcher.
adapter: adapter,
store: this.store
});
} catch (e) {
Y.log('Error from dispatch on instance \'' +
(command.instance.base || '@' + command.instance.type) +
'\':', 'error', NAME);
Y.log(e.message, 'error', NAME);
Y.log(e.stack, 'error', NAME);
adapter.error(e);
}
// HookSystem::StartBlock
Y.mojito.hooks.hook('dispatchCreateAction', adapter.hook, 'end', command);
// HookSystem::EndBlock
},
/**
* Executes a command in a remote runtime if possible.
* @method rpc
* @public
* @param {object} command the command to dispatch
* @param {OutputAdapter} adapter the output adapter
*/
rpc: function (command, adapter) {
if (this.tunnel) {
// this prevents the server from trying to RPC itself
// FUTURE: might be a better place to do this
command.rpc = false;
Y.log('Dispatching instance "' + (command.instance.base || '@' +
command.instance.type) + '" through RPC tunnel.', 'debug', NAME);
this.tunnel.rpc(command, adapter);
} else {
adapter.error(new Error('RPC tunnel is not available in the [' +
command.context.runtime + '] runtime.'));
}
},
/**
* Dispatch a command in the current runtime, or fallback
* to a remote runtime when posible.
* @method dispatch
* @public
* @param {object} command the command to dispatch
* @param {OutputAdapter} adapter the output adapter
*/
dispatch: function (command, adapter) {
var my = this,
store = this.store;
// HookSystem::StartBlock
Y.mojito.hooks.hook('dispatch', adapter.hook, 'start', command);
// HookSystem::EndBlock
store.validateContext(command.context);
if (command.rpc) {
Y.log('Command with rpc flag, dispatching through RPC tunnel',
'debug', NAME);
this.rpc(command, adapter);
return;
}
if (command.instance.instanceId && _cacheInstances[command.instance.instanceId]) {
Y.log('Re-using instance with instanceId=' +
command.instance.instanceId, 'debug', NAME);
command.instance = _cacheInstances[command.instance.instanceId];
this._useController(command, adapter);
return;
}
// if no rpc flag and no instance cached, we try to
// expand the instance before creating the ActionContext.
store.expandInstance(command.instance, command.context,
function (err, instance) {
// HookSystem::StartBlock
Y.mojito.hooks.hook('dispatch', adapter.hook, 'end', command);
// HookSystem::EndBlock
if (err || !instance || !instance.controller) {
// error expanding the instance, potentially
// a remote instance that can't be expanded in the
// current runtime and should be dispatched through RPC
Y.log('Cannot expand instance "' + (command.instance.base || '@' +
command.instance.type) + '". Trying with the tunnel in case ' +
'it is a remote mojit.', 'debug', NAME);
if (err) {
// logging the error
Y.log(err, 'warn', NAME);
}
my.rpc(command, adapter);
return;
}
// the instance is not stateless on the client, we
// store it for re-use.
// TODO: we need to find a way to clean this for apps
// that attent to create and destroy mojits from the page
// but maybe we can just wait for the YAF refactor.
_cacheInstances[instance.instanceId] = instance;
// We replace the given instance with the expanded instance.
command.instance = instance;
// requiring the controller and its dependencies
// before dispatching AC
my._useController(command, adapter);
});
}
};
}, '0.1.0', {requires: [
'mojito-action-context',
'mojito-util',
'mojito-hooks'
]});