diff --git a/addon/models/resource.js b/addon/models/resource.js index 2bd61a1..c16e35a 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,19 @@ const Resource = Ember.Object.extend(ResourceOperationsMixin, { ref.added.push({type: pluralize(relation), id: id}); } } + + // 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); + } + } }, /** @@ -364,6 +401,18 @@ const Resource = Ember.Object.extend(ResourceOperationsMixin, { ref.removals.pushObject({ type: pluralize(relation), id: id }); } } + + // 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); + } + } }, /** @@ -455,6 +504,7 @@ const Resource = Ember.Object.extend(ResourceOperationsMixin, { delete this._attributes[attr]; } } + this.set('_changedAttributes', Ember.A([])); }, /** @@ -499,6 +549,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 27428f8..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) { + // 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 = Ember.copy(value, true); + let service = this.get('service'); if (service) { service.trigger('attributeChanged', this);