<html>
<head>
<script data-require="jquery@*" data-semver="2.0.3" src="http://code.jquery.com/jquery-2.0.3.min.js"></script>
<link data-require="jqueryui@*" data-semver="1.10.0" rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/jqueryui/1.10.0/css/smoothness/jquery-ui-1.10.0.custom.min.css" />
<script data-require="jqueryui@*" data-semver="1.10.0" src="//cdnjs.cloudflare.com/ajax/libs/jqueryui/1.10.0/jquery-ui.js"></script>
<script data-require="angular.js@*" data-semver="1.3.0-beta.5" src="https://code.angularjs.org/1.3.0-beta.5/angular.js"></script>
<script src="https://rawgit.com/angular-ui/ui-sortable/master/src/sortable.js"></script>
<script src="angular-ui-sortable-multiselect.js"></script>
<script src="script.js"></script>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div ng-app="sortableApp" ng-controller="sortableController" class="container">
<h2>angular-ui-sortable-multiselection</h2>
<div class="floatleft">
<div ui-sortable="sortableOptions" ng-model="trash" class="list">
<div ui-sortable-selectable="" ng-repeat="item in trash" class="item">
{{item.text}}
</div>
</div>
</div>
<div class="floatleft" style="margin-left: 20px;">
<div ui-sortable="sortableOptions" ng-model="notTrash" class="list">
<div ui-sortable-selectable="" ng-repeat="item in notTrash" class="item">
{{item.text}}
</div>
</div>
</div>
<div class="clear"></div>
</div>
</body>
</html>
var myapp = angular.module('sortableApp', ['ui.sortable', 'ui.sortable.multiselection']);
myapp.controller('sortableController', function ($scope, uiSortableMultiSelectionMethods, uiSortableMulitSelectionCollapse) {
var tmpList = [];
for (var i = 1; i <= 10; i++){
tmpList.push({
text: 'Item ' + i,
value: i
});
}
$scope.trash = tmpList.slice(0, 5);
$scope.notTrash = tmpList.slice(5);
$scope.sortableOptions = {
helper: uiSortableMultiSelectionMethods.helper,
connectWith: ".list",
stop: function(e, ui) {
uiSortableMultiSelectionMethods.stop(e, ui);
}
};
});
.list {
list-style: none outside none;
margin: 10px 0 30px;
border: 1px solid #000;
width: 250px;
height: 500px;
}
.item {
width: 200px;
padding: 5px 10px;
margin: 5px 0;
border: 2px solid #444;
border-radius: 5px;
background-color: #EA8A8A;
font-size: 1.1em;
font-weight: bold;
text-align: center;
cursor: move;
}
.item.ui-sortable-selected {
background-color: yellow;
}
/*** Extra ***/
body {
font-family: Verdana, 'Trebuchet ms', Tahoma;
}
.logList {
margin-top: 20px;
width: 250px;
min-height: 200px;
padding: 5px 15px;
border: 5px solid #000;
border-radius: 15px;
}
.logList:before {
content: 'log';
padding: 0 5px;
position: relative;
top: -1.1em;
background-color: #FFF;
}
.container {
width: 600px;
margin: auto;
}
h2 {
text-align: center;
}
.floatleft {
float: left;
}
.clear {
clear: both;
}
angular.module('ui.sortable.multiselection', [])
.constant('uiSortableMultiSelectionClass', 'ui-sortable-selected')
.directive('uiSortableSelectable', [
'uiSortableMultiSelectionClass',
function(selectedItemClass) {
return {
link: function(scope, element/*, attrs*/) {
element.on('click', function (e) {
var $this = angular.element(this);
var lastIndex = scope.$parent._lastSelectedIndex;
if(!e.ctrlKey && !e.metaKey && !e.shiftKey) {
$this.siblings().removeClass(selectedItemClass);
$this.addClass(selectedItemClass);
} else if(event.shiftKey && !event.ctrlKey && lastIndex > -1) {
var curIndex = $this.index();
if(curIndex > lastIndex) {
$this.parent().children().slice(lastIndex, curIndex+1).addClass(selectedItemClass);
} else if(curIndex < lastIndex) {
$this.parent().children().slice(curIndex, lastIndex).addClass(selectedItemClass);
}
} else if(event.ctrlKey && !event.shiftKey) {
if($this.hasClass(selectedItemClass)) {
$this.removeClass(selectedItemClass);
} else {
$this.addClass(selectedItemClass);
}
}
scope.$parent._lastSelectedIndex = $this.index();
});
}
};
}
])
.factory('uiSortableMulitSelectionCollapse', [
'uiSortableMultiSelectionClass',
function(selectedItemClass) {
return {
helper: function(e, item) {
if (!item.hasClass(selectedItemClass)) {
item.addClass(selectedItemClass)
.siblings()
.removeClass(selectedItemClass);
}
var selectedElements = item.parent().children('.' + selectedItemClass);
var selectedSiblings = item.siblings('.' + selectedItemClass);
// indexes of the selected siblings
var indexes = angular.element.map(selectedSiblings, function (element) {
return angular.element(element).index();
});
item.sortableMultiSelect = {
indexes: indexes
};
var helperTag = item[0].tagName;
var helper = null;
if(indexes.length > 1) {
helper = item.clone();
helper.text('You have '+indexes.length+' items selected.');
} else {
helper = item.clone();
}
return helper;
},
stop: function(e, item) {
//TODO implement this
}
};
}
])
.factory('uiSortableMultiSelectionMethods', [
'uiSortableMultiSelectionClass',
function (selectedItemClass) {
return {
helper: function (e, item) {
// when starting to sort an unhighlighted item ,
// deselect any existing highlighted items
if (!item.hasClass(selectedItemClass)) {
item.addClass(selectedItemClass)
.siblings()
.removeClass(selectedItemClass);
}
var selectedElements = item.parent().children('.' + selectedItemClass);
var selectedSiblings = item.siblings('.' + selectedItemClass);
// indexes of the selected siblings
var indexes = angular.element.map(selectedSiblings, function (element) {
return angular.element(element).index();
});
item.sortableMultiSelect = {
indexes: indexes
};
// Clone the selected items and to put them inside the helper
var elements = selectedElements.clone();
// like `helper: 'clone'` does, hide the dragged elements
selectedSiblings.hide();
// Create the helper to act as a bucket for the cloned elements
var helperTag = item[0].tagName;
var helper = angular.element('<' + helperTag + '/>');
return helper.append(elements);
},
stop: function (e, ui) {
console.log(
ui,
ui.item.attr('ng-model'),
ui.item.parent().attr('ng-model'),
ui.item.scope(),
ui.item.parent(),
ui.item.parent().scope(),
// ui.item.sortable.droptarget.scope(),
ui.item.sortable.droptarget.scope().$eval(ui.item.parent().attr('ng-model'))
);
var isSelfList = ui.item.sortable.droptarget.scope();
var ngModel = ui.item.parent().scope() ?
ui.item.parent().scope().$eval(ui.item.parent().attr('ng-model')) :
ui.item.sortable.droptarget.scope().$eval(ui.item.parent().attr('ng-model'));
var otherModel = ui.item.sortable.droptarget.scope().$eval(ui.item.sortable.droptarget.attr('ng-model'));
var oldPosition = ui.item.sortable.index;
var newPosition = ui.item.sortable.dropindex;
function fixIndex (x) {
if (oldPosition < newPosition && oldPosition < x && x <= newPosition) {
return x - 1;
} else if (newPosition < oldPosition && newPosition <= x && x < oldPosition) {
return x + 1;
}
return x;
}
function groupIndexes (indexes) {
var above = [],
below = [];
for (var i = 0; i < indexes.length; i++) {
var x = indexes[i];
if (x < oldPosition) {
above.push(fixIndex(x));
} else if (oldPosition < x) {
below.push(fixIndex(x));
}
}
return {
above: above,
below: below
};
}
function getModelsFromIndexes (indexes) {
var result = [];
for (var i = indexes.length - 1; i >= 0; i--) {
result.push(ngModel.splice(indexes[i], 1)[0]);
}
result.reverse();
return result;
}
var draggedElementIndexes = ui.item.sortableMultiSelect.indexes;
if (!draggedElementIndexes.length) {
return;
}
var indexes = groupIndexes(draggedElementIndexes);
console.log(ngModel, otherModel);
// get the model of the dragged item
// so that we can locate its position
// after we remove the co-dragged elements
var draggedModel = ngModel[newPosition];
// the code should run in reverse order,
// so that the indexes will not break
var models = {
below: getModelsFromIndexes(indexes.below),
above: getModelsFromIndexes(indexes.above)
};
Array.prototype.splice.apply(
otherModel,
[otherModel.indexOf(draggedModel) + 1, 0]
.concat(models.below));
Array.prototype.splice.apply(
otherModel,
[otherModel.indexOf(draggedModel), 0]
.concat(models.above));
ui.item.parent().find('> .' + selectedItemClass).removeClass('' + selectedItemClass).show();
}
};
}]);