Skip to content

Commit

Permalink
Merge pull request YahooArchive#459 from caridy/yaf-develop
Browse files Browse the repository at this point in the history
support for route events in controllers, support global models, and spec->models mapping.
  • Loading branch information
caridy committed Sep 5, 2012
2 parents a87298c + b6c3992 commit bb539ea
Show file tree
Hide file tree
Showing 3 changed files with 211 additions and 43 deletions.
16 changes: 13 additions & 3 deletions lib/app/autoload/action-context.common.js
Original file line number Diff line number Diff line change
Expand Up @@ -303,9 +303,12 @@ YUI.add('mojito-action-context', function(Y, NAME) {
// If the action is not found try the '__call' function
if (Y.Lang.isFunction(controller.__call)) {
actionFunction = '__call';
Y.log('__call is deprecated, please use a router event instead.', 'warn', NAME);
} else if (controller.fire && controller.getEvent && controller.getEvent(actionFunction)) {
Y.log('Using a custom event to trigger the action into the controller', 'info', NAME);
} else {
// If there is still no joy then die
error = new Error("No method '" + command.action +
// If there is still no joy then die
error = new Error("No method or event '" + command.action +
"' on controller type '" + instance.type + "'");
error.code = 404;
throw error;
Expand All @@ -320,7 +323,14 @@ YUI.add('mojito-action-context', function(Y, NAME) {
Y.log('action context created, executing action "' + actionFunction +
'"', 'mojito', 'qeperf');

controller[actionFunction](this);
if (controller[actionFunction]) {
Y.log('Action as a method is deprecated, please use a route event instead for action: ' + actionFunction, 'warn', NAME);
controller[actionFunction](this);
} else {
controller.fire(actionFunction, {
ac: this
});
}
}

Y.namespace('mojito').ActionContext = ActionContext;
Expand Down
236 changes: 197 additions & 39 deletions lib/app/autoload/controller-context.common.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,75 +29,158 @@ YUI.add('mojito-controller-context', function(Y, NAME) {
// controller.init() call below.
instance = this.instance,
controller,
models,
actions = this.Y.mojito.actions || [],
ControllerClass,
shareYUIInstance = this.shareYUIInstance,

// do a shallow merge of app-level and mojit-level configs
// mojit config properties take precedence
configApp = this.store.getAppConfig({}).config,
configCombo = Y.merge(configApp, instance.config),

controllerName = instance['controller-module'],
// Y.mojito.controller for legacy, multi-instance.
// Y.mojito.controllers for shared instance
c = this.Y.mojito.controller ||
this.Y.mojito.controllers[instance['controller-module']];
originalController = this.Y.mojito.controller ||
this.Y.mojito.controllers[controllerName];

// If sharing YUI and controller clobbers, log an error.
if (shareYUIInstance && this.Y.mojito.controller) {
this.Y.log(instance['controller-module'] + ' mojit' +
this.Y.log(controllerName + ' mojit' +
' clobbers Y.mojito.controller namespace. Please use' +
' `Y.namespace(\'mojito.controllers\')[NAME]` when ' +
' declaring controllers.', 'error', NAME);
}

if (!Y.Lang.isObject(c)) {
// expanding models so we can pass the references into the
// initialization process.
models = this._expandModels(configCombo);

// using instance->models as a mapping to pass some models
// instances as part of the configuration into the
// controller. This will facilitate the access to global models in
// a form of config.modelName or this.get('modelName') if using
// attributes in the controller.
Y.Object.each((instance.models || {}), function(modelName, attrName) {
if (models[modelName]) {
configCombo[attrName] = models[modelName];
}
});

if (Y.Lang.isFunction(originalController)) {

// Using the original function as a synthetic controller class
// to mix in few other stuff
if (originalController._yuibuild) {

// TODO: what if the class extends Y.Base but was not built
// using Y.Base.create?

this.Y.log('Mix in actions and Y.EventTarget into a controller' +
' created with Y.Base.create', 'debug', NAME);

// the original controller was build with Y.Base.create
// we just need to mix few more things, including actions
// in which case they need to be extensions.
ControllerClass = Y.Base.create(controllerName, originalController,
Y.Object.values(actions), {}, {});

// Make controller an EventTarget, we want to keep this very
// light-weight and obscure for now.
// TODO: how can we know if EventTarget is really needed
// it might be part of the original class already.
Y.mix(ControllerClass, Y.EventTarget, false, null, 1);

} else {

// the original controller seems to be a regular class
// let's just augment it to ger EventTarget support

this.Y.log('Mix in actions and Y.EventTarget into a class' +
' controller', 'debug', NAME);

// TODO: how can I preserve the original class definition?
ControllerClass = originalController;

// TODO: should we use augment?

// Make controller an EventTarget, we want to keep this very
// light-weight and obscure for now.
Y.augment(ControllerClass, Y.EventTarget, true, null, {
emitFacade: true
});

// mix in any (new) actions (the actions namespace here would be
// populated by the resource store...but currently unused? Could
// this be replaced by light inheritance to the controllers here).
// TODO: should we keep supporting this? or should we just mix
// common + server or common + client controllers now that we
// have support for events?
Y.Object.each(actions, function(action, actionName) {
this.Y.log('mixing action \'' + actionName +
'\' into controller...', 'debug', NAME);
ControllerClass[actionName] = action;
});
}

} else if (Y.Lang.isObject(originalController)) {

// Creating a synthetic controller class to mix in few other stuff
ControllerClass = function() {};
ControllerClass.prototype = originalController;

// TODO: should we use augment?

this.Y.log('Mix in actions and Y.EventTarget into an object' +
' controller', 'debug', NAME);

// Make controller an EventTarget, we want to keep this very
// light-weight and obscure for now.
Y.augment(ControllerClass, Y.EventTarget, true, null, {
emitFacade: true
});

// mix in any (new) actions (the actions namespace here would be
// populated by the resource store...but currently unused? Could
// this be replaced by light inheritance to the controllers here).
// TODO: should we keep supporting this? or should we just mix
// common + server or common + client controllers now that we
// have support for events?
Y.Object.each(this.Y.mojito.actions, function(action, actionName) {
this.Y.log('mixing action \'' + actionName +
'\' into controller...', 'debug', NAME);
ControllerClass[actionName] = action;
});

} else {
error = new Error('Mojit controller prototype is not an' +
' object! (mojit id: \'' + instance.id + '\')');
' object or a function! (mojit id: \'' + instance.id + '\')');

error.code = 500;
throw error;
}

// we make a controller instance by using the heir() function, this
// gives us proper function scope within the controller actions
controller = this.controller = Y.mojito.util.heir(c);
// we make a controller instance by using the controller class, this
// gives us proper function scope within the controller actions and
// add EventTarget capabilities into the controller instance.
// TODO: expand configCombo->models to plug app level models
controller = this.controller = new ControllerClass(configCombo);

if (Y.Lang.isFunction(controller.init)) {
// explicitly checking the original controller, otherwise
// we should not try to deal with "init" since the Y.BaseCore
// life-cycle will handle that.
if (Y.Lang.isFunction(originalController.init)) {
// Use the instance data which isn't really an instance to
// provide construction parameters for the controller init().
controller.init(configCombo);
}

// mix in any (new) actions (the actions namespace here would be
// populated by the resource store...but currently unused? Could
// this be replaced by light inheritance to the controllers here).
Y.Object.each(this.Y.mojito.actions, function(action, actionName) {
this.Y.log('mixing action \'' + actionName +
'\' into controller...', 'debug', NAME);
controller[actionName] = function() {
action.apply(controller, arguments);
};
});

// stash the models this controller has available to be later
// attached to the ActionContext
this.models = {};

Y.Object.each(this.Y.mojito.models, function(model, modelName) {

if (!shareYUIInstance || (instance.models &&
instance.models[modelName])) {

// TODO: Why? There's no particular reason to inherit here.
var modelInstance = Y.mojito.util.heir(model);

if (Y.Lang.isFunction(modelInstance.init)) {
// NOTE that we use the same config here that we use to
// config the controller
modelInstance.init(configCombo);
}
this.models[modelName] = modelInstance;
}
}, this);
// TODO: this is for BC for those folks who are using
// ac.models.foo to get a model reference
this.models = models;
},


Expand Down Expand Up @@ -160,12 +243,87 @@ YUI.add('mojito-controller-context', function(Y, NAME) {

this.Y.mojito.perf.mark('mojito', 'core_dispatch_end[' +
(instance.id || '@' + instance.type) + ':' + action + ']');
},


_expandModels: function (mojitConfig) {
var error,
instance = this.instance,
models = {},
shareYUIInstance = this.shareYUIInstance,

// global models that inherit from Y.Model can
// be defined on application.json
globalModels = this.store.getAppConfig({}).models || {};


// TODO: for now, we have a hack to instantiate global models
// per mojit instance, but in the future, these models
// should be sandboxed as part of the "frame" mojit, and
// a reference should be hand over to the mojit that is
// trying to use it, so, part of the complexity of this
// method will be removed.

// BC for mojito models, so devs can get a reference to it
// from ac.models.<modelName>
Y.Object.each(this.Y.mojito.models, function(originalModel, modelName) {
var modelInstance,
ModelClass,
// in case a global model definition is passing some custom
// configurations, in which case they have precedence. Also,
// models might change it, better to preserve the mojitConfig.
modelConfig = Y.merge(mojitConfig, (globalModels[modelName] || {}));

// TODO: why? why do we need to worry about instance.models
if (!shareYUIInstance || (instance.models &&
instance.models[modelName])) {


if (Y.Lang.isFunction(originalModel)) {

// TODO: should we do something with the function? or should
// we just assume it extends Y.Model?
ModelClass = originalModel;

} else if (Y.Lang.isObject(originalModel)) {

// Creating a synthetic model class
ModelClass = function() {};
ModelClass.prototype = originalModel;

}


// we make a controller instance by using the controller class, this
// gives us proper function scope within the controller actions and
// add EventTarget capabilities into the controller instance.
// TODO: expand configCombo->models to plug app level models
modelInstance = new ModelClass(modelConfig);


// explicitly checking the original model, otherwise
// we should not try to deal with "init" since the Y.BaseCore
// life-cycle will handle that.
if (Y.Lang.isFunction(originalModel.init)) {
// NOTE that we use the same config here that we use to
// config the controller
modelInstance.init(modelConfig);
}
models[modelName] = modelInstance;
}

}, this);

return models;
}

};

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

}, '0.1.0', {requires: [
'mojito-action-context',
'mojito-util'
'mojito-util',
'event-custom',
'base-build'
]});
2 changes: 1 addition & 1 deletion travis/before.sh
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ fi
# YUI bleeding
cd ../
echo "Cloning YUI Repository"
git clone git://github.com/yui/yui3.git yui-src
git clone -b 3.x git://github.com/yui/yui3.git yui-src
wait
cd ./yui-src/src/yui
echo "Making YUI NPM Module"
Expand Down

0 comments on commit bb539ea

Please sign in to comment.