Skip welcome & menu and move to editor
Welcome to JS Bin
Load cached copy from
 
<!doctype html>
<html ng-app="taskboard">
  <head>
    <link href="styles.css" rel="stylesheet">
    <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script>
    <script src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.9.2/jquery-ui.min.js"></script>
    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.1/angular.js"></script>
    <script src="https://rawgithub.com/angular-ui/ui-sortable/master/src/sortable.js"></script>
  </head>
  <body ng-controller="TaskController">
    <div class="column-wrapper" ng-repeat="column in board.columns">
      <div class="column-title">{{ column.name }}</div>
      <div ui-multi-sortable ng-model="board" model-subset="columns[{{ $index }}].items" class="column">
        <div class="item" ng-repeat="item in column.items">{{ item.name }}</div>
      </div>
    </div>
    <div style="clear: both"></div>
    <span>Has underlying model changed? {{changed}}</span>
    <div style="clear: both"></div>
    <pre ng-bind="board | json"></pre>
    <script type="text/javascript">
      (function(){
        angular.module('ui.sortable').value('uiSortableConfig', {
          sortable: {
            connectWith: '.column',
            update: 'itemsChanged',
          }
        });
        this.taskboard = angular.module('taskboard', ['ui.sortable']);
        this.TaskController = function($scope) {
          $scope.changed = 'Unfortunately not';
          $scope.board = {
            columns: [
              { name: 'todo',
                items: [
                  {name: 'todo 1'},
                  {name: 'todo 2'},
                  {name: 'todo 3'},
                  {name: 'todo 4'}
                ]
              },
              { name: 'done',
                items: [
                  {name: 'done 1'},
                  {name: 'done 2'}
                ]
              },
              { name: 'onhold',
                items: []
              }
            ]
          };
          $scope.itemsChanged = function() {
            $scope.changed = 'Yes, of course :)';
          }
        };
      }).call(this);
    </script>
  </body>
</html>
 
.column-wrapper {
  width: 200px;
  float: left;
  margin-right: 20px;
}
.column-title {
  background-color: #aaa;
  font-size: 1.3em;
}
.column {
  height: 300px;
  background: #ededed;
}
.item {
  box-sizing: border-box;
  padding: 2px;
  background: #8c0;
  margin-bottom: 1px;
}
.ui-sortable-helper {
  background: #8cf;
}
 
/**
  Angular directive for JQuery UI sortable.
  Built on angular-ui "uiSortable" directive.
  Adds ability to sort between multiple sortables.
  @author: Michal Ostruszka (http://michalostruszka.pl)
**/
angular.module('ui.sortable').directive('uiMultiSortable', ['uiSortableConfig', '$parse', function(uiConfig, $parse) {
    var options = {};
    if (uiConfig.sortable !== null) {
      angular.extend(options, uiConfig.sortable);
    }
    var ModelSynchronizer = function(uiElement, attrs) {
      var MODEL_SUBSET_ATTR = 'ui-sortable-model-subset';
      var INITIAL_POSITION_ATTR = 'ui-sortable-start-pos';
      var self = this;
      // Set some data-* attributes on element being sorted just before sorting starts
      this.appendDataOnStart = function() {
        uiElement.item.data(INITIAL_POSITION_ATTR, uiElement.item.index());
        uiElement.item.data(MODEL_SUBSET_ATTR, attrs.modelSubset);
      };
      // Update underlying model when elements sorted within one "sortable"
      this.updateSingleSortableModel = function(model) {
        _collectDataRequiredForModelSync();
        if(_isInternalUpdate() && _hasPositionChanged()) {
          _update(model);
        }
      };
      // Update underlying model when elements sorted between different "sortables"
      this.updateMultiSortableModel = function(model) {
        _collectDataRequiredForModelSync();
        _update(model);
      };
      function _collectDataRequiredForModelSync() {
        self.data = {
          origSubset: uiElement.item.data(MODEL_SUBSET_ATTR),
          destSubset: attrs.modelSubset,
          origPosition: uiElement.item.data(INITIAL_POSITION_ATTR),
          destPosition: uiElement.item.index()
        };
      }
      function _hasPositionChanged() {
        return (self.data.origPosition !== self.data.destPosition) || !_isInternalUpdate();
      }
      function _isInternalUpdate() {
        return attrs.modelSubset === undefined || self.data.origSubset === self.data.destSubset;
      }
      function _update(model) {
        if(attrs.modelSubset === undefined) {
          model.splice(self.data.destPosition, 0, model.splice(self.data.origPosition, 1)[0]);
        } else {
          ($parse(self.data.destSubset)(model)).splice(self.data.destPosition, 0, ($parse(self.data.origSubset)(model)).splice(self.data.origPosition, 1)[0]);
        }
      }
    };
    return {
      require: '?ngModel',
      link: function(scope, element, attrs, ngModel) {
        var opts = angular.extend({}, options, scope.$eval(attrs.uiOptions));
        if (ngModel !== null) {
          var _start = opts.start;
          opts.start = function(e, ui) {
            new ModelSynchronizer(ui, attrs).appendDataOnStart();
            _callUserDefinedCallback(_start)(e, ui);
            return scope.$apply();
          };
          var _update = opts.update;
          opts.update = function(e, ui) {
            _callUserDefinedCallback(_update)(e, ui);
            return scope.$apply();
          };
          var _stop = opts.stop;
          opts.stop = function(e, ui) {
            var modelSync = new ModelSynchronizer(ui, attrs);
            modelSync.updateSingleSortableModel(ngModel.$modelValue);
            _callUserDefinedCallback(_stop)(e, ui);
            return scope.$apply();
          };
          var _receive = opts.receive;
          opts.receive = function(e, ui) {
            var modelSync = new ModelSynchronizer(ui, attrs);
            modelSync.updateMultiSortableModel(ngModel.$modelValue);
            _callUserDefinedCallback(_receive)(e, ui);
            return scope.$apply();
          };
        }
        function _callUserDefinedCallback(callback) {
          if (typeof callback === "function") {
            return callback; // regular callback
          }
          if(typeof scope[callback] === "function") {
            return scope[callback]; // $scope function as callback
          }
          return function() {}; // noop function
        }
        return element.sortable(opts);
      }
    };
  }
]);
Output

You can jump to the latest bin by adding /latest to your URL

Dismiss x
public
Bin info
ajshapiropro
0viewers