<!DOCTYPE html>
<html data-ng-app="dndApp1" ng-strict-di>
<head>
<title>test</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.1/css/bootstrap.css" />
<link rel="stylesheet" href="https://rawgithub.com/a5hik/ng-sortable/master/dist/ng-sortable.css" />
<link rel="stylesheet" href="app.css" />
</head>
<body>
<div class="container" data-ng-controller="MainController">
<br/>
<!--
<div class="row" style="border: 1px solid #000; width:510px;height:510px;">
<div ng-repeat="row in range(dropGrid.rows) track by $index"class="
style="width:500px;height:100px;">
<div ng-repeat="col in range(dropGrid.columns) track by $index"class=""
data-as-sortable="dndListeners" data-ng-model="items2"
style="background-color: beige;border: 1px solid grey; width:100px;height:100px;display:inline-block">
</div>
</div>
</div>
-->
<div class="row" style="border: 1px solid #000; width:510px;height:510px;">
<div ng-repeat="row in range(maxRows) track by $index" class=""
style="width:500px;height:100px;">
<div ng-repeat="col in range(maxCols) track by $index" class=""
data-as-sortable="dndListeners" data-ng-model="gridBlock"
style="background-color: beige;border: 1px solid grey; width:100px;height:100px;display:inline-block">
</div>
</div>
</div>
<br /><br /><br />
<div class="" data-as-sortable="dndListeners" data-ng-model="gridBlock"
style="border: 1px solid #000; width:70%;margin:0px 15%">
<div data-ng-repeat="item in gridBlock"
data-ng-style="item.style"
data-ng-class="{'lastMovedItem' : item === lastMovedItem}" data-as-sortable-item >
<i class="glyphicon glyphicon-move text-info "
data-as-sortable-item-handle
style="color:{{item.style.color}}"> </i>
<span>{{item.name}}</span> <span>{{item.description}}</span>
<span class="badge" >{{item.id}}</span>
</div>
</div>
<!--
<div class="row">
<div class="" data-as-sortable="dndListeners" data-ng-model="items2"
style="border: 1px solid #000; width:70%;margin:0px 15%">
<div data-ng-repeat="item in items2"
data-ng-style="item.style"
data-ng-class="{'lastMovedItem' : item === lastMovedItem}" data-as-sortable-item >
<i class="glyphicon glyphicon-move text-info "
data-as-sortable-item-handle
style="color:{{item.style.color}}"> </i>
<span>{{item.name}}</span> <span>{{item.description}}</span>
<span class="badge" >{{item.id}}</span>
</div>
</div>
</div>
-->
<div class="row">
<div class="col-sm-1 col-xs-1" ng-repeat="column in columns">
<div class="" data-as-sortable="dndListeners" data-ng-model="column.cards"
style="border: 1px solid #000; width:70%;margin:0px 15%">
<div data-ng-repeat="item in column.cards"
data-ng-style="item.style"
data-ng-class="{'lastMovedItem' : item === lastMovedItem}" data-as-sortable-item >
<i class="glyphicon glyphicon-move text-info "
data-as-sortable-item-handle
style="color:{{item.style.color}}"> </i>
<span>{{item.name}}</span> <span>{{item.description}}</span>
<span class="badge" >{{item.id}}</span>
</div>
</div>
</div>
</div>
<div class="" style="">
<div class="page-header">
<h3>Card blocks of different sizes.</h3>
</div>
<div id="columns" class="row">
<div class="col-sm-6 col-xs-6" ng-repeat="column in columns">
<div class="column">
<ul class="cards card-list ng-pristine ng-untouched ng-valid"
data-as-sortable="dndListeners" data-ng-model="column.cards">
<!-- ngRepeat: card in column.cards -->
<li class="card as-sortable-item" ng-repeat="card in column.cards" data-as-sortable-item>
<div as-sortable-item-handle class="as-sortable-item-handle">
<div class="row">
<div class="col-sm-12">
<span class="card-title">{{card.title}}</span>
<span class="card-title">{{card.description}}</span>
</div>
</div>
</div>
</li><!-- end ngRepeat: card in column.cards -->
</ul>
</div>
</div><!-- end ngRepeat: column in columns -->
</div>
</div>
</div>
<script src="//cdnjs.cloudflare.com/ajax/libs/angular.js/1.3.8/angular.js"></script>
<script src="ng-sortable.js"></script>
<script src="app.js"></script>
</body>
</html>
li.as-sortable-placeholder {
list-style: none;
}
.well .list-group {
min-height: 2em;
}
.m-b-zero {
margin-bottom: 0;
}
tbody.as-sortable-drag {
display: table;
border:1px solid #eee;
box-shadow: 0 0 5px 0px #ccc;
background-color: #fff;
}
tbody > .as-sortable-placeholder {
display: table-row;
}
.as-sortable-placeholder {
display: table-row;
}
tr.lastMovedItem td,
.lastMovedItem {
-webkit-animation: backgroundTransform 0.4s;
animation: backgroundTransform 0.4s;
}
/*
@-webkit-keyframes backgroundTransform{
from{
}
to{
background-color: #def;
}
}*/
@-webkit-keyframes backgroundTransform{
50%{
background-color: #def;
}
}
@keyframes backgroundTransform {
50%{
background-color: #def;
}
}
tr.as-sortable-item {
display:table-row;
}
#board { margin: 0; padding: 0 10px 0 10px;}
.board-header >ul>li> a {
border-radius: 4px !important;
background-color: lightgrey;
border-color: darkgrey;
}
.board-header >ul>li> .active{
background-color: darkgrey;
border-color: #000;
}
.board-header >ul>li> a:hover {
border-color: #000;
}
#board table {
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25);
}
#board {
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
#board .table td {
padding: 5px 5px 0 5px;
}
#board .table th {
padding: 10px 0 10px 5px;
}
.columnHeader,
#board .table thead th {
text-align: center;
background: #fafafa linear-gradient(to bottom, #ffffff, #f2f2f2) repeat-x;
padding: 5px 0;
font-size: 20px;
font-weight: normal;
text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.3);
border-bottom: 1px solid #dddddd;
}
#board .table thead th:first-of-type {
padding: 5px 10px 5px 20px;
white-space: nowrap;
}
#board .table thead {
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25);
}
#board .table > tbody > tr > th,
#board .table > tbody > tr > td {
border-top: none;
}
#board .table thead i,
#board .table tbody i {
font-size: 14px;
color: green;
}
#board tbody th,
#board tbody td {
background-color: #ffffff;
text-align: center;
}
#board .table td.addTask,
#board .table td.addStory {
margin: 0;
padding: 0;
}
#board .table td.addTask button,
#board .table th.addStory button {
width: 100%;
height: 100%;
margin: 0;
padding: 0 3px;
border: none;
background-color: #f2f2f2;
}
#board .table td.addTask button:hover,
#board .table th.addStory button:hover {
background-color: #fafafa;
}
#board .table .story,
#board .table .task {
-webkit-transform: rotate(-2deg);
-o-transform: rotate(-2deg);
-moz-transform:rotate(-2deg);
}
#board .table .story:hover,
#board .table .story:focus,
#board .table .task:hover,
#board .table .task:focus {
box-shadow: 3px 6px 20px rgba(0, 0, 0, 0.4);
-webkit-transform: scale(1.12);
-moz-transform: scale(1.12);
-o-transform: scale(1.12);
position: relative;
z-index: 1035;
}
div.story,
div.task {
position: relative;
padding: 5px 0;
margin: 4px 4px 4px 0;
text-align: center;
box-shadow: 2px 2px 3px rgba(0,0,0,0.1);
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
cursor: pointer;
}
div.story {
margin: 0 5px 0 0;
}
div.story h3,
div.task h3 {
font-size: 13px;
line-height: 13px;
padding: 0;
margin: 5px;
text-align: center;
text-shadow: none;
font-weight: normal;
transform-style: preserve-3d;
-webkit-backface-vivbility: hidden;
-webkit-transform: translate3d(0,0,0);
-webkit-transform-style: preserve-3d;
}
#columns { margin: 20px 0 5px 0;}
#columns > div { padding-left: 5px; padding-right:5px;}
.column {
border: solid 1px darkgrey;
box-shadow: rgba(0, 0, 0, 0.7) 0 1px 10px 0;
}
.card-list {
margin: 0;
padding: 0;
list-style: none;
min-height: 50px;
}
.noMargin { margin-bottom: 0;}
.default {
box-shadow: inset rgba(0, 0, 0, 0.0980392) 0 1px 10px 0;
}
.default-color { background-color: #ffffff; }
.card {
padding: 10px;
}
.card-title {
color: #7c9eb2;
text-decoration: none;
font-weight: bold;
line-height: 1.5em;
padding: 2px 1px;
display: block;
}
.addNewCard {
float: right;
padding-right: 30px;
color: green;
}
.removeCard {
padding: 12px;
color: #444;
}
(function () {
"use strict";
var app = angular.module("dndApp1", ['ui.sortable']);
app.controller("MainController", ['$scope', function ($scope) {
var pageSize = 8;
var itemCount = 54;
$scope.ctrl = this;
$scope.dropGrid = {
"rows": 5,
"columns": 5
};
$scope.range = function(n) {
return new Array(n);
};
$scope.items2 = [
{
id: 1,
name: "Aaaa",
description: "AAaa a aaaa a",
style:{'width':'100px','height':'100px','background-color':'green','color':'#fff'}
},
{
id: 2,
name: "bbb",
description: "bb b bbbb",
style:{'width':'100px','height':'100px','background-color':'blue','color':'#fff'}
},
{
id: 3,
name: "ccc",
description: "ccc c cccccc c",
style:{'width':'100px','height':'100px','background-color':'red','color':'#fff'}
},
{
id: 4,
name: "ddd",
description: "dd dd dddddd",
style:{'width':'100px','height':'100px','background-color':'yellow'}
},
{
id: 5,
name: "eee",
description: "eee eeeee ee",
style:{'width':'100px','height':'100px','background-color':'purple','color':'#fff'}
}
];
$scope.maxRows = 5, $scope.maxCols = 5;
var itemCount = 0;
$scope.gridBlock = [];
for (var i = 0, length = $scope.maxRows; i < length; i++) {
var row = $scope.maxRows[i]
for (var j = 0, colLength = $scope.maxCols; j < colLength; j++) {
var col = $scope.maxCols[j]
if(itemCount < $scope.items2.length){
$scope.gridBlock.push($scope.items2[itemCount]);
itemCount++;
}
else{
$scope.gridBlock.push({});
}
}
}
console.log('$scope.gridBlock: ', $scope.gridBlock);
angular.forEach($scope.item2, function(item){
// $scope.gridBlock.push(items);
});
$scope.columns = [
{
cards:[
{'name': 'Come up with a POC for new Project',
'description': 'Testing Card Details Testing Card Details Testing Card Details Testing Card Details',
'style':{'width':'300px','height':'100px','background-color':'green','color':'#fff'}
},
{'name': 'Design new framework for reporting module',
'description': 'Testing Card Details',
'style':{'width':'100px','height':'400px','background-color':'blue','color':'#fff'}
},
{'name': 'CI for user module',
'description': 'Testing Card Details Testing Card Details Testing Card Details',
'style':{'width':'200px','height':'100px','background-color':'red','color':'#fff'}
},
{'name': 'Test user module',
'description': 'Test user module',
'style':{'width':'100px','height':'100px','background-color':'brown','color':'#fff'}
}
]
},
{
cards:[
{'name': 'Explore new IDE for Development',
'description': 'Testing Card Details Testing Card Details Testing Card Details',
'style':{'width':'100px','height':'300px','background-color':'green','color':'#fff'}
},
{'name': 'Get new resource for new Project',
'description': 'Testing Card Details',
'style':{'width':'200px','height':'100px','background-color':'purple','color':'#fff'}
},
{'name': 'End to End Testing for user group module',
'description': 'Testing Card Details Testing Card Details Testing Card Details Testing Card Details Testing Card DetailsTesting Card Details Testing Card Details Testing Card Details Testing Card Details Testing Card DetailsTesting Card Details Testing Card Details Testing Card Details Testing Card Details Testing Card Details',
'style':{'width':'300px','height':'200px','background-color':'blue','color':'#fff'}
},
{'name': 'Develop ui for tracker module',
'description': 'Testing Card Details Testing Card Details Testing Card Details Testing Card Details Testing CardBig Card Big Card Big Card Big Card Big Card Big Card Big Card Big Card Big Card Big Card Big Card Big CardBig Card Big Card Big Card Big Card Big Card Big Card Big Card Big Card Big Card Big Card Big Card Big CardBig Card Big Card Big Card Big Card Big Card Big Card Big Card Big Card Big Card Big Card Big Card Big CardBig Card Big Card Big Card Big Card Big Card Big Card Big Card Big Card Big Card Big Card Big Card Big Card',
'style':{'width':'100px','height':'200px','background-color':'red','color':'#fff'}
},
{'name': 'Develop backend for plan module',
'description': 'Testing Card Details',
'style':{'width':'100px','height':'100px','background-color':'purple','color':'#fff'}
}
]
}
]
$scope.dndListeners = {
dragStart: function (evt){
console.log('dragStart:', evt);
delete $scope.lastMovedItem;
},
dragEnd: function (evt) {
console.log('dragEnd:', evt);
$scope.lastMovedItem = evt.source.itemScope.item;
},
itemMoved: function (evt) {
console.log('itemMoved:', evt);
var elem = evt.source.itemScope.element[0];
elem.style.opacity = 0;
window.getComputedStyle(elem).opacity;
elem.style.opacity = 1;
},
orderChanged: function (evt) {
console.log('orderChanged:', evt);
}
};
}]);
}());
/*
The MIT License (MIT)
Copyright (c) 2014 Muhammed Ashik
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
/*jshint indent: 2 */
/*global angular: false */
(function () {
'use strict';
angular.module('ui.sortable', [])
.constant('sortableConfig', {
itemClass: 'as-sortable-item',
handleClass: 'as-sortable-item-handle',
placeHolderClass: 'as-sortable-placeholder',
dragClass: 'as-sortable-drag',
hiddenClass: 'as-sortable-hidden'
});
}());
/*jshint indent: 2 */
/*global angular: false */
(function () {
'use strict';
var mainModule = angular.module('ui.sortable');
/**
* Helper factory for sortable.
*/
mainModule.factory('$helper', ['$document', '$window',
function ($document, $window) {
return {
/**
* Get the height of an element.
*
* @param {Object} element Angular element.
* @returns {String} Height
*/
height: function (element) {
return element.prop('offsetHeight');
},
/**
* Get the width of an element.
*
* @param {Object} element Angular element.
* @returns {String} Width
*/
width: function (element) {
return element.prop('offsetWidth');
},
/**
* Get the offset values of an element.
*
* @param {Object} element Angular element.
* @param {Object} [scrollableContainer] Scrollable container object for calculating relative top & left (optional, defaults to Document)
* @returns {Object} Object with properties width, height, top and left
*/
offset: function (element, scrollableContainer) {
var boundingClientRect = element[0].getBoundingClientRect();
if (!scrollableContainer) {
scrollableContainer = $document[0].documentElement;
}
return {
width: boundingClientRect.width || element.prop('offsetWidth'),
height: boundingClientRect.height || element.prop('offsetHeight'),
top: boundingClientRect.top + ($window.pageYOffset || scrollableContainer.scrollTop - scrollableContainer.offsetTop),
left: boundingClientRect.left + ($window.pageXOffset || scrollableContainer.scrollLeft - scrollableContainer.offsetLeft)
};
},
/**
* get the event object for touch.
*
* @param {Object} event the touch event
* @return {Object} the touch event object.
*/
eventObj: function (event) {
var obj = event;
if (event.targetTouches !== undefined) {
obj = event.targetTouches.item(0);
} else if (event.originalEvent !== undefined && event.originalEvent.targetTouches !== undefined) {
obj = event.originalEvent.targetTouches.item(0);
}
return obj;
},
/**
* Checks whether the touch is valid and multiple.
*
* @param event the event object.
* @returns {boolean} true if touch is multiple.
*/
isTouchInvalid: function (event) {
var touchInvalid = false;
if (event.touches !== undefined && event.touches.length > 1) {
touchInvalid = true;
} else if (event.originalEvent !== undefined &&
event.originalEvent.touches !== undefined && event.originalEvent.touches.length > 1) {
touchInvalid = true;
}
return touchInvalid;
},
/**
* Get the start position of the target element according to the provided event properties.
*
* @param {Object} event Event
* @param {Object} target Target element
* @param {Object} [scrollableContainer] (optional) Scrollable container object
* @returns {Object} Object with properties offsetX, offsetY.
*/
positionStarted: function (event, target, scrollableContainer) {
var pos = {};
pos.offsetX = event.pageX - this.offset(target, scrollableContainer).left;
pos.offsetY = event.pageY - this.offset(target, scrollableContainer).top;
pos.startX = pos.lastX = event.pageX;
pos.startY = pos.lastY = event.pageY;
pos.nowX = pos.nowY = pos.distX = pos.distY = pos.dirAx = 0;
pos.dirX = pos.dirY = pos.lastDirX = pos.lastDirY = pos.distAxX = pos.distAxY = 0;
return pos;
},
/**
* Calculates the event position and sets the direction
* properties.
*
* @param pos the current position of the element.
* @param event the move event.
*/
calculatePosition: function (pos, event) {
// mouse position last events
pos.lastX = pos.nowX;
pos.lastY = pos.nowY;
// mouse position this events
pos.nowX = event.pageX;
pos.nowY = event.pageY;
// distance mouse moved between events
pos.distX = pos.nowX - pos.lastX;
pos.distY = pos.nowY - pos.lastY;
// direction mouse was moving
pos.lastDirX = pos.dirX;
pos.lastDirY = pos.dirY;
// direction mouse is now moving (on both axis)
pos.dirX = pos.distX === 0 ? 0 : pos.distX > 0 ? 1 : -1;
pos.dirY = pos.distY === 0 ? 0 : pos.distY > 0 ? 1 : -1;
// axis mouse is now moving on
var newAx = Math.abs(pos.distX) > Math.abs(pos.distY) ? 1 : 0;
// calc distance moved on this axis (and direction)
if (pos.dirAx !== newAx) {
pos.distAxX = 0;
pos.distAxY = 0;
} else {
pos.distAxX += Math.abs(pos.distX);
if (pos.dirX !== 0 && pos.dirX !== pos.lastDirX) {
pos.distAxX = 0;
}
pos.distAxY += Math.abs(pos.distY);
if (pos.dirY !== 0 && pos.dirY !== pos.lastDirY) {
pos.distAxY = 0;
}
}
pos.dirAx = newAx;
},
/**
* Move the position by applying style.
*
* @param event the event object
* @param element - the dom element
* @param pos - current position
* @param container - the bounding container.
* @param containerPositioning - absolute or relative positioning.
* @param {Object} [scrollableContainer] (optional) Scrollable container object
*/
movePosition: function (event, element, pos, container, containerPositioning, scrollableContainer) {
var bounds;
var useRelative = (containerPositioning === 'relative');
element.x = event.pageX - pos.offsetX;
element.y = event.pageY - pos.offsetY;
if (container) {
bounds = this.offset(container, scrollableContainer);
if (useRelative) {
// reduce positioning by bounds
element.x -= bounds.left;
element.y -= bounds.top;
// reset bounds
bounds.left = 0;
bounds.top = 0;
}
if (element.x < bounds.left) {
element.x = bounds.left;
} else if (element.x >= bounds.width + bounds.left - this.offset(element).width) {
element.x = bounds.width + bounds.left - this.offset(element).width;
}
if (element.y < bounds.top) {
element.y = bounds.top;
} else if (element.y >= bounds.height + bounds.top - this.offset(element).height) {
element.y = bounds.height + bounds.top - this.offset(element).height;
}
}
element.css({
'left': element.x + 'px',
'top': element.y + 'px'
});
this.calculatePosition(pos, event);
},
/**
* The drag item info and functions.
* retains the item info before and after move.
* holds source item and target scope.
*
* @param item - the drag item
* @returns {{index: *, parent: *, source: *,
* sourceInfo: {index: *, itemScope: (*|.dragItem.sourceInfo.itemScope|$scope.itemScope|itemScope), sortableScope: *},
* moveTo: moveTo, isSameParent: isSameParent, isOrderChanged: isOrderChanged, eventArgs: eventArgs, apply: apply}}
*/
dragItem: function (item) {
return {
index: item.index(),
parent: item.sortableScope,
source: item,
sourceInfo: {
index: item.index(),
itemScope: item.itemScope,
sortableScope: item.sortableScope
},
moveTo: function (parent, index) { // Move the item to a new position
this.parent = parent;
//If source Item is in the same Parent.
if (this.isSameParent() && this.source.index() < index) { // and target after
index = index - 1;
}
this.index = index;
},
isSameParent: function () {
return this.parent.element === this.sourceInfo.sortableScope.element;
},
isOrderChanged: function () {
return this.index !== this.sourceInfo.index;
},
eventArgs: function () {
return {
source: this.sourceInfo,
dest: {
index: this.index,
sortableScope: this.parent
}
};
},
apply: function () {
this.sourceInfo.sortableScope.removeItem(this.sourceInfo.index); // Remove from source.
this.parent.insertItem(this.index, this.source.modelValue); // Insert in to destination.
}
};
},
/**
* Check the drag is not allowed for the element.
*
* @param element - the element to check
* @returns {boolean} - true if drag is not allowed.
*/
noDrag: function (element) {
return element.attr('no-drag') !== undefined || element.attr('data-no-drag') !== undefined;
}
};
}
]);
}());
/*jshint undef: false, unused: false, indent: 2*/
/*global angular: false */
(function () {
'use strict';
var mainModule = angular.module('ui.sortable');
/**
* Controller for Sortable.
* @param $scope - the sortable scope.
*/
mainModule.controller('ui.sortable.sortableController', ['$scope', function ($scope) {
this.scope = $scope;
$scope.modelValue = null; // sortable list.
$scope.callbacks = null;
$scope.type = 'sortable';
$scope.options = {};
$scope.isDisabled = false;
/**
* Inserts the item in to the sortable list.
*
* @param index - the item index.
* @param itemData - the item model data.
*/
$scope.insertItem = function (index, itemData) {
$scope.safeApply(function () {
$scope.modelValue.splice(index, 0, itemData);
});
};
/**
* Removes the item from the sortable list.
*
* @param index - index to be removed.
* @returns {*} - removed item.
*/
$scope.removeItem = function (index) {
var removedItem = null;
if (index > -1) {
$scope.safeApply(function () {
removedItem = $scope.modelValue.splice(index, 1)[0];
});
}
return removedItem;
};
/**
* Checks whether the sortable list is empty.
*
* @returns {null|*|$scope.modelValue|boolean}
*/
$scope.isEmpty = function () {
return ($scope.modelValue && $scope.modelValue.length === 0);
};
/**
* Wrapper for the accept callback delegates to callback.
*
* @param sourceItemHandleScope - drag item handle scope.
* @param destScope - sortable target scope.
* @param destItemScope - sortable destination item scope.
* @returns {*|boolean} - true if drop is allowed for the drag item in drop target.
*/
$scope.accept = function (sourceItemHandleScope, destScope, destItemScope) {
return $scope.callbacks.accept(sourceItemHandleScope, destScope, destItemScope);
};
/**
* Checks the current phase before executing the function.
*
* @param fn the function to execute.
*/
$scope.safeApply = function (fn) {
var phase = this.$root.$$phase;
if (phase === '$apply' || phase === '$digest') {
if (fn && (typeof fn === 'function')) {
fn();
}
} else {
this.$apply(fn);
}
};
}]);
/**
* Sortable directive - defines callbacks.
* Parent directive for draggable and sortable items.
* Sets modelValue, callbacks, element in scope.
*/
mainModule.directive('asSortable',
function () {
return {
require: 'ngModel', // get a hold of NgModelController
restrict: 'A',
scope: true,
controller: 'ui.sortable.sortableController',
link: function (scope, element, attrs, ngModelController) {
var ngModel, callbacks;
ngModel = ngModelController;
if (!ngModel) {
return; // do nothing if no ng-model
}
// Set the model value in to scope.
ngModel.$render = function () {
//set an empty array, in case if none is provided.
if (!ngModel.$modelValue || !angular.isArray(ngModel.$modelValue)) {
ngModel.$setViewValue([]);
}
scope.modelValue = ngModel.$modelValue;
};
//set the element in scope to be accessed by its sub scope.
scope.element = element;
callbacks = {accept: null, orderChanged: null, itemMoved: null, dragStart: null, dragCancel: null, dragEnd: null};
/**
* Invoked to decide whether to allow drop.
*
* @param sourceItemHandleScope - the drag item handle scope.
* @param destSortableScope - the drop target sortable scope.
* @param destItemScope - the drop target item scope.
* @returns {boolean} - true if allowed for drop.
*/
callbacks.accept = function (sourceItemHandleScope, destSortableScope, destItemScope) {
return true;
};
/**
* Invoked when order of a drag item is changed.
*
* @param event - the event object.
*/
callbacks.orderChanged = function (event) {
};
/**
* Invoked when the item is moved to other sortable.
*
* @param event - the event object.
*/
callbacks.itemMoved = function (event) {
};
/**
* Invoked when the drag started successfully.
*
* @param event - the event object.
*/
callbacks.dragStart = function (event) {
};
/**
* Invoked when the drag cancelled.
*
* @param event - the event object.
*/
callbacks.dragCancel = function (event) {
};
/**
* Invoked when the drag stopped.
*
* @param event - the event object.
*/
callbacks.dragEnd = function (event) {
};
//Set the sortOptions callbacks else set it to default.
scope.$watch(attrs.asSortable, function (newVal, oldVal) {
angular.forEach(newVal, function (value, key) {
if (callbacks[key]) {
if (typeof value === 'function') {
callbacks[key] = value;
}
} else {
scope.options[key] = value;
}
});
scope.callbacks = callbacks;
}, true);
// Set isDisabled if attr is set, if undefined isDisabled = false
if (angular.isDefined(attrs.isDisabled)) {
scope.$watch(attrs.isDisabled, function (newVal, oldVal) {
if (!angular.isUndefined(newVal)) {
scope.isDisabled = newVal;
}
}, true);
}
}
};
});
}());
/*jshint indent: 2 */
/*global angular: false */
(function () {
'use strict';
var mainModule = angular.module('ui.sortable');
/**
* Controller for sortableItemHandle
*
* @param $scope - item handle scope.
*/
mainModule.controller('ui.sortable.sortableItemHandleController', ['$scope', function ($scope) {
this.scope = $scope;
$scope.itemScope = null;
$scope.type = 'handle';
}]);
/**
* Directive for sortable item handle.
*/
mainModule.directive('asSortableItemHandle', ['sortableConfig', '$helper', '$window', '$document',
function (sortableConfig, $helper, $window, $document) {
return {
require: '^asSortableItem',
scope: true,
restrict: 'A',
controller: 'ui.sortable.sortableItemHandleController',
link: function (scope, element, attrs, itemController) {
var dragElement, //drag item element.
placeHolder, //place holder class element.
placeElement, //hidden place element.
itemPosition, //drag item element position.
dragItemInfo, //drag item data.
containment, //the drag container.
containerPositioning, // absolute or relative positioning.
dragListen, // drag listen event.
scrollableContainer, //the scrollable container
dragStart, // drag start event.
dragMove, //drag move event.
dragEnd, //drag end event.
dragCancel, //drag cancel event.
isDraggable, //is element draggable.
isDragBefore, //is element moved up direction.
isPlaceHolderPresent, //is placeholder present.
bindDrag, //bind drag events.
unbindDrag, //unbind drag events.
bindEvents, //bind the drag events.
unBindEvents, //unbind the drag events.
hasTouch, // has touch support.
dragHandled, //drag handled.
isDisabled = false; // drag enabled
hasTouch = $window.hasOwnProperty('ontouchstart');
if (sortableConfig.handleClass) {
element.addClass(sortableConfig.handleClass);
}
scope.itemScope = itemController.scope;
scope.$watch('sortableScope.isDisabled', function (newVal) {
if (isDisabled !== newVal) {
isDisabled = newVal;
if (isDisabled) {
unbindDrag();
} else {
bindDrag();
}
}
});
/**
* Listens for a 10px movement before
* dragStart is called to allow for
* a click event on the element.
*
* @param event - the event object.
*/
dragListen = function (event) {
var unbindMoveListen = function () {
angular.element($document).unbind('mousemove', moveListen);
angular.element($document).unbind('touchmove', moveListen);
element.unbind('mouseup', unbindMoveListen);
element.unbind('touchend', unbindMoveListen);
element.unbind('touchcancel', unbindMoveListen);
};
var startPosition;
var moveListen = function (e) {
e.preventDefault();
var eventObj = $helper.eventObj(e);
if (!startPosition) {
startPosition = {clientX: eventObj.clientX, clientY: eventObj.clientY};
}
if (Math.abs(eventObj.clientX - startPosition.clientX) + Math.abs(eventObj.clientY - startPosition.clientY) > 10) {
unbindMoveListen();
dragStart(event);
}
};
angular.element($document).bind('mousemove', moveListen);
angular.element($document).bind('touchmove', moveListen);
element.bind('mouseup', unbindMoveListen);
element.bind('touchend', unbindMoveListen);
element.bind('touchcancel', unbindMoveListen);
};
/**
* Triggered when drag event starts.
*
* @param event the event object.
*/
dragStart = function (event) {
var eventObj, tagName;
if (!hasTouch && (event.button === 2 || event.which === 3)) {
// disable right click
return;
}
if (hasTouch && $helper.isTouchInvalid(event)) {
return;
}
if (dragHandled || !isDraggable(event)) {
// event has already fired in other scope.
return;
}
// Set the flag to prevent other items from inheriting the drag event
dragHandled = true;
event.preventDefault();
eventObj = $helper.eventObj(event);
// (optional) Scrollable container as reference for top & left offset calculations, defaults to Document
scrollableContainer = angular.element($document[0].querySelector(scope.sortableScope.options.scrollableContainer)).length > 0 ?
$document[0].querySelector(scope.sortableScope.options.scrollableContainer) : $document[0].documentElement;
containment = angular.element($document[0].querySelector(scope.sortableScope.options.containment)).length > 0 ?
angular.element($document[0].querySelector(scope.sortableScope.options.containment)) : angular.element($document[0].body);
//capture mouse move on containment.
containment.css('cursor', 'move');
// container positioning
containerPositioning = scope.sortableScope.options.containerPositioning || 'absolute';
dragItemInfo = $helper.dragItem(scope);
tagName = scope.itemScope.element.prop('tagName');
dragElement = angular.element($document[0].createElement(scope.sortableScope.element.prop('tagName')))
.addClass(scope.sortableScope.element.attr('class')).addClass(sortableConfig.dragClass);
dragElement.css('width', $helper.width(scope.itemScope.element) + 'px');
dragElement.css('height', $helper.height(scope.itemScope.element) + 'px');
placeHolder = angular.element($document[0].createElement(tagName))
.addClass(sortableConfig.placeHolderClass).addClass(scope.sortableScope.options.additionalPlaceholderClass);
placeHolder.css('width', $helper.width(scope.itemScope.element) + 'px');
placeHolder.css('height', $helper.height(scope.itemScope.element) + 'px');
placeElement = angular.element($document[0].createElement(tagName));
if (sortableConfig.hiddenClass) {
placeElement.addClass(sortableConfig.hiddenClass);
}
itemPosition = $helper.positionStarted(eventObj, scope.itemScope.element, scrollableContainer);
//fill the immediate vacuum.
scope.itemScope.element.after(placeHolder);
//hidden place element in original position.
scope.itemScope.element.after(placeElement);
dragElement.append(scope.itemScope.element);
containment.append(dragElement);
$helper.movePosition(eventObj, dragElement, itemPosition, containment, containerPositioning, scrollableContainer);
scope.sortableScope.$apply(function () {
scope.callbacks.dragStart(dragItemInfo.eventArgs());
});
bindEvents();
};
/**
* Allow Drag if it is a proper item-handle element.
*
* @param event - the event object.
* @return boolean - true if element is draggable.
*/
isDraggable = function (event) {
var elementClicked, elementClickedData, sourceScope, isDraggable;
elementClicked = angular.element(event.target);
elementClickedData = elementClicked.data();
if(elementClickedData.$asSortableItemHandleController) {
sourceScope = elementClickedData.$asSortableItemHandleController.scope;
}
// look for the handle on the current scope or parent scopes
isDraggable = false;
while (!isDraggable && sourceScope !== undefined) {
if (sourceScope.type && sourceScope.type === 'handle') {
isDraggable = true;
} else {
sourceScope = sourceScope.$parent;
}
}
//If a 'no-drag' element inside item-handle if any.
while (isDraggable && elementClicked[0] !== element[0]) {
if ($helper.noDrag(elementClicked)) {
isDraggable = false;
}
elementClicked = elementClicked.parent();
}
return isDraggable;
};
/**
* Inserts the placeHolder in to the targetScope.
*
* @param targetElement the target element
* @param targetScope the target scope
*/
function insertBefore(targetElement, targetScope) {
targetElement[0].parentNode.insertBefore(placeHolder[0], targetElement[0]);
dragItemInfo.moveTo(targetScope.sortableScope, targetScope.index());
}
/**
* Inserts the placeHolder next to the targetScope.
*
* @param targetElement the target element
* @param targetScope the target scope
*/
function insertAfter(targetElement, targetScope) {
targetElement.after(placeHolder);
dragItemInfo.moveTo(targetScope.sortableScope, targetScope.index() + 1);
}
/**
* Triggered when drag is moving.
*
* @param event - the event object.
*/
dragMove = function (event) {
var eventObj, targetX, targetY, targetScope, targetElement;
if (hasTouch && $helper.isTouchInvalid(event)) {
return;
}
// Ignore event if not handled
if (!dragHandled) {
return;
}
if (dragElement) {
event.preventDefault();
eventObj = $helper.eventObj(event);
$helper.movePosition(eventObj, dragElement, itemPosition, containment, containerPositioning, scrollableContainer);
targetX = eventObj.pageX - $document[0].documentElement.scrollLeft;
targetY = eventObj.pageY - ($window.pageYOffset || $document[0].documentElement.scrollTop);
//IE fixes: hide show element, call element from point twice to return pick correct element.
dragElement.addClass(sortableConfig.hiddenClass);
$document[0].elementFromPoint(targetX, targetY);
targetElement = angular.element($document[0].elementFromPoint(targetX, targetY));
dragElement.removeClass(sortableConfig.hiddenClass);
var asSortableItemController, targetElementData, parentTargetElementData;
targetElementData = targetElement.data();
parentTargetElementData = targetElement.parent().data();
asSortableItemController = targetElementData.$asSortableItemController || targetElementData.$asSortableController;
if(!asSortableItemController) {
asSortableItemController = parentTargetElementData.$asSortableItemController || parentTargetElementData.$asSortableController;
}
targetScope = asSortableItemController ? asSortableItemController.scope : null;
if (!targetScope || !targetScope.type) {
return;
}
if (targetScope.type === 'handle') {
targetScope = targetScope.itemScope;
}
if (targetScope.type !== 'item' && targetScope.type !== 'sortable') {
return;
}
if (targetScope.type === 'item') {
targetElement = targetScope.element;
if (targetScope.sortableScope.accept(scope, targetScope.sortableScope, targetScope)) {
if (itemPosition.dirAx && //move horizontal
scope.itemScope.sortableScope.$id === targetScope.sortableScope.$id) { //move same column
itemPosition.distAxX = 0;
if (itemPosition.distX < 0) {//move left
insertBefore(targetElement, targetScope);
} else if (itemPosition.distX > 0) {//move right
insertAfter(targetElement, targetScope);
}
} else { //move vertical
if (isDragBefore(eventObj, targetElement)) {//move up
insertBefore(targetElement, targetScope);
} else {//move bottom
insertAfter(targetElement, targetScope);
}
}
}
}
if (targetScope.type === 'sortable') {//sortable scope.
if (targetScope.accept(scope, targetScope) &&
targetElement[0].parentNode !== targetScope.element[0]) {
//moving over sortable bucket. not over item.
if (!isPlaceHolderPresent(targetElement)) {
//append to bottom.
targetElement[0].appendChild(placeHolder[0]);
dragItemInfo.moveTo(targetScope, targetScope.modelValue.length);
}
}
}
}
};
/**
* Check there is no place holder placed by itemScope.
* @param targetElement the target element to check with.
* @returns {*} true if place holder present.
*/
isPlaceHolderPresent = function (targetElement) {
var itemElements, hasPlaceHolder, i;
itemElements = targetElement.children();
for (i = 0; i < itemElements.length; i += 1) {
if (angular.element(itemElements[i]).hasClass(sortableConfig.placeHolderClass)) {
hasPlaceHolder = true;
break;
}
}
return hasPlaceHolder;
};
/**
* Determines whether the item is dragged upwards.
*
* @param eventObj - the event object.
* @param targetElement - the target element.
* @returns {boolean} - true if moving upwards.
*/
isDragBefore = function (eventObj, targetElement) {
var dragBefore, targetOffset;
dragBefore = false;
// check it's new position
targetOffset = $helper.offset(targetElement);
if ($helper.offset(placeHolder).top > targetOffset.top) { // the move direction is up?
dragBefore = $helper.offset(dragElement).top < targetOffset.top + $helper.height(targetElement) / 2;
} else {
dragBefore = eventObj.pageY < targetOffset.top;
}
return dragBefore;
};
/**
* Rollback the drag data changes.
*/
function rollbackDragChanges() {
placeElement.replaceWith(scope.itemScope.element);
placeHolder.remove();
dragElement.remove();
dragElement = null;
dragHandled = false;
containment.css('cursor', '');
}
/**
* triggered while drag ends.
*
* @param event - the event object.
*/
dragEnd = function (event) {
// Ignore event if not handled
if (!dragHandled) {
return;
}
event.preventDefault();
if (dragElement) {
//rollback all the changes.
rollbackDragChanges();
// update model data
dragItemInfo.apply();
scope.sortableScope.$apply(function () {
if (dragItemInfo.isSameParent()) {
if (dragItemInfo.isOrderChanged()) {
scope.callbacks.orderChanged(dragItemInfo.eventArgs());
}
} else {
scope.callbacks.itemMoved(dragItemInfo.eventArgs());
}
});
scope.sortableScope.$apply(function () {
scope.callbacks.dragEnd(dragItemInfo.eventArgs());
});
dragItemInfo = null;
}
unBindEvents();
};
/**
* triggered while drag is cancelled.
*
* @param event - the event object.
*/
dragCancel = function (event) {
// Ignore event if not handled
if (!dragHandled) {
return;
}
event.preventDefault();
if (dragElement) {
//rollback all the changes.
rollbackDragChanges();
scope.sortableScope.$apply(function () {
scope.callbacks.dragCancel(dragItemInfo.eventArgs());
});
dragItemInfo = null;
}
unBindEvents();
};
/**
* Binds the drag start events.
*/
bindDrag = function () {
element.bind('touchstart', dragListen);
element.bind('mousedown', dragListen);
};
/**
* Unbinds the drag start events.
*/
unbindDrag = function () {
element.unbind('touchstart', dragListen);
element.unbind('mousedown', dragListen);
};
//bind drag start events.
bindDrag();
//Cancel drag on escape press.
angular.element($document[0].body).bind('keydown', function (event) {
if (event.keyCode === 27) {
dragCancel(event);
}
});
/**
* Binds the events based on the actions.
*/
bindEvents = function () {
angular.element($document).bind('touchmove', dragMove);
angular.element($document).bind('touchend', dragEnd);
angular.element($document).bind('touchcancel', dragCancel);
angular.element($document).bind('mousemove', dragMove);
angular.element($document).bind('mouseup', dragEnd);
};
/**
* Un binds the events for drag support.
*/
unBindEvents = function () {
angular.element($document).unbind('touchend', dragEnd);
angular.element($document).unbind('touchcancel', dragCancel);
angular.element($document).unbind('touchmove', dragMove);
angular.element($document).unbind('mouseup', dragEnd);
angular.element($document).unbind('mousemove', dragMove);
};
}
};
}]);
}());
/*jshint indent: 2 */
/*global angular: false */
(function () {
'use strict';
var mainModule = angular.module('ui.sortable');
/**
* Controller for sortable item.
*
* @param $scope - drag item scope
*/
mainModule.controller('ui.sortable.sortableItemController', ['$scope', function ($scope) {
this.scope = $scope;
$scope.sortableScope = null;
$scope.modelValue = null; // sortable item.
$scope.type = 'item';
/**
* returns the index of the drag item from the sortable list.
*
* @returns {*} - index value.
*/
$scope.index = function () {
return $scope.sortableScope.modelValue.indexOf($scope.modelValue);
};
/**
* Returns the item model data.
*
* @returns {*} - item model value.
*/
$scope.itemData = function () {
return $scope.sortableScope.modelValue[$scope.$index];
};
}]);
/**
* sortableItem directive.
*/
mainModule.directive('asSortableItem', ['sortableConfig',
function (sortableConfig) {
return {
require: ['^asSortable', '?ngModel'],
restrict: 'A',
controller: 'ui.sortable.sortableItemController',
link: function (scope, element, attrs, ctrl) {
var sortableController = ctrl[0];
var ngModelController = ctrl[1];
if (sortableConfig.itemClass) {
element.addClass(sortableConfig.itemClass);
}
scope.sortableScope = sortableController.scope;
if (ngModelController) {
ngModelController.$render = function () {
scope.modelValue = ngModelController.$modelValue;
};
} else {
scope.modelValue = sortableController.scope.modelValue[scope.$index];
}
scope.element = element;
}
};
}]);
}());