From 0d36bf1bbe9b851f34eaca1490886a52ec1f9818 Mon Sep 17 00:00:00 2001 From: Caridy Patino Date: Tue, 26 Feb 2013 11:38:22 -0500 Subject: [PATCH 1/4] introducing the concept of adapter.page which represents an abstraction of req in the server and doc in the client. This structure could be use to store data per request on the server and per page on the client. at the moment, this structure will not be serialized or used after the dispatcher ends --- lib/app/autoload/mojito-client.client.js | 1 + lib/app/autoload/output-handler.client.js | 1 + lib/output-handler.server.js | 1 + 3 files changed, 3 insertions(+) diff --git a/lib/app/autoload/mojito-client.client.js b/lib/app/autoload/mojito-client.client.js index bab6df7d3..6098e26c8 100644 --- a/lib/app/autoload/mojito-client.client.js +++ b/lib/app/autoload/mojito-client.client.js @@ -218,6 +218,7 @@ YUI.add('mojito-client', function(Y, NAME) { * server to start up mojito. */ function MojitoClient(config) { + this.page = {}; this.timeLogStack = []; this.yuiConsole = null; this._pauseQueue = []; diff --git a/lib/app/autoload/output-handler.client.js b/lib/app/autoload/output-handler.client.js index fbcdcb227..2417d8b53 100644 --- a/lib/app/autoload/output-handler.client.js +++ b/lib/app/autoload/output-handler.client.js @@ -139,6 +139,7 @@ YUI.add('mojito-output-handler', function(Y, NAME) { this.callback = cb; this.buffer = ''; this.mojitoClient = mojitoClient; + this.page = mojitoClient.page; } diff --git a/lib/output-handler.server.js b/lib/output-handler.server.js index 44879b7b8..737177a13 100644 --- a/lib/output-handler.server.js +++ b/lib/output-handler.server.js @@ -30,6 +30,7 @@ var NAME = 'OutputHandler.server', this.res = res; this.next = next; this.headers = {}; + this.page = {}; }; From d5c203184c530f4ef497774735e5e7a31d3a3ef5 Mon Sep 17 00:00:00 2001 From: Caridy Patino Date: Tue, 26 Feb 2013 12:19:01 -0500 Subject: [PATCH 2/4] adding support for custom helpers per instance (instance.helpers) when rendering a particular view using HB --- lib/app/addons/view-engines/hb.client.js | 13 +++++++++++-- lib/app/addons/view-engines/hb.server.js | 13 +++++++++++-- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/lib/app/addons/view-engines/hb.client.js b/lib/app/addons/view-engines/hb.client.js index 6a75e3fa0..3a50a80d5 100644 --- a/lib/app/addons/view-engines/hb.client.js +++ b/lib/app/addons/view-engines/hb.client.js @@ -41,15 +41,24 @@ YUI.add('mojito-hb', function(Y, NAME) { render: function (data, instance, template, adapter, meta, more) { var cacheTemplates = (this.options.cacheTemplates === false ? false : true), handler = function (err, obj) { - var output; + var output, + helpers = HB.helpers; if (err) { adapter.error(err); return; } + // making sure we preserve the original helpers + // from Y.Handlebars.helpers but letting the custom + // helpers per mojit instance to override any of them. + if (instance && instance.helpers) { + helpers = Y.merge(HB.helpers, instance.helpers); + } + output = obj.compiled(data, { - partials: obj.partials + partials: obj.partials, + helpers: helpers }); if (more) { diff --git a/lib/app/addons/view-engines/hb.server.js b/lib/app/addons/view-engines/hb.server.js index bb502c3ce..79b722625 100644 --- a/lib/app/addons/view-engines/hb.server.js +++ b/lib/app/addons/view-engines/hb.server.js @@ -43,15 +43,24 @@ YUI.add('mojito-hb', function (Y, NAME) { render: function (data, instance, template, adapter, meta, more) { var cacheTemplates = (this.options.cacheTemplates === false ? false : true), handler = function (err, obj) { - var output; + var output, + helpers = HB.helpers; if (err) { adapter.error(err); return; } + // making sure we preserve the original helpers + // from Y.Handlebars.helpers but letting the custom + // helpers per mojit instance to override any of them. + if (instance && instance.helpers) { + helpers = Y.merge(HB.helpers, instance.helpers); + } + output = obj.compiled(data, { - partials: obj.partials + partials: obj.partials, + helpers: helpers }); // HookSystem::StartBlock From 091bd6c12132befcdc1df93aacf90c2968ecaaf6 Mon Sep 17 00:00:00 2001 From: Caridy Patino Date: Tue, 26 Feb 2013 12:36:18 -0500 Subject: [PATCH 3/4] refactor on the experimental mojito-models-addon to use the new global adapter.page object to expose models per request. also, changing the api to align with helpers --- lib/app/addons/ac/models.common.js | 79 +++++++-------- .../lib/app/addons/ac/test-models.common.js | 97 +++++++++++-------- 2 files changed, 98 insertions(+), 78 deletions(-) diff --git a/lib/app/addons/ac/models.common.js b/lib/app/addons/ac/models.common.js index 1c776ee10..0f72318d4 100644 --- a/lib/app/addons/ac/models.common.js +++ b/lib/app/addons/ac/models.common.js @@ -14,26 +14,14 @@ YUI.add('mojito-models-addon', function (Y, NAME) { 'use strict'; - // global model cache when on the client to store - // models at the page level, while running on the - // server we store them on adapter.req object per - // request. - var _clientCache = {}; - - // TODO: - // - update tests - // - update fixtures - // - update documentation - // - update examples - /** - * Access point: ac.models.get() + * Access point: ac.models.* * Addon that provides access to the models collection * @class Models.common */ function Addon(command, adapter) { this._models = {}; - this._adapter = adapter; + this._page = adapter.page; this._instance = command.instance; } @@ -49,9 +37,8 @@ YUI.add('mojito-models-addon', function (Y, NAME) { */ get: function (modelName) { - var model = this._models[modelName] || _clientCache[modelName] || - (this._adapter.req && this._adapter.req.models && - this._adapter.req.models[modelName]); + var cache = this._page.models || {}, + model = this._models[modelName] || cache[modelName]; // instantanting the model once during the lifetime of // the ac object, this acts like an internal cache. @@ -76,33 +63,47 @@ YUI.add('mojito-models-addon', function (Y, NAME) { }, /** - * Set a model instance as global. On the server side + * set a model instance at the mojit instance. + * @method set + * @param {string} modelName The model name. + * @param {object} model The model instance. + */ + set: function (modelName, model) { + if (!modelName || !model) { + Y.log('Invalid model name or model instance ' + + 'when calling `ac.models.set()`: ' + modelName, 'error', NAME); + return; + } + if (this._models[modelName]) { + Y.log('Overiding an existing model instance with name: ' + + modelName, 'warn', NAME); + } + this._models[modelName] = model; + }, + + /** + * Expose a model instance as global. On the server side * this means any mojit instance under a particular request * will have access to the model. On the client, any * mojit instance on the page will have access to * the model as well. - * @method get - * @param {string} name The model name. - * @param {object} model The model instance + * @method expose + * @param {string} modelName The model name. + * @param {object} model Optional model instance, if not + * present, the instance will be lookup by modelName. */ - registerGlobal: function (name, model) { - if (this._adapter.req) { - // server side routine to store on the request - // to avoid leaks. - // NOTE: models on req object will be destroyed - // with the request lifecycle. - this._adapter.req.models = this._adapter.req.models || {}; - this._adapter.req.models[name] = model; - Y.log('Storing a global model on the server: ' + name, 'info', NAME); - } else { - // client side routine to store on a global - // cache structure. - // NOTE: there is no way to destroy this model at - // the moment, it is now tied to the page - // life cycle. - _clientCache[name] = model; - Y.log('Storing a global model on the client: ' + name, 'info', NAME); - } + expose: function (modelName, model) { + this._page.models = this._page.models || {}; + // you might want to expose an existing local model + model = model || this.get(modelName); + + // NOTE: models on the server will be destroyed + // with the request lifecycle. + // NOTE: for models on the client, there is no way + // to destroy them at the moment, it is tied + // to the page life cycle. + this._page.models[modelName] = model; + Y.log('Exposing a global model: ' + modelName, 'info', NAME); } }; diff --git a/tests/unit/lib/app/addons/ac/test-models.common.js b/tests/unit/lib/app/addons/ac/test-models.common.js index 64115ecd1..4218d99c1 100644 --- a/tests/unit/lib/app/addons/ac/test-models.common.js +++ b/tests/unit/lib/app/addons/ac/test-models.common.js @@ -13,7 +13,9 @@ YUI().use('mojito-models-addon', 'test', function(Y) { name: 'model tests', 'invalid model name': function () { - var adapter = {}; + var adapter = { + page: {} + }; var addon = new Y.mojito.addons.ac.models({ instance: {} @@ -25,7 +27,9 @@ YUI().use('mojito-models-addon', 'test', function(Y) { }, 'valid model name': function () { - var adapter = {}; + var adapter = { + page: {} + }; var addon = new Y.mojito.addons.ac.models({ instance: { @@ -43,7 +47,9 @@ YUI().use('mojito-models-addon', 'test', function(Y) { }, 'test instances of a model': function () { - var adapter = {}; + var adapter = { + page: {} + }; var addon = new Y.mojito.addons.ac.models({ instance: {} @@ -68,9 +74,9 @@ YUI().use('mojito-models-addon', 'test', function(Y) { name: 'global model tests', - 'register global model in server with req object': function() { - var serverAdapter = { - req: {} + 'test register global model': function() { + var adapter = { + page: {} }; var bar = { init: function () { @@ -81,13 +87,13 @@ YUI().use('mojito-models-addon', 'test', function(Y) { // creating first addon instance var addon1 = new Y.mojito.addons.ac.models({ instance: {} - }, serverAdapter); - addon1.registerGlobal('bar', bar); + }, adapter); + addon1.expose('bar', bar); // creating second addon instance var addon2 = new Y.mojito.addons.ac.models({ instance: {} - }, serverAdapter); + }, adapter); // testing models var model1 = addon1.get('bar'); @@ -98,36 +104,10 @@ YUI().use('mojito-models-addon', 'test', function(Y) { A.areSame(bar, model2, 'should return the registered model'); }, - 'register global model in client without req object': function() { - var clientAdapter = {}; - var baz = { - init: function () { - A.fail('init of a global model should never be called, it is an instance already'); - } - }; - - // creating first addon instance - var addon1 = new Y.mojito.addons.ac.models({ - instance: {} - }, clientAdapter); - addon1.registerGlobal('baz', baz); - - // creating second addon instance - var addon2 = new Y.mojito.addons.ac.models({ - instance: {} - }, clientAdapter); - - // testing models - var model1 = addon1.get('baz'); - var model2 = addon2.get('baz'); - A.isObject(model1, 'registered global model should return an instance'); - A.areSame(baz, model1, 'should return the registered model'); - A.isObject(model2, 'registered global model by other mojit should return an instance'); - A.areSame(baz, model2, 'should return the registered model'); - }, - 'test global vs local': function() { - var adapter = {}; + var adapter = { + page: {} + }; var addon = new Y.mojito.addons.ac.models({ instance: {} @@ -141,11 +121,50 @@ YUI().use('mojito-models-addon', 'test', function(Y) { }; Y.mojito.models.cuba = localModel; - addon.registerGlobal('cuba', globalModel); + addon.expose('cuba', globalModel); model = addon.get('cuba'); A.isObject(model, 'registered model should return an instance'); A.areSame(globalModel, model, 'global registered should have priority over local models'); + }, + + 'test expose': function() { + var adapter = { + page: {} + }; + var baz = { + init: function () { + A.fail('init of a global model should never be called, it is an instance already'); + } + }; + // creating first addon instance + var addon = new Y.mojito.addons.ac.models({ + instance: {} + }, adapter); + addon.expose('baz', baz); + + // testing models + A.areSame(baz, adapter.page.models.baz, 'models should be exposed thru adapter.page.models.*'); + }, + + 'test expose by name': function() { + var adapter = { + page: {} + }; + var baz = { + init: function () { + A.fail('init of a global model should never be called, it is an instance already'); + } + }; + // creating first addon instance + var addon = new Y.mojito.addons.ac.models({ + instance: {} + }, adapter); + addon.set('baz', baz); + addon.expose('baz'); + + // testing models + A.areSame(baz, adapter.page.models.baz, 'models should be exposed thru adapter.page.models.*'); } })); From 3ba7e9bc1d8c2a69a3b247ba666838e5f58f722a Mon Sep 17 00:00:00 2001 From: Caridy Patino Date: Tue, 26 Feb 2013 12:37:11 -0500 Subject: [PATCH 4/4] introducing mojito-helpers-addon which happens to use the same api that mojito-models-addon uses. this is experimental in 0.5.5 --- lib/app/addons/ac/helpers.common.js | 92 +++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 lib/app/addons/ac/helpers.common.js diff --git a/lib/app/addons/ac/helpers.common.js b/lib/app/addons/ac/helpers.common.js new file mode 100644 index 000000000..43346167a --- /dev/null +++ b/lib/app/addons/ac/helpers.common.js @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2011-2013, Yahoo! Inc. All rights reserved. + * Copyrights licensed under the New BSD License. + * See the accompanying LICENSE file for terms. + */ + +/*jslint nomen:true*/ +/*global YUI*/ + +/** + * @module ActionContextAddon + */ +YUI.add('mojito-helpers-addon', function (Y, NAME) { + + 'use strict'; + + /** + * Access point: ac.helpers.* + * Addon that provides helpers functionalities + * @class Helpers.common + */ + function Addon(command, adapter) { + this._page = adapter.page; + // exposing instance helpers for render engine can use it, + // also, mixing in any previously exposed helper. + this._helpers = command.instance.helpers = Y.merge({}, + (this._page.helpers || {})); + } + + Addon.prototype = { + + namespace: 'helpers', + + /** + * Gets one specific helper if the name is specified, + * otherwise returns all available helpers. + * @method get + * @param {string} helperName The optional helper name + * @return {function|object} a helper function or all available helpers + */ + get: function (helperName) { + return helperName ? this._helpers[helperName] : this._helpers; + }, + + /** + * set a helper function at the mojit instance. + * @method set + * @param {string} helperName The helper name. + * @param {function} helper The helper function. + */ + set: function (helperName, helper) { + if (!helperName || !helper) { + Y.log('Invalid helper name or helper function ' + + 'when calling `ac.helpers.set()`: ' + helperName, 'error', NAME); + return; + } + if (this._helpers[helperName]) { + Y.log('Overiding an existing helper function with name: ' + + helperName, 'warn', NAME); + } + this._helpers[helperName] = helper; + }, + + /** + * Expose a helper function as global. On the server side + * this means any mojit instance under a particular request + * can use the helper. On the client, any + * mojit instance on the page can use the helper. + * @method expose + * @param {string} helperName The helper name. + * @param {function} helper Optional helper function, if not + * present, the helper will be lookup by name. + */ + expose: function (helperName, helper) { + this._page.helpers = this._page.helpers || {}; + // you might want to expose an existing local helper + helper = helper || this._helpers[helperName]; + // exposing thru global page object + this._page.helpers[helperName] = helper; + // exposing at the instance level + this._helpers[helperName] = helper; + Y.log('Exposing a global helper: ' + helperName, 'info', NAME); + } + + }; + + Y.namespace('mojito.addons.ac').helpers = Addon; + +}, '0.1.0', {requires: [ + 'mojito', + 'mojito-util' +]});