diff --git a/bower.json b/bower.json index a065ac9f7baa1..96ae9a7f36aec 100755 --- a/bower.json +++ b/bower.json @@ -3,6 +3,7 @@ "version": "0.0.0", "dependencies": { "pegjs": "~0.8.0", - "flot": "~0.8.3" + "flot": "~0.8.3", + "angular-sortable-view": "~0.0.13" } } diff --git a/bower_components/angular-sortable-view/.bower.json b/bower_components/angular-sortable-view/.bower.json new file mode 100644 index 0000000000000..1df5c612a514e --- /dev/null +++ b/bower_components/angular-sortable-view/.bower.json @@ -0,0 +1,37 @@ +{ + "name": "angular-sortable-view", + "version": "0.0.13", + "homepage": "http://kamilkp.github.io/angular-sortable-view/", + "authors": [ + "Kamil Pękala " + ], + "description": "Fully declarative (multi)sortable for AngularJS", + "main": "./src/angular-sortable-view.js", + "keywords": [ + "angularjs", + "sortable", + "ui-sortable", + "sort", + "sorting", + "declarative" + ], + "license": "MIT", + "ignore": [ + "**/.*", + "node_modules", + "bower_components", + "test", + "tests", + "lib" + ], + "_release": "0.0.13", + "_resolution": { + "type": "version", + "tag": "v0.0.13", + "commit": "187250cb1cfbd25096185049bded7bcedeab4dbd" + }, + "_source": "git://github.com/kamilkp/angular-sortable-view.git", + "_target": "~0.0.13", + "_originalSource": "angular-sortable-view", + "_direct": true +} \ No newline at end of file diff --git a/bower_components/angular-sortable-view/CHANGELOG.md b/bower_components/angular-sortable-view/CHANGELOG.md new file mode 100644 index 0000000000000..16d7683b72a16 --- /dev/null +++ b/bower_components/angular-sortable-view/CHANGELOG.md @@ -0,0 +1,86 @@ +Version 0.0.13 - 2015/01/13 +================ + + * bug fixes + +Version 0.0.12 - 2014/12/09 +================ + + * bug fixes + +Version 0.0.11 - 2014/11/28 +================ + + * touch support + +Version 0.0.9 - 2014/09/11 +================ + + * fixed a bug where `$digest` hasn't been called after `sv-on-start` handler + * added a `sv-on-stop` optional attribute. Function bound there will be called when dragging ended regardless of the fact whether elements have been reordered or not + +Version 0.0.8 - 2014/07/15 +================ + + * fixed a bug where nothing within an element without handle could have been clicked + + +Version 0.0.7 - 2014/07/02 +================ + +You can now use these optional attributes on the element with `sv-root`: + * `sv-on-sort` - The expression passed as a value of that attribute will be evaluated when elements order has changed after sorting. Several parameters can be injected there like: `sv-on-sort="foo($item, $partFrom, $partTo, $indexFrom, $indexTo)"` where: + + + * `sv-on-start` - The expression passed as a value of that attribute will be evaluated when a user starts moving an element. Several parameters can be injected there like: `sv-on-start="bar($item, $part, $index, $helper)"` where: + + + +Version 0.0.6 - 2014/07/01 +================ + + * You can now listen for resorting. An `sv-on-sort` attribute can be now placed on an element with `sv-root`. The expression passed as a value of that attribute will be evaluated when elements order has changed after sorting. + +Version 0.0.5 - 2014/06/26 +================ + + * source element for sorting is now detached from DOM instead of giving him `display: none` + +Version 0.0.4 - 2014/06/25 +================ + + * Fixed the issue with helper styles + * Dropped the need for the browser to support pointer-events CSS property + * Added the project to the bower registy, it is available to download via `bower install angular-sortable-view` + +Version 0.0.3 - 2014/06/11 +================ + + * Added support for custom placeholders + * Better containment handling + * Bug fixes + * BREAKING CHANGE: the module name is now `angular-sortable-view` + +Version 0.0.2 - 2014/06/11 +================ + + * Added support for empty lists + * Added support for custom helpers + +Version 0.0.1 +================ + + * Support for setting a containment + * Support for multiple sortable lists connected with each other + * Support for specifying a handle element diff --git a/bower_components/angular-sortable-view/Gruntfile.js b/bower_components/angular-sortable-view/Gruntfile.js new file mode 100644 index 0000000000000..a4e4414ead0f6 --- /dev/null +++ b/bower_components/angular-sortable-view/Gruntfile.js @@ -0,0 +1,43 @@ +module.exports = function(grunt){ + grunt.loadNpmTasks('grunt-contrib-jshint'); + grunt.loadNpmTasks('grunt-contrib-uglify'); + // grunt.loadNpmTasks('grunt-karma'); + + grunt.initConfig({ + uglify: { + target: { + files: { + 'src/angular-sortable-view.min.js': ['src/angular-sortable-view.js'] + } + }, + options: { + banner: '/*\n\tCopyright Kamil Pękala http://github.com/kamilkp\n' + + '\tangular-sortable-view v0.0.13 2015/01/13\n*/\n' + } + }, + jshint: { + all: [ + 'src/angular-sortable-view.js', + 'Gruntfile.js' + ] + }//, + // karma: { + // unit: { + // configFile: 'karma.conf.js', + // singleRun: true, + // }, + // travis: { + // configFile: 'karma.conf.js', + // singleRun: true, + // browsers: [ + // 'Firefox' + // ] + // } + // } + }); + + grunt.registerTask('min', 'Minify javascript source code', 'uglify'); + // grunt.registerTask('test', 'Run unit tests', ['jshint', 'min', 'karma:unit']); + // grunt.registerTask('default', ['test']); + // grunt.registerTask('travis', ['jshint', 'min', 'karma:travis']); +}; \ No newline at end of file diff --git a/bower_components/angular-sortable-view/LICENSE b/bower_components/angular-sortable-view/LICENSE new file mode 100644 index 0000000000000..5e3e3639e3bfb --- /dev/null +++ b/bower_components/angular-sortable-view/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014 kamilkp + +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. \ No newline at end of file diff --git a/bower_components/angular-sortable-view/README.md b/bower_components/angular-sortable-view/README.md new file mode 100644 index 0000000000000..6373e3c246534 --- /dev/null +++ b/bower_components/angular-sortable-view/README.md @@ -0,0 +1,159 @@ +angular-sortable-view v0.0.13 [![Bower version](https://badge.fury.io/bo/angular-sortable-view.svg)](http://badge.fury.io/bo/angular-sortable-view) +================= + +Fully declarative (multi)sortable for AngularJS + +Demo: http://kamilkp.github.io/angular-sortable-view/ + +You can find the source code for this demo on branch "gh-pages". + +Also see the changelog [here](https://github.com/kamilkp/angular-sortable-view/blob/master/CHANGELOG.md) + +###DESCRIPTION: + +This is a simple library written as a module for [AngularJS](https://github.com/angular/angular.js) for sorting elements in the UI. It supports both single elements list, and multiple connected lists, where an element can be moved from one to another. + +This library requires ***no dependencies whatsoever*** (except angular.js of course), so ***you no longer need to include jQuery and jQueryUI and angularUI*** which altogether gives the size of around ***340kB minified***. Whereas the [angular-sortable-view](https://github.com/kamilkp/angular-sortable-view) is only ***5kB minified!***. + +###API: + +The API is declarative. There are four directives (hooked on attributes) that need to be nested properly: + + * `sv-root` - this is where all the logic is happening. If multiple lists should be connected with each other so that elements can be moved between them and they have a common ancestor, put this attribute on that element. If not and you still want the multi-sortable behaviour a value for that attribue must be provided. That value will be used as an identifier to connect those roots together. + **Optional attributes:** + * `sv-on-sort` - The expression passed as a value of that attribute will be evaluated when elements order has changed after sorting. Several parameters can be injected there like: `sv-on-sort="foo($item, $partFrom, $partTo, $indexFrom, $indexTo)"` where: + + +
  • `sv-on-start` - The expression passed as a value of that attribute will be evaluated when a user starts moving an element. Several parameters can be injected there like: `sv-on-start="bar($item, $part, $index, $helper)"` where: + +
  • +
  • `sv-on-stop` - The expression passed as a value of that attribute will be evaluated when a user stops moving an element (drops it). This will be called regardless of the fact whether elements have been reordered or now. Several parameters can be injected there like: `sv-on-end="baz($item, $part, $index)"` where: + +
  • + * `sv-part` - this attribute should be placed on an element that is a container for the `ngRepeat`'ed elements. Its value should be the same as the right hand side expression in `ng-repeat` attribute. + * `sv-element` - this attribute should be placed on the same element as `ng-repeat` attribute. Its (optional) value should be an expression that evaluates to the options object. + * `sv-handle` - this attribute is optional. If needed it can be placed on an element within the sortable element. This element will be the handle for sorting operations. + * `sv-helper` - the element with this attribute will serve as a custom helper for sorting operations + * `sv-placeholder` - the element with this attribute will serve as a custom placeholder for sorting operations + +###Example of single sortable list + +```html +
    +
    +
    {{item}}
    +
    +
    +``` + +###Example of multiple sortable lists with common ancestor + +```html +
    +
    +
    +
    {{item}}
    +
    +
    +
    +
    +
    {{item}}
    +
    +
    +
    +``` + +###Example of multiple sortable lists without common ancestor + +```html +
    +
    +
    +
    {{item}}
    +
    +
    +
    +
    +
    {{item}}
    +
    +
    +
    +``` + +###Example of using handles + +```html +
    +
    +
    {{item}}
    + ` +
    +
    +``` + +###Example of using custom helpers per part + +```html +
    +
    + custom helper +
    +
    + {{item}} +
    +
    +``` + +###Example of using custom helpers per element + +```html +
    +
    +
    + custom helper {{item}} +
    + {{item}} +
    +
    +``` + +###Example of using custom placeholders per part + +```html +
    +
    + custom placeholder +
    +
    + {{item}} +
    +
    +``` + +###Example of using custom placeholders per element + +```html +
    +
    +
    + custom placeholder {{item}} +
    + {{item}} +
    +
    +``` diff --git a/bower_components/angular-sortable-view/bower.json b/bower_components/angular-sortable-view/bower.json new file mode 100644 index 0000000000000..bdf9d8e5f6443 --- /dev/null +++ b/bower_components/angular-sortable-view/bower.json @@ -0,0 +1,27 @@ +{ + "name": "angular-sortable-view", + "version": "0.0.13", + "homepage": "http://kamilkp.github.io/angular-sortable-view/", + "authors": [ + "Kamil Pękala " + ], + "description": "Fully declarative (multi)sortable for AngularJS", + "main": "./src/angular-sortable-view.js", + "keywords": [ + "angularjs", + "sortable", + "ui-sortable", + "sort", + "sorting", + "declarative" + ], + "license": "MIT", + "ignore": [ + "**/.*", + "node_modules", + "bower_components", + "test", + "tests", + "lib" + ] +} diff --git a/bower_components/angular-sortable-view/package.json b/bower_components/angular-sortable-view/package.json new file mode 100644 index 0000000000000..7e9281d15e3a5 --- /dev/null +++ b/bower_components/angular-sortable-view/package.json @@ -0,0 +1,24 @@ +{ + "name": "angular-sortable-view", + "version": "0.0.13", + "description": "Fully declarative (multi)sortable for AngularJS", + "homepage": "http://kamilkp.github.io/angular-sortable-view", + "author": { + "name": "Kamil Pekala", + "email": "kamilkp@gmail.com", + "url": "http://github.com/kamilkp" + }, + "repository": { + "type": "git", + "url": "https://github.com/kamilkp/angular-sortable-view.git" + }, + "devDependencies": { + "grunt": "^0.4.5", + "grunt-contrib-jshint": "^0.10.0", + "grunt-contrib-uglify": "^0.4.0" + }, + "scripts": { + "test": "grunt travis" + }, + "dependencies": {} +} diff --git a/bower_components/angular-sortable-view/src/angular-sortable-view.js b/bower_components/angular-sortable-view/src/angular-sortable-view.js new file mode 100644 index 0000000000000..808fe405f1f3e --- /dev/null +++ b/bower_components/angular-sortable-view/src/angular-sortable-view.js @@ -0,0 +1,625 @@ +// +// Copyright Kamil Pękala http://github.com/kamilkp +// angular-sortable-view v0.0.13 2015/01/13 +// + +;(function(window, angular){ + 'use strict'; + /* jshint eqnull:true */ + /* jshint -W041 */ + /* jshint -W030 */ + + var module = angular.module('angular-sortable-view', []); + module.directive('svRoot', [function(){ + function shouldBeAfter(elem, pointer, isGrid){ + return isGrid ? elem.x - pointer.x < 0 : elem.y - pointer.y < 0; + } + function getSortableElements(key){ + return ROOTS_MAP[key]; + } + function removeSortableElements(key){ + delete ROOTS_MAP[key]; + } + + var sortingInProgress; + var ROOTS_MAP = Object.create(null); + // window.ROOTS_MAP = ROOTS_MAP; // for debug purposes + + return { + restrict: 'A', + controller: ['$scope', '$attrs', '$interpolate', '$parse', function($scope, $attrs, $interpolate, $parse){ + var mapKey = $interpolate($attrs.svRoot)($scope) || $scope.$id; + if(!ROOTS_MAP[mapKey]) ROOTS_MAP[mapKey] = []; + + var that = this; + var candidates; // set of possible destinations + var $placeholder;// placeholder element + var options; // sortable options + var $helper; // helper element - the one thats being dragged around with the mouse pointer + var $original; // original element + var $target; // last best candidate + var isGrid = false; + var onSort = $parse($attrs.svOnSort); + + // ----- hack due to https://github.com/angular/angular.js/issues/8044 + $attrs.svOnStart = $attrs.$$element[0].attributes['sv-on-start']; + $attrs.svOnStart = $attrs.svOnStart && $attrs.svOnStart.value; + + $attrs.svOnStop = $attrs.$$element[0].attributes['sv-on-stop']; + $attrs.svOnStop = $attrs.svOnStop && $attrs.svOnStop.value; + // ------------------------------------------------------------------- + + var onStart = $parse($attrs.svOnStart); + var onStop = $parse($attrs.svOnStop); + + this.sortingInProgress = function(){ + return sortingInProgress; + }; + + if($attrs.svGrid){ // sv-grid determined explicite + isGrid = $attrs.svGrid === "true" ? true : $attrs.svGrid === "false" ? false : null; + if(isGrid === null) + throw 'Invalid value of sv-grid attribute'; + } + else{ + // check if at least one of the lists have a grid like layout + $scope.$watchCollection(function(){ + return getSortableElements(mapKey); + }, function(collection){ + isGrid = false; + var array = collection.filter(function(item){ + return !item.container; + }).map(function(item){ + return { + part: item.getPart().id, + y: item.element[0].getBoundingClientRect().top + }; + }); + var dict = Object.create(null); + array.forEach(function(item){ + if(dict[item.part]) + dict[item.part].push(item.y); + else + dict[item.part] = [item.y]; + }); + Object.keys(dict).forEach(function(key){ + dict[key].sort(); + dict[key].forEach(function(item, index){ + if(index < dict[key].length - 1){ + if(item > 0 && item === dict[key][index + 1]){ + isGrid = true; + } + } + }); + }); + }); + } + + this.$moveUpdate = function(opts, mouse, svElement, svOriginal, svPlaceholder, originatingPart, originatingIndex){ + var svRect = svElement[0].getBoundingClientRect(); + if(opts.tolerance === 'element') + mouse = { + x: ~~(svRect.left + svRect.width/2), + y: ~~(svRect.top + svRect.height/2) + }; + + sortingInProgress = true; + candidates = []; + if(!$placeholder){ + if(svPlaceholder){ // custom placeholder + $placeholder = svPlaceholder.clone(); + $placeholder.removeClass('ng-hide'); + } + else{ // default placeholder + $placeholder = svOriginal.clone(); + $placeholder.addClass('sv-visibility-hidden'); + $placeholder.addClass('sv-placeholder'); + $placeholder.css({ + 'height': svRect.height + 'px', + 'width': svRect.width + 'px' + }); + } + + svOriginal.after($placeholder); + svOriginal.addClass('ng-hide'); + + // cache options, helper and original element reference + $original = svOriginal; + options = opts; + $helper = svElement; + + onStart($scope, { + $helper: $helper, + $part: originatingPart.model(originatingPart.scope), + $index: originatingIndex, + $item: originatingPart.model(originatingPart.scope)[originatingIndex] + }); + $scope.$root && $scope.$root.$$phase || $scope.$apply(); + } + + // ----- move the element + $helper[0].reposition({ + x: mouse.x + document.body.scrollLeft - mouse.offset.x*svRect.width, + y: mouse.y + document.body.scrollTop - mouse.offset.y*svRect.height + }); + + // ----- manage candidates + getSortableElements(mapKey).forEach(function(se, index){ + if(opts.containment != null){ + // TODO: optimize this since it could be calculated only once when the moving begins + if( + !elementMatchesSelector(se.element, opts.containment) && + !elementMatchesSelector(se.element, opts.containment + ' *') + ) return; // element is not within allowed containment + } + var rect = se.element[0].getBoundingClientRect(); + var center = { + x: ~~(rect.left + rect.width/2), + y: ~~(rect.top + rect.height/2) + }; + if(!se.container && // not the container element + (se.element[0].scrollHeight || se.element[0].scrollWidth)){ // element is visible + candidates.push({ + element: se.element, + q: (center.x - mouse.x)*(center.x - mouse.x) + (center.y - mouse.y)*(center.y - mouse.y), + view: se.getPart(), + targetIndex: se.getIndex(), + after: shouldBeAfter(center, mouse, isGrid) + }); + } + if(se.container && !se.element[0].querySelector('[sv-element]:not(.sv-placeholder):not(.sv-source)')){ // empty container + candidates.push({ + element: se.element, + q: (center.x - mouse.x)*(center.x - mouse.x) + (center.y - mouse.y)*(center.y - mouse.y), + view: se.getPart(), + targetIndex: 0, + container: true + }); + } + }); + var pRect = $placeholder[0].getBoundingClientRect(); + var pCenter = { + x: ~~(pRect.left + pRect.width/2), + y: ~~(pRect.top + pRect.height/2) + }; + candidates.push({ + q: (pCenter.x - mouse.x)*(pCenter.x - mouse.x) + (pCenter.y - mouse.y)*(pCenter.y - mouse.y), + element: $placeholder, + placeholder: true + }); + candidates.sort(function(a, b){ + return a.q - b.q; + }); + + candidates.forEach(function(cand, index){ + if(index === 0 && !cand.placeholder && !cand.container){ + $target = cand; + cand.element.addClass('sv-candidate'); + if(cand.after) + cand.element.after($placeholder); + else + insertElementBefore(cand.element, $placeholder); + } + else if(index === 0 && cand.container){ + $target = cand; + cand.element.append($placeholder); + } + else + cand.element.removeClass('sv-candidate'); + }); + }; + + this.$drop = function(originatingPart, index, options){ + if(!$placeholder) return; + + if(options.revert){ + var placeholderRect = $placeholder[0].getBoundingClientRect(); + var helperRect = $helper[0].getBoundingClientRect(); + var distance = Math.sqrt( + Math.pow(helperRect.top - placeholderRect.top, 2) + + Math.pow(helperRect.left - placeholderRect.left, 2) + ); + + var duration = +options.revert*distance/200; // constant speed: duration depends on distance + duration = Math.min(duration, +options.revert); // however it's not longer that options.revert + + ['-webkit-', '-moz-', '-ms-', '-o-', ''].forEach(function(prefix){ + if(typeof $helper[0].style[prefix + 'transition'] !== "undefined") + $helper[0].style[prefix + 'transition'] = 'all ' + duration + 'ms ease'; + }); + setTimeout(afterRevert, duration); + $helper.css({ + 'top': placeholderRect.top + document.body.scrollTop + 'px', + 'left': placeholderRect.left + document.body.scrollLeft + 'px' + }); + } + else + afterRevert(); + + function afterRevert(){ + sortingInProgress = false; + $placeholder.remove(); + $helper.remove(); + $original.removeClass('ng-hide'); + + candidates = void 0; + $placeholder = void 0; + options = void 0; + $helper = void 0; + $original = void 0; + + // sv-on-stop callback + onStop($scope, { + $part: originatingPart.model(originatingPart.scope), + $index: index, + $item: originatingPart.model(originatingPart.scope)[index] + }); + + if($target){ + $target.element.removeClass('sv-candidate'); + var spliced = originatingPart.model(originatingPart.scope).splice(index, 1); + var targetIndex = $target.targetIndex; + if($target.view === originatingPart && $target.targetIndex > index) + targetIndex--; + if($target.after) + targetIndex++; + $target.view.model($target.view.scope).splice(targetIndex, 0, spliced[0]); + + // sv-on-sort callback + if($target.view !== originatingPart || index !== targetIndex) + onSort($scope, { + $partTo: $target.view.model($target.view.scope), + $partFrom: originatingPart.model(originatingPart.scope), + $item: spliced[0], + $indexTo: targetIndex, + $indexFrom: index + }); + + } + $target = void 0; + + $scope.$root && $scope.$root.$$phase || $scope.$apply(); + } + }; + + this.addToSortableElements = function(se){ + getSortableElements(mapKey).push(se); + }; + this.removeFromSortableElements = function(se){ + var elems = getSortableElements(mapKey); + var index = elems.indexOf(se); + if(index > -1){ + elems.splice(index, 1); + if(elems.length === 0) + removeSortableElements(mapKey); + } + }; + }] + }; + }]); + + module.directive('svPart', ['$parse', function($parse){ + return { + restrict: 'A', + require: '^svRoot', + controller: ['$scope', function($scope){ + $scope.$ctrl = this; + this.getPart = function(){ + return $scope.part; + }; + this.$drop = function(index, options){ + $scope.$sortableRoot.$drop($scope.part, index, options); + }; + }], + scope: true, + link: function($scope, $element, $attrs, $sortable){ + if(!$attrs.svPart) throw new Error('no model provided'); + var model = $parse($attrs.svPart); + if(!model.assign) throw new Error('model not assignable'); + + $scope.part = { + id: $scope.$id, + element: $element, + model: model, + scope: $scope + }; + $scope.$sortableRoot = $sortable; + + var sortablePart = { + element: $element, + getPart: $scope.$ctrl.getPart, + container: true + }; + $sortable.addToSortableElements(sortablePart); + $scope.$on('$destroy', function(){ + $sortable.removeFromSortableElements(sortablePart); + }); + } + }; + }]); + + module.directive('svElement', ['$parse', function($parse){ + return { + restrict: 'A', + require: ['^svPart', '^svRoot'], + controller: ['$scope', function($scope){ + $scope.$ctrl = this; + }], + link: function($scope, $element, $attrs, $controllers){ + var sortableElement = { + element: $element, + getPart: $controllers[0].getPart, + getIndex: function(){ + return $scope.$index; + } + }; + $controllers[1].addToSortableElements(sortableElement); + $scope.$on('$destroy', function(){ + $controllers[1].removeFromSortableElements(sortableElement); + }); + + var handle = $element; + handle.on('mousedown touchstart', onMousedown); + $scope.$watch('$ctrl.handle', function(customHandle){ + if(customHandle){ + handle.off('mousedown touchstart', onMousedown); + handle = customHandle; + handle.on('mousedown touchstart', onMousedown); + } + }); + + var helper; + $scope.$watch('$ctrl.helper', function(customHelper){ + if(customHelper){ + helper = customHelper; + } + }); + + var placeholder; + $scope.$watch('$ctrl.placeholder', function(customPlaceholder){ + if(customPlaceholder){ + placeholder = customPlaceholder; + } + }); + + var body = angular.element(document.body); + var html = angular.element(document.documentElement); + + var moveExecuted; + + function onMousedown(e){ + touchFix(e); + + if($controllers[1].sortingInProgress()) return; + if(e.button != 0 && e.type === 'mousedown') return; + + moveExecuted = false; + var opts = $parse($attrs.svElement)($scope); + opts = angular.extend({}, { + tolerance: 'pointer', + revert: 200, + containment: 'html' + }, opts); + if(opts.containment){ + var containmentRect = closestElement.call($element, opts.containment)[0].getBoundingClientRect(); + } + + var target = $element; + var clientRect = $element[0].getBoundingClientRect(); + var clone; + + if(!helper) helper = $controllers[0].helper; + if(!placeholder) placeholder = $controllers[0].placeholder; + if(helper){ + clone = helper.clone(); + clone.removeClass('ng-hide'); + clone.css({ + 'left': clientRect.left + document.body.scrollLeft + 'px', + 'top': clientRect.top + document.body.scrollTop + 'px' + }); + target.addClass('sv-visibility-hidden'); + } + else{ + clone = target.clone(); + clone.addClass('sv-helper').css({ + 'left': clientRect.left + document.body.scrollLeft + 'px', + 'top': clientRect.top + document.body.scrollTop + 'px', + 'width': clientRect.width + 'px' + }); + } + + clone[0].reposition = function(coords){ + var targetLeft = coords.x; + var targetTop = coords.y; + var helperRect = clone[0].getBoundingClientRect(); + + var body = document.body; + + if(containmentRect){ + if(targetTop < containmentRect.top + body.scrollTop) // top boundary + targetTop = containmentRect.top + body.scrollTop; + if(targetTop + helperRect.height > containmentRect.top + body.scrollTop + containmentRect.height) // bottom boundary + targetTop = containmentRect.top + body.scrollTop + containmentRect.height - helperRect.height; + if(targetLeft < containmentRect.left + body.scrollLeft) // left boundary + targetLeft = containmentRect.left + body.scrollLeft; + if(targetLeft + helperRect.width > containmentRect.left + body.scrollLeft + containmentRect.width) // right boundary + targetLeft = containmentRect.left + body.scrollLeft + containmentRect.width - helperRect.width; + } + this.style.left = targetLeft - body.scrollLeft + 'px'; + this.style.top = targetTop - body.scrollTop + 'px'; + }; + + var pointerOffset = { + x: (e.clientX - clientRect.left)/clientRect.width, + y: (e.clientY - clientRect.top)/clientRect.height + }; + html.addClass('sv-sorting-in-progress'); + html.on('mousemove touchmove', onMousemove).on('mouseup touchend touchcancel', function mouseup(e){ + html.off('mousemove touchmove', onMousemove); + html.off('mouseup touchend', mouseup); + html.removeClass('sv-sorting-in-progress'); + if(moveExecuted) + $controllers[0].$drop($scope.$index, opts); + else + $element.removeClass('sv-visibility-hidden'); + }); + + // onMousemove(e); + function onMousemove(e){ + touchFix(e); + if(!moveExecuted){ + $element.parent().prepend(clone); + moveExecuted = true; + } + $controllers[1].$moveUpdate(opts, { + x: e.clientX, + y: e.clientY, + offset: pointerOffset + }, clone, $element, placeholder, $controllers[0].getPart(), $scope.$index); + } + } + } + }; + }]); + + module.directive('svHandle', function(){ + return { + require: '?^svElement', + link: function($scope, $element, $attrs, $ctrl){ + if($ctrl) + $ctrl.handle = $element.add($ctrl.handle); // support multiple handles + } + }; + }); + + module.directive('svHelper', function(){ + return { + require: ['?^svPart', '?^svElement'], + link: function($scope, $element, $attrs, $ctrl){ + $element.addClass('sv-helper').addClass('ng-hide'); + if($ctrl[1]) + $ctrl[1].helper = $element; + else if($ctrl[0]) + $ctrl[0].helper = $element; + } + }; + }); + + module.directive('svPlaceholder', function(){ + return { + require: ['?^svPart', '?^svElement'], + link: function($scope, $element, $attrs, $ctrl){ + $element.addClass('sv-placeholder').addClass('ng-hide'); + if($ctrl[1]) + $ctrl[1].placeholder = $element; + else if($ctrl[0]) + $ctrl[0].placeholder = $element; + } + }; + }); + + angular.element(document.head).append([ + '' + ].join('')); + + function touchFix(e){ + if(!('clientX' in e) && !('clientY' in e)) { + var touches = e.touches || e.originalEvent.touches; + if(touches && touches.length) { + e.clientX = touches[0].clientX; + e.clientY = touches[0].clientY; + } + e.preventDefault(); + } + } + + function getPreviousSibling(element){ + element = element[0]; + if(element.previousElementSibling) + return angular.element(element.previousElementSibling); + else{ + var sib = element.previousSibling; + while(sib != null && sib.nodeType != 1) + sib = sib.previousSibling; + return angular.element(sib); + } + } + + function insertElementBefore(element, newElement){ + var prevSibl = getPreviousSibling(element); + if(prevSibl.length > 0){ + prevSibl.after(newElement); + } + else{ + element.parent().prepend(newElement); + } + } + + var dde = document.documentElement, + matchingFunction = dde.matches ? 'matches' : + dde.matchesSelector ? 'matchesSelector' : + dde.webkitMatches ? 'webkitMatches' : + dde.webkitMatchesSelector ? 'webkitMatchesSelector' : + dde.msMatches ? 'msMatches' : + dde.msMatchesSelector ? 'msMatchesSelector' : + dde.mozMatches ? 'mozMatches' : + dde.mozMatchesSelector ? 'mozMatchesSelector' : null; + if(matchingFunction == null) + throw 'This browser doesn\'t support the HTMLElement.matches method'; + + function elementMatchesSelector(element, selector){ + if(element instanceof angular.element) element = element[0]; + if(matchingFunction !== null) + return element[matchingFunction](selector); + } + + var closestElement = angular.element.prototype.closest || function (selector){ + var el = this[0].parentNode; + while(el !== document.documentElement && !el[matchingFunction](selector)) + el = el.parentNode; + + if(el[matchingFunction](selector)) + return angular.element(el); + else + return angular.element(); + }; + + /* + Simple implementation of jQuery's .add method + */ + if(typeof angular.element.prototype.add !== 'function'){ + angular.element.prototype.add = function(elem){ + var i, res = angular.element(); + elem = angular.element(elem); + for(i=0;i0?c.after(b):a.parent().prepend(b)}function f(a,c){return a instanceof b.element&&(a=a[0]),null!==i?a[i](c):void 0}var g=b.module("angular-sortable-view",[]);g.directive("svRoot",[function(){function a(a,b,c){return c?a.x-b.x<0:a.y-b.y<0}function b(a){return g[a]}function c(a){delete g[a]}var d,g=Object.create(null);return{restrict:"A",controller:["$scope","$attrs","$interpolate","$parse",function(h,i,j,k){var l=j(i.svRoot)(h)||h.$id;g[l]||(g[l]=[]);var m,n,o,p,q,r,s=!1,t=k(i.svOnSort);i.svOnStart=i.$$element[0].attributes["sv-on-start"],i.svOnStart=i.svOnStart&&i.svOnStart.value,i.svOnStop=i.$$element[0].attributes["sv-on-stop"],i.svOnStop=i.svOnStop&&i.svOnStop.value;var u=k(i.svOnStart),v=k(i.svOnStop);if(this.sortingInProgress=function(){return d},i.svGrid){if(s="true"===i.svGrid?!0:"false"===i.svGrid?!1:null,null===s)throw"Invalid value of sv-grid attribute"}else h.$watchCollection(function(){return b(l)},function(a){s=!1;var b=a.filter(function(a){return!a.container}).map(function(a){return{part:a.getPart().id,y:a.element[0].getBoundingClientRect().top}}),c=Object.create(null);b.forEach(function(a){c[a.part]?c[a.part].push(a.y):c[a.part]=[a.y]}),Object.keys(c).forEach(function(a){c[a].sort(),c[a].forEach(function(b,d){d0&&b===c[a][d+1]&&(s=!0)})})});this.$moveUpdate=function(c,g,i,j,k,t,v){var w=i[0].getBoundingClientRect();"element"===c.tolerance&&(g={x:~~(w.left+w.width/2),y:~~(w.top+w.height/2)}),d=!0,m=[],n||(k?(n=k.clone(),n.removeClass("ng-hide")):(n=j.clone(),n.addClass("sv-visibility-hidden"),n.addClass("sv-placeholder"),n.css({height:w.height+"px",width:w.width+"px"})),j.after(n),j.addClass("ng-hide"),q=j,o=c,p=i,u(h,{$helper:p,$part:t.model(t.scope),$index:v,$item:t.model(t.scope)[v]}),h.$root&&h.$root.$$phase||h.$apply()),p[0].reposition({x:g.x+document.body.scrollLeft-g.offset.x*w.width,y:g.y+document.body.scrollTop-g.offset.y*w.height}),b(l).forEach(function(b){if(null==c.containment||f(b.element,c.containment)||f(b.element,c.containment+" *")){var d=b.element[0].getBoundingClientRect(),e={x:~~(d.left+d.width/2),y:~~(d.top+d.height/2)};b.container||!b.element[0].scrollHeight&&!b.element[0].scrollWidth||m.push({element:b.element,q:(e.x-g.x)*(e.x-g.x)+(e.y-g.y)*(e.y-g.y),view:b.getPart(),targetIndex:b.getIndex(),after:a(e,g,s)}),b.container&&!b.element[0].querySelector("[sv-element]:not(.sv-placeholder):not(.sv-source)")&&m.push({element:b.element,q:(e.x-g.x)*(e.x-g.x)+(e.y-g.y)*(e.y-g.y),view:b.getPart(),targetIndex:0,container:!0})}});var x=n[0].getBoundingClientRect(),y={x:~~(x.left+x.width/2),y:~~(x.top+x.height/2)};m.push({q:(y.x-g.x)*(y.x-g.x)+(y.y-g.y)*(y.y-g.y),element:n,placeholder:!0}),m.sort(function(a,b){return a.q-b.q}),m.forEach(function(a,b){0!==b||a.placeholder||a.container?0===b&&a.container?(r=a,a.element.append(n)):a.element.removeClass("sv-candidate"):(r=a,a.element.addClass("sv-candidate"),a.after?a.element.after(n):e(a.element,n))})},this.$drop=function(a,b,c){function e(){if(d=!1,n.remove(),p.remove(),q.removeClass("ng-hide"),m=void 0,n=void 0,c=void 0,p=void 0,q=void 0,v(h,{$part:a.model(a.scope),$index:b,$item:a.model(a.scope)[b]}),r){r.element.removeClass("sv-candidate");var e=a.model(a.scope).splice(b,1),f=r.targetIndex;r.view===a&&r.targetIndex>b&&f--,r.after&&f++,r.view.model(r.view.scope).splice(f,0,e[0]),(r.view!==a||b!==f)&&t(h,{$partTo:r.view.model(r.view.scope),$partFrom:a.model(a.scope),$item:e[0],$indexTo:f,$indexFrom:b})}r=void 0,h.$root&&h.$root.$$phase||h.$apply()}if(n)if(c.revert){var f=n[0].getBoundingClientRect(),g=p[0].getBoundingClientRect(),i=Math.sqrt(Math.pow(g.top-f.top,2)+Math.pow(g.left-f.left,2)),j=+c.revert*i/200;j=Math.min(j,+c.revert),["-webkit-","-moz-","-ms-","-o-",""].forEach(function(a){"undefined"!=typeof p[0].style[a+"transition"]&&(p[0].style[a+"transition"]="all "+j+"ms ease")}),setTimeout(e,j),p.css({top:f.top+document.body.scrollTop+"px",left:f.left+document.body.scrollLeft+"px"})}else e()},this.addToSortableElements=function(a){b(l).push(a)},this.removeFromSortableElements=function(a){var d=b(l),e=d.indexOf(a);e>-1&&(d.splice(e,1),0===d.length&&c(l))}}]}}]),g.directive("svPart",["$parse",function(a){return{restrict:"A",require:"^svRoot",controller:["$scope",function(a){a.$ctrl=this,this.getPart=function(){return a.part},this.$drop=function(b,c){a.$sortableRoot.$drop(a.part,b,c)}}],scope:!0,link:function(b,c,d,e){if(!d.svPart)throw new Error("no model provided");var f=a(d.svPart);if(!f.assign)throw new Error("model not assignable");b.part={id:b.$id,element:c,model:f,scope:b},b.$sortableRoot=e;var g={element:c,getPart:b.$ctrl.getPart,container:!0};e.addToSortableElements(g),b.$on("$destroy",function(){e.removeFromSortableElements(g)})}}}]),g.directive("svElement",["$parse",function(a){return{restrict:"A",require:["^svPart","^svRoot"],controller:["$scope",function(a){a.$ctrl=this}],link:function(d,e,f,g){function h(h){function i(a){c(a),n||(e.parent().prepend(q),n=!0),g[1].$moveUpdate(k,{x:a.clientX,y:a.clientY,offset:t},q,e,m,g[0].getPart(),d.$index)}if(c(h),!g[1].sortingInProgress()&&(0==h.button||"mousedown"!==h.type)){n=!1;var k=a(f.svElement)(d);if(k=b.extend({},{tolerance:"pointer",revert:200,containment:"html"},k),k.containment)var p=j.call(e,k.containment)[0].getBoundingClientRect();var q,r=e,s=e[0].getBoundingClientRect();l||(l=g[0].helper),m||(m=g[0].placeholder),l?(q=l.clone(),q.removeClass("ng-hide"),q.css({left:s.left+document.body.scrollLeft+"px",top:s.top+document.body.scrollTop+"px"}),r.addClass("sv-visibility-hidden")):(q=r.clone(),q.addClass("sv-helper").css({left:s.left+document.body.scrollLeft+"px",top:s.top+document.body.scrollTop+"px",width:s.width+"px"})),q[0].reposition=function(a){var b=a.x,c=a.y,d=q[0].getBoundingClientRect(),e=document.body;p&&(cp.top+e.scrollTop+p.height&&(c=p.top+e.scrollTop+p.height-d.height),bp.left+e.scrollLeft+p.width&&(b=p.left+e.scrollLeft+p.width-d.width)),this.style.left=b-e.scrollLeft+"px",this.style.top=c-e.scrollTop+"px"};var t={x:(h.clientX-s.left)/s.width,y:(h.clientY-s.top)/s.height};o.addClass("sv-sorting-in-progress"),o.on("mousemove touchmove",i).on("mouseup touchend touchcancel",function u(){o.off("mousemove touchmove",i),o.off("mouseup touchend",u),o.removeClass("sv-sorting-in-progress"),n?g[0].$drop(d.$index,k):e.removeClass("sv-visibility-hidden")})}}var i={element:e,getPart:g[0].getPart,getIndex:function(){return d.$index}};g[1].addToSortableElements(i),d.$on("$destroy",function(){g[1].removeFromSortableElements(i)});var k=e;k.on("mousedown touchstart",h),d.$watch("$ctrl.handle",function(a){a&&(k.off("mousedown touchstart",h),k=a,k.on("mousedown touchstart",h))});var l;d.$watch("$ctrl.helper",function(a){a&&(l=a)});var m;d.$watch("$ctrl.placeholder",function(a){a&&(m=a)});var n,o=(b.element(document.body),b.element(document.documentElement))}}}]),g.directive("svHandle",function(){return{require:"?^svElement",link:function(a,b,c,d){d&&(d.handle=b.add(d.handle))}}}),g.directive("svHelper",function(){return{require:["?^svPart","?^svElement"],link:function(a,b,c,d){b.addClass("sv-helper").addClass("ng-hide"),d[1]?d[1].helper=b:d[0]&&(d[0].helper=b)}}}),g.directive("svPlaceholder",function(){return{require:["?^svPart","?^svElement"],link:function(a,b,c,d){b.addClass("sv-placeholder").addClass("ng-hide"),d[1]?d[1].placeholder=b:d[0]&&(d[0].placeholder=b)}}}),b.element(document.head).append([""].join(""));var h=document.documentElement,i=h.matches?"matches":h.matchesSelector?"matchesSelector":h.webkitMatches?"webkitMatches":h.webkitMatchesSelector?"webkitMatchesSelector":h.msMatches?"msMatches":h.msMatchesSelector?"msMatchesSelector":h.mozMatches?"mozMatches":h.mozMatchesSelector?"mozMatchesSelector":null;if(null==i)throw"This browser doesn't support the HTMLElement.matches method";var j=b.element.prototype.closest||function(a){for(var c=this[0].parentNode;c!==document.documentElement&&!c[i](a);)c=c.parentNode;return c[i](a)?b.element(c):b.element()};"function"!=typeof b.element.prototype.add&&(b.element.prototype.add=function(a){var c,d=b.element();for(a=b.element(a),c=0;c -
    -
    -
    +
    -