Skip to content

Loading…

Support for bound handlebars helpers #615

Closed
wants to merge 1 commit into from

5 participants

@ghempton
Ember.js member

This is my first attempt at adding a convenient way to create bound handlebars helpers. Bound handlebars helpers can now be created as follows:

  Ember.Handlebars.registerBoundHelper('capitalize', function(value) {
    return value.toUpperCase();
  });

Would love some feedback as to how this could be cleaned up.

@wagenet
Ember.js member

This is based on the BoundProperty stuff I did right? If so, and if we merge this, should we change basic property bindings to use this as well?

@smadep

would this support bidirectional binding?!

@wagenet
Ember.js member

@smadep No. If you want to bind to the DOM you have to do some extra work. As an example, the TextSupport mixin handle this for TextField and TextArea: https://github.com/emberjs/ember.js/blob/master/packages/ember-handlebars/lib/controls/text_support.js

@wagenet
Ember.js member

@ghempton I think it would definitely be useful to get something like this in core. However, I know there was some concern from @wycats about the way rendering is handled here. @wycats, any more thoughts on the matter?

@trek
Ember.js member

this isn't on the 1.0 milestone, but probably should be.

@nhoffmann

+1 for putting this on the 1.0 milestone

@wagenet
Ember.js member

@nhoffmann Already there :)

@ghempton
Ember.js member

I'd be happy to take another pass on this. What is the desired implementation? I think @wycats was wanting to reuse the code path from {{bind}}, but I'm wary of making Ember._HandlebarsBoundView more complex.

@ghempton ghempton referenced this pull request
Merged

Bound handlebars helpers #1274

@wagenet
Ember.js member

Closing in favor of #1274,

@wagenet wagenet closed this
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Mar 24, 2012
  1. @ghempton
View
1 .gitignore
@@ -31,3 +31,4 @@ tmp
tmp*.gem
tmp.bpm
tmp.spade
+.project
View
28 packages/ember-handlebars/lib/ext.js
@@ -165,3 +165,31 @@ Ember.Handlebars.registerHelper('helperMissing', function(path, options) {
throw new Ember.Error(Ember.String.fmt(error, [view, path, this]));
});
+/**
+ Registers a bound helper in handlebars. This helper will automatically
+ update when the specified property path changes.
+
+ @param {String} name
+ @param {Function} func
+*/
+Ember.Handlebars.registerBoundHelper = function(name, func) {
+ var propertyPaths = Array.prototype.slice.call(arguments, 2);
+ Ember.Handlebars.registerHelper(name, function(property, options) {
+ var data = options.data,
+ view = data.view,
+ ctx = this;
+
+ var bindView = view.createChildView(Ember._BoundHelperView, {
+ property: property,
+ propertyPaths: propertyPaths,
+ context: ctx,
+ options: options.hash,
+ value: func
+ });
+
+ view.appendChild(bindView);
+ });
+};
+
+
+
View
1 packages/ember-handlebars/lib/views.js
@@ -5,4 +5,5 @@
// ==========================================================================
require("ember-handlebars/views/bindable_span");
+require("ember-handlebars/views/bound_helper_view");
require("ember-handlebars/views/metamorph_view");
View
51 packages/ember-handlebars/lib/views/bound_helper_view.js
@@ -0,0 +1,51 @@
+var get = Ember.get, set = Ember.set, getPath = Ember.Handlebars.getPath;
+
+require('ember-views/views/view');
+require('ember-handlebars/views/metamorph_view');
+
+Ember._BoundHelperView = Ember.View.extend(Ember.Metamorph, {
+
+ context: null,
+ options: null,
+ property: null,
+ // paths of the property that are also observed
+ propertyPaths: [],
+
+ value: Ember.K,
+
+ valueForRender: function() {
+ var value = this.value(Ember.getPath(this.context, this.property), this.options);
+ if (this.options.escaped) { value = Handlebars.Utils.escapeExpression(value); }
+ return value;
+ },
+
+ render: function(buffer) {
+ buffer.push(this.valueForRender());
+ },
+
+ valueDidChange: function() {
+ if (this.morph.isRemoved()) { return; }
+ this.morph.html(this.valueForRender());
+ },
+
+ didInsertElement: function() {
+ this.valueDidChange();
+ },
+
+ init: function() {
+ this._super();
+ Ember.addObserver(this.context, this.property, this, 'valueDidChange');
+ this.get('propertyPaths').forEach(function(propName) {
+ Ember.addObserver(this.context, this.property + '.' + propName, this, 'valueDidChange');
+ }, this);
+ },
+
+ destroy: function() {
+ Ember.removeObserver(this.context, this.property, this, 'valueDidChange');
+ this.get('propertyPaths').forEach(function(propName) {
+ this.context.removeObserver(this.property + '.' + propName, this, 'valueDidChange');
+ }, this);
+ this._super();
+ }
+
+});
View
63 packages/ember-handlebars/tests/handlebars_test.js
@@ -1709,4 +1709,67 @@ test("should bind to the property if no registered helper found for a mustache w
ok(view.$().text() === 'foobarProperty', "Property was bound to correctly");
});
+test("should update bound helpers when properties change", function() {
+ Ember.Handlebars.registerBoundHelper('capitalize', function(value) {
+ return value.toUpperCase();
+ });
+
+ view = Ember.View.create({
+ name: "Brogrammer",
+ template: Ember.Handlebars.compile("{{capitalize name}}")
+ });
+
+ appendView();
+
+ ok(view.$().text() === 'BROGRAMMER', "helper output is correct");
+
+ Ember.run(function() {
+ set(view, 'name', 'wes');
+ });
+
+ ok(view.$().text() === 'WES', "helper output updated");
+});
+
+test("should update bound helpers when sub-properties change", function() {
+ Ember.Handlebars.registerBoundHelper('capitalizeName', function(value) {
+ return get(value, 'name').toUpperCase();
+ }, 'name');
+
+ view = Ember.View.create({
+ person: Ember.Object.create({
+ name: 'Brogrammer'
+ }),
+ template: Ember.Handlebars.compile("{{capitalizeName person}}")
+ });
+
+ appendView();
+
+ ok(view.$().text() === 'BROGRAMMER', "helper output is correct");
+
+ Ember.run(function() {
+ set(view, 'person', Ember.Object.create({name: 'wes'}));
+ });
+
+ ok(view.$().text() === 'WES', "helper output updated");
+});
+
+test("bound helpers should support options", function() {
+ Ember.Handlebars.registerBoundHelper('repeat', function(value, options) {
+ var count = options.count;
+ var a = [];
+ while(a.length < count){
+ a.push(value);
+ }
+ return a.join('');
+ });
+
+ view = Ember.View.create({
+ text: 'ab',
+ template: Ember.Handlebars.compile("{{repeat text count=3}}")
+ });
+
+ appendView();
+
+ ok(view.$().text() === 'ababab', "helper output is correct");
+});
Something went wrong with that request. Please try again.