Drag and drop sortable ng:repeats in AngularJS?
Angular UI has a sortable directive,Click Here for Demo
Code located at ui-sortable, usage:
<ul ui-sortable ng-model="items" ui-sortable-update="sorted">
<li ng-repeat="item in items track by $index" id="{{$index}}">{{ item }}</li>
</ul>
$scope.sorted = (event, ui) => { console.log(ui.item[0].getAttribute('id')) }
I tried to do the same and came up with the following solution:
angular.directive("my:sortable", function(expression, compiledElement){
return function(linkElement){
var scope = this;
linkElement.sortable(
{
placeholder: "ui-state-highlight",
opacity: 0.8,
update: function(event, ui) {
var model = scope.$tryEval(expression);
var newModel = [];
var items = [];
linkElement.children().each(function() {
var item = $(this);
// get old item index
var oldIndex = item.attr("ng:repeat-index");
if(oldIndex) {
// new model in new order
newModel.push(model[oldIndex]);
// items in original order
items[oldIndex] = item;
// and remove
item.detach();
}
});
// restore original dom order, so angular does not get confused
linkElement.append.apply(linkElement,items);
// clear old list
model.length = 0;
// add elements in new order
model.push.apply(model, newModel);
// presto
scope.$eval();
// Notify event handler
var onSortExpression = linkElement.attr("my:onsort");
if(onSortExpression) {
scope.$tryEval(onSortExpression, linkElement);
}
}
});
};
});
Used like this:
<ol id="todoList" my:sortable="todos" my:onsort="onSort()">
It seems to work fairly well. The trick is to undo the DOM manipulation made by sortable before updating the model, otherwise angular gets desynchronized from the DOM.
Notification of the changes works via the my:onsort expression which can call the controller methods.
I created a JsFiddle based on the angular todo tutorial to shows how it works: http://jsfiddle.net/M8YnR/180/
This is how I am doing it with angular v0.10.6. Here is the jsfiddle
angular.directive("my:sortable", function(expression, compiledElement){
// add my:sortable-index to children so we know the index in the model
compiledElement.children().attr("my:sortable-index","{{$index}}");
return function(linkElement){
var scope = this;
linkElement.sortable({
placeholder: "placeholder",
opacity: 0.8,
axis: "y",
update: function(event, ui) {
// get model
var model = scope.$apply(expression);
// remember its length
var modelLength = model.length;
// rember html nodes
var items = [];
// loop through items in new order
linkElement.children().each(function(index) {
var item = $(this);
// get old item index
var oldIndex = parseInt(item.attr("my:sortable-index"), 10);
// add item to the end of model
model.push(model[oldIndex]);
if(item.attr("my:sortable-index")) {
// items in original order to restore dom
items[oldIndex] = item;
// and remove item from dom
item.detach();
}
});
model.splice(0, modelLength);
// restore original dom order, so angular does not get confused
linkElement.append.apply(linkElement,items);
// notify angular of the change
scope.$digest();
}
});
};
});
Here's my implementation of sortable Angular.js directive without jquery.ui :
- https://github.com/schartier/angular-sortable