normalizedValue: function() { var path = this.path; var pathRoot = this.pathRoot; var escape = this.isEscaped; var result, templateData; // Use the pathRoot as the result if no path is provided. This // happens if the path is `this`, which gets normalized into // a `pathRoot` of the current Handlebars context and a path // of `''`. if (path === '') { result = pathRoot; } else { templateData = this.templateData; result = handlebarsGet(pathRoot, path, { data: templateData }); } if (result === null || result === undefined) { result = ""; } else if (!escape && !(result instanceof EmberHandlebars.SafeString)) { result = new EmberHandlebars.SafeString(result); } return result; },
/** `log` allows you to output the value of variables in the current rendering context. `log` also accepts primitive types such as strings or numbers. ```handlebars {{log "myVariable:" myVariable }} ``` @method log @for Ember.Handlebars.helpers @param {String} property */ function logHelper() { var params = a_slice.call(arguments, 0, -1), options = arguments[arguments.length - 1], logger = Logger.log, values = [], allowPrimitives = true; for (var i = 0; i < params.length; i++) { var type = options.types[i]; if (type === 'ID' || !allowPrimitives) { var context = (options.contexts && options.contexts[i]) || this, normalized = normalizePath(context, params[i], options.data); if (normalized.path === 'this') { values.push(normalized.root); } else { values.push(handlebarsGet(normalized.root, normalized.path, options)); } } else { values.push(params[i]); } } logger.apply(logger, values); }
function makeBindings(thisContext, options) { var hash = options.hash, hashType = options.hashTypes; for (var prop in hash) { if (hashType[prop] === 'ID') { var value = hash[prop]; if (IS_BINDING.test(prop)) { Ember.warn("You're attempting to render a view by passing " + prop + "=" + value + " to a view helper, but this syntax is ambiguous. You should either surround " + value + " in quotes or remove `Binding` from " + prop + "."); } else { hash[prop + 'Binding'] = value; hashType[prop + 'Binding'] = 'STRING'; delete hash[prop]; delete hashType[prop]; } } } if (hash.hasOwnProperty('idBinding')) { // id can't be bound, so just perform one-time lookup. hash.id = handlebarsGet(thisContext, hash.idBinding, options); hashType.id = 'STRING'; delete hash.idBinding; delete hashType.idBinding; } }
function _resolveOption(context, options, key) { if (options.hashTypes[key] === "ID") { return handlebarsGet(context, options.hash[key], options); } else { return options.hash[key]; } }
return map.call(resolvePaths(context, params, options), function(path, i) { if (null === path) { // Param was string/number, not a path, so just return raw string/number. return params[i]; } else { return handlebarsGet(context, path, options); } });
helper: function(thisContext, path, options) { var data = options.data, fn = options.fn, newView; makeBindings(thisContext, options); if ('string' === typeof path) { var lookup; // TODO: this is a lame conditional, this should likely change // but something along these lines will likely need to be added // as deprecation warnings // if (options.types[0] === 'STRING' && LOWERCASE_A_Z.test(path) && !VIEW_PREFIX.test(path)) { lookup = path; } else { options.silenceGlobalDeprecation = true; newView = handlebarsGet(thisContext, path, options); if (typeof newView === 'string') { lookup = newView; } } if (lookup) { Ember.assert("View requires a container", !!data.view.container); newView = data.view.container.lookupFactory('view:' + lookup); } Ember.assert("Unable to find view at path '" + path + "'", !!newView); } else { newView = path; } Ember.assert(EmberString.fmt('You must pass a view to the #view helper, not %@ (%@)', [path, newView]), View.detect(newView) || View.detectInstance(newView)); var viewOptions = this.propertiesFromHTMLOptions(options, thisContext); var currentView = data.view; viewOptions.templateData = data; var newViewProto = newView.proto ? newView.proto() : newView; if (fn) { Ember.assert("You cannot provide a template block if you also specified a templateName", !get(viewOptions, 'templateName') && !get(newViewProto, 'templateName')); viewOptions.template = fn; } // We only want to override the `_context` computed property if there is // no specified controller. See View#_context for more information. if (!newViewProto.controller && !newViewProto.controllerBinding && !viewOptions.controller && !viewOptions.controllerBinding) { viewOptions._context = thisContext; } // for instrumentation if (options.helperName) { viewOptions.helperName = options.helperName; } currentView.appendChild(newView, viewOptions); }
forEach.call(attrKeys, function(attr) { var path = attrs[attr], normalized; Ember.assert(fmt("You must provide an expression as the value of bound attribute. You specified: %@=%@", [attr, path]), typeof path === 'string'); normalized = normalizePath(ctx, path, options.data); var value = (path === 'this') ? normalized.root : handlebarsGet(ctx, path, options), type = typeOf(value); Ember.assert(fmt("Attributes must be numbers, strings or booleans, not %@", [value]), value === null || value === undefined || type === 'number' || type === 'string' || type === 'boolean'); var observer; observer = function observer() { var result = handlebarsGet(ctx, path, options); Ember.assert(fmt("Attributes must be numbers, strings or booleans, not %@", [result]), result === null || result === undefined || typeof result === 'number' || typeof result === 'string' || typeof result === 'boolean'); var elem = view.$("[data-bindattr-" + dataId + "='" + dataId + "']"); // If we aren't able to find the element, it means the element // to which we were bound has been removed from the view. // In that case, we can assume the template has been re-rendered // and we need to clean up the observer. if (!elem || elem.length === 0) { removeObserver(normalized.root, normalized.path, observer); return; } View.applyAttributeBindings(elem, attr, result); }; // Add an observer to the view for when the property changes. // When the observer fires, find the element using the // unique data id and update the attribute to the new value. // Note: don't add observer when path is 'this' or path // is whole keyword e.g. {{#each x in list}} ... {{bind-attr attr="x"}} if (path !== 'this' && !(normalized.isKeyword && normalized.path === '' )) { view.registerObserver(normalized.root, normalized.path, observer); } // if this changes, also change the logic in ember-views/lib/views/view.js if ((type === 'string' || (type === 'number' && !isNaN(value)))) { ret.push(attr + '="' + Handlebars.Utils.escapeExpression(value) + '"'); } else if (value && type === 'boolean') { // The developer controls the attr name, so it should always be safe ret.push(attr + '="' + attr + '"'); } }, this);
export default function resolvePaths(options) { var ret = [], contexts = options.contexts, roots = options.roots, data = options.data; for (var i=0, l=contexts.length; i<l; i++) { ret.push(handlebarsGet(roots[i], contexts[i], { data: data })); } return ret; }
var classStringForPath = function(root, parsedPath, options) { var val, path = parsedPath.path; if (path === 'this') { val = root; } else if (path === '') { val = true; } else { val = handlebarsGet(root, path, options); } return View._classStringForValue(path, val, parsedPath.className, parsedPath.falsyClassName); };
handler: function handleRegisteredAction(event) { if (!isAllowedEvent(event, allowedKeys)) { return true; } if (options.preventDefault !== false) { event.preventDefault(); } if (options.bubbles === false) { event.stopPropagation(); } var target = options.target; var parameters = options.parameters; var eventName = options.eventName; var actionName; if (Ember.FEATURES.isEnabled("ember-routing-handlebars-action-with-key-code")) { if (ignoreKeyEvent(eventName, event, options.withKeyCode)) { return; } } if (target.target) { target = handlebarsGet(target.root, target.target, target.options); } else { target = target.root; } if (options.boundProperty) { actionName = resolveParams(parameters.context, [actionNameOrPath], { types: ['ID'], data: parameters.options.data })[0]; if (typeof actionName === 'undefined' || typeof actionName === 'function') { Ember.deprecate("You specified a quoteless path to the {{action}} helper '" + actionNameOrPath + "' which did not resolve to an actionName. Perhaps you meant to use a quoted actionName? (e.g. {{action '" + actionNameOrPath + "'}})."); actionName = actionNameOrPath; } } if (!actionName) { actionName = actionNameOrPath; } run(function runRegisteredAction() { if (target.send) { target.send.apply(target, args(parameters, actionName)); } else { Ember.assert("The action '" + actionName + "' did not exist on " + target, typeof target[actionName] === 'function'); target[actionName].apply(target, args(parameters)); } }); }
/** @private Use the `unboundIf` helper to create a conditional that evaluates once. ```handlebars {{#unboundIf "content.shouldDisplayTitle"}} {{content.title}} {{/unboundIf}} ``` @method unboundIf @for Ember.Handlebars.helpers @param {String} property Property to bind @param {Function} fn Context to provide for rendering @return {String} HTML string @since 1.4.0 */ function unboundIfHelper(property, fn) { var context = (fn.contexts && fn.contexts.length) ? fn.contexts[0] : this, data = fn.data, template = fn.fn, inverse = fn.inverse, normalized, propertyValue, result; normalized = normalizePath(context, property, data); propertyValue = handlebarsGet(context, property, fn); if (!shouldDisplayIfHelperContent(propertyValue)) { template = inverse; } template(context, { data: data }); }
/** `unbound` allows you to output a property without binding. *Important:* The output will not be updated if the property changes. Use with caution. ```handlebars <div>{{unbound somePropertyThatDoesntChange}}</div> ``` `unbound` can also be used in conjunction with a bound helper to render it in its unbound form: ```handlebars <div>{{unbound helperName somePropertyThatDoesntChange}}</div> ``` @method unbound @for Ember.Handlebars.helpers @param {String} property @return {String} HTML string */ export default function unboundHelper(property, fn) { var options = arguments[arguments.length - 1]; var container = options.data.view.container; var helper, context, out, ctx; ctx = this; if (arguments.length > 2) { // Unbound helper call. options.data.isUnbound = true; helper = resolveHelper(container, property) || helpers.helperMissing; out = helper.apply(ctx, slice.call(arguments, 1)); delete options.data.isUnbound; return out; } context = (fn.contexts && fn.contexts.length) ? fn.contexts[0] : ctx; return handlebarsGet(context, property, fn); }
normalizedValue: function() { var path = this.path, pathRoot = this.pathRoot, result, templateData; // Use the pathRoot as the result if no path is provided. This // happens if the path is `this`, which gets normalized into // a `pathRoot` of the current Handlebars context and a path // of `''`. if (path === '') { result = pathRoot; } else { templateData = this.templateData; result = handlebarsGet(pathRoot, path, { data: templateData }); } return result; },
normalizedValue: function() { var path = get(this, 'path'); var pathRoot = get(this, 'pathRoot'); var valueNormalizer = get(this, 'valueNormalizerFunc'); var result, templateData; // Use the pathRoot as the result if no path is provided. This // happens if the path is `this`, which gets normalized into // a `pathRoot` of the current Handlebars context and a path // of `''`. if (path === '') { result = pathRoot; } else { templateData = get(this, 'templateData'); result = handlebarsGet(pathRoot, path, { data: templateData }); } return valueNormalizer ? valueNormalizer(result) : result; },
observer = function observer() { var result = handlebarsGet(ctx, path, options); Ember.assert(fmt("Attributes must be numbers, strings or booleans, not %@", [result]), result === null || result === undefined || typeof result === 'number' || typeof result === 'string' || typeof result === 'boolean'); var elem = view.$("[data-bindattr-" + dataId + "='" + dataId + "']"); // If we aren't able to find the element, it means the element // to which we were bound has been removed from the view. // In that case, we can assume the template has been re-rendered // and we need to clean up the observer. if (!elem || elem.length === 0) { removeObserver(normalized.root, normalized.path, observer); return; } View.applyAttributeBindings(elem, attr, result); };
ignoreDeprecation(function() { equal(handlebarsGet(context, path, options), lookup.Blammo.foo); });
options.fn = function(context, fnOptions) { var partialName = handlebarsGet(context, name, fnOptions); renderPartial(context, partialName, fnOptions); };
view.registerObserver(root, contextString, function() { controller.set('model', handlebarsGet(root, contextString, options)); });
// requireModule('ember-handlebars'); /** @module ember @submodule ember-routing */ /** Calling ``{{render}}`` from within a template will insert another template that matches the provided name. The inserted template will access its properties on its own controller (rather than the controller of the parent template). If a view class with the same name exists, the view class also will be used. Note: A given controller may only be used *once* in your app in this manner. A singleton instance of the controller will be created for you. Example: ```javascript App.NavigationController = Ember.Controller.extend({ who: "world" }); ``` ```handlebars <!-- navigation.hbs --> Hello, {{who}}. ``` ```handelbars <!-- application.hbs --> <h1>My great app</h1> {{render "navigation"}} ``` ```html <h1>My great app</h1> <div class='ember-view'> Hello, world. </div> ``` Optionally you may provide a second argument: a property path that will be bound to the `model` property of the controller. If a `model` property path is specified, then a new instance of the controller will be created and `{{render}}` can be used multiple times with the same name. For example if you had this `author` template. ```handlebars <div class="author"> Written by {{firstName}} {{lastName}}. Total Posts: {{postCount}} </div> ``` You could render it inside the `post` template using the `render` helper. ```handlebars <div class="post"> <h1>{{title}}</h1> <div>{{body}}</div> {{render "author" author}} </div> ``` @method render @for Ember.Handlebars.helpers @param {String} name @param {Object?} contextString @param {Hash} options @return {String} HTML string */ function renderHelper(name, contextString, options) { var length = arguments.length; var contextProvided = length === 3, container, router, controller, view, context, lookupOptions; container = (options || contextString).data.keywords.controller.container; router = container.lookup('router:main'); if (length === 2) { // use the singleton controller options = contextString; contextString = undefined; Ember.assert("You can only use the {{render}} helper once without a model object as its second argument, as in {{render \"post\" post}}.", !router || !router._lookupActiveView(name)); } else if (length === 3) { // create a new controller context = handlebarsGet(options.contexts[1], contextString, options); } else { throw EmberError("You must pass a templateName to render"); } Ember.deprecate("Using a quoteless parameter with {{render}} is deprecated. Please update to quoted usage '{{render \"" + name + "\"}}.", options.types[0] !== 'ID'); // # legacy namespace name = name.replace(/\//g, '.'); // \ legacy slash as namespace support view = container.lookup('view:' + name) || container.lookup('view:default'); // provide controller override var controllerName = options.hash.controller || name; var controllerFullName = 'controller:' + controllerName; if (options.hash.controller) { Ember.assert("The controller name you supplied '" + controllerName + "' did not resolve to a controller.", container.has(controllerFullName)); } var parentController = options.data.keywords.controller; // choose name if (length > 2) { var factory = container.lookupFactory(controllerFullName) || generateControllerFactory(container, controllerName, context); controller = factory.create({ model: context, parentController: parentController, target: parentController }); } else { controller = container.lookup(controllerFullName) || generateController(container, controllerName); controller.setProperties({ target: parentController, parentController: parentController }); } var root = options.contexts[1]; if (root) { view.registerObserver(root, contextString, function() { controller.set('model', handlebarsGet(root, contextString, options)); }); } options.hash.viewName = EmberStringUtils.camelize(name); var templateName = 'template:' + name; Ember.assert("You used `{{render '" + name + "'}}`, but '" + name + "' can not be found as either a template or a view.", container.has("view:" + name) || container.has(templateName) || options.fn); options.hash.template = container.lookup(templateName); options.hash.controller = controller; if (router && !context) { router._connectActiveView(name, view); } viewHelper.call(this, view, options); };
lookupContent: function() { return handlebarsGet(this.normalizedRoot, this.normalizedPath, this.options); },
ignoreDeprecation(function() { equal(handlebarsGet(context, path, options), 'bar'); });
expectDeprecation(function() { handlebarsGet(context, path, options); }, 'Usage of Ember.Handlebars.get is deprecated, use a Component or Ember.Handlebars.makeBoundHelper instead.');
/** `{{collection}}` is a `Ember.Handlebars` helper for adding instances of `Ember.CollectionView` to a template. See [Ember.CollectionView](/api/classes/Ember.CollectionView.html) for additional information on how a `CollectionView` functions. `{{collection}}`'s primary use is as a block helper with a `contentBinding` option pointing towards an `Ember.Array`-compatible object. An `Ember.View` instance will be created for each item in its `content` property. Each view will have its own `content` property set to the appropriate item in the collection. The provided block will be applied as the template for each item's view. Given an empty `<body>` the following template: ```handlebars {{#collection contentBinding="App.items"}} Hi {{view.content.name}} {{/collection}} ``` And the following application code ```javascript App = Ember.Application.create() App.items = [ Ember.Object.create({name: 'Dave'}), Ember.Object.create({name: 'Mary'}), Ember.Object.create({name: 'Sara'}) ] ``` Will result in the HTML structure below ```html <div class="ember-view"> <div class="ember-view">Hi Dave</div> <div class="ember-view">Hi Mary</div> <div class="ember-view">Hi Sara</div> </div> ``` ### Blockless use in a collection If you provide an `itemViewClass` option that has its own `template` you can omit the block. The following template: ```handlebars {{collection contentBinding="App.items" itemViewClass="App.AnItemView"}} ``` And application code ```javascript App = Ember.Application.create(); App.items = [ Ember.Object.create({name: 'Dave'}), Ember.Object.create({name: 'Mary'}), Ember.Object.create({name: 'Sara'}) ]; App.AnItemView = Ember.View.extend({ template: Ember.Handlebars.compile("Greetings {{view.content.name}}") }); ``` Will result in the HTML structure below ```html <div class="ember-view"> <div class="ember-view">Greetings Dave</div> <div class="ember-view">Greetings Mary</div> <div class="ember-view">Greetings Sara</div> </div> ``` ### Specifying a CollectionView subclass By default the `{{collection}}` helper will create an instance of `Ember.CollectionView`. You can supply a `Ember.CollectionView` subclass to the helper by passing it as the first argument: ```handlebars {{#collection App.MyCustomCollectionClass contentBinding="App.items"}} Hi {{view.content.name}} {{/collection}} ``` ### Forwarded `item.*`-named Options As with the `{{view}}`, helper options passed to the `{{collection}}` will be set on the resulting `Ember.CollectionView` as properties. Additionally, options prefixed with `item` will be applied to the views rendered for each item (note the camelcasing): ```handlebars {{#collection contentBinding="App.items" itemTagName="p" itemClassNames="greeting"}} Howdy {{view.content.name}} {{/collection}} ``` Will result in the following HTML structure: ```html <div class="ember-view"> <p class="ember-view greeting">Howdy Dave</p> <p class="ember-view greeting">Howdy Mary</p> <p class="ember-view greeting">Howdy Sara</p> </div> ``` @method collection @for Ember.Handlebars.helpers @param {String} path @param {Hash} options @return {String} HTML string @deprecated Use `{{each}}` helper instead. */ function collectionHelper(path, options) { Ember.deprecate("Using the {{collection}} helper without specifying a class has been deprecated as the {{each}} helper now supports the same functionality.", path !== 'collection'); // If no path is provided, treat path param as options. if (path && path.data && path.data.isRenderData) { options = path; path = undefined; Ember.assert("You cannot pass more than one argument to the collection helper", arguments.length === 1); } else { Ember.assert("You cannot pass more than one argument to the collection helper", arguments.length === 2); } var fn = options.fn; var data = options.data; var inverse = options.inverse; var view = options.data.view; var controller, container; // If passed a path string, convert that into an object. // Otherwise, just default to the standard class. var collectionClass; if (path) { controller = data.keywords.controller; container = controller && controller.container; options.silenceGlobalDeprecation = true; collectionClass = handlebarsGet(this, path, options) || container.lookupFactory('view:' + path); Ember.assert(fmt("%@ #collection: Could not find collection class %@", [data.view, path]), !!collectionClass); } else { collectionClass = CollectionView; } var hash = options.hash; var itemHash = {}; var match; // Extract item view class if provided else default to the standard class var collectionPrototype = collectionClass.proto(); var itemViewClass; if (hash.itemView) { controller = data.keywords.controller; Ember.assert('You specified an itemView, but the current context has no ' + 'container to look the itemView up in. This probably means ' + 'that you created a view manually, instead of through the ' + 'container. Instead, use container.lookup("view:viewName"), ' + 'which will properly instantiate your view.', controller && controller.container); container = controller.container; itemViewClass = container.lookupFactory('view:' + hash.itemView); Ember.assert('You specified the itemView ' + hash.itemView + ", but it was " + "not found at " + container.describe("view:" + hash.itemView) + " (and it was not registered in the container)", !!itemViewClass); } else if (hash.itemViewClass) { options.silenceGlobalDeprecation = true; itemViewClass = handlebarsGet(collectionPrototype, hash.itemViewClass, options); } else { itemViewClass = collectionPrototype.itemViewClass; } Ember.assert(fmt("%@ #collection: Could not find itemViewClass %@", [data.view, itemViewClass]), !!itemViewClass); delete hash.itemViewClass; delete hash.itemView; // Go through options passed to the {{collection}} helper and extract options // that configure item views instead of the collection itself. for (var prop in hash) { if (hash.hasOwnProperty(prop)) { match = prop.match(/^item(.)(.*)$/); if (match && prop !== 'itemController') { // Convert itemShouldFoo -> shouldFoo itemHash[match[1].toLowerCase() + match[2]] = hash[prop]; // Delete from hash as this will end up getting passed to the // {{view}} helper method. delete hash[prop]; } } } if (fn) { itemHash.template = fn; delete options.fn; } var emptyViewClass; if (inverse && inverse !== EmberHandlebars.VM.noop) { emptyViewClass = get(collectionPrototype, 'emptyViewClass'); emptyViewClass = emptyViewClass.extend({ template: inverse, tagName: itemHash.tagName }); } else if (hash.emptyViewClass) { options.silenceGlobalDeprecation = true; emptyViewClass = handlebarsGet(this, hash.emptyViewClass, options); } if (emptyViewClass) { hash.emptyView = emptyViewClass; } if (hash.keyword) { itemHash._context = this; } else { itemHash._context = alias('content'); } var viewOptions = ViewHelper.propertiesFromHTMLOptions({ data: data, hash: itemHash }, this); hash.itemViewClass = itemViewClass.extend(viewOptions); options.helperName = options.helperName || 'collection'; return helpers.view.call(this, collectionClass, 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. function bind(property, options, preserveContext, shouldDisplay, valueNormalizer, childProperties) { var data = options.data, fn = options.fn, inverse = options.inverse, view = data.view, normalized, observer, i; // we relied on the behavior of calling without // context to mean this === window, but when running // "use strict", it's possible for this to === undefined; var currentContext = this || window; normalized = normalizePath(currentContext, property, data); // Set up observers for observable objects if ('object' === typeof this) { if (data.insideGroup) { observer = function() { while (view._contextView) { view = view._contextView; } run.once(view, 'rerender'); }; var template, context, result = handlebarsGet(currentContext, property, options); result = valueNormalizer ? valueNormalizer(result) : result; context = preserveContext ? currentContext : result; if (shouldDisplay(result)) { template = fn; } else if (inverse) { template = inverse; } template(context, { data: options.data }); } else { var viewClass = _HandlebarsBoundView; var viewOptions = { preserveContext: preserveContext, shouldDisplayFunc: shouldDisplay, valueNormalizerFunc: valueNormalizer, displayTemplate: fn, inverseTemplate: inverse, path: property, pathRoot: currentContext, previousContext: currentContext, isEscaped: !options.hash.unescaped, templateData: options.data, templateHash: options.hash, helperName: options.helperName }; if (options.isWithHelper) { viewClass = WithView; } // 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(viewClass, viewOptions); view.appendChild(bindView); observer = function() { run.scheduleOnce('render', bindView, 'rerenderIfNeeded'); }; } // Observes the given property on the context and // tells the Ember._HandlebarsBoundView 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 (normalized.path !== '') { view.registerObserver(normalized.root, normalized.path, observer); if (childProperties) { for (i=0; i<childProperties.length; i++) { view.registerObserver(normalized.root, normalized.path+'.'+childProperties[i], observer); } } } } else { // The object is not observable, so just render it out and // be done with it. data.buffer.push(handlebarsGetEscaped(currentContext, property, options)); } }