diff --git a/backbone.modelbinding.js b/backbone.modelbinding.js index db1f68c..3750714 100644 --- a/backbone.modelbinding.js +++ b/backbone.modelbinding.js @@ -336,46 +336,64 @@ var modelbinding = (function(Backbone, _, $) { view.$(selector).each(function(index){ var element = view.$(this); var bindingAttr = config.getBindingAttr('checkbox'); - var attribute_name = config.getBindingValue(element, 'checkbox'); - + // Does this attribute refer to an array? ('foo[]' => true, 'bar' => false) + var arrayAttr = config.getBindingValue(element, 'checkbox').substr(-2) == '[]' + // The name of the element in the DOM (ie 'foo[]') + var element_name = config.getBindingValue(element, 'checkbox') + // The name of the attribute on the model (ie 'foo') + var attribute_name = arrayAttr ? element_name.slice(0,-2) : element_name; + + + // Update DOM var modelChange = function(model, val){ - if (val){ - element.attr("checked", "checked"); + if(arrayAttr) { + var test = val.indexOf(element.val()) > -1; + } else { + var test = val; } - else{ - element.removeAttr("checked"); + + if(test) { + element.attr('checked', 'checked'); + } else { + element.removeAttr('checked'); } }; + // Update model var setModelValue = function(attr_name, value){ var data = {}; data[attr_name] = value; model.set(data); }; + // Get new value var elementChange = function(ev){ - var changedElement = view.$(ev.target); - var checked = changedElement.is(":checked")? true : false; - setModelValue(attribute_name, checked); + var changedElement = view.$(ev.target || ev); + if (arrayAttr) { + var values = $.map( + view.$("input:checkbox["+bindingAttr+"="+attribute_name+"\\[\\]]:checked"), + function(elem) { return $(elem).val(); } + ) + setModelValue(attribute_name, values); + } else { + var checked = changedElement.is(":checked")? true : false; + setModelValue(attribute_name, checked); + } }; + // Bind model changes to DOM modelBinder.registerModelBinding(model, attribute_name, modelChange); + // Bind DOM changes to element modelBinder.registerElementBinding(element, elementChange); var attr_exists = model.attributes.hasOwnProperty(attribute_name); if (attr_exists) { // set the default value on the form, from the model var attr_value = model.get(attribute_name); - if (typeof attr_value !== "undefined" && attr_value !== null && attr_value != false) { - element.attr("checked", "checked"); - } - else{ - element.removeAttr("checked"); - } + if (typeof attr_value !== "undefined" && attr_value !== null ) modelChange(model, attr_value) } else { // bind the form's value to the model - var checked = element.is(":checked")? true : false; - setModelValue(attribute_name, checked); + elementChange(element) } }); }; diff --git a/spec/SpecRunner.html b/spec/SpecRunner.html index 36bd322..61fcccf 100644 --- a/spec/SpecRunner.html +++ b/spec/SpecRunner.html @@ -42,7 +42,7 @@ - + diff --git a/spec/javascripts/checkboxConventionBindings.spec.js b/spec/javascripts/checkboxConventionBindings.spec.js index 12b6db1..fe93e42 100644 --- a/spec/javascripts/checkboxConventionBindings.spec.js +++ b/spec/javascripts/checkboxConventionBindings.spec.js @@ -16,7 +16,7 @@ describe("checkbox convention bindings", function(){ el.trigger('change'); expect(this.model.get('drivers_license')).toBeFalsy(); }); - + it("bind model field changes to the form input", function(){ var el = this.view.$("#drivers_license"); @@ -91,6 +91,52 @@ describe("checkbox convention bindings", function(){ }); }); + describe("when binding an array to a checkbox", function(){ + beforeEach(function(){ + this.model = new AModel({ + endorsements: ['class_a', 'class_b'] + }); + this.view = new AView({model: this.model}); + this.view.render(); + }); + + it("bind addition to the model's field", function(){ + var el = this.view.$("#endorsements\\[\\][value=class_c]"); + el.attr("checked", true); + el.trigger('change'); + expect(this.model.get('endorsements')).toContain('class_c'); + }); + + it("bind removal to the model's field", function(){ + expect(this.model.get('endorsements')).toContain('class_a'); + var el = this.view.$("#endorsements\\[\\][value=class_a]"); + el.removeAttr("checked"); + el.trigger('change'); + expect(this.model.get('endorsements')).not.toContain('class_a'); + }); + + it("bind model field changes to the form input", function(){ + var el = this.view.$("#endorsements\\[\\][value=class_a]"); + + // uncheck it + this.model.set({endorsements: ['class_b']}); + var selected = el.attr("checked"); + expect(selected).toBeFalsy(); + + // then check it + this.model.set({endorsements: ['class_a']}); + var selected = el.attr("checked"); + expect(selected).toBeTruthy(); + }); + + it("unchecks the box for a falsy value, on render", function(){ + var el = this.view.$("#endorsements\\[\\][value=class_c]"); + var selected = el.attr("checked"); + + expect(selected).toBeFalsy(); + }); + }); + describe("when there is no value in the model", function(){ beforeEach(function(){ this.model = new AModel(); diff --git a/spec/javascripts/helpers/sample.backbone.app.js b/spec/javascripts/helpers/sample.backbone.app.js index f71cb4c..240c25e 100644 --- a/spec/javascripts/helpers/sample.backbone.app.js +++ b/spec/javascripts/helpers/sample.backbone.app.js @@ -52,6 +52,9 @@ AView = Backbone.View.extend({ \ \ \ + \ + \ + \ \

\ \