Ember Data + jQuery UI Sortable Problems

by Kerrick

HTML

<script src="http://cdnjs.cloudflare.com/ajax/libs/handlebars.js/1.1.2/handlebars.min.js"></script>
<script src="http://builds.emberjs.com/tags/v1.2.2/ember.js"></script>
<script src="http://builds.emberjs.com/beta/ember-data.js"></script>
<script type="text/x-handlebars" data-template-name="application">
    <h1>Ember Data + jQueryUI Sortable Problems</h1>
    {{outlet}}
    Try sorting and then deleting something. The model is deleted properly, but the DOM does not reflect that. Sometimes it stays in the DOM, sometimes the wrong one is deleted, and sometimes both the right and a wrong one is deleted.
</script>
<script type="text/x-handlebars" data-template-name="index">
    {{render 'formFields' formFields}}
</script>
<script type="text/x-handlebars" data-template-name="formFields">
    {{#each arrangedContent}}
        {{render 'formField' this}}
    {{/each}}
</script>
<script type="text/x-handlebars" data-template-name="formField">
    <div class="form-field" {{bind-attr data-id=id}}>
        <span class="delete" {{action 'delete'}}>X</span>
        {{name}} ({{displayOrder}})
        <span class="handle">#</span>
    </div>
</script>

CSS

body { padding: 2em; }
.form-field {
  border:1px solid #ccc;
  margin:2px;
  padding:2px;
  background:white;
  overflow: hidden;
}
.ui-sortable-helper {
  -webkit-transform: scale(1.03);
  -moz-transform: scale(1.03);
  transform: scale(1.03);
  box-shadow: 1px 3px 5px rgba(0, 0, 0, 0.4);
}
.handle {
  cursor: move;
  font-size: 2em;
  float: right;
  margin-right: 0.5em;
}
.delete {
    cursor: pointer;
    color: red;
}

JavaScript

App = Ember.Application.create({});

App.ApplicationAdapter = DS.FixtureAdapter.extend();

App.IndexRoute = Ember.Route.extend({
    model: function(){
        return this.store.find('formField');
    }
});

App.IndexController = Ember.ObjectController.extend({
    formFields: Ember.computed.alias('content')
});

App.FormFieldsController = Ember.ArrayController.extend({
    sortProperties: ['displayOrder'], // Force sort by displayOrder, not ID
    updatePositions : function(positionData){
          this.propertyWillChange('content'); // Manually notify Ember
          this.get('content').forEach(function(formField) {
              var key = formField.get('id');
              formField.set('displayOrder', positionData[key] + 1);
          });
          this.propertyDidChange('content'); // Manually notify Ember
      },
    actions: {
        compactDisplayOrder: function() {
            window.console.log('compactDisplayOrder');
            this.get('content').sortBy('displayOrder').forEach(function(formField, i) {
                formField.set('displayOrder', i + 1);
            });
        }
    }
    });

App.FormFieldsView = Ember.View.extend({
    handleSort: function(event,ui){
        var positionData = {};
        this.$(".form-field").each(function(index, element){
            // Get model ID from bind-attr in template
            var key = $(element).data('id');
            positionData[key] = index;
        });
        this.get('controller').updatePositions(positionData);
        // Delay recreating the sortable
        Ember.run.next(function(){ this.makeSortable(); }.bind(this));
    },
    makeSortable: Ember.on('didInsertElement', function(){
        try {
            this.$().sortable("destroy");
        } catch(err){ 
            window.console.warn('No sortable to destroy', err);
        }
        finally {
            this.$().sortable({
                handle: '.handle',
                axis: 'y',
                update:...