A way to let users define custom made bound {{if}} statements


#1

Hi, I want to discus the merits and downsides of my pull request some more.

Here’s the pull request:

As you can see it is closed because my use case seems better suited for a computed property.

I however respectfully disagree:

Here’s my situation, I work for a company that makes a product that listens to a variety of sensors such as rain meters, uv meters, anemometer (wind), power (electricity) and water usage meters. All these sensors send their data via radio, which is not the most reliable means of communication. So every once in a while our server does not receive data from the sensors.

When this is the case the value of that sensor becomes ‘null’. For instance the rain meter would be {rainfall: null}, and the uv meter {uvi: null}}. So ‘null’ is means ‘no data’ and the user must be able to see this in the UI… What further complicates things is that the number zero is a valid value, sometimes there is no rain, wind or the temperature is zero degrees. The normal {{if}} treats the number zero as a false value so it executes the {{else}} branch. So In my case I need an {{if}} that treats only ‘null’ as a false value.

Now I can create a computed property that only returns false when the value is ‘null’ which is a great solution. But I have 6 different kinds of sensors each with multiple properties. For instance a thermometer also has a humidity, max temperature, min temperature, min humidity and max humidity. I’ll have to program an enormous pile of computed properties. (Unless I’m missing something obvious that makes this easier, if this is the case please inform me)

My DRY solution is to create a custom if statement which checks for ‘null’ and ‘undefined’ everything else is a truthy value, including the number zero. This is a version I use for the old app that uses Ember 0.9.81:

Handlebars.registerHelper("ifData", function(property, options) {
  if (property === null || property === undefined) {
	  return options.inverse(this);
  }

  var value = Ember.getPath(this, property);

  if (value === null || value === undefined) {
	  return options.inverse(this);		
  }
  else {
	return options.fn(this);
  }
});

This was taken from the handlebars website: http://handlebarsjs.com/block_helpers.html

This saves me from writing allot of computed properties, and keeps the code DRY. Now there is one problem when this approach, it is not bindings aware, when the property changes nothing is reevaluated. So if a thermometer is ‘null’ at the start and ‘22.0’ somewhere along the line ‘No Data’ is still displayed.

In my search to make my ‘ifData’ bindings aware I came across a function called ‘boundIf’ which almost did what I wanted. It defines ‘truthy’ and sends this to a private method called ‘bind’. If you change the definition of ‘truthy’ and call ‘bind’ yourself you can create your own specialized bounded if statements. The only problem is that ‘bind’ is a private method and I haven’t found a way to call it myself.

So my pull request makes ‘bound’ available as a Ember.Handlebars.helpers._bind I use it like this:

Handlebars.registerHelper("ifData", function(property, fn) {
  var context = (fn.contexts && fn.contexts[0]) || this;

  var func = function(result) {
    if (typeof result === 'boolean') {
      return result;
    }

    return result != undefined && result != null;
  };

  return Ember.Handlebars.helpers._bind.call(context, property, fn, true, func, func);
});

I agree ‘_bind’ is a bad name and it’s name probably should be changed to reflect it’s purpose better. But not giving it to the users access to it is a shame, it is really useful. Perhaps ‘bindDOM’ will make it more clear.

The main argument against this is that you can use computed properties for this case. But you can use computed properties for every helper you define in Ember. For instance the often used example of a Handlebars helper ‘capitalize’ can be expressed in a computed property.

Since Handlebars supports writing our own custom ‘block’ helpers why not Ember bounded block helpers?

So why can’t we express bounded custom {{if}} statements if it makes our code more DRY?

Thank you for reading.

PS: If you know of a way to express my case with a computed property that I can use everywhere please let me know


#2

I’d like a way to define bound Handlebars helpers with blocks. Anyone want to take this?


#3

I’d like to report that since 1.3.0 it is now possible to define custom made bound {{if}} statements!

You can now reach ‘bind’ via Ember.Handlebars.bind.

So in my case “ifData” would become:

Handlebars.registerHelper("ifData", function(property, fn)
{
  var context = (fn.contexts && fn.contexts[0]) || this;

  var func = function(result)
  {
    if (typeof result === 'boolean')
    {
	  return result;
    }

	return result != undefined && result != null;
  };

  return Em.Handlebars.bind.call(context, property, fn, true, func, func);
});

#4

Thanks @MrHus for sharing! I’ve created a few helpers that were inspired by your approach that I wanted to share as well. I needed to be able to bind to all arguments, not just the first, so my helpers have the unfortunate side effect of only being compatible with contexts that are Ember.ObjectController instances (or anything that proxies properties through the content property).


#5

But still, ember’s registerBoundHelper doesn’t support use with Handlebars blocks other than the “if” custom blocks?


#6

I’d just like to add that there is another way to control the Ember/Handlebars {{#if}} block. Imagine you had a data type called SensorReading that has a value. That value might be null. You could define this:

SensorReading = Ember.Object.extend({
  isTruthy: function() {
    return !Ember.isNone(@get("value"))
  }.property("value")
})

Perhaps this isn’t intended to be public, but as you can see, {{#if}} will look at the value of isTruthy to determine if the block should show or not. So you could have:

{{#if reading}}
  {{value}}
{{/if}}

…where reading is always an object.


#7

@MrHus @slindberg When I follow your examples the helper is not reevaluated when an object provided as a parameter to #ifData is updated. Is this the expected behavior and is it what you observe as well? If so, is it possible in Ember to implement something else that reevalute the helper when the bound objects change?


#8

@asabjorn Yes, that’s the expected behavior, and the reason for creating those helpers. I’ve created a jsbin with a working example of the if-equal helper. A limitation of the helpers is that they must be used on controllers (or any proxy object that has a content property) in order to observe multiple arguments. Hope this helps.

[EDIT] Another implication of observing properties on the content property is that you cannot use it to observe properties defined on the controller itself.


#9

thank you soo much @slindberg, those helpers are awesome. huge time saver.


#10

Hello all. I was looking at a way to improve this so it will update when the length of the array changes. Reason being {{index_1}} doesn’t look too great when you delete a middle value and it goes 1, 3, 4…

I tried tweaking previous answers to work but couldn’t quite get it. Thanks :smile: