12ab5ac8 by Adam Heath

Implement most of what we need; this isn't fully compatible yet, tho.

1 parent 57c20328
{
"name": "backbone-model-overlay",
"version": "0.0.0",
"version": "2016.06.21-0",
"authors": [
"Adam Heath <doogie@brainfood.com>"
],
......
{
"name": "backbone-model-overlay",
"version": "0.0.0",
"version": "2016.06.21-0",
"main": [
"src/scripts/backbone-model-overlay.js"
],
......
......@@ -2,10 +2,92 @@ define(function(require) {
'use strict';
var module = require('module');
var Backbone = require('backbone');
var _ = require('underscore');
var BackboneModelOverlay = Backbone.Model.extend({
initialize: function(data, options) {
BackboneModelOverlay.__super__.initialize.apply(this, arguments);
var parent = this._parent = options.parent;
if (parent) {
parent.on('change', function(value, parentModel, options) {
var parentChanged = this._parent.changedAttributes();
this.changed = parentChanged;
_.each(parentChanged, function(value, key) {
this.trigger('change:' + key, value, this, options);
}, this);
this.trigger('change', this, options);
}, this);
}
var override = this._override = {};
_.each(_.keys(this.attributes), function(key) {
override[key] = true;
});
},
get: function(key) {
if (this._override[key]) {
return BackboneModelOverlay.__super__.get.apply(this, arguments);
} else {
var parent = this._parent;
return parent && parent.get.apply(parent, arguments);
}
},
previous: function(attr) {
return this.previousAttributes()[attr];
},
previousAttributes: function() {
var parent = this._parent;
var data = BackboneModelOverlay.__super__.previousAttributes.apply(this, arguments);
var parentData = parent ? parent.previousAttributes.apply(parent, arguments) : null;
return _.extend({}, parentData, data);
},
changedAttributes: function() {
var parent = this._parent;
var data = BackboneModelOverlay.__super__.changedAttributes.apply(this, arguments);
var parentData = parent ? parent.changedAttributes.apply(parent, arguments) : null;
return _.extend({}, parentData, data);
},
set: function(key, value, options) {
if (!!!key) {
return this;
}
var attrs;
if (typeof key === 'object') {
attrs = key;
options = value;
} else {
(attrs = {})[key] = value;
}
options || (options = {});
var unset = options.unset;
var parent = this._parent;
if (parent) {
if (!options.overlay) {
return parent.set.apply(this, arguments);
}
var override = this._override;
_.each(attrs, function(value, key) {
if (unset) {
delete override[key];
} else {
override[key] = true;
}
});
}
return BackboneModelOverlay.__super__.set.apply(this, arguments);
},
toJSON: function(options) {
var data = BackboneModelOverlay.__super__.toJSON.apply(this, arguments);
var parent = this._parent;
if (parent) {
data = _.extend(data, parent.toJSON.apply(parent, arguments));
}
var superGet = BackboneModelOverlay.__super__.get;
_.each(this._override, function(value, key) {
data[key] = superGet.call(this, key, options);
}, this);
return data;
},
});
return BackboneModelOverlay;
......
......@@ -8,21 +8,148 @@ define(function(require) {
var BackboneModelOverlay = require('backbone-model-overlay');
describe('BackboneModelOverlay', function() {
var staticParentData = {inParent: 'Parent'};
var staticChildData = {inChild: 'Child'};
it('exists', function() {
expect(BackboneModelOverlay).toBeDefined();
});
});
describe('BackboneModelOverlay', function() {
var model;
beforeEach(function() {
model = new BackboneModelOverlay();
var model, parent;
var attributes;
var perAttributeChange;
var previous;
function doBeforeEach(parentData, childData) {
var options;
if (parentData) {
parent = new Backbone.Model(parentData);
options = {parent: parent};
} else {
parent = null;
}
model = new BackboneModelOverlay(childData, options);
attributes = [];
perAttributeChange = {};
previous = [];
model.on('change', function() {
attributes.push(model.toJSON());
previous.push(model.previousAttributes());
_.each(model.changedAttributes(), function(value, key) {
if (perAttributeChange[key] === undefined) {
perAttributeChange[key] = [value];
} else {
perAttributeChange[key].push(value);
}
});
});
}
describe('no-parent', function() {
describe('empty', function() {
beforeEach(function() {
doBeforeEach();
});
it('get', function() {
expect(perAttributeChange).toEqual({});
expect(previous).toEqual([]);
expect(attributes).toEqual([]);
expect(model.get('missing')).toBeUndefined();
expect(model.toJSON()).toEqual({});
});
});
describe('baseline', function() {
beforeEach(function() {
doBeforeEach(null, staticChildData);
model.set('newChild', 'OldChild', {overlay: true});
model.set('newChild', 'NewChild', {overlay: true});
});
it('get', function() {
expect(perAttributeChange).toEqual({
newChild: ['OldChild', 'NewChild'],
});
expect(previous).toEqual([
{inChild: 'Child'},
{inChild: 'Child', newChild: 'OldChild'},
]);
expect(attributes).toEqual([
{inChild: 'Child', newChild: 'OldChild'},
{inChild: 'Child', newChild: 'NewChild'},
]);
expect(model.get('missing')).toBeUndefined();
expect(model.toJSON()).toEqual({inChild: 'Child', newChild: 'NewChild'});
});
});
});
describe('empty', function() {
it('get', function() {
expect(model.get('missing')).toBeUndefined();
describe('with-parent', function() {
beforeEach(function() {
doBeforeEach(staticParentData, staticChildData);
});
it('toJSON', function() {
expect(model.toJSON()).toEqual({});
describe('initial', function() {
it('get', function() {
expect(parent.get('missing')).toBeUndefined();
expect(model.get('missing')).toBeUndefined();
expect(parent.get('inParent')).toEqual('Parent');
expect(model.get('inParent')).toEqual('Parent');
expect(parent.get('inChild')).toBeUndefined();
expect(model.get('inChild')).toEqual('Child');
expect(attributes).toEqual([]);
expect(model.toJSON()).toEqual({inParent: 'Parent', inChild: 'Child'});
});
});
describe('add', function() {
beforeEach(function() {
parent.set('newParent', 'OldParent');
model.set('newChild', 'OldChild', {overlay: true});
parent.set('newParent', 'NewParent');
model.set('newChild', 'NewChild', {overlay: true});
model.set('passThruParent', 'PassThruParent');
});
it('get:perAttributeChange', function() {
expect(perAttributeChange).toEqual({
newParent: ['OldParent', 'OldParent', 'NewParent', 'NewParent', 'NewParent'],
newChild: ['OldChild', 'NewChild'],
passThruParent: ['PassThruParent'],
});
});
it('get:previous', function() {
_.each(previous, function(v) {
//console.log('111', JSON.stringify(v));
});
expect(previous).toEqual([
{inParent: 'Parent'},
{inParent: 'Parent', inChild: 'Child'},
{inParent: 'Parent', newParent: 'OldParent', inChild: 'Child'},
{inParent: 'Parent', newParent: 'OldParent', inChild: 'Child', newChild: 'OldChild'},
{inParent: 'Parent', newParent: 'OldParent', inChild: 'Child', newChild: 'NewChild'},
]);
});
it('get:attributes', function() {
expect(attributes).toEqual([
{inParent: 'Parent', newParent: 'OldParent', inChild: 'Child'},
{inParent: 'Parent', newParent: 'OldParent', inChild: 'Child', newChild: 'OldChild'},
{inParent: 'Parent', newParent: 'NewParent', inChild: 'Child', newChild: 'OldChild'},
{inParent: 'Parent', newParent: 'NewParent', inChild: 'Child', newChild: 'NewChild'},
{inParent: 'Parent', newParent: 'NewParent', inChild: 'Child', newChild: 'NewChild', passThruParent: 'PassThruParent'},
]);
});
it('get:parent:separate-checks', function() {
expect(parent.get('missing')).toBeUndefined();
expect(model.get('missing')).toBeUndefined();
expect(parent.get('inParent')).toEqual('Parent');
expect(parent.get('newParent')).toEqual('NewParent');
expect(model.get('inParent')).toEqual('Parent');
expect(model.get('newParent')).toEqual('NewParent');
});
it('get:child:separate-checks:1', function() {
expect(parent.get('inChild')).toBeUndefined();
expect(parent.get('newChild')).toBeUndefined();
expect(model.get('inChild')).toEqual('Child');
});
it('get:child:separate-checks:2', function() {
expect(model.get('newChild')).toEqual('NewChild');
expect(model.toJSON()).toEqual({inParent: 'Parent', newParent: 'NewParent', inChild: 'Child', newChild: 'NewChild', passThruParent: 'PassThruParent'});
});
});
});
});
......