├── .gitignore
├── demo
├── images
│ └── GitHub-Mark-32px.png
├── demo.coffee
├── styles
│ ├── style.scss
│ └── reset.scss
└── index.html
├── .editorconfig
├── bower.json
├── package.json
├── LICENSE
├── dist
├── angular-drag-to-reorder.min.js
└── angular-drag-to-reorder.js
├── README.md
├── gulpfile.js
└── src
└── angular-drag-to-reorder.coffee
/.gitignore:
--------------------------------------------------------------------------------
1 | bower_components/
2 | node_modules/
3 | build/
4 | gh-pages/
5 |
--------------------------------------------------------------------------------
/demo/images/GitHub-Mark-32px.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brandly/angular-drag-to-reorder/HEAD/demo/images/GitHub-Mark-32px.png
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # http://editorconfig.org
2 |
3 | root = true
4 |
5 | [*]
6 | charset = utf-8
7 | end_of_line = lf
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 | indent_style = space
11 | indent_size = 2
12 |
13 | [**.sass]
14 | indent_size = 4
15 |
--------------------------------------------------------------------------------
/demo/demo.coffee:
--------------------------------------------------------------------------------
1 | demo = angular.module 'demo', ['mb-dragToReorder']
2 |
3 | demo.controller 'BasicListCtrl', ($scope) ->
4 | $scope.planets = [
5 | 'Mercury', 'Venus', 'Earth', 'Mars',
6 | 'Jupiter', 'Saturn', 'Uranus', 'Neptune'
7 | ]
8 |
9 | $scope.$on 'dragToReorder.reordered', ($event, reordered) ->
10 | console.log "Moved #{reordered.item} from #{reordered.from} to #{reordered.to}"
11 |
--------------------------------------------------------------------------------
/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "angular-drag-to-reorder",
3 | "version": "0.0.2",
4 | "description": "Drag items in an ng-repeat to reorder them",
5 | "keywords": [
6 | "angular",
7 | "drag",
8 | "reorder",
9 | "ng-repeat",
10 | "directive"
11 | ],
12 | "license": "MIT",
13 | "homepage": "https://github.com/brandly/angular-drag-to-reorder",
14 | "main": "dist/angular-drag-to-reorder.js"
15 | }
16 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "angular-drag-to-reorder",
3 | "description": "Drag items in an ng-repeat to reorder them",
4 | "author": "Matthew Brandly",
5 | "version": "0.0.2",
6 | "homepage": "http://brandly.github.io/angular-drag-to-reorder/",
7 | "private": true,
8 | "devDependencies": {
9 | "gulp": "~3.5.1",
10 | "gulp-concat": "^2.1.7",
11 | "gulp-htmlbuild": "^0.1.1",
12 | "gulp-util": "~2.2.14",
13 | "gulp-coffee": "~1.4.1",
14 | "gulp-sass": "^0.6.0",
15 | "gulp-uglify": "~0.2.0",
16 | "gulp-minify-css": "~0.3.0",
17 | "express": "^3.4.8",
18 | "path": "~0.4.9",
19 | "gulp-rename": "^1.2.0",
20 | "gulp-header": "^1.2.2"
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (C) 2014 Matthew Brandly.
2 |
3 | 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:
4 |
5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6 |
7 | 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.
8 |
--------------------------------------------------------------------------------
/demo/styles/style.scss:
--------------------------------------------------------------------------------
1 | @import 'reset';
2 | *,
3 | *:after,
4 | *:before {
5 | box-sizing: border-box;
6 | }
7 |
8 | body {
9 | font-family: "HelveticaNeue-Light", "Helvetica Neue Light", "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif;
10 | text-align: center;
11 | color: #333;
12 | }
13 |
14 | h1 {
15 | font-size: 36px;
16 | line-height: 40px;
17 | }
18 |
19 | .content {
20 | margin: 40px auto;
21 | max-width: 300px;
22 | }
23 |
24 | .example {
25 | margin: 20px 0;
26 | }
27 |
28 | $border-style: 1px solid #ddd;
29 | $base-padding: 8px;
30 |
31 | li {
32 | padding: $base-padding;
33 | border-bottom: $border-style;
34 | line-height: 1.2;
35 |
36 | &:first-child {
37 | border-top: $border-style;
38 | }
39 | }
40 |
41 | .octocat {
42 | display: block;
43 | width: 32px;
44 | margin: 0 auto;
45 | }
46 |
47 | /*
48 | * dragging stuff
49 | */
50 |
51 | .dragging {
52 | opacity: 0.3;
53 | }
54 |
55 | .dropping {
56 | background-color: rgba(231, 237, 242, 0.63);
57 | }
58 |
59 | .dropping-above {
60 | padding-top: $base-padding * 2;
61 | }
62 |
63 | .dropping-below {
64 | padding-bottom: $base-padding * 2;
65 | }
66 |
--------------------------------------------------------------------------------
/demo/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Drag to Reorder
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
Drag to Reorder
16 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/demo/styles/reset.scss:
--------------------------------------------------------------------------------
1 | /* http://meyerweb.com/eric/tools/css/reset/
2 | v2.0 | 20110126
3 | License: none (public domain)
4 | */
5 |
6 | html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn, em, img, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var, b, u, i, center, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td, article, aside, canvas, details, embed, figure, figcaption, footer, header, hgroup, menu, nav, output, ruby, section, summary, time, mark, audio, video {
7 | margin: 0;
8 | padding: 0;
9 | border: 0;
10 | font-size: 100%;
11 | font: inherit;
12 | vertical-align: baseline; }
13 |
14 | /* HTML5 display-role reset for older browsers */
15 |
16 | article, aside, details, figcaption, figure, footer, header, hgroup, menu, nav, section {
17 | display: block; }
18 |
19 | body {
20 | line-height: 1; }
21 |
22 | ol, ul {
23 | list-style: none; }
24 |
25 | blockquote, q {
26 | quotes: none; }
27 |
28 | blockquote {
29 | &:before, &:after {
30 | content: '';
31 | content: none; } }
32 |
33 | q {
34 | &:before, &:after {
35 | content: '';
36 | content: none; } }
37 |
38 | table {
39 | border-collapse: collapse;
40 | border-spacing: 0; }
41 |
--------------------------------------------------------------------------------
/dist/angular-drag-to-reorder.min.js:
--------------------------------------------------------------------------------
1 | /*
2 | angular-drag-to-reorder v0.0.2
3 | http://brandly.github.io/angular-drag-to-reorder/
4 | */
5 | (function(){angular.module("mb-dragToReorder",[]).directive("dragToReorder",[function(){return{link:function(r,e,a){var o,n,t,d,s,i;if(null==r[a.dragToReorder])throw"Must specify the list to reorder";return n="dragging",e.attr("draggable",!0),e.on("dragstart",function(a){return e.addClass(n),a.dataTransfer.setData("text/plain",r.$index)}),e.on("dragend",function(){return e.removeClass(n)}),i="dropping",d="dropping-above",s="dropping-below",o=function(r){var a;return r.preventDefault(),a=r.offsetY||r.layerY,an)for(l=v=n;u>v;l=v+=1)g[l]=g[l+1];else if(n>u)for(l=p=n;p>u;l=p+=-1)g[l]=g[l-1];return g[u]=f,r.$apply(function(){return r.$emit("dragToReorder.reordered",{array:g,item:f,from:n,to:u})}),e.removeClass(i),e.removeClass(d),e.removeClass(s),e.off("drop",t)},e.on("dragenter",function(){return e.hasClass(n)?void 0:(e.addClass(i),e.on("dragover",o),e.on("drop",t))}),e.on("dragleave",function(){return e.removeClass(i),e.removeClass(d),e.removeClass(s),e.off("dragover",o),e.off("drop",t)})}}}])}).call(this);
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Angular Drag to Reorder
2 |
3 | > Drag items in an ng-repeat to reorder them
4 |
5 | ```shell
6 | $ bower install --save angular-drag-to-reorder
7 | ```
8 |
9 | I wrote this for [another small project](http://brandly.github.io/thelist/#/), and I figured someone else might find it useful. It's probably far less stable than something like [UI Sortable](https://github.com/angular-ui/ui-sortable), but that has all kinds of dependencies. This just needs Angular.
10 |
11 | There aren't many frills, but it seems to do the job in up-to-date browsers.
12 |
13 | ## But how?
14 |
15 | Add `drag-to-reorder` alongside your `ng-repeat` and specify the name of the collection.
16 |
17 | ```html
18 |
21 | ```
22 |
23 | And it should Just Work.
24 |
25 | ## What else?
26 |
27 | ### Classes
28 |
29 | When dragging and dropping elements, some classes will be added to those elements, so you can style accordingly.
30 |
31 | The element being dragged will have a `dragging` class on it.
32 |
33 | The element that is being hovered over by a dragged element will have a `dropping` class. More specifically, you'll see a `dropping-above` or `dropping-below` class on there, depending on where the dragged element will end up after being dropped.
34 |
35 | ### Events
36 |
37 | There's just one. When the list gets reordered, `dragToReorder.reordered` will fire, passing you some relevant data.
38 |
39 | ```js
40 | $scope.$on('dragToReorder.reordered', function ($event, reordered) {
41 | // The list being reordered
42 | reordered.array
43 |
44 | // The item that was relocated
45 | reordered.item
46 |
47 | // The initial index of that item
48 | reordered.from
49 |
50 | // The index where it ended up
51 | reordered.to
52 | });
53 | ```
54 |
55 | ### Demo
56 |
57 | [Look!](http://brandly.github.io/angular-drag-to-reorder/)
58 |
59 | ### Development
60 |
61 | Get your dependencies
62 | ```shell
63 | $ npm install
64 | ```
65 |
66 | And use `gulp` to build, watch, and host the project.
67 | ```shell
68 | $ gulp
69 | ```
70 |
--------------------------------------------------------------------------------
/gulpfile.js:
--------------------------------------------------------------------------------
1 | var
2 | gulp = require('gulp'),
3 | coffee = require('gulp-coffee'),
4 | concat = require('gulp-concat'),
5 | sass = require('gulp-sass'),
6 | gutil = require('gulp-util'),
7 | uglify = require('gulp-uglify'),
8 | minify = require('gulp-minify-css'),
9 | rename = require('gulp-rename'),
10 | header = require('gulp-header'),
11 | path = require('path'),
12 | express = require('express'),
13 | package = require('./package.json'),
14 |
15 | build = gutil.env.gh ? './gh-pages/' : './build/',
16 |
17 | banner = [
18 | '/*',
19 | ' <%= package.name %> v<%= package.version %>',
20 | ' <%= package.homepage %>',
21 | '*/',
22 | ''
23 | ].join('\n'),
24 | libFileName = 'angular-drag-to-reorder.js';
25 |
26 | function onError(err) {
27 | gutil.log(err);
28 | gutil.beep();
29 | this.emit('end');
30 | }
31 |
32 | gulp.task('coffee:lib', function () {
33 | return gulp.src('src/angular-drag-to-reorder.coffee')
34 | .pipe(coffee())
35 | .on('error', onError)
36 | .pipe(concat(libFileName))
37 | .pipe(gulp.dest(build))
38 | //dist
39 | .pipe(header(banner, {package: package}))
40 | .pipe(gulp.dest('dist/'))
41 | .pipe(uglify())
42 | .pipe(rename(function (path) {
43 | path.basename += '.min';
44 | }))
45 | .pipe(header(banner, {package: package}))
46 | .pipe(gulp.dest('dist/'));
47 | });
48 |
49 | gulp.task('coffee:demo', function () {
50 | return gulp.src('demo/**/*.coffee')
51 | .pipe(coffee())
52 | .on('error', onError)
53 | .pipe(concat('demo.js'))
54 | .pipe(gulp.dest(build));
55 | });
56 |
57 | gulp.task('coffee', ['coffee:lib', 'coffee:demo']);
58 |
59 | gulp.task('sass', function () {
60 | return gulp.src('demo/styles/style.scss')
61 | .pipe(sass())
62 | .on('error', onError)
63 | .pipe(concat('demo.css'))
64 | .pipe(gulp.dest(build));
65 | });
66 |
67 | gulp.task('images', function () {
68 | return gulp.src('demo/images/*')
69 | .pipe(gulp.dest(build));
70 | });
71 |
72 | gulp.task('index', function () {
73 | return gulp.src('demo/index.html')
74 | .pipe(gulp.dest(build));
75 | });
76 |
77 | gulp.task('build', [
78 | 'index',
79 | 'coffee',
80 | 'sass',
81 | 'images'
82 | ]);
83 |
84 | gulp.task('default', ['build'], function () {
85 | if (!gutil.env.gh) {
86 | gulp.watch(['src/**', 'demo/**'], ['build']);
87 |
88 | var
89 | app = express(),
90 | port = 8888;
91 | app.use(express.static(path.resolve(build)));
92 | app.listen(port, function() {
93 | gutil.log('Listening on', port);
94 | });
95 | }
96 | });
97 |
--------------------------------------------------------------------------------
/src/angular-drag-to-reorder.coffee:
--------------------------------------------------------------------------------
1 | angular.module('mb-dragToReorder', [])
2 | .directive('dragToReorder', [ ->
3 | link: (scope, element, attrs) ->
4 | unless scope[attrs.dragToReorder]?
5 | throw 'Must specify the list to reorder'
6 | ###
7 | drag stuff
8 | ###
9 |
10 | draggingClassName = 'dragging'
11 | element.attr 'draggable', true
12 |
13 | element.on 'dragstart', (e) ->
14 | element.addClass draggingClassName
15 | e.dataTransfer.setData 'text/plain', scope.$index
16 |
17 | element.on 'dragend', ->
18 | element.removeClass draggingClassName
19 |
20 | ###
21 | drop stuff
22 | ###
23 |
24 | droppingClassName = 'dropping'
25 | droppingAboveClassName = 'dropping-above'
26 | droppingBelowClassName = 'dropping-below'
27 |
28 | dragOverHandler = (e) ->
29 | e.preventDefault()
30 | offsetY = e.offsetY or e.layerY
31 |
32 | # above halfway
33 | if offsetY < (@offsetHeight / 2)
34 | element.removeClass droppingBelowClassName
35 | element.addClass droppingAboveClassName
36 | # below
37 | else
38 | element.removeClass droppingAboveClassName
39 | element.addClass droppingBelowClassName
40 |
41 | dropHandler = (e) ->
42 | e.preventDefault()
43 | droppedItemIndex = parseInt e.dataTransfer.getData('text/plain'), 10
44 | theList = scope[attrs.dragToReorder]
45 |
46 | newIndex = null
47 | # dropping item above us
48 | if element.hasClass droppingAboveClassName
49 | if droppedItemIndex < scope.$index
50 | # dropped item was already above us,
51 | # so now it'll be one index above
52 | newIndex = scope.$index - 1
53 | else
54 | # since it's moving from below to above us,
55 | # it'll need to take our index
56 | newIndex = scope.$index
57 |
58 | # dropping item below us
59 | else
60 | if droppedItemIndex < scope.$index
61 | # moving above to below
62 | newIndex = scope.$index
63 | else
64 | # still below
65 | newIndex = scope.$index + 1
66 |
67 | itemToMove = theList[droppedItemIndex]
68 | # moving down the list
69 | if newIndex > droppedItemIndex
70 | # move all items below it...
71 | for i in [droppedItemIndex...newIndex] by 1
72 | # ...up one spot
73 | theList[i] = theList[i + 1]
74 |
75 | # moving up the list
76 | else if newIndex < droppedItemIndex
77 | for i in [droppedItemIndex...newIndex] by -1
78 | theList[i] = theList[i - 1]
79 |
80 | # put it in its new home
81 | theList[newIndex] = itemToMove
82 | # let angular rearrange the DOM
83 | scope.$apply ->
84 | # and let em know
85 | scope.$emit 'dragToReorder.reordered',
86 | array: theList
87 | item: itemToMove
88 | from: droppedItemIndex
89 | to: newIndex
90 |
91 | # cleanup
92 | element.removeClass droppingClassName
93 | element.removeClass droppingAboveClassName
94 | element.removeClass droppingBelowClassName
95 | element.off 'drop', dropHandler
96 |
97 | element.on 'dragenter', (e) ->
98 | # make sure we're not dropping on the dragged element
99 | return if element.hasClass draggingClassName
100 |
101 | element.addClass droppingClassName
102 | element.on 'dragover', dragOverHandler
103 | element.on 'drop', dropHandler
104 |
105 | element.on 'dragleave', (e) ->
106 | element.removeClass droppingClassName
107 | element.removeClass droppingAboveClassName
108 | element.removeClass droppingBelowClassName
109 | element.off 'dragover', dragOverHandler
110 | element.off 'drop', dropHandler
111 | ])
112 |
--------------------------------------------------------------------------------
/dist/angular-drag-to-reorder.js:
--------------------------------------------------------------------------------
1 | /*
2 | angular-drag-to-reorder v0.0.2
3 | http://brandly.github.io/angular-drag-to-reorder/
4 | */
5 | (function() {
6 | angular.module('mb-dragToReorder', []).directive('dragToReorder', [
7 | function() {
8 | return {
9 | link: function(scope, element, attrs) {
10 | var dragOverHandler, draggingClassName, dropHandler, droppingAboveClassName, droppingBelowClassName, droppingClassName;
11 | if (scope[attrs.dragToReorder] == null) {
12 | throw 'Must specify the list to reorder';
13 | }
14 |
15 | /*
16 | drag stuff
17 | */
18 | draggingClassName = 'dragging';
19 | element.attr('draggable', true);
20 | element.on('dragstart', function(e) {
21 | element.addClass(draggingClassName);
22 | return e.dataTransfer.setData('text/plain', scope.$index);
23 | });
24 | element.on('dragend', function() {
25 | return element.removeClass(draggingClassName);
26 | });
27 |
28 | /*
29 | drop stuff
30 | */
31 | droppingClassName = 'dropping';
32 | droppingAboveClassName = 'dropping-above';
33 | droppingBelowClassName = 'dropping-below';
34 | dragOverHandler = function(e) {
35 | var offsetY;
36 | e.preventDefault();
37 | offsetY = e.offsetY || e.layerY;
38 | if (offsetY < (this.offsetHeight / 2)) {
39 | element.removeClass(droppingBelowClassName);
40 | return element.addClass(droppingAboveClassName);
41 | } else {
42 | element.removeClass(droppingAboveClassName);
43 | return element.addClass(droppingBelowClassName);
44 | }
45 | };
46 | dropHandler = function(e) {
47 | var droppedItemIndex, i, itemToMove, newIndex, theList, _i, _j;
48 | e.preventDefault();
49 | droppedItemIndex = parseInt(e.dataTransfer.getData('text/plain'), 10);
50 | theList = scope[attrs.dragToReorder];
51 | newIndex = null;
52 | if (element.hasClass(droppingAboveClassName)) {
53 | if (droppedItemIndex < scope.$index) {
54 | newIndex = scope.$index - 1;
55 | } else {
56 | newIndex = scope.$index;
57 | }
58 | } else {
59 | if (droppedItemIndex < scope.$index) {
60 | newIndex = scope.$index;
61 | } else {
62 | newIndex = scope.$index + 1;
63 | }
64 | }
65 | itemToMove = theList[droppedItemIndex];
66 | if (newIndex > droppedItemIndex) {
67 | for (i = _i = droppedItemIndex; _i < newIndex; i = _i += 1) {
68 | theList[i] = theList[i + 1];
69 | }
70 | } else if (newIndex < droppedItemIndex) {
71 | for (i = _j = droppedItemIndex; _j > newIndex; i = _j += -1) {
72 | theList[i] = theList[i - 1];
73 | }
74 | }
75 | theList[newIndex] = itemToMove;
76 | scope.$apply(function() {
77 | return scope.$emit('dragToReorder.reordered', {
78 | array: theList,
79 | item: itemToMove,
80 | from: droppedItemIndex,
81 | to: newIndex
82 | });
83 | });
84 | element.removeClass(droppingClassName);
85 | element.removeClass(droppingAboveClassName);
86 | element.removeClass(droppingBelowClassName);
87 | return element.off('drop', dropHandler);
88 | };
89 | element.on('dragenter', function(e) {
90 | if (element.hasClass(draggingClassName)) {
91 | return;
92 | }
93 | element.addClass(droppingClassName);
94 | element.on('dragover', dragOverHandler);
95 | return element.on('drop', dropHandler);
96 | });
97 | return element.on('dragleave', function(e) {
98 | element.removeClass(droppingClassName);
99 | element.removeClass(droppingAboveClassName);
100 | element.removeClass(droppingBelowClassName);
101 | element.off('dragover', dragOverHandler);
102 | return element.off('drop', dropHandler);
103 | });
104 | }
105 | };
106 | }
107 | ]);
108 |
109 | }).call(this);
110 |
--------------------------------------------------------------------------------