Last active
December 30, 2015 17:59
-
-
Save cmackay/7864569 to your computer and use it in GitHub Desktop.
Angular Hammer.js Sortable
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html ng-app='app'> | |
<head> | |
<meta name="description" content="Angular Hammer.js Sortable" /> | |
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> | |
<title></title> | |
<!-- Styles --> | |
<style> | |
body { | |
background-color: #fff; | |
font-family: sans-serif; | |
font-size: 18px; | |
} | |
body > div { | |
margin-bottom: 20px; | |
border: 1px solid #f00; | |
} | |
.placeholder { | |
} | |
.helper { | |
background-color: #fff; | |
} | |
.sortable { | |
height: 200px; | |
} | |
.drag { | |
border: 1px solid #ccc; | |
/*background-color: #fff;*/ | |
padding: 5px; | |
cursor: pointer; | |
} | |
.info { | |
font-size: 14px; | |
padding: 10px; | |
} | |
</style> | |
</head> | |
<body> | |
<!-- Main Controller --> | |
<div ng-controller='MainCtrl'> | |
<div sortable='sortableOptions'> | |
<div id='{{item.name}}' class='drag' ng-repeat='item in items'>{{item.name}}</div> | |
</div> | |
<div class='info'>Items: {{items}}</div> | |
</div> | |
<div ng-controller='MainCtrl'> | |
<div sortable='sortableOptions'> | |
<div id='{{item.name}}' class='drag' ng-repeat='item in items'>{{item.name}}</div> | |
</div> | |
<div class='info'>Items: {{items}}</div> | |
</div> | |
<!-- Dependencies --> | |
<script src='http://cdnjs.cloudflare.com/ajax/libs/hammer.js/1.0.5/hammer.min.js'></script> | |
<script src='http://cdnjs.cloudflare.com/ajax/libs/angular.js/1.2.1/angular.js'></script> | |
</body> | |
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
'use strict'; | |
/* global angular:false */ | |
angular.module('app', []) | |
/* global Hammer:false */ | |
.constant('Hammer', Hammer) | |
.controller('MainCtrl', function ($scope, $log) { | |
$scope.items = [{ | |
name: $scope.$id + '-1' | |
},{ | |
name: $scope.$id + '-2' | |
},{ | |
name: $scope.$id + '-3' | |
}]; | |
$scope.sortableOptions = { | |
draggableClass: 'drag', | |
helperClass: 'helper', | |
forceHelperSize: true, | |
placeholderClass: 'placeholder', | |
forcePlaceholderSize: true, | |
connectWith: 'sortable', | |
zIndex: 1000 | |
}; | |
}) | |
.factory('touch', function ($rootScope, $document, $log, Hammer) { | |
var hammerTime, events = [ | |
'drag', | |
'dragstart', | |
'dragend' | |
]; | |
return { | |
init: function () { | |
if (!hammerTime) { | |
hammerTime = new Hammer($document[0].body, { | |
prevent_default: true | |
}).on(events.join(' '), function(e) { | |
if (!e.gesture) return; | |
$rootScope.$broadcast('hammer:' + event.type, e); | |
}); | |
} | |
} | |
}; | |
}) | |
.directive('sortable', function ($rootScope, $document, $log, Hammer) { | |
var indexOf = Array.prototype.indexOf, | |
dragContext; | |
function getIndex(el) { | |
$log.debug('getIndex', el); | |
return indexOf.call(el.parentNode.children, el); | |
} | |
function forceSize(el, otherEl) { | |
$log.debug('forceSize', el, otherEl); | |
el.css({ | |
width: otherEl[0].clientWidth + 'px', | |
height: otherEl[0].clientHeight + 'px' | |
}); | |
} | |
function DragContext(opts) { | |
this.config = opts.config; | |
this.source = opts.source; | |
this.sourceIndex = getIndex(opts.draggable[0]); | |
this.draggable = opts.draggable; | |
} | |
DragContext.prototype = { | |
onDragStart: function (e) { | |
$log.info('onDragStart', this); | |
var touch = e.gesture.touches[0]; | |
this.click = { | |
left: touch.pageX - touch.target.offsetLeft, | |
top: touch.pageY - touch.target.offsetTop | |
}; | |
this._createHelper(); | |
this._createPlaceholder(); | |
// hide the initial item | |
this.draggable.css('display', 'none'); | |
}, | |
onDrag: function (e) { | |
$log.info('onDrag', e); | |
// because of pointer events being set to none | |
// the srcEl should be the item behind the helper | |
var srcEl = angular.element(e.srcElement); | |
// move the helper | |
this.helper.css({ | |
left: e.gesture.touches[0].pageX - this.click.left + 'px', | |
top: e.gesture.touches[0].pageY - this.click.top + 'px' | |
}); | |
if (srcEl.hasClass(this.config.draggableClass)) { | |
// pointer is over a draggable | |
var direction = this.getDirection(e.gesture.center); | |
// pointer direction on the page is down | |
if (direction === 'down') { | |
// move placeholder after srcEl | |
srcEl[0].parentNode.insertBefore( | |
this.placeholder[0], srcEl[0].nextSibling); | |
this.lastMove = new Date(); | |
} else if (direction === 'up') { | |
// move placeholder before srcEl | |
srcEl[0].parentNode.insertBefore(this.placeholder[0], srcEl[0]); | |
} | |
} else if (srcEl.hasClass(this.config.connectWith)) { | |
srcEl[0].appendChild(this.placeholder[0]); | |
} | |
this.lastCenter = e.gesture.center; | |
}, | |
onDragEnd: function (e) { | |
$log.info('onDragEnd', e); | |
var source = this.source, | |
sourceIndex = this.sourceIndex, | |
sourceScope = angular.element(source).scope(), | |
target = this.placeholder.parent(), | |
targetIndex = getIndex(this.placeholder[0]), | |
targetScope = angular.element(target).scope(); | |
this.draggable.css('display', ''); | |
this.helper.remove(); | |
this.placeholder.remove(); | |
if (!targetScope.items) { | |
return; | |
} | |
if (sourceScope === targetScope) { | |
// move within current sortable | |
if (sourceIndex !== targetIndex) { | |
sourceScope.$apply(function () { | |
var items = sourceScope.items; | |
items.splice(targetIndex, 0, items.splice(sourceIndex, 1)[0]); | |
}); | |
} | |
} else { | |
// move to a different sortable | |
$rootScope.$apply(function () { | |
var sourceItems = sourceScope.items, | |
targetItems = targetScope.items; | |
var item = sourceItems.splice(sourceIndex, 1)[0]; | |
targetItems.splice(targetIndex, 0, item); | |
}); | |
} | |
}, | |
_createHelper: function () { | |
$log.debug('_createHelper'); | |
// clone the draggable | |
var helper = this.draggable.clone(); | |
// set the css so it that the helper can move | |
helper.css({ | |
zIndex: this.config.zIndex || 1000, | |
position: 'absolute', | |
pointerEvents: 'none' | |
}); | |
// set the helper class | |
helper.addClass(this.config.helperClass); | |
// set the helper size the same as the draggable | |
if (this.config.forceHelperSize) { | |
forceSize(helper, this.draggable); | |
} | |
// add the element | |
angular.element(this.config.appendTo || $document[0].body) | |
.append(helper); | |
this.helper = helper; | |
}, | |
_createPlaceholder: function () { | |
$log.debug('_createPlaceholder'); | |
// create a new element of the same node type | |
var nodeName = this.draggable[0].nodeName.toLowerCase(), | |
placeholder = angular.element('<' + nodeName + '>'); | |
// add the placeholder class | |
placeholder.addClass(this.config.placeholderClass); | |
// add placeholder after the draggable | |
this.draggable[0].parentNode.insertBefore( | |
placeholder[0], this.draggable[0].nextSibling); | |
// set the size to the draggable's size | |
if (this.config.forcePlaceholderSize) { | |
forceSize(placeholder, this.draggable); | |
} | |
this.placeholder = placeholder; | |
}, | |
isSource: function (source) { | |
return this.source === source; | |
}, | |
getDirection: function (center) { | |
if (this.lastCenter) { | |
return (this.lastCenter.pageY > center.pageY) ? 'up' : 'down'; | |
} | |
return ''; | |
} | |
}; | |
return { | |
restrict: 'A', | |
link: function (scope, element, attrs) { | |
var config = scope.$eval(attrs.sortable); | |
// add the sortable class | |
element.addClass('sortable'); | |
scope.$on('hammer:dragstart', function (event, e) { | |
var draggable = angular.element(e.gesture.touches[0].target); | |
// only continue if the touch target has the draggable | |
// class and this directive's element contains the | |
// draggable | |
if (!draggable.hasClass(config.draggableClass) || | |
(element[0] !== draggable[0].parentNode)) { | |
return; | |
} | |
dragContext = new DragContext({ | |
config: config, | |
source: element, | |
draggable: draggable | |
}); | |
dragContext.onDragStart(e); | |
}); | |
scope.$on('hammer:drag', function (event, e) { | |
if (dragContext && dragContext.isSource(element)) { | |
dragContext.onDrag(e); | |
} | |
}); | |
scope.$on('hammer:dragend', function (event, e) { | |
if (dragContext && dragContext.isSource(element)) { | |
dragContext.onDragEnd(e); | |
} | |
}); | |
$log.info('drag - link'); | |
} | |
}; | |
}) | |
.run(function ($log, touch) { | |
$log.info('app - run'); | |
touch.init(); | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment