API Docs for: 0.4
Show:

File: mojito_src/lib/app/autoload/action-context.common.js

/*
 * Copyright (c) 2011-2012, Yahoo! Inc.  All rights reserved.
 * Copyrights licensed under the New BSD License.
 * See the accompanying LICENSE file for terms.
 */


/*jslint anon:true, sloppy:true, nomen:true*/
/*global YUI*/


/**
 * The Action Context is a key part of the Mojito framework. The <em>ac</em>,
 * for short, gives you access to the frameworks features from within a
 * controller function. The ac is an abstraction that allows you to execute
 * mojit actions within either a server or client context.
 * @module ActionContext
 */
YUI.add('mojito-action-context', function(Y, NAME) {

    var CACHE;


    // -------------------------------------------------------------------------
    // Comments below are so generated comments for flush, done, etc. are found
    // on ActionContext even though they're not really done here.
    // -------------------------------------------------------------------------

    /**
     * Returns data in the request and allows you to carry on execution.
     * @method flush
     * @param {object|string} data The data you want return by the request.
     * @param {object} meta Any meta-data required to service the request.
     */

    /**
     * Returns data and closes the request.
     * @method done
     * @param {object|string} data The data you want return by the request.
     * @param {object} meta Any meta-data required to service the request.
     */

    /**
     * Programatically report an error to Mojito, which will handle it
     * gracefully.
     * @method error
     * @param {Error} err A normal JavaScript Error object is expected, but you
     *     may add a "code" property to the error if you want the framework to
     *     report a certain HTTP status code for the error. For example, if the
     *     status code is 404, Mojito will generate a 404 page. Additionally you
     *     might provide a reasonPhrase property, to override the default human
     *     readable description for this status code with one specific to your
     *     application. For example for the status code 404 you could provide
     *     "This does not exist in my app".
     */

    /**
     * This dispatch function is called one time per Mojito execution. It
     * creates a contextualized Y instance for all further internal dispatches
     * to use. It also creates the ActionContext for the mojit.
     *
     * The command has three main parts:  the "instance", the "context", and the
     * "params".
     * <pre>
     *  command: {
     *      instance: ...see below...
     *      context: ...see below...
     *      params: ...see below...
     *  }
     * </pre>
     *
     * The "instance" is a partial instance with details of the mojit instance.
     * See `ServerStore.expandInstance()` for details of the structure and which
     * fields are required.
     *
     * The "context" is the request context.  It is built by the
     * "contextualizer" middleware.
     *
     * The "params" is a structured set of parameters to pass to the mojit.
     * <pre>
     *  params: {
     *      route: {},
     *      url: {},
     *      body: {},
     *      file: {},
     *      ...
     *  }
     * </pre>
     *
     * <pre>
     * adapter: {
     *      flush: function(data, meta){},
     *      done: function(data, meta){},
     *      error: function(err){}
     * }
     * </pre>
     * @method dispatch
     * @param {map} command the "command" describing how to dispatch the mojit.
     *     See above.
     * @param {object} adapter the output adapter to pass to the mojit. See
     *     above.
     * @deprecated Use 'ac._dispatch()' instead. See https://github.com/yahoo/mojito/blob/develop/DEPRECATIONS.md
     * for details.
     */
     /**
     * This _dispatch function is called one time per Mojito execution. It
     * creates a contextualized Y instance for all further internal dispatches
     * to use. It also creates the ActionContext for the mojit.
     *
     * The command has three main parts:  the "instance", the "context", and the
     * "params".
     * <pre>
     *  command: {
     *      instance: ...see below...
     *      context: ...see below...
     *      params: ...see below...
     *  }
     * </pre>
     *
     * The "instance" is a partial instance with details of the mojit instance.
     * See `ServerStore.expandInstance()` for details of the structure and which
     * fields are required.
     *
     * The "context" is the request context.  It is built by the
     * "contextualizer" middleware.
     *
     * The "params" is a structured set of parameters to pass to the mojit.
     * <pre>
     *  params: {
     *      route: {},
     *      url: {},
     *      body: {},
     *      file: {},
     *      ...
     *  }
     * </pre>
     *
     * <pre>
     * adapter: {
     *      flush: function(data, meta){},
     *      done: function(data, meta){},
     *      error: function(err){}
     * }
     * </pre>
     * @method _dispatch
     * @param {map} command the "command" describing how to dispatch the mojit.
     *     See above.
     * @param {object} adapter the output adapter to pass to the mojit. See
     *     above.
     */

    // TODO: probably should move to mojito.common.js (namespace definitions).
    if (!YUI._mojito) {
        YUI._mojito = {};
    }

    if (!YUI._mojito._cache) {
        YUI._mojito._cache = {};
    }

    if (!YUI._mojito._cache.addons) {
        YUI._mojito._cache.addons = {};
    }

    CACHE = YUI._mojito._cache.addons;


    function calculateAddonDependencies(addon, addons, dependencies) {
        var dep,
            dependsOn = addon.dependsOn,
            i;

        if (!Y.Lang.isArray(dependsOn)) {
            return;
        }

        for (i = 0; i < dependsOn.length; i += 1) {
            dep = dependsOn[i];
            if (!dependencies[dep]) {
                if (!addons[dep]) {
                    throw new Error(addon.prototype.namespace +
                        " addon has invalid dependency: '" + dep + "'");
                }
                calculateAddonDependencies(addons[dep], addons, dependencies);
            }
            dependencies[dep] = true;
        }
    }


    /**
     * Mixes all the Action Context addons into the Action Context
     * @private
     * @method attachActionContextAddons
     * @param {Array} addons The action context addons.
     * @param {object} command The command object.
     * @param {object} adapter The output adapter.
     * @param {Y.mojito.ActionContext} ac The action context.
     * @param {ResourceStore} store the resource store
     */
    function attachActionContextAddons(addons, command, adapter, ac, store) {
        var addonName,
            addon,
            dependencies = {};

        if (CACHE[ac.type]) {
            dependencies = CACHE[ac.type];
        } else {
            for (addonName in addons) {
                if (addons.hasOwnProperty(addonName)) {
                    if (!dependencies[addonName]) {
                        calculateAddonDependencies(addons[addonName], addons,
                            dependencies);
                    }
                    dependencies[addonName] = true;
                }
            }
            CACHE[ac.type] = dependencies;
        }

        for (addonName in dependencies) {
            if (dependencies.hasOwnProperty(addonName)) {
                addon = new addons[addonName](command, adapter, ac);
                if (addon.namespace) {
                    ac[addon.namespace] = addon;
                    if (Y.Lang.isFunction(addon.setStore)) {
                        addon.setStore(store);
                    }
                }
            }
        }
    }


    /**
     * The main point of entry for all mojits into Mojito. The Action Context is
     * passed to every mojit action during execution, either on the client or
     * server. This object is the API into Mojito, can can have many plugins
     * attached the provide extra functionality.
     * @class ActionContext
     */
    function ActionContext(opts) {
        Y.log('constructing action context', 'mojito', 'qeperf');

        var self = this,
            command = opts.command,
            instance = command.instance,
            controller = opts.controller,
            models = opts.models,
            dispatch = opts.dispatch,
            adapter = opts.adapter,
            store = opts.store,
            actionFunction,
            error;

        // "init" is not an action
        if (command.action === 'init') {
            throw new Error('Cannot execute action \'init\' on any mojit.' +
                ' This name is reserved by the Mojito framework.');
        }

        // we want to make these easily accessible to any functions that addons
        // attach directly to the ac object.
        // TODO: These properties should be hidden behind accessor functions.
        this.command = command;
        this.instance = instance;
        this.action = command.action;
        this.type = instance.type;
        this.context = command.context;
        this.models = models;

        // identify this as internal... users probably won't want to use it, but
        // addons might need
        this._dispatch = dispatch;
        this._adapter = adapter;

        // deprecated this function for current users
        this.dispatch = function() {
            Y.log('ac.dispatch() will soon be deprecated to discourage' +
                ' usage from within controllers. If you want to dispatch' +
                ' a command from within an ActionContext addon, please use' +
                ' ac._dispatch().', 'warn', NAME);
            self._dispatch.apply(self, arguments);
        };

        this.app = {
            config: store.getAppConfig(this.context),
            routes: store.getRoutes(this.context)
        };

        // this is where the addons list is injected onto the action
        // context...yay!
        attachActionContextAddons(Y.mojito.addons.ac, command, adapter, this, store);

        Y.log('ActionContext created for "' + (instance.id || '@' +
            instance.type) + '/' + command.action + '"', 'mojito', NAME);

        // Grab the action here as me may change it
        actionFunction = command.action;

        // Check if the controller has the requested action
        if (!Y.Lang.isFunction(controller[actionFunction])) {
            // If the action is not found try the '__call' function
            if (Y.Lang.isFunction(controller.__call)) {
                actionFunction = '__call';
            } else {
                // If there is still no joy then die
                error = new Error("No method '" + command.action +
                    "' on controller type '" + instance.type + "'");
                error.code = 404;
                throw error;
            }
        }

        // Time marker
        Y.mojito.perf.mark('mojito', 'core_action_start[' + instance.type +
            ':' + command.action + ']', 'Calling the Mojit "' + instance.type +
            '" with action "' + command.action + '"');

        Y.log('action context created, executing action "' + actionFunction +
            '"', 'mojito', 'qeperf');

        controller[actionFunction](this);
    }

    Y.namespace('mojito').ActionContext = ActionContext;

}, '0.1.0', {requires: [
    // following are ACPs are always available
    'mojito-config-addon',
    'mojito-output-adapter-addon',
    'mojito-url-addon',
    'mojito-assets-addon',
    'mojito-cookie-addon',
    'mojito-params-addon',
    'mojito-composite-addon',
    'mojito-perf'
]});