Fork this to experiment with Ember.js

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</h1>
    {{outlet}}
</script>
<script type="text/x-handlebars" data-template-name="index">
    {{#each model}}
        {{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.ArrayController.extend({
    sortProperties: ['displayOrder'], // Force sort by displayOrder, not ID
    updatePositions : function(positionData){
        /* Position Data
          {
            //'The Model ID': 'Its Display Order, zero-indexed',
            48: 0,
            52: 1,
            39: 2
          }
          */
          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
      }
    });

App.IndexView = 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: this.handleSort.bind(this)
            });
        }
    })
});

App.FormFieldController = Ember.ObjectController.extend({
    actions: {
        'delete': function() {
            window.console.log('deleting record', this.get('name'));
    ...