From 3b4ab60f096ba65aa2174b58fad8d3c54e7aab49 Mon Sep 17 00:00:00 2001 From: Aaron Heesakkers Date: Wed, 1 Mar 2017 15:28:14 +0100 Subject: [PATCH 1/2] live updated dirty tracking (attrs and rels) --- addon/models/resource.js | 49 ++++++++++++++++++++++++++++++++++++++-- addon/utils/attr.js | 35 ++++++++++++++++++++++++++-- 2 files changed, 80 insertions(+), 4 deletions(-) diff --git a/addon/models/resource.js b/addon/models/resource.js index 2bd61a1..9aa9301 100644 --- a/addon/models/resource.js +++ b/addon/models/resource.js @@ -103,6 +103,21 @@ const Resource = Ember.Object.extend(ResourceOperationsMixin, { */ _attributes: null, + /** + Array of changed attribute keys. + + The Actual changed values (previous, changed) are stored on _attributes, + but we can not use that object or it's keys to create computed properties + that respond to changes (i.e. no live isDirty property). + This array is a simple list of attributes that are not in their original + state, and can be used to track the (dirty-)state of the resource. + + @protected + @property _changedAttributes + @type Array + */ + _changedAttributes: null, + /** Hash of relationships that were changed @@ -112,6 +127,15 @@ const Resource = Ember.Object.extend(ResourceOperationsMixin, { */ _relationships: null, + /** + Array of changed relationship keys. Same use as _changedAttributes. + + @protected + @property _changedRelationships + @type Array + */ + _changedRelationships: null, + /** Flag for new instance, e.g. not persisted @@ -139,7 +163,7 @@ const Resource = Ember.Object.extend(ResourceOperationsMixin, { */ _updateRelationshipsData(relation, ids) { if (!Array.isArray(ids)) { - this._updateToOneRelationshipData(relation, ids); + this._updateToOneRelationshipData(relation, ids); } else { let existing = this._existingRelationshipData(relation); if (!existing.length) { @@ -178,7 +202,7 @@ const Resource = Ember.Object.extend(ResourceOperationsMixin, { */ _replaceRelationshipsData(relation, ids) { if (!Array.isArray(ids)) { - this._updateToOneRelationshipData(relation, ids); + this._updateToOneRelationshipData(relation, ids); } else { let existing = this._existingRelationshipData(relation); if (!existing.length) { @@ -300,6 +324,16 @@ const Resource = Ember.Object.extend(ResourceOperationsMixin, { ref.added.push({type: pluralize(relation), id: id}); } } + + if (!this.get('_changedRelationships')) { + this.set('_changedRelationships', Ember.A([])); + } + + if (!Ember.isEmpty(ref.added) || + !Ember.isEmpty(ref.removals) || + !Ember.isEmpty(ref.changed)) { + this.get('_changedRelationships').pushObject(relation); + } }, /** @@ -364,6 +398,15 @@ const Resource = Ember.Object.extend(ResourceOperationsMixin, { ref.removals.pushObject({ type: pluralize(relation), id: id }); } } + + if (!this.get('_changedRelationships')) { + this.set('_changedRelationships', Ember.A([])); + } + if (!Ember.isEmpty(ref.added) || + !Ember.isEmpty(ref.removals) || + !Ember.isEmpty(ref.previous)) { + this.get('_changedRelationships').removeObject(relation); + } }, /** @@ -455,6 +498,7 @@ const Resource = Ember.Object.extend(ResourceOperationsMixin, { delete this._attributes[attr]; } } + this.set('_changedAttributes', Ember.A([])); }, /** @@ -499,6 +543,7 @@ const Resource = Ember.Object.extend(ResourceOperationsMixin, { delete this._relationships[attr]; } } + this.set('_changedRelationships', Ember.A([])); }, /** diff --git a/addon/utils/attr.js b/addon/utils/attr.js index 2c6bdbc..ed02727 100644 --- a/addon/utils/attr.js +++ b/addon/utils/attr.js @@ -63,18 +63,49 @@ export default function attr(type = 'any', mutable = true) { set: function (key, value) { const lastValue = this.get('attributes.' + key); + // Don't allow set on immutable values. if (!_mutable) { return immutableValue(key, value, lastValue); } + // Don't do anything if same value is set. if (value === lastValue) { return value; } + + // Check value type. assertType.call(this, key, value); + + // Set value. this.set('attributes.' + key, value); + + // Track changes. + // Only on non-isNew resources, which are 'dirty' be default if (!this.get('isNew')) { + // Initialize tracking object and array for this attribute. this._attributes[key] = this._attributes[key] || {}; + if (!this.get('_changedAttributes')) { + this.set('_changedAttributes', Ember.A([])); + } + + // Track change(d key) and store previous/changed value. + // We (Ember.)Copy values to `previous` and `changed` to prevent both + // being a reference to the same object (and thus never showing up on + // computed property 'changedAttributes') if (this._attributes[key].previous === undefined) { - this._attributes[key].previous = lastValue; + // Value changed for the first time. + this._attributes[key].previous = Ember.copy(lastValue, true); + this.get('_changedAttributes').pushObject(key); + } else { + // Value changed again. + if (this._attributes[key].previous === value) { + // Value reverted to previous. No longer dirty. Remove from tracking. + this.get('_changedAttributes').removeObject(key); + } else if (this.get('_changedAttributes').indexOf(key) === -1){ + // Value changed again, wasn't tracked anymore. Track it. + this.get('_changedAttributes').pushObject(key); + } } - this._attributes[key].changed = value; + + this._attributes[key].changed = Ember.copy(value, true); + let service = this.get('service'); if (service) { service.trigger('attributeChanged', this); From a041a5d3ffaff9ba821090e42fee8ee38972b451 Mon Sep 17 00:00:00 2001 From: Aaron Heesakkers Date: Tue, 7 Mar 2017 10:22:18 +0100 Subject: [PATCH 2/2] Only track relationship changes for non new resources --- addon/models/resource.js | 34 ++++++++++++++++++++-------------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/addon/models/resource.js b/addon/models/resource.js index 9aa9301..c16e35a 100644 --- a/addon/models/resource.js +++ b/addon/models/resource.js @@ -325,14 +325,17 @@ const Resource = Ember.Object.extend(ResourceOperationsMixin, { } } - if (!this.get('_changedRelationships')) { - this.set('_changedRelationships', Ember.A([])); - } + // Track relationship changes + if (!this.get('isNew')) { + if (!this.get('_changedRelationships')) { + this.set('_changedRelationships', Ember.A([])); + } - if (!Ember.isEmpty(ref.added) || - !Ember.isEmpty(ref.removals) || - !Ember.isEmpty(ref.changed)) { - this.get('_changedRelationships').pushObject(relation); + if (!Ember.isEmpty(ref.added) || + !Ember.isEmpty(ref.removals) || + !Ember.isEmpty(ref.changed)) { + this.get('_changedRelationships').pushObject(relation); + } } }, @@ -399,13 +402,16 @@ const Resource = Ember.Object.extend(ResourceOperationsMixin, { } } - if (!this.get('_changedRelationships')) { - this.set('_changedRelationships', Ember.A([])); - } - if (!Ember.isEmpty(ref.added) || - !Ember.isEmpty(ref.removals) || - !Ember.isEmpty(ref.previous)) { - this.get('_changedRelationships').removeObject(relation); + // Track relationship changes. + if (!this.get('isNew')) { + if (!this.get('_changedRelationships')) { + this.set('_changedRelationships', Ember.A([])); + } + if (!Ember.isEmpty(ref.added) || + !Ember.isEmpty(ref.removals) || + !Ember.isEmpty(ref.previous)) { + this.get('_changedRelationships').removeObject(relation); + } } },