diff --git a/packages/ember-handlebars/lib/helpers/binding.js b/packages/ember-handlebars/lib/helpers/binding.js index dd4fc0f..c0d28a7 100644 --- a/packages/ember-handlebars/lib/helpers/binding.js +++ b/packages/ember-handlebars/lib/helpers/binding.js @@ -13,141 +13,139 @@ var forEach = Ember.ArrayUtils.forEach; var EmberHandlebars = Ember.Handlebars, helpers = EmberHandlebars.helpers; -(function() { - // Binds a property into the DOM. This will create a hook in DOM that the - // KVO system will look for and update if the property changes. - var bind = function(property, options, preserveContext, shouldDisplay, valueNormalizer) { - var data = options.data, - fn = options.fn, - inverse = options.inverse, - view = data.view, - ctx = this, - normalized; - - normalized = Ember.Handlebars.normalizePath(ctx, property, data); - - ctx = normalized.root; - property = normalized.path; - - // Set up observers for observable objects - if ('object' === typeof this) { - // Create the view that will wrap the output of this template/property - // and add it to the nearest view's childViews array. - // See the documentation of Ember._HandlebarsBoundView for more. - var bindView = view.createChildView(Ember._HandlebarsBoundView, { - preserveContext: preserveContext, - shouldDisplayFunc: shouldDisplay, - valueNormalizerFunc: valueNormalizer, - displayTemplate: fn, - inverseTemplate: inverse, - property: property, - previousContext: ctx, - isEscaped: options.hash.escaped, - templateData: options.data - }); - - view.appendChild(bindView); - - /** @private */ - var observer = function() { - Ember.run.once(bindView, 'rerenderIfNeeded'); - }; - - // Observes the given property on the context and - // tells the Ember._BindableSpan to re-render. If property - // is an empty string, we are printing the current context - // object ({{this}}) so updating it is not our responsibility. - if (property !== '') { - Ember.addObserver(ctx, property, observer); - } - } else { - // The object is not observable, so just render it out and - // be done with it. - data.buffer.push(getPath(this, property, options)); - } - }; +// Binds a property into the DOM. This will create a hook in DOM that the +// KVO system will look for and update if the property changes. +var bind = function(property, options, preserveContext, shouldDisplay, valueNormalizer) { + var data = options.data, + fn = options.fn, + inverse = options.inverse, + view = data.view, + ctx = this, + normalized; + + normalized = Ember.Handlebars.normalizePath(ctx, property, data); + + ctx = normalized.root; + property = normalized.path; + + // Set up observers for observable objects + if ('object' === typeof this) { + // Create the view that will wrap the output of this template/property + // and add it to the nearest view's childViews array. + // See the documentation of Ember._HandlebarsBoundView for more. + var bindView = view.createChildView(Ember._HandlebarsBoundView, { + preserveContext: preserveContext, + shouldDisplayFunc: shouldDisplay, + valueNormalizerFunc: valueNormalizer, + displayTemplate: fn, + inverseTemplate: inverse, + property: property, + previousContext: ctx, + isEscaped: options.hash.escaped, + templateData: options.data + }); - /** - '_triageMustache' is used internally select between a binding and helper for - the given context. Until this point, it would be hard to determine if the - mustache is a property reference or a regular helper reference. This triage - helper resolves that. - - This would not be typically invoked by directly. - - @private - @name Handlebars.helpers._triageMustache - @param {String} property Property/helperID to triage - @param {Function} fn Context to provide for rendering - @returns {String} HTML string - */ - EmberHandlebars.registerHelper('_triageMustache', function(property, fn) { - Ember.assert("You cannot pass more than one argument to the _triageMustache helper", arguments.length <= 2); - if (helpers[property]) { - return helpers[property].call(this, fn); - } - else { - return helpers.bind.apply(this, arguments); + view.appendChild(bindView); + + /** @private */ + var observer = function() { + Ember.run.once(bindView, 'rerenderIfNeeded'); + }; + + // Observes the given property on the context and + // tells the Ember._BindableSpan to re-render. If property + // is an empty string, we are printing the current context + // object ({{this}}) so updating it is not our responsibility. + if (property !== '') { + Ember.addObserver(ctx, property, observer); } - }); + } else { + // The object is not observable, so just render it out and + // be done with it. + data.buffer.push(getPath(this, property, options)); + } +}; - /** - `bind` can be used to display a value, then update that value if it - changes. For example, if you wanted to print the `title` property of - `content`: +/** + '_triageMustache' is used internally select between a binding and helper for + the given context. Until this point, it would be hard to determine if the + mustache is a property reference or a regular helper reference. This triage + helper resolves that. - {{bind "content.title"}} + This would not be typically invoked by directly. - This will return the `title` property as a string, then create a new - observer at the specified path. If it changes, it will update the value in - DOM. Note that if you need to support IE7 and IE8 you must modify the - model objects properties using Ember.get() and Ember.set() for this to work as - it relies on Ember's KVO system. For all other browsers this will be handled - for you automatically. + @private + @name Handlebars.helpers._triageMustache + @param {String} property Property/helperID to triage + @param {Function} fn Context to provide for rendering + @returns {String} HTML string +*/ +EmberHandlebars.registerHelper('_triageMustache', function(property, fn) { + Ember.assert("You cannot pass more than one argument to the _triageMustache helper", arguments.length <= 2); + if (helpers[property]) { + return helpers[property].call(this, fn); + } + else { + return helpers.bind.apply(this, arguments); + } +}); - @private - @name Handlebars.helpers.bind - @param {String} property Property to bind - @param {Function} fn Context to provide for rendering - @returns {String} HTML string - */ - EmberHandlebars.registerHelper('bind', function(property, fn) { - Ember.assert("You cannot pass more than one argument to the bind helper", arguments.length <= 2); +/** + `bind` can be used to display a value, then update that value if it + changes. For example, if you wanted to print the `title` property of + `content`: + + {{bind "content.title"}} + + This will return the `title` property as a string, then create a new + observer at the specified path. If it changes, it will update the value in + DOM. Note that if you need to support IE7 and IE8 you must modify the + model objects properties using Ember.get() and Ember.set() for this to work as + it relies on Ember's KVO system. For all other browsers this will be handled + for you automatically. + + @private + @name Handlebars.helpers.bind + @param {String} property Property to bind + @param {Function} fn Context to provide for rendering + @returns {String} HTML string +*/ +EmberHandlebars.registerHelper('bind', function(property, fn) { + Ember.assert("You cannot pass more than one argument to the bind helper", arguments.length <= 2); - var context = (fn.contexts && fn.contexts[0]) || this; + var context = (fn.contexts && fn.contexts[0]) || this; - return bind.call(context, property, fn, false, function(result) { - return !Ember.none(result); - }); + return bind.call(context, property, fn, false, function(result) { + return !Ember.none(result); }); +}); - /** - Use the `boundIf` helper to create a conditional that re-evaluates - whenever the bound value changes. - - {{#boundIf "content.shouldDisplayTitle"}} - {{content.title}} - {{/boundIf}} - - @private - @name Handlebars.helpers.boundIf - @param {String} property Property to bind - @param {Function} fn Context to provide for rendering - @returns {String} HTML string - */ - EmberHandlebars.registerHelper('boundIf', function(property, fn) { - var context = (fn.contexts && fn.contexts[0]) || this; - var func = function(result) { - if (Ember.typeOf(result) === 'array') { - return get(result, 'length') !== 0; - } else { - return !!result; - } - }; +/** + Use the `boundIf` helper to create a conditional that re-evaluates + whenever the bound value changes. - return bind.call(context, property, fn, true, func, func); - }); -})(); + {{#boundIf "content.shouldDisplayTitle"}} + {{content.title}} + {{/boundIf}} + + @private + @name Handlebars.helpers.boundIf + @param {String} property Property to bind + @param {Function} fn Context to provide for rendering + @returns {String} HTML string +*/ +EmberHandlebars.registerHelper('boundIf', function(property, fn) { + var context = (fn.contexts && fn.contexts[0]) || this; + var func = function(result) { + if (Ember.typeOf(result) === 'array') { + return get(result, 'length') !== 0; + } else { + return !!result; + } + }; + + return bind.call(context, property, fn, true, func, func); +}); /** @name Handlebars.helpers.with @@ -156,10 +154,27 @@ var EmberHandlebars = Ember.Handlebars, helpers = EmberHandlebars.helpers; @returns {String} HTML string */ EmberHandlebars.registerHelper('with', function(context, options) { - Ember.assert("You must pass exactly one argument to the with helper", arguments.length === 2); - Ember.assert("You must pass a block to the with helper", options.fn && options.fn !== Handlebars.VM.noop); + if (arguments.length === 4) { + var keywordName, path; + + Ember.assert("If you pass more than one argument to the with helper, it must be in the form #with foo as bar", arguments[1] === "as"); + options = arguments[3]; + preserveContext = true; + keywordName = arguments[2]; + path = arguments[0]; - return helpers.bind.call(options.contexts[0], context, options); + Ember.assert("You must pass a block to the with helper", options.fn && options.fn !== Handlebars.VM.noop); + + options.data.keywords[keywordName] = getPath(this, path); + + return bind.call(this, path, options.fn, true, function(result) { + return !Ember.none(result); + }); + } else { + Ember.assert("You must pass exactly one argument to the with helper", arguments.length === 2); + Ember.assert("You must pass a block to the with helper", options.fn && options.fn !== Handlebars.VM.noop); + return helpers.bind.call(options.contexts[0], context, options); + } }); diff --git a/packages/ember-handlebars/tests/helpers/with_test.js b/packages/ember-handlebars/tests/helpers/with_test.js new file mode 100644 index 0000000..cdf98f9 --- /dev/null +++ b/packages/ember-handlebars/tests/helpers/with_test.js @@ -0,0 +1,17 @@ +var appendView = function(view) { + Ember.run(function() { view.appendTo('#qunit-fixture'); }); +}; + +module("Handlebars {{#with}} helper"); + +test("it should support #with foo as bar", function() { + var view = Ember.View.create({ + template: Ember.Handlebars.compile("{{#with person as tom}}{{title}}: {{tom.name}}{{/with}}"), + title: "Señor Engineer", + person: { name: "Tom Dale" } + }); + + appendView(view); + + equal(view.$().text(), "Señor Engineer: Tom Dale", "should be properly scoped"); +}); diff --git a/packages/ember-views/lib/views/view.js b/packages/ember-views/lib/views/view.js index 63ce13c..43b1b76 100644 --- a/packages/ember-views/lib/views/view.js +++ b/packages/ember-views/lib/views/view.js @@ -743,20 +743,23 @@ Ember.View = Ember.Object.extend(Ember.Evented, templateData = this.get('templateData'), controller = this.get('controller'); + var keywords = templateData ? Ember.copy(templateData.keywords) : {}; + keywords.view = get(this, 'concreteView'); + + // If the view has a controller specified, make it available to the + // template. If not, pass along the parent template's controller, + // if it exists. + if (controller) { + keywords.controller = controller; + } + var data = { view: this, buffer: buffer, isRenderData: true, - keywords: { - view: get(this, 'concreteView') - } + keywords: keywords }; - // If the view has a controller specified, make it available to the - // template. If not, pass along the parent template's controller, - // if it exists. - data.keywords.controller = controller || (templateData && templateData.keywords.controller); - // Invoke the template with the provided template context, which // is the view by default. A hash of data is also passed that provides // the template with access to the view and render buffer.