12ab5ac8 by Adam Heath

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

1 parent 57c20328
1 { 1 {
2 "name": "backbone-model-overlay", 2 "name": "backbone-model-overlay",
3 "version": "0.0.0", 3 "version": "2016.06.21-0",
4 "authors": [ 4 "authors": [
5 "Adam Heath <doogie@brainfood.com>" 5 "Adam Heath <doogie@brainfood.com>"
6 ], 6 ],
......
1 { 1 {
2 "name": "backbone-model-overlay", 2 "name": "backbone-model-overlay",
3 "version": "0.0.0", 3 "version": "2016.06.21-0",
4 "main": [ 4 "main": [
5 "src/scripts/backbone-model-overlay.js" 5 "src/scripts/backbone-model-overlay.js"
6 ], 6 ],
......
...@@ -2,10 +2,92 @@ define(function(require) { ...@@ -2,10 +2,92 @@ define(function(require) {
2 'use strict'; 2 'use strict';
3 var module = require('module'); 3 var module = require('module');
4 var Backbone = require('backbone'); 4 var Backbone = require('backbone');
5 var _ = require('underscore');
5 6
6 var BackboneModelOverlay = Backbone.Model.extend({ 7 var BackboneModelOverlay = Backbone.Model.extend({
7 initialize: function(data, options) { 8 initialize: function(data, options) {
8 BackboneModelOverlay.__super__.initialize.apply(this, arguments); 9 BackboneModelOverlay.__super__.initialize.apply(this, arguments);
10 var parent = this._parent = options.parent;
11 if (parent) {
12 parent.on('change', function(value, parentModel, options) {
13 var parentChanged = this._parent.changedAttributes();
14 this.changed = parentChanged;
15 _.each(parentChanged, function(value, key) {
16 this.trigger('change:' + key, value, this, options);
17 }, this);
18 this.trigger('change', this, options);
19 }, this);
20 }
21 var override = this._override = {};
22 _.each(_.keys(this.attributes), function(key) {
23 override[key] = true;
24 });
25 },
26 get: function(key) {
27 if (this._override[key]) {
28 return BackboneModelOverlay.__super__.get.apply(this, arguments);
29 } else {
30 var parent = this._parent;
31 return parent && parent.get.apply(parent, arguments);
32 }
33 },
34 previous: function(attr) {
35 return this.previousAttributes()[attr];
36 },
37 previousAttributes: function() {
38 var parent = this._parent;
39 var data = BackboneModelOverlay.__super__.previousAttributes.apply(this, arguments);
40 var parentData = parent ? parent.previousAttributes.apply(parent, arguments) : null;
41 return _.extend({}, parentData, data);
42 },
43 changedAttributes: function() {
44 var parent = this._parent;
45 var data = BackboneModelOverlay.__super__.changedAttributes.apply(this, arguments);
46 var parentData = parent ? parent.changedAttributes.apply(parent, arguments) : null;
47 return _.extend({}, parentData, data);
48 },
49 set: function(key, value, options) {
50 if (!!!key) {
51 return this;
52 }
53 var attrs;
54 if (typeof key === 'object') {
55 attrs = key;
56 options = value;
57 } else {
58 (attrs = {})[key] = value;
59 }
60
61 options || (options = {});
62 var unset = options.unset;
63
64 var parent = this._parent;
65 if (parent) {
66 if (!options.overlay) {
67 return parent.set.apply(this, arguments);
68 }
69 var override = this._override;
70 _.each(attrs, function(value, key) {
71 if (unset) {
72 delete override[key];
73 } else {
74 override[key] = true;
75 }
76 });
77 }
78 return BackboneModelOverlay.__super__.set.apply(this, arguments);
79 },
80 toJSON: function(options) {
81 var data = BackboneModelOverlay.__super__.toJSON.apply(this, arguments);
82 var parent = this._parent;
83 if (parent) {
84 data = _.extend(data, parent.toJSON.apply(parent, arguments));
85 }
86 var superGet = BackboneModelOverlay.__super__.get;
87 _.each(this._override, function(value, key) {
88 data[key] = superGet.call(this, key, options);
89 }, this);
90 return data;
9 }, 91 },
10 }); 92 });
11 return BackboneModelOverlay; 93 return BackboneModelOverlay;
......
...@@ -8,21 +8,148 @@ define(function(require) { ...@@ -8,21 +8,148 @@ define(function(require) {
8 var BackboneModelOverlay = require('backbone-model-overlay'); 8 var BackboneModelOverlay = require('backbone-model-overlay');
9 9
10 describe('BackboneModelOverlay', function() { 10 describe('BackboneModelOverlay', function() {
11 var staticParentData = {inParent: 'Parent'};
12 var staticChildData = {inChild: 'Child'};
11 it('exists', function() { 13 it('exists', function() {
12 expect(BackboneModelOverlay).toBeDefined(); 14 expect(BackboneModelOverlay).toBeDefined();
13 }); 15 });
14 }); 16 var model, parent;
15 describe('BackboneModelOverlay', function() { 17 var attributes;
16 var model; 18 var perAttributeChange;
17 beforeEach(function() { 19 var previous;
18 model = new BackboneModelOverlay(); 20 function doBeforeEach(parentData, childData) {
21 var options;
22 if (parentData) {
23 parent = new Backbone.Model(parentData);
24 options = {parent: parent};
25 } else {
26 parent = null;
27 }
28 model = new BackboneModelOverlay(childData, options);
29 attributes = [];
30 perAttributeChange = {};
31 previous = [];
32 model.on('change', function() {
33 attributes.push(model.toJSON());
34 previous.push(model.previousAttributes());
35 _.each(model.changedAttributes(), function(value, key) {
36 if (perAttributeChange[key] === undefined) {
37 perAttributeChange[key] = [value];
38 } else {
39 perAttributeChange[key].push(value);
40 }
41 });
42 });
43 }
44 describe('no-parent', function() {
45 describe('empty', function() {
46 beforeEach(function() {
47 doBeforeEach();
48 });
49 it('get', function() {
50 expect(perAttributeChange).toEqual({});
51 expect(previous).toEqual([]);
52 expect(attributes).toEqual([]);
53 expect(model.get('missing')).toBeUndefined();
54 expect(model.toJSON()).toEqual({});
55 });
56 });
57 describe('baseline', function() {
58 beforeEach(function() {
59 doBeforeEach(null, staticChildData);
60 model.set('newChild', 'OldChild', {overlay: true});
61 model.set('newChild', 'NewChild', {overlay: true});
62 });
63 it('get', function() {
64 expect(perAttributeChange).toEqual({
65 newChild: ['OldChild', 'NewChild'],
66 });
67 expect(previous).toEqual([
68 {inChild: 'Child'},
69 {inChild: 'Child', newChild: 'OldChild'},
70 ]);
71 expect(attributes).toEqual([
72 {inChild: 'Child', newChild: 'OldChild'},
73 {inChild: 'Child', newChild: 'NewChild'},
74 ]);
75 expect(model.get('missing')).toBeUndefined();
76 expect(model.toJSON()).toEqual({inChild: 'Child', newChild: 'NewChild'});
77 });
78 });
19 }); 79 });
20 describe('empty', function() { 80 describe('with-parent', function() {
21 it('get', function() { 81 beforeEach(function() {
22 expect(model.get('missing')).toBeUndefined(); 82 doBeforeEach(staticParentData, staticChildData);
23 }); 83 });
24 it('toJSON', function() { 84 describe('initial', function() {
25 expect(model.toJSON()).toEqual({}); 85 it('get', function() {
86 expect(parent.get('missing')).toBeUndefined();
87 expect(model.get('missing')).toBeUndefined();
88
89 expect(parent.get('inParent')).toEqual('Parent');
90 expect(model.get('inParent')).toEqual('Parent');
91
92 expect(parent.get('inChild')).toBeUndefined();
93 expect(model.get('inChild')).toEqual('Child');
94
95 expect(attributes).toEqual([]);
96 expect(model.toJSON()).toEqual({inParent: 'Parent', inChild: 'Child'});
97 });
98 });
99 describe('add', function() {
100 beforeEach(function() {
101 parent.set('newParent', 'OldParent');
102 model.set('newChild', 'OldChild', {overlay: true});
103 parent.set('newParent', 'NewParent');
104 model.set('newChild', 'NewChild', {overlay: true});
105 model.set('passThruParent', 'PassThruParent');
106 });
107 it('get:perAttributeChange', function() {
108 expect(perAttributeChange).toEqual({
109 newParent: ['OldParent', 'OldParent', 'NewParent', 'NewParent', 'NewParent'],
110 newChild: ['OldChild', 'NewChild'],
111 passThruParent: ['PassThruParent'],
112 });
113 });
114 it('get:previous', function() {
115 _.each(previous, function(v) {
116 //console.log('111', JSON.stringify(v));
117 });
118 expect(previous).toEqual([
119 {inParent: 'Parent'},
120 {inParent: 'Parent', inChild: 'Child'},
121 {inParent: 'Parent', newParent: 'OldParent', inChild: 'Child'},
122 {inParent: 'Parent', newParent: 'OldParent', inChild: 'Child', newChild: 'OldChild'},
123 {inParent: 'Parent', newParent: 'OldParent', inChild: 'Child', newChild: 'NewChild'},
124 ]);
125 });
126 it('get:attributes', function() {
127 expect(attributes).toEqual([
128 {inParent: 'Parent', newParent: 'OldParent', inChild: 'Child'},
129 {inParent: 'Parent', newParent: 'OldParent', inChild: 'Child', newChild: 'OldChild'},
130 {inParent: 'Parent', newParent: 'NewParent', inChild: 'Child', newChild: 'OldChild'},
131 {inParent: 'Parent', newParent: 'NewParent', inChild: 'Child', newChild: 'NewChild'},
132 {inParent: 'Parent', newParent: 'NewParent', inChild: 'Child', newChild: 'NewChild', passThruParent: 'PassThruParent'},
133 ]);
134 });
135 it('get:parent:separate-checks', function() {
136 expect(parent.get('missing')).toBeUndefined();
137 expect(model.get('missing')).toBeUndefined();
138
139 expect(parent.get('inParent')).toEqual('Parent');
140 expect(parent.get('newParent')).toEqual('NewParent');
141 expect(model.get('inParent')).toEqual('Parent');
142 expect(model.get('newParent')).toEqual('NewParent');
143 });
144 it('get:child:separate-checks:1', function() {
145 expect(parent.get('inChild')).toBeUndefined();
146 expect(parent.get('newChild')).toBeUndefined();
147 expect(model.get('inChild')).toEqual('Child');
148 });
149 it('get:child:separate-checks:2', function() {
150 expect(model.get('newChild')).toEqual('NewChild');
151 expect(model.toJSON()).toEqual({inParent: 'Parent', newParent: 'NewParent', inChild: 'Child', newChild: 'NewChild', passThruParent: 'PassThruParent'});
152 });
26 }); 153 });
27 }); 154 });
28 }); 155 });
......