1 Comment

Changes Pending! Ember Data Dirty State Tracking

emberlogoI have been working with Ember and Ember Data for the past few projects. We’ve recently had cases where we have a dirty form state (unsaved changes) and we want to restrict actions to prevent data loss.

We’ve come up with a solution that lets us get a rough track on the current state of Ember Data objects without implementing state tracking in every Ember controller. But we warned, it’s not entirely fool-proof, there are some manual parts to the process, and it may require a little fixing to fit your needs.

Ember Store Improvements

DS.Store.reopen
  recordsToReset: []
  dataWasUpdated: (type, record) ->
    recordsToReset = @get('recordsToReset')
    return unless $.inArray(record, recordsToReset) == -1
 
    if record.get('isDirty')
      changes = record.changedAttributes()
      changedKeys = Ember.keys(changes)
      if record.get('dirtyType') == "created"
        validChanges = false
        record.eachAttribute (attr, meta) ->
          if attr in changedKeys
            hasChanges = false
            if meta.options and meta.options.defaultValue
              hasChanges = true unless changes[attr][1] == meta.options.defaultValue
            else
              hasChanges = true
 
            if hasChanges
              validChanges = true unless Ember.isBlank(changes[attr][0]) and Ember.isBlank(changes[attr][1])
 
        return unless validChanges
 
      recordsToReset.pushObject(record)
 
  resetDirtyRecords: ->
    @get('recordsToReset').forEach (record) ->
      while record.get('isDirty')
        record.rollback()
    @set('recordsToReset', [])
 
  hasDirtyRecords: ->
    for dirty in @get('recordsToReset')
      changes = dirty.changedAttributes()
    !!@get('recordsToReset').findBy('isDirty', true)

Usage

The code above works well for Ember 1.5 and Ember Data 1.0 Beta 7. You can manually call store.resetDirtyRecords() to clean up at any point. This would generally follow a call to store.hasDirtyRecords() to check if there are dirty records then prompting the user if they want to cancel edits, if there are any. When the user agrees to cancel edits, you can then use @store.resetDirtyRecords() to clear the changes.

Caveats

Careful! This will clear all changes tracked. If you only want to cancel changes on a certain model, you should call .rollback() on that individual model. Also, the logic behind the what makes a “dirty record” is as follows:

  1. A new record has values that are not default. (If you didn’t specify a default, then default is undefined.)
  2. The change to a blank value must not change to another blank value. (Undefined to blank string changes do not dirty the record.)
  3. Relationship changes can dirty a record. (Adding a child to a has many will unintentionally dirty a record.)