diff --git a/.travis.yml b/.travis.yml index 6487ac8..6983520 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,3 +1,4 @@ +sudo: false language: node_js before_install: npm install -g grunt-cli node_js: diff --git a/readme.md b/readme.md index d607791..b506351 100644 --- a/readme.md +++ b/readme.md @@ -123,6 +123,7 @@ By default, a checkbox will return a boolean value signifying whether or not it
``` @@ -133,7 +134,8 @@ Backbone.Syphon.serialize(view); { a: false, - b: true + b: true, + c: null } ``` @@ -437,6 +439,7 @@ For more information on Key Assignment Validators, see the full There some known limitations in Backbone.Syphon, partially by design and partially implemented as default behaviors. * An input of type `checkbox` will return a boolean value. This can be overriden by replacing the Input Reader for checkboxes. +* Yo avoid circular references, care should be taken when using Backbone.Relational. See (#33)[https://github.com/marionettejs/backbone.syphon/issues/33]. ## Building Backbone.Syphon diff --git a/spec/javascripts/deserialize.spec.js b/spec/javascripts/deserialize.spec.js index f73914b..e2f9454 100644 --- a/spec/javascripts/deserialize.spec.js +++ b/spec/javascripts/deserialize.spec.js @@ -96,6 +96,34 @@ describe('deserializing an object into a form', function() { }); }); + describe('when deserializing into a multi-select box', function() { + beforeEach(function() { + this.View = Backbone.View.extend({ + render: function() { + this.$el.html( + '' + ); + } + }); + + this.view = new this.View(); + this.view.render(); + + Backbone.Syphon.deserialize(this.view, {foo: ['bar1', 'bar2']}); + this.result = this.view.$('select').val(); + }); + + it('should select the options corresponding to the values in the given object', function() { + expect(this.result).to.deep.equal(['bar1', 'bar2']); + }); + }); + describe('when deserializing into a checkbox', function() { beforeEach(function() { this.View = Backbone.View.extend({ @@ -137,6 +165,21 @@ describe('deserializing an object into a form', function() { expect(this.result).to.be.false; }); }); + + describe('and the corresponding value in the given object is null', function() { + beforeEach(function() { + this.view = new this.View(); + this.view.render(); + this.view.$('#the-checkbox').prop('checked', false); + + Backbone.Syphon.deserialize(this.view, {chk: null}); + this.result = this.view.$('#the-checkbox').prop('indeterminate'); + }); + + it('should add an indeterminate attribute', function() { + expect(this.result).to.be.true; + }); + }); }); describe('when deserializing into a button', function() { diff --git a/spec/javascripts/serialize.spec.js b/spec/javascripts/serialize.spec.js index 4931e9d..47e45b3 100644 --- a/spec/javascripts/serialize.spec.js +++ b/spec/javascripts/serialize.spec.js @@ -114,11 +114,38 @@ describe('serializing a form', function() { this.result = Backbone.Syphon.serialize(this.view); }); - it('should have the textarea\'s value', function() { + it('should return the selected option value', function() { expect(this.result.foo).to.equal('bar'); }); }); + describe('when serializing a multi-select box', function() { + beforeEach(function() { + this.View = Backbone.View.extend({ + render: function() { + this.$el.html( + '' + ); + } + }); + + this.view = new this.View(); + this.view.render(); + + this.result = Backbone.Syphon.serialize(this.view); + }); + + it('should return the selected options values', function() { + expect(this.result.foo).to.deep.equal(['bar1', 'bar3']); + }); + }); + describe('when serializing a checkbox', function() { beforeEach(function() { this.View = Backbone.View.extend({ @@ -158,6 +185,20 @@ describe('serializing a form', function() { expect(this.result.chk).to.be.false; }); }); + + describe('and the checkbox is indeterminate', function() { + beforeEach(function() { + this.view = new this.View(); + this.view.render(); + this.view.$('#the-checkbox').prop('indeterminate', true); + + this.result = Backbone.Syphon.serialize(this.view); + }); + + it('should return an object with a value of null', function() { + expect(this.result.chk).to.be.null; + }); + }); }); describe('when serializing a button', function() { diff --git a/src/backbone.syphon.inputreaders.js b/src/backbone.syphon.inputreaders.js index ccca818..2636d28 100644 --- a/src/backbone.syphon.inputreaders.js +++ b/src/backbone.syphon.inputreaders.js @@ -17,5 +17,5 @@ InputReaders.registerDefault(function($el) { // Checkbox reader, returning a boolean value for // whether or not the checkbox is checked. InputReaders.register('checkbox', function($el) { - return $el.prop('checked'); + return ($el.prop('indeterminate')) ? null : $el.prop('checked'); }); diff --git a/src/backbone.syphon.inputwriters.js b/src/backbone.syphon.inputwriters.js index 7affde5..a84b828 100644 --- a/src/backbone.syphon.inputwriters.js +++ b/src/backbone.syphon.inputwriters.js @@ -17,7 +17,11 @@ InputWriters.registerDefault(function($el, value) { // Checkbox writer, set whether or not the checkbox is checked // depending on the boolean value. InputWriters.register('checkbox', function($el, value) { - $el.prop('checked', value); + if (value === null) { + $el.prop('indeterminate', true); + } else { + $el.prop('checked', value); + } }); // Radio button writer, set whether or not the radio button is diff --git a/src/backbone.syphon.js b/src/backbone.syphon.js index f15d9e3..ab37a8f 100644 --- a/src/backbone.syphon.js +++ b/src/backbone.syphon.js @@ -202,11 +202,15 @@ var assignKeyValue = function(obj, keychain, value) { // if it's the last key in the chain, assign the value directly if (keychain.length === 0) { - if (_.isArray(obj[key])) { - obj[key].push(value); - } else { - obj[key] = value; - } + value = _.isArray(value) ? value : [value]; + + _.each(value, function(v) { + if (_.isArray(obj[key])) { + obj[key].push(v); + } else { + obj[key] = v; + } + }); } // recursive parsing of the array, depth-first