theironcook / Backbone.ModelBinder

Simple, flexible and powerful Model-View binding for Backbone.
1.42k stars 159 forks source link

Converter applied to non-converter bindings #220

Closed khcoder closed 9 years ago

khcoder commented 9 years ago

I'm having trouble binding a single model attribute to multiple HTML elements when one of those bindings uses a converter and others do not require the converter.

Stepping through the ModelBinder source, it appears that once a converter is bound to a model attribute, that converter is used for all HTML elements (even those that do not have a converter specified). In other words, all of the HTML elements in the "boundEls" will have the converter applied if any of the bindings for that model attribute uses a converter.

In my case, I have an attribute "errorMsg" on my model that contains the error message for a model. I'm binding errorMsg to a "p" element. The message is displayed perfectly as expected until I try to use a converter to toggle the "div" that wraps it. This "p" is wrapped in a "div" which contains other formatting. If errorMsg is not defined, then I want the entire "div" hidden. So, I specified a converter on the "hidden" HTML attribute. The problem is that the "p" element gets the converted value, even though no converter is defined for the "p" element's binding.

I can think of other ways around this (like setting a hasError attribute on the model), but I was hoping there was a way to achieve my goal without having to hang helper attributes off all my models.

Thanks for any help!

Here is the HTML showing the "div" that is toggled by the converter and the "p" element that should receive the text of errorMsg:

<div id="errorMsg" class="alert alert-danger" role="alert" >
    <span class="sr-only"></span>
    <p id="errorMsg"></p>
</div>

Below is the converter and also the bindings that are passed into ModelBinder:

 //Converter returns false if a model.errorMsg is not specified, this will hide the error div:
var errorEnabledConverter = function (direction, value) { return (!value || 0 === value.length); };

//Setup the bindings for modelbinder:
var bindings = {
     errorMsg: '#errorMsg',
     errorMsg: { selector: '#errorMsg', elAttribute: 'hidden', converter: errorEnabledConverter },
     email: '#email',
     oldPassword: '#oldPassword',
     newPassword: '#newPassword',
     confirmPassword: '#confirmPassword'
};
amakhrov commented 9 years ago

@khcoder you have an issue in your bindings variable and in your html. html: there should be only one element with a given ID bindings: objects can't have duplicate keys (they get overriden in this case).

A proper way to have multiple bindings for the same model propery is using an array:

bindings = {
  'errorMessage': [
    'selector1',
    { selector: 'selector2', elAttribute: 'hidden', converter: errorEnabledConverter }
  ]
}
theironcook commented 9 years ago

@amakhrov - nice - I was about to write the same thing. You are fast!

khcoder commented 9 years ago

Thanks for the ultra fast response guys! I completely misunderstood the documentation and didn't realize the bindings need to be passed as an array when multiple elements are bound to a single model attribute. Using @amakhrov suggestion the following makes this work exactly as expected. Thanks again....now to tackle the next task.

errorMsg: [
       { selector: '#errorMsg'},
       { selector: '#alert', elAttribute: 'hidden', converter: errorEnabledConverter }
],