/** Lookup both on root and on window. If the path starts with a keyword, the corresponding object will be looked up in the template's data hash and used to resolve the path. @method get @for Ember.Handlebars @param {Object} root The object to look up the property on @param {String} path The path to be lookedup @param {Object} options The template's option hash */ function handlebarsGet(root, path, options) { var data = options && options.data; var normalizedPath = normalizePath(root, path, data); var value; // In cases where the path begins with a keyword, change the // root to the value represented by that keyword, and ensure // the path is relative to it. root = normalizedPath.root; path = normalizedPath.path; value = get(root, path); if (isGlobalPath(path)) { if (value === undefined && root !== Ember.lookup) { root = Ember.lookup; value = get(root, path); } if (root === Ember.lookup || root === null) { Ember.deprecate("Global lookup of "+path+" from a Handlebars template is deprecated.", options.silenceGlobalDeprecation); } } return value; }
/** Use the `{{with}}` helper when you want to scope context. Take the following code as an example: ```handlebars <h5>{{user.name}}</h5> <div class="role"> <h6>{{user.role.label}}</h6> <span class="role-id">{{user.role.id}}</span> <p class="role-desc">{{user.role.description}}</p> </div> ``` `{{with}}` can be our best friend in these cases, instead of writing `user.role.*` over and over, we use `{{#with user.role}}`. Now the context within the `{{#with}} .. {{/with}}` block is `user.role` so you can do the following: ```handlebars <h5>{{user.name}}</h5> <div class="role"> {{#with user.role}} <h6>{{label}}</h6> <span class="role-id">{{id}}</span> <p class="role-desc">{{description}}</p> {{/with}} </div> ``` ### `as` operator This operator aliases the scope to a new name. It's helpful for semantic clarity and to retain default scope or to reference from another `{{with}}` block. ```handlebars // posts might not be {{#with user.posts as blogPosts}} <div class="notice"> There are {{blogPosts.length}} blog posts written by {{user.name}}. </div> {{#each post in blogPosts}} <li>{{post.title}}</li> {{/each}} {{/with}} ``` Without the `as` operator, it would be impossible to reference `user.name` in the example above. NOTE: The alias should not reuse a name from the bound property path. For example: `{{#with foo.bar as foo}}` is not supported because it attempts to alias using the first part of the property path, `foo`. Instead, use `{{#with foo.bar as baz}}`. ### `controller` option Adding `controller='something'` instructs the `{{with}}` helper to create and use an instance of the specified controller with the new context as its content. This is very similar to using an `itemController` option with the `{{each}}` helper. ```handlebars {{#with users.posts controller='userBlogPosts'}} {{!- The current context is wrapped in our controller instance }} {{/with}} ``` In the above example, the template provided to the `{{with}}` block is now wrapped in the `userBlogPost` controller, which provides a very elegant way to decorate the context with custom functions/properties. @method with @for Ember.Handlebars.helpers @param {Function} context @param {Hash} options @return {String} HTML string */ function withHelper(context, options) { var bindContext, preserveContext, controller, helperName = 'with'; if (arguments.length === 4) { var keywordName, path, rootPath, normalized, contextPath; 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]; keywordName = arguments[2]; path = arguments[0]; if (path) { helperName += ' ' + path + ' as ' + keywordName; } Ember.assert("You must pass a block to the with helper", options.fn && options.fn !== Handlebars.VM.noop); var localizedOptions = o_create(options); localizedOptions.data = o_create(options.data); localizedOptions.data.keywords = o_create(options.data.keywords || {}); if (isGlobalPath(path)) { contextPath = path; } else { normalized = normalizePath(this, path, options.data); path = normalized.path; rootPath = normalized.root; // This is a workaround for the fact that you cannot bind separate objects // together. When we implement that functionality, we should use it here. var contextKey = jQuery.expando + guidFor(rootPath); localizedOptions.data.keywords[contextKey] = rootPath; // if the path is '' ("this"), just bind directly to the current context contextPath = path ? contextKey + '.' + path : contextKey; } localizedOptions.hash.keywordName = keywordName; localizedOptions.hash.keywordPath = contextPath; bindContext = this; context = path; options = localizedOptions; preserveContext = true; } 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); helperName += ' ' + context; bindContext = options.contexts[0]; preserveContext = false; } options.helperName = helperName; options.isWithHelper = true; return bind.call(bindContext, context, options, preserveContext, exists); }
contextualizeBindingPath: function(path, data) { var normalized = normalizePath(null, path, data); if (normalized.isKeyword) { return 'templateData.keywords.' + path; } else if (isGlobalPath(path)) { return null; } else if (path === 'this' || path === '') { return '_parentView.context'; } else { return '_parentView.context.' + path; } },
contextualizeBindingPath: function(path, data) { if (SELF_BINDING.test(path)) { return path.slice(6); // Lop off "_view." } var normalized = normalizePath(null, path, data); if (normalized.isKeyword) { return 'templateData.keywords.' + path; } else if (isGlobalPath(path)) { return null; } else if (path === 'this' || path === '') { return '_parentView.context'; } else { return '_parentView.context.' + path; } },
arrayDidChange: function(content, start, removed, added) { var addedViews = []; var view, item, idx, len, itemViewClass, emptyView; len = content ? get(content, 'length') : 0; if (len) { itemViewClass = get(this, 'itemViewClass'); itemViewClass = readViewFactory(itemViewClass, this.container); for (idx = start; idx < start+added; idx++) { item = content.objectAt(idx); view = this.createChildView(itemViewClass, { content: item, contentIndex: idx, _blockArguments: [item] }); addedViews.push(view); } } else { emptyView = get(this, 'emptyView'); if (!emptyView) { return; } if ('string' === typeof emptyView && isGlobalPath(emptyView)) { emptyView = get(emptyView) || emptyView; } emptyView = this.createChildView(emptyView); addedViews.push(emptyView); set(this, 'emptyView', emptyView); if (CoreView.detect(emptyView)) { this._createdEmptyView = emptyView; } } this.replace(start, 0, addedViews); },
/** Use the `{{with}}` helper when you want to scope context. Take the following code as an example: ```handlebars <h5>{{user.name}}</h5> <div class="role"> <h6>{{user.role.label}}</h6> <span class="role-id">{{user.role.id}}</span> <p class="role-desc">{{user.role.description}}</p> </div> ``` `{{with}}` can be our best friend in these cases, instead of writing `user.role.*` over and over, we use `{{#with user.role}}`. Now the context within the `{{#with}} .. {{/with}}` block is `user.role` so you can do the following: ```handlebars <h5>{{user.name}}</h5> <div class="role"> {{#with user.role}} <h6>{{label}}</h6> <span class="role-id">{{id}}</span> <p class="role-desc">{{description}}</p> {{/with}} </div> ``` ### `as` operator This operator aliases the scope to a new name. It's helpful for semantic clarity and to retain default scope or to reference from another `{{with}}` block. ```handlebars // posts might not be {{#with user.posts as blogPosts}} <div class="notice"> There are {{blogPosts.length}} blog posts written by {{user.name}}. </div> {{#each post in blogPosts}} <li>{{post.title}}</li> {{/each}} {{/with}} ``` Without the `as` operator, it would be impossible to reference `user.name` in the example above. NOTE: The alias should not reuse a name from the bound property path. For example: `{{#with foo.bar as foo}}` is not supported because it attempts to alias using the first part of the property path, `foo`. Instead, use `{{#with foo.bar as baz}}`. ### `controller` option Adding `controller='something'` instructs the `{{with}}` helper to create and use an instance of the specified controller with the new context as its content. This is very similar to using an `itemController` option with the `{{each}}` helper. ```handlebars {{#with users.posts controller='userBlogPosts'}} {{!- The current context is wrapped in our controller instance }} {{/with}} ``` In the above example, the template provided to the `{{with}}` block is now wrapped in the `userBlogPost` controller, which provides a very elegant way to decorate the context with custom functions/properties. @method with @for Ember.Handlebars.helpers @param {Function} context @param {Hash} options @return {String} HTML string */ function withHelper(context, options) { if (arguments.length === 4) { var keywordName, path, rootPath, normalized, contextPath; 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]; keywordName = arguments[2]; path = arguments[0]; Ember.assert("You must pass a block to the with helper", options.fn && options.fn !== Handlebars.VM.noop); var localizedOptions = o_create(options); localizedOptions.data = o_create(options.data); localizedOptions.data.keywords = o_create(options.data.keywords || {}); if (isGlobalPath(path)) { contextPath = path; } else { normalized = normalizePath(this, path, options.data); path = normalized.path; rootPath = normalized.root; // This is a workaround for the fact that you cannot bind separate objects // together. When we implement that functionality, we should use it here. var contextKey = jQuery.expando + guidFor(rootPath); localizedOptions.data.keywords[contextKey] = rootPath; // if the path is '' ("this"), just bind directly to the current context contextPath = path ? contextKey + '.' + path : contextKey; } emberBind(localizedOptions.data.keywords, keywordName, contextPath); return bind.call(this, path, localizedOptions, true, exists); } 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); } }
test("if there is a 'this' in the path, it's not a global path", function() { ok( !isGlobalPath('this.myProperty') ); ok( !isGlobalPath('this') ); });
test("global path's are recognized", function() { ok( isGlobalPath('App.myProperty') ); ok( isGlobalPath('App.myProperty.subProperty') ); });
test("if the path starts with a lowercase character, it is not a global path", function() { ok( !isGlobalPath('myObj') ); ok( !isGlobalPath('myObj.SecondProperty') ); });
QUnit.test('if there is a \'this\' in the path, it\'s not a global path', function() { ok(!isGlobalPath('this.myProperty')); ok(!isGlobalPath('this')); });
QUnit.test('global path\'s are recognized', function() { ok(isGlobalPath('App.myProperty')); ok(isGlobalPath('App.myProperty.subProperty')); });