Skip to content

Instantly share code, notes, and snippets.

@nnarhinen
Last active December 11, 2015 01:39
Show Gist options
  • Save nnarhinen/4524828 to your computer and use it in GitHub Desktop.
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
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');
});
});
});
});
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