Last active
December 11, 2015 01:39
-
-
Save nnarhinen/4524828 to your computer and use it in GitHub Desktop.
A simple helper to bind input values to Backbone models. Hint: use Transparency.js for rendering
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
define(['chai-helper', 'helpers/form-helper', 'backbone', 'jquery'], function(expect, FormHelper, Backbone, $) { | |
'use strict'; | |
var tmpl = '<div> \ | |
<div data-context="model"> \ | |
<input type="text" data-bind="name" class="name"> \ | |
<input type="text" data-bind="percent" class="percent" data-factor="0.01" > \ | |
<input type="checkbox" data-bind="is_enabled" class="check"> \ | |
<input type="checkbox" data-bind="is_private" class="check2"> \ | |
<div data-context="bind:embedded"> \ | |
<textarea class="memo" data-bind="memo"> \ | |
</textarea> \ | |
</div> \ | |
<div data-context="bind:coreproperty"> \ | |
<input class="name3" type="text" data-bind="name"> \ | |
</div> \ | |
</div> \ | |
<div data-context="otherProperty"> \ | |
<input type="text" data-bind="name" class="name2"> \ | |
</div> \ | |
<ul data-context="collection"> \ | |
<li data-index="0"><input data-bind="name" class="item1"></li> \ | |
<li data-index="1"><input data-bind="name" class="item2"></li> \ | |
</ul> \ | |
<div data-context="otherCollection"> \ | |
<div data-index="0"> \ | |
<div data-context="bind:innerCollection"> \ | |
<div class="innerFirst" data-index="0"> \ | |
<input data-bind="name" class="innerItem1"></li> \ | |
</div> \ | |
<div class="innerSecond" data-index="1"> \ | |
<input data-bind="name" class="innerItem2"></li> \ | |
</div> \ | |
</div> \ | |
</div> \ | |
<div data-index="1"> \ | |
<div data-context="bind:innerCollection"> \ | |
<div class="innerThird" data-index="0"> \ | |
<input data-bind="name" class="innerItem3"></li> \ | |
</div> \ | |
<div class="innerFourth" data-index="1"> \ | |
<input data-bind="name" class="innerItem4"></li> \ | |
</div> \ | |
</div> \ | |
</div> \ | |
</div> \ | |
</div>'; | |
var el = $(tmpl); | |
var TestView = Backbone.View.extend({ | |
events: { | |
'change input,textarea,select': 'updateValue' | |
}, | |
el: el, | |
updateValue: function(ev) { | |
FormHelper.updateValue(ev.target, this); | |
} | |
}); | |
var view, model, listModel1, listModel2, collection, otherCollection, containerModel1, containerModel2, innerModel1, innerModel2, innerModel3, innerModel4, innerCollection1, innerCollection2; | |
beforeEach(function() { | |
model = new Backbone.Model(); | |
model.coreproperty = new Backbone.Model(); | |
collection = new Backbone.Collection(); | |
listModel1 = new Backbone.Model(); | |
listModel2 = new Backbone.Model(); | |
collection.add(listModel1); | |
collection.add(listModel2); | |
innerModel1 = new Backbone.Model(); | |
innerModel2 = new Backbone.Model(); | |
innerModel3 = new Backbone.Model(); | |
innerModel4 = new Backbone.Model(); | |
innerCollection1 = new Backbone.Collection(); | |
innerCollection2 = new Backbone.Collection(); | |
innerCollection1.add(innerModel1); | |
innerCollection1.add(innerModel2); | |
innerCollection2.add(innerModel3); | |
innerCollection2.add(innerModel4); | |
otherCollection = new Backbone.Collection(); | |
containerModel1 = new Backbone.Model(); | |
containerModel1.innerCollection = innerCollection1; | |
containerModel2 = new Backbone.Model(); | |
containerModel2.innerCollection = innerCollection2; | |
otherCollection.add(containerModel1); | |
otherCollection.add(containerModel2); | |
view = new TestView({ | |
model: model, | |
collection: collection | |
}); | |
view.otherCollection = otherCollection; | |
}); | |
describe('FormHelper', function() { | |
it('updates model value when input changed', function() { | |
el.find('input.name').val('Test value').trigger('change'); | |
expect(model.get('name')).to.equal('Test value'); | |
}); | |
it('respects data-context attribute', function() { | |
el.find('input.name2').val('Test value').trigger('change'); | |
expect(model.get('name')).to.be.undefined; | |
}); | |
it('handles checkbox check', function() { | |
el.find('input.check').attr('checked', 'checked').trigger('change'); | |
expect(model.get('is_enabled')).to.be.true; | |
}); | |
it ('handles checkbox uncheck', function() { | |
el.find('input.check2').removeAttr('checked').trigger('change'); | |
expect(model.get('is_private')).to.be.false; | |
}); | |
it('converts the value by a factor', function() { | |
el.find('input.percent').val('15').trigger('change'); | |
expect(model.get('percent')).to.equal(0.15); | |
}); | |
it('updates model in collection', function() { | |
el.find('input.item2').val('Test value').trigger('change'); | |
expect(listModel2.get('name')).to.equal('Test value'); | |
expect(listModel1.get('name')).to.be.undefined; | |
}); | |
describe('with data-bound datacontext', function() { | |
it('updates the embedded object', function() { | |
el.find('input.name3').val('Coreproperty test').trigger('change'); | |
expect(model.coreproperty.get('name')).to.equal('Coreproperty test'); | |
}); | |
it('updates embedded object with complex attributes', function() { | |
el.find('textarea.memo').val('Memo for embedded').trigger('change'); | |
expect(model.get('embedded').memo).to.equal('Memo for embedded'); | |
}); | |
it('supports nested collections', function() { | |
el.find('input.innerItem2').val('Nested collections test').trigger('change'); | |
expect(innerModel3.get('name')).to.be.undefined; | |
expect(innerModel2.get('name')).to.equal('Nested collections test'); | |
}); | |
}); | |
}); | |
}); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
define(['jquery'], function($) { | |
'use strict'; | |
var setValue = function(obj, propertyName, value) { | |
if (typeof obj.set === 'function') { | |
obj.set(propertyName, value); | |
} else { | |
obj[propertyName] = value; | |
} | |
}; | |
var getObjectFromCollection = function(obj, collectionIndex) { | |
if (typeof obj.at === 'function') { | |
return obj.at(collectionIndex); | |
} | |
return obj[collectionIndex]; | |
}; | |
var getObjectFromObject = function(obj, property) { | |
var ret; | |
if (typeof obj.get === 'function') { | |
ret = obj.get(property); | |
} | |
if (!ret) { | |
ret = obj[property]; | |
} | |
if (!ret) { | |
ret = {}; | |
setValue(obj, property, ret); | |
} | |
return ret; | |
}; | |
var getValueFromInput = function(input) { | |
var value = input.val(); | |
var dataType = input.attr('data-type'); | |
if (dataType === 'integer'){ | |
value = parseInt(value, 10); | |
} | |
var factor = input.attr('data-factor'); | |
if (factor) { | |
value = value * factor; | |
} | |
return value; | |
}; | |
return { | |
updateValue: function(input, view, object) { | |
var inputJq = $(input), | |
chain = inputJq.parentsUntil(view.el, '[data-context],[data-index]'), | |
obj = (object === undefined)?view:object, | |
propertyName = inputJq.attr('data-bind'); | |
if (!propertyName) { return; } | |
var i = chain.length; | |
while (i >= 0) { | |
var current = chain.eq(i), ctxProperty, collectionIndex; | |
if ((ctxProperty = current.attr('data-context')) && obj[ctxProperty]) { | |
obj = obj[ctxProperty]; | |
} else if (ctxProperty && ctxProperty.indexOf('bind:') === 0) { | |
obj = getObjectFromObject(obj, ctxProperty.slice(5)); | |
} | |
if (typeof (collectionIndex = current.attr('data-index')) !== 'undefined' && collectionIndex !== false) { | |
obj = getObjectFromCollection(obj, collectionIndex); | |
} | |
i--; | |
} | |
if(obj === undefined && view.model !== undefined){ | |
obj = view.model; | |
//return setValue(obj.model, propertyName, inputJq.val()); | |
} | |
if (inputJq.attr('type') === 'checkbox') { | |
return setValue(obj, propertyName, inputJq.is(':checked')); | |
} | |
setValue(obj, propertyName, getValueFromInput(inputJq)); | |
} | |
}; | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment