Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
0471aa6
Clarifies the second setup case
solsson Oct 2, 2015
e707740
Adds test that can run with command line mocha
solsson Oct 6, 2015
ef75278
Reduces backbone-polymer to only run notifications on changes to exis…
solsson Oct 6, 2015
8da661e
Moves modelSetup to our overridden .add
solsson Oct 6, 2015
5ce4ad0
First attempt at running a regular Polymer splice and an emulated Bac…
solsson Oct 6, 2015
b363b42
tests for Options object.
Oct 6, 2015
99bfa39
The length property is now set in the add override, and tests for it.
Oct 7, 2015
be395a3
Removes debug logging
solsson Oct 7, 2015
2dd756b
Clarifies some test and uncovers a serious flaw in backbone compatibi…
solsson Oct 7, 2015
afca153
Uncovers another flaw: that we don't trigger the event on model.
solsson Oct 7, 2015
5469a38
Further reduces scope of add, so we don't need to re-implement all of…
solsson Oct 7, 2015
2c89fed
Fixes .get but fails to fix .at
solsson Oct 7, 2015
528552b
Something is pretty badly messed up
solsson Oct 7, 2015
80440b7
Does the actual splice from the mock element, so that tests can asser…
solsson Oct 7, 2015
77dfa29
Converts the backbone test to running in WCT with npm test
solsson Oct 7, 2015
5a2fcaf
Complies with the new rule to use real models. If we don't like these…
solsson Oct 7, 2015
efc4e49
Converts mocha test to WCT so all tests execute the same way. We migh…
solsson Oct 8, 2015
64baa18
Fixes setup of change notification after collection add. All tests pa…
solsson Oct 8, 2015
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 28 additions & 37 deletions backbone-polymer-mixin.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@

var BackbonePolymerAttach = function(element, pathPrefix) {
console.log('BackbonePolymerAttach', this, element, pathPrefix);

var indexOf = this.indexOf.bind(this);

Expand All @@ -14,44 +13,36 @@ var BackbonePolymerAttach = function(element, pathPrefix) {
});
};

var splicesObject = this.models;

var addNotify = function(model) {
var ix = indexOf(model);

// https://www.polymer-project.org/1.0/docs/devguide/properties.html#array-observation
var change = {keySplices:[], indexSplices:[]};
change.keySplices.push({
index: ix,
removed: [],
removedItems: [],
added: [ix]
});
change.indexSplices.push({
index: ix,
addedCount: 1,
removed: [],
object: splicesObject,
type: 'splice',
addedKeys: [ix]
});

element.notifyPath(pathPrefix + '.models.splices', change);
this.each(modelSetup.bind(this));

// remove this timeout and the rendered element goes blank
window.setTimeout(function() {
// TODO would it be possible to notify .* here?
for (var attribute in model.attributes) {
// we could reuse code with modelSetup here
var value = model.get(attribute);
element.notifyPath(pathPrefix + '.models.' + ix + '.attributes.' + attribute, value);
}
}, 1);
// override Backbone add
var _add = this.add;
var addOptions = {add: true, remove: false}; // from backbone source
this.add = function(model, options) {
this.length = this.length + 1;
if (_.isArray(model)) {
throw new Error('backbone-polymer only accepts add of single model');
}
if (!this._isModel(model)) {
throw new Error('backbone-polymer requires model instances, not just attributes');
}
if (this.get(model)) {
throw new Error('backbone-polymer model already exists as cid ' + this.get(model).cid);
}

var options = _.extend({merge: false}, options, addOptions);
var ix = options.at || 0;

element.splice(pathPrefix + '.models', ix, 0, model);
this._addReference(model, options);
if (!options.silent) {
model.trigger('add', model, this, options);
this.trigger('update', this, options);
}

modelSetup.call(this, model);
return model;
};

this.each(modelSetup.bind(this));
this.on('add', addNotify.bind(this));
this.on('add', modelSetup.bind(this));
};

if (typeof module !== 'undefined') {
Expand Down
153 changes: 153 additions & 0 deletions test/backbone-compatibility.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<script src="../bower_components/webcomponentsjs/webcomponents.min.js"></script>
<script src="../node_modules/mocha/mocha.js"></script>
<script src="../node_modules/web-component-tester/browser.js"></script>

<!-- lib dependencies -->
<script src="../node_modules/yobo/node_modules/underscore/underscore.js"></script>
<script src="../node_modules/yobo/node_modules/backbone/backbone.js"></script>
<script src="../backbone-polymer-mixin.js"></script>

<link rel="import" href="../bower_components/polymer/polymer.html">
</head>
<body>
<script>

var PolymerElementMock = function(testArray) {

var spliced = this.spliced = [];

this.splice = function(path, index, removeCount, items) {
spliced.push({path: path, index: index, removeCount: removeCount, items: items});
if (typeof testArray !== 'undefined') {
testArray.splice.apply(testArray, [index, removeCount].concat(items));
}
};

this.notifyPath = function() {
// our code still does this for attribute changes
};

};

describe("Array modification through Polymer splices, emulate backbone events", function() {

describe("Backbone events before backbone-polymer", function() {

it("add model", function() {
var c = new Backbone.Collection();
var m = new Backbone.Model({id: 'a1', type: 'testmodel'})
c.on('add', function(model, collection, options) {
console.log('add', m === model ? '(model)' : model, c === collection ? '(collection)' : collection, JSON.stringify(options));
});
c.add(m);
});

it("add model at index", function() {
var c = new Backbone.Collection();
c.add({id: 'a2', type: 'testmodel2'});
c.add({id: 'a3', type: 'testmodel3'});
var m = new Backbone.Model({id: 'a1', type: 'testmodel'});
c.on('add', function(model, collection, options) {
console.log('add', m === model ? '(model)' : model, c === collection ? '(collection)' : collection, JSON.stringify(options));
});
m.on('add', function(model, collection, options) {
console.log('model add', m === model ? '(model)' : model, c === collection ? '(collection)' : collection, JSON.stringify(options));
});
var added = c.add(m, {at: 1});
console.log('add returned', m === added ? '(model)' : model);
// common Backbone operations
expect(c.get('a1')).to.exist.and.have.property('cid');
expect(c.at(0)).to.exist.and.have.property('cid');
});

});

describe("#add, reduced Backbone functionality", function() {

it("Accepts only a single item", function() {
expect(function() {
var c = new Backbone.Collection();
BackbonePolymerAttach.call(c, new PolymerElementMock(), 'edit.units');
c.add([]);
}).to.throw(/single/);
});

it("Requires item to be a real model", function() {
expect(function() {
var c = new Backbone.Collection();
BackbonePolymerAttach.call(c, new PolymerElementMock(), 'edit.units');
c.add({id: 'add1', type: 'test'});
}).to.throw(/requires model instance/);
});

it("Bails out if the model already exists", function() {
expect(function() {
var c = new Backbone.Collection();
BackbonePolymerAttach.call(c, new PolymerElementMock(), 'edit.units');
var m = new Backbone.Model({id: 'add1', type: 'test'});
c.add(m);
c.add(m);
}).to.throw(/backbone-polymer model already exists as cid \w+/);
});

it("Is transparent to backbone add event listener", function() {
var c = new Backbone.Collection();
var e = new PolymerElementMock(c.models);

var adds = [];
c.on('add', function(m, c, o) {
adds.push({model:m, collection:c, options:o});
});

BackbonePolymerAttach.call(c, e, 'edit.units');
var m = c.add(new Backbone.Model({id: 'add1', type: 'test'}));

expect(m.get('type')).to.equal('test');
expect(adds).to.have.length(1);
expect(e.spliced).to.have.length(1);

//Expects on the options obj.
expect(adds[0].options).to.be.an('object');
expect(adds[0].options).to.have.property('add').that.equals(true);
expect(adds[0].options).to.have.property('merge').that.equals(false);
expect(adds[0].options).to.have.property('remove').that.equals(false);
expect(adds[0].options).to.not.have.property('at');
expect(adds[0].collection).to.have.property('length').and.equal(1);

var m1 = new Backbone.Model({id: 'add2', type: 'test'});
modeladd = [];
m1.on('add', function(m, c, o) {
modeladd.push({model:m, collection:c, options:o});
});
c.add(m1, {at: 1});

//Adding at an index, expects Options to have 'at'
expect(adds[1].options).to.have.property('add').that.equals(true);
expect(adds[1].options).to.have.property('at').that.equals(1);
expect(adds[1].collection).to.have.length(2);
expect(e.spliced).to.have.length(2);
expect(modeladd).to.have.length(1);
expect(modeladd[0]).to.deep.equal(adds[1]);

//Adding again at index 1, i.e. insert
var m2 = new Backbone.Model({id: 'add3', type: 'test'});
c.add(m2, {at: 1});

expect(e.spliced[2]).to.have.property('index').and.equal(1);
expect(adds[0].collection).to.have.property('length').and.equal(3);

// common Backbone operations
expect(c.get('add1')).to.exist.and.have.property('cid');
expect(c.models).to.have.length(3);
expect(c.at(1)).to.exist.and.have.property('cid');
});
});
});

</script>
</body>
</html>
4 changes: 2 additions & 2 deletions test/test.html
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@

});

it("Uses 'this' for the collection, like a regular Backbone member function", function(done) {
it("Can be used with .call instead of as mixin", function(done) {

var element1 = document.querySelector('#collection1');

Expand All @@ -106,7 +106,7 @@
expect(renderTimeAllowMs).to.be.at.least(2); // the sequence below is fragile, and there's a timeout in the notification too

window.setTimeout(function() {
m1 = c1.add({type: 'text', content: 'As added after setup.'});
m1 = c1.add(new Backbone.Model({type: 'text', content: 'As added after setup.'}));
}, renderTimeAllowMs);

window.setTimeout(function() {
Expand Down