├── .gitignore ├── LICENSE ├── README.md ├── bower.json ├── build └── js │ ├── ng-draggable-widgets.js │ └── ng-draggable-widgets.min.js ├── demo ├── drag_groups │ ├── demo.css │ ├── demo.js │ └── index.html ├── nested_divs │ ├── demo.css │ ├── demo.js │ └── index.html ├── percentage_widths │ ├── demo.css │ ├── demo.js │ └── index.html └── simple_dragging │ ├── demo.css │ ├── demo.js │ └── index.html ├── gulpfile.js ├── index.html ├── karma.conf.js ├── karma ├── hit_zones.js ├── ng-draggable-widgets.testable.js ├── placeholder.js └── widget.js ├── package.json └── source ├── demo ├── drag_groups │ ├── demo.js │ ├── demo.scss │ └── index.jade ├── nested_divs │ ├── demo.js │ ├── demo.scss │ └── index.jade ├── percentage_widths │ ├── demo.js │ ├── demo.scss │ └── index.jade └── simple_dragging │ ├── demo.js │ ├── demo.scss │ └── index.jade └── js └── ng-draggable-widgets.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # node-waf configuration 20 | .lock-wscript 21 | 22 | # Compiled binary addons (http://nodejs.org/api/addons.html) 23 | build/Release 24 | 25 | # Dependency directory 26 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 27 | node_modules 28 | 29 | # Bower components 30 | public/components/* 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Nicholas Johnson 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ng draggable widgets 2 | 3 | Draggable widgets for Angular. No jQuery dependency. 4 | 5 | ## Demo 6 | 7 | * [Demo index](http://forwardadvance.github.io/ng-draggable-widgets/) 8 | * [Simple Widget Dragging](http://forwardadvance.github.io/ng-draggable-widgets/demo/simple_dragging/) 9 | * [Dragging between groups](http://forwardadvance.github.io/ng-draggable-widgets/demo/drag_groups/) 10 | * [Widths can also be specified as percentages](http://forwardadvance.github.io/ng-draggable-widgets/demo/percentage_widths/) 11 | * [Dragged widgets can include complex arbitrary HTML](http://forwardadvance.github.io/ng-draggable-widgets/demo/nested_divs/) 12 | 13 | 14 | ## Usage 15 | 16 | Each draggable widget gets an attribute of draggable-widget and must contain an element with a draggable-widget-handle attribute. 17 | 18 | 19 |
20 |
21 |
drag handle
22 | Widget content 23 |
24 |
25 | 26 | 27 | Dragging the handle sets position absolute on the element causing it to drop out of the layout flow. On completion the callback is executed. 28 | 29 | ## Drag-group 30 | 31 | If you want automatic updating you must specify at least one drag group that points to an array in scope. 32 | 33 | ## Callback 34 | 35 | Specify a callback function with the draggable-widget-callback attribute. The callback will receive a drag object that contains references to the old and new arrays (assuming you have dragged between groups, and the insertion point. See the demo app for examples. 36 | 37 | ## Classes 38 | 39 | * Elements gain a class of dragging while being dragged. 40 | 41 | ## The Placeholder 42 | 43 | A div with a class of placeholder will be inserted into the DOM at the drop point. This will have the correct width and height. You may wish to style this div to match your widgets. 44 | 45 | ## Browser compatibility 46 | 47 | IE9+ only please. Mobile support not guaranteed. 48 | 49 | ## Licence 50 | 51 | MIT license, use it as you see fit. I'm not going to sue you. 52 | 53 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ng-drag-to-order", 3 | "main": "ngDragOrder.js", 4 | "version": "0.0.1", 5 | "homepage": "https://github.com/forwardadvance/ngDraggable", 6 | "ignore": [ 7 | "**/.*", 8 | "node_modules", 9 | "bower_components", 10 | "test", 11 | "tests" 12 | ], 13 | "dependencies": { 14 | "angular": "latest", 15 | "angular-mocks": "latest" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /build/js/ng-draggable-widgets.js: -------------------------------------------------------------------------------- 1 | // Nicholas Johnson (www.nicholasjohnson.com) 2 | // Forward Advance Training (www.forwardadvance.com) 3 | // MIT licence 4 | 5 | (function() { 6 | // Hitzones are positioned on the left and right of widgets 7 | // We call check to see if the current drag location is in a hitzone 8 | // If so we move the placeholder and update the drag destination. 9 | var hitZones = { 10 | size: 10, 11 | check: function(e) { 12 | var i, groups, widgets, widget, rect, width; 13 | groups = document.querySelectorAll('[drag-group]'); 14 | for (var j = 0; j < groups.length; j++) { 15 | widgets = groups[j].querySelectorAll('[draggable-widget]:not(.dragging)'); 16 | for (i = 0; i < widgets.length; i++) { 17 | widget = widgets[i]; 18 | rect = widget.getBoundingClientRect(); 19 | width = widget.offsetWidth; 20 | if ((e.pageY > rect.top) && 21 | (e.pageY < rect.bottom) && 22 | (e.pageX > rect.left) && 23 | (e.pageX < rect.right - (width / 2))) { 24 | placeholder.insertBefore(widget); 25 | console.log(i); 26 | return { 27 | group: angular.element(widget).scope().dragGroup, 28 | index: i 29 | }; 30 | } 31 | if ((e.pageY > rect.top) && 32 | (e.pageY < rect.bottom) && 33 | (e.pageX > rect.left + (width / 2)) && 34 | (e.pageX < rect.right)) { 35 | placeholder.insertAfter(widget); 36 | console.log(i+1); 37 | return { 38 | group: angular.element(widget).scope().dragGroup, 39 | index: i+1 40 | }; 41 | } 42 | } 43 | } 44 | } 45 | }; 46 | 47 | var widget = { 48 | initDrag: function(el) { 49 | el.addClass('dragging'); 50 | el.css({ 51 | position:'absolute', 52 | 'z-index':100000 53 | }); 54 | }, 55 | setPosition: function(el, x, y, offsetX, offsetY) { 56 | el.css({ 57 | left: x - offsetX - 20 + 'px', 58 | top: y - offsetY - 20 + 'px' 59 | }); 60 | }, 61 | stopDrag: function(el) { 62 | el.removeClass('dragging'); 63 | el.css({ 64 | position:'', 65 | left: '', 66 | top: '', 67 | 'z-index': '' 68 | }); 69 | }, 70 | lockSize: function(el) { 71 | el.css({ 72 | width: el[0].offsetWidth + 'px', 73 | height: el[0].offsetHeight + 'px' 74 | }); 75 | }, 76 | unlockSize: function(el) { 77 | el.css({ 78 | width: '', 79 | height: '' 80 | }); 81 | } 82 | }; 83 | 84 | // The placeholder object is a little rectangle with the same dimensions as the dragged widget 85 | // It gives the user a preview of page layout. 86 | // We can show or hide it, and insert it before or after another widget. 87 | var placeholder = { 88 | el: angular.element([ 89 | '
', 90 | '
' 91 | ].join('')), 92 | show: function(el) { 93 | placeholder.el[0].hidden = false; 94 | placeholder.el.css({ 95 | width: el[0].clientWidth +'px', 96 | height: el[0].clientHeight +'px' 97 | }); 98 | el.after(placeholder.el); 99 | }, 100 | hide: function() { 101 | placeholder.el[0].hidden = true; 102 | }, 103 | insertBefore: function(el) { 104 | el.parentElement.insertBefore(placeholder.el[0], el); 105 | }, 106 | insertAfter: function(el) { 107 | angular.element(el).after(placeholder.el); 108 | } 109 | }; 110 | 111 | // controller for the drag directive. 112 | // initialises the drag 113 | var dragController = /*@ngInject*/["$scope", function($scope) { 114 | var drag = $scope.drag = {}; 115 | drag.start = function(e) { 116 | drag.initDrag(e); 117 | }; 118 | drag.end = function() { 119 | drag.destroyDrag(); 120 | }; 121 | }]; 122 | dragController.$inject = ["$scope"]; 123 | 124 | angular.module('ng-draggable-widgets', []) 125 | .directive('dragGroup', ["$parse", function($parse) { 126 | return { 127 | scope:true, 128 | restrict: 'A', 129 | link: { 130 | pre: function(scope, el, attrs) { 131 | scope.dragGroup = {}; 132 | scope.dragGroup = $parse(attrs.dragGroup)(scope); 133 | } 134 | } 135 | }; 136 | }]) 137 | 138 | .directive('draggableWidget', ["$rootScope", function($rootScope) { 139 | return { 140 | scope:true, 141 | restrict: 'A', 142 | controller: dragController, 143 | link: { 144 | pre: function(scope, el, attrs) { 145 | var drag = scope.drag; 146 | 147 | drag.callback = attrs.draggableWidgetCallback; 148 | 149 | // Set up the DOM for dragging 150 | drag.initDrag = function(e) { 151 | var dest; 152 | console.log('start drag'); 153 | drag.offsetX = e.offsetX; 154 | drag.offsetY = e.offsetY; 155 | widget.lockSize(el); 156 | widget.initDrag(el); 157 | widget.setPosition(el, e.pageX, e.pageY, scope.drag.offsetX, scope.drag.offsetY); 158 | placeholder.show(el); 159 | scope.drag.source = { 160 | group: scope.dragGroup, 161 | index: scope.$index 162 | }; 163 | angular.element(window).on('mousemove', function(e) { 164 | widget.setPosition(el, e.pageX, e.pageY, scope.drag.offsetX, scope.drag.offsetY); 165 | dest = hitZones.check(e); 166 | if (dest) { 167 | scope.drag.dest = dest; 168 | console.log('dest', dest); 169 | } 170 | }); 171 | }; 172 | 173 | // Unset the DOM for dragging 174 | scope.drag.destroyDrag = function() { 175 | var obj; 176 | console.log('stop drag'); 177 | angular.element(window).off('mousemove'); 178 | widget.stopDrag(el); 179 | widget.unlockSize(el); 180 | placeholder.hide(); 181 | if (scope.drag.dest) { 182 | obj = scope.drag.source.group[scope.drag.source.index]; 183 | scope.drag.source.group.splice(scope.drag.source.index, 1); 184 | scope.drag.dest.group.splice(scope.drag.dest.index, 0, obj); 185 | } 186 | if (drag.callback) { 187 | scope[drag.callback](scope.drag); 188 | } 189 | $rootScope.$apply(); 190 | }; 191 | } 192 | } 193 | }; 194 | }]) 195 | 196 | .directive('draggableWidgetHandle', function() { 197 | return { 198 | scope:true, 199 | restrict: 'A', 200 | link: { 201 | pre: function(scope, el) { 202 | el.on('mousedown', function(e) { 203 | scope.drag.initDrag(e); 204 | angular.element(document).one('mouseup', function(e) { 205 | scope.drag.destroyDrag(e); 206 | }); 207 | }); 208 | } 209 | } 210 | }; 211 | }); 212 | 213 | })(); -------------------------------------------------------------------------------- /build/js/ng-draggable-widgets.min.js: -------------------------------------------------------------------------------- 1 | // Nicholas Johnson (www.nicholasjohnson.com) 2 | // Forward Advance Training (www.forwardadvance.com) 3 | // MIT licence 4 | 5 | !function(){var e={size:10,check:function(e){var t,r,n,i,a,g;r=document.querySelectorAll("[drag-group]");for(var d=0;da.top&&e.pageYa.left&&e.pageXa.top&&e.pageYa.left+g/2&&e.pageX',""].join("")),show:function(e){o.el[0].hidden=!1,o.el.css({width:e[0].clientWidth+"px",height:e[0].clientHeight+"px"}),e.after(o.el)},hide:function(){o.el[0].hidden=!0},insertBefore:function(e){e.parentElement.insertBefore(o.el[0],e)},insertAfter:function(e){angular.element(e).after(o.el)}},r=["$scope",function(e){var t=e.drag={};t.start=function(e){t.initDrag(e)},t.end=function(){t.destroyDrag()}}];r.$inject=["$scope"],angular.module("ng-draggable-widgets",[]).directive("dragGroup",["$parse",function(e){return{scope:!0,restrict:"A",link:{pre:function(t,o,r){t.dragGroup={},t.dragGroup=e(r.dragGroup)(t)}}}}]).directive("draggableWidget",["$rootScope",function(n){return{scope:!0,restrict:"A",controller:r,link:{pre:function(r,i,a){var g=r.drag;g.callback=a.draggableWidgetCallback,g.initDrag=function(n){var a;console.log("start drag"),g.offsetX=n.offsetX,g.offsetY=n.offsetY,t.lockSize(i),t.initDrag(i),t.setPosition(i,n.pageX,n.pageY,r.drag.offsetX,r.drag.offsetY),o.show(i),r.drag.source={group:r.dragGroup,index:r.$index},angular.element(window).on("mousemove",function(o){t.setPosition(i,o.pageX,o.pageY,r.drag.offsetX,r.drag.offsetY),a=e.check(o),a&&(r.drag.dest=a,console.log("dest",a))})},r.drag.destroyDrag=function(){var e;console.log("stop drag"),angular.element(window).off("mousemove"),t.stopDrag(i),t.unlockSize(i),o.hide(),r.drag.dest&&(e=r.drag.source.group[r.drag.source.index],r.drag.source.group.splice(r.drag.source.index,1),r.drag.dest.group.splice(r.drag.dest.index,0,e)),g.callback&&r[g.callback](r.drag),n.$apply()}}}}}]).directive("draggableWidgetHandle",function(){return{scope:!0,restrict:"A",link:{pre:function(e,t){t.on("mousedown",function(t){e.drag.initDrag(t),angular.element(document).one("mouseup",function(t){e.drag.destroyDrag(t)})})}}}})}(); -------------------------------------------------------------------------------- /demo/drag_groups/demo.css: -------------------------------------------------------------------------------- 1 | html { 2 | box-sizing: border-box; } 3 | 4 | *, *:before, *:after { 5 | box-sizing: inherit; } 6 | 7 | body { 8 | width: 800px; 9 | margin: 0 auto; 10 | font-family: sans-serif; 11 | color: #fff; 12 | font-size: 100%; } 13 | 14 | .group-one, .group-two { 15 | width: 380px; 16 | background: grey; 17 | margin: 10px; 18 | padding: 10px; } 19 | 20 | .group-one { 21 | float: left; } 22 | 23 | .group-two { 24 | float: right; } 25 | 26 | div[draggable-widget] { 27 | width: 100px; 28 | height: 200px; 29 | border-radius: 5px; 30 | padding: 10px; 31 | float: left; 32 | margin: 10px; } 33 | div[draggable-widget].woks { 34 | background: aqua; } 35 | div[draggable-widget].mocks { 36 | background: cadetblue; } 37 | div[draggable-widget].socks { 38 | background: red; 39 | width: 220px; } 40 | div[draggable-widget].pops { 41 | background: darkseagreen; } 42 | div[draggable-widget].hocks { 43 | background: darkred; } 44 | 45 | div.widget-placeholder { 46 | border: 1px solid red; 47 | float: left; 48 | height: 200px; 49 | margin: 10px; } 50 | 51 | div[draggable-widget-handle] { 52 | border: 3px solid white; 53 | padding: 10px; 54 | background: rgba(0, 0, 0, 0.3); 55 | border-radius: 3px; 56 | color: white; 57 | margin-bottom: 1em; 58 | cursor: move; } 59 | -------------------------------------------------------------------------------- /demo/drag_groups/demo.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 3 | var widgetGroups = [ 4 | [ 5 | { 6 | title:'Cats with Woks', 7 | class: 'woks' 8 | }, 9 | { 10 | title:'Socks on Sticks', 11 | class: 'socks' 12 | }, 13 | { 14 | title:'Mocks of Macs', 15 | class: 'mocks' 16 | }, 17 | { 18 | title:'Pops in Pumps', 19 | class: 'pops' 20 | }, 21 | { 22 | title:'Hocks of Rumps', 23 | class: 'hocks' 24 | } 25 | ], 26 | [ 27 | { 28 | title:'Hocks of Rumps', 29 | class: 'hocks' 30 | }, 31 | { 32 | title:'Cats with Woks', 33 | class: 'woks' 34 | }, 35 | { 36 | title:'Pops in Pumps', 37 | class: 'pops' 38 | } 39 | ] 40 | ]; 41 | 42 | angular.module('app', ['ng-draggable-widgets']) 43 | .controller('dragController', function($scope) { 44 | $scope.widgetGroups = widgetGroups; 45 | $scope.moveWidget = function(drag) { 46 | console.log(drag); 47 | } 48 | }); 49 | })(); 50 | -------------------------------------------------------------------------------- /demo/drag_groups/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Widget dragging demo 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 |
13 |
drag
14 |

{{widget.title}}

15 |
16 |
17 |
18 |
19 |
drag
20 |

{{widget.title}}

21 |
22 |
23 | 24 | 25 | -------------------------------------------------------------------------------- /demo/nested_divs/demo.css: -------------------------------------------------------------------------------- 1 | html { 2 | box-sizing: border-box; } 3 | 4 | *, *:before, *:after { 5 | box-sizing: inherit; } 6 | 7 | body { 8 | font-family: sans-serif; 9 | color: #fff; 10 | font-size: 100%; 11 | width: 900px; 12 | margin: 0 auto; } 13 | 14 | div[draggable-widget] { 15 | width: 200px; 16 | height: 300px; 17 | border-radius: 5px; 18 | padding: 10px; 19 | float: left; 20 | margin: 10px; } 21 | div[draggable-widget].woks { 22 | background: aqua; } 23 | div[draggable-widget].mocks { 24 | background: cadetblue; } 25 | div[draggable-widget].socks { 26 | background: red; 27 | width: 420px; } 28 | div[draggable-widget].pops { 29 | background: darkseagreen; } 30 | div[draggable-widget].hocks { 31 | background: darkred; } 32 | 33 | div.widget-placeholder { 34 | border: 1px solid red; 35 | float: left; 36 | height: 300px; 37 | margin: 10px; } 38 | 39 | div[draggable-widget-handle] { 40 | border: 3px solid white; 41 | padding: 10px; 42 | background: rgba(0, 0, 0, 0.3); 43 | border-radius: 3px; 44 | color: white; 45 | margin-bottom: 1em; 46 | cursor: move; } 47 | -------------------------------------------------------------------------------- /demo/nested_divs/demo.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 3 | var widgets = [ 4 | { 5 | title:'Cats with Woks', 6 | class: 'woks' 7 | }, 8 | { 9 | title:'Socks on Sticks', 10 | class: 'socks' 11 | }, 12 | { 13 | title:'Mocks of Macs', 14 | class: 'mocks' 15 | }, 16 | { 17 | title:'Pops in Pumps', 18 | class: 'pops' 19 | }, 20 | { 21 | title:'Hocks of Rumps', 22 | class: 'hocks' 23 | } 24 | ]; 25 | 26 | angular.module('app', ['ng-draggable-widgets']) 27 | .controller('dragController', function($scope) { 28 | $scope.widgets = widgets; 29 | $scope.moveWidget = function(drag) { 30 | console.log(drag); 31 | } 32 | }); 33 | })(); 34 | -------------------------------------------------------------------------------- /demo/nested_divs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Widget dragging demo 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 |
13 |
14 |
15 |
16 |
17 |
drag
18 |

{{widget.title}}

19 |
20 |
21 |
22 |
23 |
24 |
25 | 26 | 27 | -------------------------------------------------------------------------------- /demo/percentage_widths/demo.css: -------------------------------------------------------------------------------- 1 | html { 2 | box-sizing: border-box; } 3 | 4 | *, *:before, *:after { 5 | box-sizing: inherit; } 6 | 7 | body { 8 | font-family: sans-serif; 9 | color: #fff; 10 | font-size: 100%; 11 | width: 900px; 12 | margin: 0 auto; } 13 | 14 | div[draggable-widget] { 15 | width: 33.333%; 16 | height: 300px; 17 | padding: 10px; 18 | float: left; } 19 | div[draggable-widget].woks { 20 | background: aqua; } 21 | div[draggable-widget].mocks { 22 | background: cadetblue; } 23 | div[draggable-widget].socks { 24 | background: red; 25 | width: 66.6666%; } 26 | div[draggable-widget].pops { 27 | background: darkseagreen; } 28 | div[draggable-widget].hocks { 29 | background: darkred; } 30 | 31 | div.widget-placeholder { 32 | background: rgba(0, 0, 0, 0.2); 33 | float: left; 34 | height: 300px; } 35 | 36 | div[draggable-widget-handle] { 37 | padding: 10px; 38 | background: rgba(0, 0, 0, 0.3); 39 | border-radius: 3px; 40 | color: white; 41 | margin-bottom: 1em; 42 | cursor: move; } 43 | -------------------------------------------------------------------------------- /demo/percentage_widths/demo.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 3 | var widgets = [ 4 | { 5 | title:'Cats with Woks', 6 | class: 'woks' 7 | }, 8 | { 9 | title:'Socks on Sticks', 10 | class: 'socks' 11 | }, 12 | { 13 | title:'Mocks of Macs', 14 | class: 'mocks' 15 | }, 16 | { 17 | title:'Pops in Pumps', 18 | class: 'pops' 19 | }, 20 | { 21 | title:'Hocks of Rumps', 22 | class: 'hocks' 23 | } 24 | ]; 25 | 26 | angular.module('app', ['ng-draggable-widgets']) 27 | .controller('dragController', function($scope) { 28 | $scope.widgets = widgets; 29 | $scope.moveWidget = function(drag) { 30 | console.log(drag); 31 | } 32 | }); 33 | })(); 34 | -------------------------------------------------------------------------------- /demo/percentage_widths/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Widget dragging demo 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 |
13 |
drag
14 |

{{widget.title}}

15 |
16 |
17 | 18 | 19 | -------------------------------------------------------------------------------- /demo/simple_dragging/demo.css: -------------------------------------------------------------------------------- 1 | html { 2 | box-sizing: border-box; } 3 | 4 | *, *:before, *:after { 5 | box-sizing: inherit; } 6 | 7 | body { 8 | font-family: sans-serif; 9 | color: #fff; 10 | font-size: 100%; 11 | width: 900px; 12 | margin: 0 auto; } 13 | 14 | div[draggable-widget] { 15 | width: 200px; 16 | height: 300px; 17 | border-radius: 5px; 18 | padding: 10px; 19 | float: left; 20 | margin: 10px; } 21 | div[draggable-widget].woks { 22 | background: aqua; } 23 | div[draggable-widget].mocks { 24 | background: cadetblue; } 25 | div[draggable-widget].socks { 26 | background: red; 27 | width: 420px; } 28 | div[draggable-widget].pops { 29 | background: darkseagreen; } 30 | div[draggable-widget].hocks { 31 | background: darkred; } 32 | 33 | div.widget-placeholder { 34 | border: 1px solid red; 35 | float: left; 36 | height: 300px; 37 | margin: 10px; } 38 | 39 | div[draggable-widget-handle] { 40 | border: 3px solid white; 41 | padding: 10px; 42 | background: rgba(0, 0, 0, 0.3); 43 | border-radius: 3px; 44 | color: white; 45 | margin-bottom: 1em; 46 | cursor: move; } 47 | -------------------------------------------------------------------------------- /demo/simple_dragging/demo.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 3 | var widgets = [ 4 | { 5 | title:'Cats with Woks', 6 | class: 'woks' 7 | }, 8 | { 9 | title:'Socks on Sticks', 10 | class: 'socks' 11 | }, 12 | { 13 | title:'Mocks of Macs', 14 | class: 'mocks' 15 | }, 16 | { 17 | title:'Pops in Pumps', 18 | class: 'pops' 19 | }, 20 | { 21 | title:'Hocks of Rumps', 22 | class: 'hocks' 23 | } 24 | ]; 25 | 26 | angular.module('app', ['ng-draggable-widgets']) 27 | .controller('dragController', function($scope) { 28 | $scope.widgets = widgets; 29 | $scope.moveWidget = function(drag) { 30 | console.log(drag); 31 | } 32 | }); 33 | })(); 34 | -------------------------------------------------------------------------------- /demo/simple_dragging/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Widget dragging demo 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 |
13 |
drag
14 |

{{widget.title}}

15 |
16 |
17 | 18 | 19 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'), 2 | livereload = require('gulp-livereload'), 3 | jshint = require('gulp-jshint'), 4 | concat = require('gulp-concat'), 5 | uglify = require('gulp-uglify'), 6 | rename = require('gulp-rename'), 7 | gutil = require('gulp-util'), 8 | ngAnnotate = require('gulp-ng-annotate'), 9 | beep = require('beepbeep'), 10 | header = require('gulp-header'), 11 | wrap = require("gulp-wrap"), 12 | sass = require('gulp-sass'), 13 | jade = require('gulp-jade'), 14 | minifycss = require('gulp-minify-css'), 15 | autoprefixer = require('gulp-autoprefixer'), 16 | plumber = require('gulp-plumber'); 17 | karma = require('karma').server, 18 | package = require('./package.json'); 19 | 20 | var dirs = { 21 | js: { 22 | src: './source/js', 23 | dest: './build/js' 24 | }, 25 | demo: { 26 | src: './source/demo', 27 | dest: './demo' 28 | }, 29 | test: { 30 | src: './source/js', 31 | dest: './karma' 32 | } 33 | }; 34 | 35 | var jsHeader = [ 36 | '// Nicholas Johnson (www.nicholasjohnson.com)', 37 | '// Forward Advance Training (www.forwardadvance.com)', 38 | '// MIT licence' 39 | ].join('\n'); 40 | 41 | gulp.task('js', function () { 42 | return gulp.src([dirs.js.src, '*.js'].join('/')) 43 | .pipe(wrap('(function() {\n<%= contents %>\n})();')) 44 | .pipe(jshint()) 45 | .pipe(jshint.reporter('default')) 46 | .on('error', beep) 47 | .pipe(concat(package.name + '.js')) 48 | .pipe(ngAnnotate()) 49 | .on('error', gutil.noop) 50 | .pipe(header(jsHeader + '\n\n')) 51 | .pipe(gulp.dest(dirs.js.dest)) 52 | .pipe(rename(package.name + '.min.js')) 53 | .pipe(uglify()) 54 | .on('error', gutil.noop) 55 | .pipe(header(jsHeader + '\n\n')) 56 | .pipe(gulp.dest(dirs.js.dest)) 57 | .pipe(livereload()); 58 | }); 59 | 60 | gulp.task('testJs', function () { 61 | return gulp.src([dirs.test.src, '*.js'].join('/')) 62 | .pipe(wrap( 63 | [ 64 | '(function() {', 65 | '<%= contents %>', 66 | '// Smuggle locals out of closure for testing', 67 | 'window.placeholder = placeholder;', 68 | 'window.hitZones = hitZones;', 69 | 'window.widget = widget;', 70 | '})();' 71 | ].join('\n') 72 | )) 73 | .pipe(concat(package.name + '.testable.js')) 74 | .pipe(ngAnnotate()) 75 | .on('error', gutil.noop) 76 | .pipe(header(jsHeader + '\n\n')) 77 | .pipe(gulp.dest(dirs.test.dest)); 78 | }); 79 | 80 | gulp.task('test', function (done) { 81 | karma.start({ 82 | configFile: __dirname + '/karma.conf.js', 83 | singleRun: true 84 | }, done); 85 | }); 86 | 87 | gulp.task('tdd', function (done) { 88 | karma.start({ 89 | configFile: __dirname + '/karma.conf.js' 90 | }, done); 91 | }); 92 | 93 | gulp.task('demo:jade', function() { 94 | return gulp.src([dirs.demo.src, '**', '*.jade'].join('/')) 95 | .pipe(jade({basedir: __dirname, pretty:true})) 96 | .on('error', gutil.log) 97 | .pipe(gulp.dest(dirs.demo.dest)); 98 | }) 99 | 100 | gulp.task('demo:js', function() { 101 | return gulp.src([dirs.demo.src, '**', '*.js'].join('/')) 102 | .pipe(gulp.dest(dirs.demo.dest)); 103 | }) 104 | 105 | gulp.task('demo:sass', function() { 106 | return gulp.src([dirs.demo.src, '**', '*.scss'].join('/')) 107 | .pipe(sass({ style: 'expanded' })) 108 | .pipe(autoprefixer('last 2 version', 'safari 5', 'ie 8', 'ie 9', 'opera 12.1')) 109 | .on('error', gutil.log) 110 | .pipe(gulp.dest(dirs.demo.dest)); 111 | }) 112 | 113 | gulp.task('demo', ['demo:jade', 'demo:js', 'demo:sass']); 114 | 115 | gulp.task('watch', function() { 116 | gulp.watch([dirs.js.src, '**', '*.js'].join('/'), ['js', 'testJs']); 117 | gulp.watch([dirs.demo.src, '**', '*'].join('/'), ['demo']); 118 | }); 119 | 120 | gulp.task('default', [ 121 | 'js', 122 | 'tdd', 123 | 'watch' 124 | ]); 125 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ng-draggable-widgets-demo 5 | 10 | 11 | 12 |

NG-Draggable Widgets

13 |

A flexible drag and drop module for Angular

14 | 28 | 29 |

30 | Source code: https://github.com/forwardadvance/ng-draggable-widgets 31 |

32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration 2 | // Generated on Thu Apr 23 2015 15:53:13 GMT+0100 (BST) 3 | 4 | module.exports = function(config) { 5 | config.set({ 6 | 7 | // base path that will be used to resolve all patterns (eg. files, exclude) 8 | basePath: '', 9 | 10 | 11 | // frameworks to use 12 | // available frameworks: https://npmjs.org/browse/keyword/karma-adapter 13 | frameworks: ['jasmine'], 14 | 15 | 16 | // list of files / patterns to load in the browser 17 | files: [ 18 | 'public/components/angular/angular.js', 19 | 'public/components/angular-mocks/angular-mocks.js', 20 | 'karma/ng-draggable-widgets.testable.js', 21 | 'karma/*.js' 22 | ], 23 | 24 | 25 | // list of files to exclude 26 | exclude: [ 27 | ], 28 | 29 | 30 | // preprocess matching files before serving them to the browser 31 | // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor 32 | preprocessors: { 33 | }, 34 | 35 | 36 | // test results reporter to use 37 | // possible values: 'dots', 'progress' 38 | // available reporters: https://npmjs.org/browse/keyword/karma-reporter 39 | reporters: ['progress'], 40 | 41 | 42 | // web server port 43 | port: 9876, 44 | 45 | 46 | // enable / disable colors in the output (reporters and logs) 47 | colors: true, 48 | 49 | 50 | // level of logging 51 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 52 | logLevel: config.LOG_INFO, 53 | 54 | 55 | // enable / disable watching file and executing tests whenever any file changes 56 | autoWatch: true, 57 | 58 | 59 | // start these browsers 60 | // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher 61 | browsers: ['Chrome'], 62 | 63 | 64 | // Continuous Integration mode 65 | // if true, Karma captures browsers, runs the tests and exits 66 | singleRun: false 67 | }); 68 | }; 69 | -------------------------------------------------------------------------------- /karma/hit_zones.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicholas-johnson/ng-draggable-widgets/4c5df9fb7146609696a28b77fb1f46861864e952/karma/hit_zones.js -------------------------------------------------------------------------------- /karma/ng-draggable-widgets.testable.js: -------------------------------------------------------------------------------- 1 | // Nicholas Johnson (www.nicholasjohnson.com) 2 | // Forward Advance Training (www.forwardadvance.com) 3 | // MIT licence 4 | 5 | (function() { 6 | // Hitzones are positioned on the left and right of widgets 7 | // We call check to see if the current drag location is in a hitzone 8 | // If so we move the placeholder and update the drag destination. 9 | var hitZones = { 10 | size: 10, 11 | check: function(e) { 12 | var i, groups, widgets, widget, rect, width; 13 | groups = document.querySelectorAll('[drag-group]'); 14 | for (var j = 0; j < groups.length; j++) { 15 | widgets = groups[j].querySelectorAll('[draggable-widget]:not(.dragging)'); 16 | for (i = 0; i < widgets.length; i++) { 17 | widget = widgets[i]; 18 | rect = widget.getBoundingClientRect(); 19 | width = widget.offsetWidth; 20 | if ((e.pageY > rect.top) && 21 | (e.pageY < rect.bottom) && 22 | (e.pageX > rect.left) && 23 | (e.pageX < rect.right - (width / 2))) { 24 | placeholder.insertBefore(widget); 25 | console.log(i); 26 | return { 27 | group: angular.element(widget).scope().dragGroup, 28 | index: i 29 | }; 30 | } 31 | if ((e.pageY > rect.top) && 32 | (e.pageY < rect.bottom) && 33 | (e.pageX > rect.left + (width / 2)) && 34 | (e.pageX < rect.right)) { 35 | placeholder.insertAfter(widget); 36 | console.log(i+1); 37 | return { 38 | group: angular.element(widget).scope().dragGroup, 39 | index: i+1 40 | }; 41 | } 42 | } 43 | } 44 | } 45 | }; 46 | 47 | var widget = { 48 | initDrag: function(el) { 49 | el.addClass('dragging'); 50 | el.css({ 51 | position:'absolute', 52 | 'z-index':100000 53 | }); 54 | }, 55 | setPosition: function(el, x, y, offsetX, offsetY) { 56 | el.css({ 57 | left: x - offsetX - 20 + 'px', 58 | top: y - offsetY - 20 + 'px' 59 | }); 60 | }, 61 | stopDrag: function(el) { 62 | el.removeClass('dragging'); 63 | el.css({ 64 | position:'', 65 | left: '', 66 | top: '', 67 | 'z-index': '' 68 | }); 69 | }, 70 | lockSize: function(el) { 71 | el.css({ 72 | width: el[0].offsetWidth + 'px', 73 | height: el[0].offsetHeight + 'px' 74 | }); 75 | }, 76 | unlockSize: function(el) { 77 | el.css({ 78 | width: '', 79 | height: '' 80 | }); 81 | } 82 | }; 83 | 84 | // The placeholder object is a little rectangle with the same dimensions as the dragged widget 85 | // It gives the user a preview of page layout. 86 | // We can show or hide it, and insert it before or after another widget. 87 | var placeholder = { 88 | el: angular.element([ 89 | '
', 90 | '
' 91 | ].join('')), 92 | show: function(el) { 93 | placeholder.el[0].hidden = false; 94 | placeholder.el.css({ 95 | width: el[0].clientWidth +'px', 96 | height: el[0].clientHeight +'px' 97 | }); 98 | el.after(placeholder.el); 99 | }, 100 | hide: function() { 101 | placeholder.el[0].hidden = true; 102 | }, 103 | insertBefore: function(el) { 104 | el.parentElement.insertBefore(placeholder.el[0], el); 105 | }, 106 | insertAfter: function(el) { 107 | angular.element(el).after(placeholder.el); 108 | } 109 | }; 110 | 111 | // controller for the drag directive. 112 | // initialises the drag 113 | var dragController = /*@ngInject*/["$scope", function($scope) { 114 | var drag = $scope.drag = {}; 115 | drag.start = function(e) { 116 | drag.initDrag(e); 117 | }; 118 | drag.end = function() { 119 | drag.destroyDrag(); 120 | }; 121 | }]; 122 | dragController.$inject = ["$scope"]; 123 | 124 | angular.module('ng-draggable-widgets', []) 125 | .directive('dragGroup', ["$parse", function($parse) { 126 | return { 127 | scope:true, 128 | restrict: 'A', 129 | link: { 130 | pre: function(scope, el, attrs) { 131 | scope.dragGroup = {}; 132 | scope.dragGroup = $parse(attrs.dragGroup)(scope); 133 | } 134 | } 135 | }; 136 | }]) 137 | 138 | .directive('draggableWidget', ["$rootScope", function($rootScope) { 139 | return { 140 | scope:true, 141 | restrict: 'A', 142 | controller: dragController, 143 | link: { 144 | pre: function(scope, el, attrs) { 145 | var drag = scope.drag; 146 | 147 | drag.callback = attrs.draggableWidgetCallback; 148 | 149 | // Set up the DOM for dragging 150 | drag.initDrag = function(e) { 151 | var dest; 152 | console.log('start drag'); 153 | drag.offsetX = e.offsetX; 154 | drag.offsetY = e.offsetY; 155 | widget.lockSize(el); 156 | widget.initDrag(el); 157 | widget.setPosition(el, e.pageX, e.pageY, scope.drag.offsetX, scope.drag.offsetY); 158 | placeholder.show(el); 159 | scope.drag.source = { 160 | group: scope.dragGroup, 161 | index: scope.$index 162 | }; 163 | angular.element(window).on('mousemove', function(e) { 164 | widget.setPosition(el, e.pageX, e.pageY, scope.drag.offsetX, scope.drag.offsetY); 165 | dest = hitZones.check(e); 166 | if (dest) { 167 | scope.drag.dest = dest; 168 | console.log('dest', dest); 169 | } 170 | }); 171 | }; 172 | 173 | // Unset the DOM for dragging 174 | scope.drag.destroyDrag = function() { 175 | var obj; 176 | console.log('stop drag'); 177 | angular.element(window).off('mousemove'); 178 | widget.stopDrag(el); 179 | widget.unlockSize(el); 180 | placeholder.hide(); 181 | if (scope.drag.dest) { 182 | obj = scope.drag.source.group[scope.drag.source.index]; 183 | scope.drag.source.group.splice(scope.drag.source.index, 1); 184 | scope.drag.dest.group.splice(scope.drag.dest.index, 0, obj); 185 | } 186 | if (drag.callback) { 187 | scope[drag.callback](scope.drag); 188 | } 189 | $rootScope.$apply(); 190 | }; 191 | } 192 | } 193 | }; 194 | }]) 195 | 196 | .directive('draggableWidgetHandle', function() { 197 | return { 198 | scope:true, 199 | restrict: 'A', 200 | link: { 201 | pre: function(scope, el) { 202 | el.on('mousedown', function(e) { 203 | scope.drag.initDrag(e); 204 | angular.element(document).one('mouseup', function(e) { 205 | scope.drag.destroyDrag(e); 206 | }); 207 | }); 208 | } 209 | } 210 | }; 211 | }); 212 | 213 | // Smuggle locals out of closure for testing 214 | window.placeholder = placeholder; 215 | window.hitZones = hitZones; 216 | window.widget = widget; 217 | })(); -------------------------------------------------------------------------------- /karma/placeholder.js: -------------------------------------------------------------------------------- 1 | describe('ng-draggable-widgets', function() { 2 | 3 | var scope, el, widgetEl; 4 | 5 | beforeEach(module('ng-draggable-widgets')); 6 | 7 | beforeEach(inject(function($rootScope, $compile) { 8 | el = angular.element([ 9 | '
', 10 | '
', 11 | '
', 12 | '
' 13 | ].join('')); 14 | scope = $rootScope.$new(); 15 | widgetEl = angular.element(el[0].getElementsByClassName('widget')[0]); 16 | $compile(el)(scope); 17 | scope.$digest(); 18 | })); 19 | 20 | it('has an element', function() { 21 | expect(placeholder.el).toBeDefined(); 22 | }); 23 | 24 | it('can be shown', function() { 25 | console.log(widgetEl); 26 | placeholder.show(widgetEl); 27 | expect(placeholder.el[0].hidden).toBe(false); 28 | expect(placeholder.el[0].style.width).toBe('0px'); 29 | expect(placeholder.el[0].style.width).toBe('0px'); 30 | }); 31 | 32 | it('can be hidden', function() { 33 | placeholder.hide(); 34 | expect(placeholder.el[0].hidden).toBe(true); 35 | }); 36 | 37 | }); 38 | -------------------------------------------------------------------------------- /karma/widget.js: -------------------------------------------------------------------------------- 1 | describe('ng-draggable-widgets', function() { 2 | 3 | var scope, el; 4 | 5 | beforeEach(module('ng-draggable-widgets')); 6 | 7 | beforeEach(inject(function($rootScope, $compile) { 8 | el = angular.element([ 9 | '
', 10 | '
' 11 | ].join('')); 12 | scope = $rootScope.$new(); 13 | $compile(el)(scope); 14 | scope.$digest(); 15 | })); 16 | 17 | it('can be initialised', function() { 18 | widget.initDrag(el); 19 | expect(el[0].style.position).toBe('absolute'); 20 | }); 21 | 22 | it('can be positioned', function() { 23 | widget.setPosition(el, 100, 100, 20, 20); 24 | expect(el[0].style.left).toBe('60px'); 25 | expect(el[0].style.top).toBe('60px'); 26 | }); 27 | 28 | it('can be unintialised', function() { 29 | widget.stopDrag(el); 30 | expect(el[0].style.position).toBe(''); 31 | expect(el[0].style.left).toBe(''); 32 | expect(el[0].style.top).toBe(''); 33 | }); 34 | 35 | it('can have a size locked', function() { 36 | widget.lockSize(el); 37 | expect(el[0].style.width).toBe('0px'); 38 | expect(el[0].style.height).toBe('0px'); 39 | }); 40 | 41 | it('can have a size unlocked', function() { 42 | widget.unlockSize(el); 43 | expect(el[0].style.width).toBe(''); 44 | expect(el[0].style.height).toBe(''); 45 | }) 46 | 47 | }); 48 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ng-draggable-widgets", 3 | "version": "0.0.1", 4 | "description": "Drag widgets to reorder", 5 | "main": "index.js", 6 | "directories": { 7 | "doc": "doc", 8 | "test": "test" 9 | }, 10 | "scripts": { 11 | "test": "echo \"Error: no test specified\" && exit 1" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "ssh://git@github.com/forwardadvance/drag-to-sort.git" 16 | }, 17 | "author": "Nicholas Johnson", 18 | "license": "MIT", 19 | "bugs": { 20 | "url": "https://github.com/forwardadvance/ng-draggable-widgets/issues" 21 | }, 22 | "homepage": "https://github.com/forwardadvance/ng-draggable-widgets", 23 | "devDependencies": { 24 | "beepbeep": "^1.2.0", 25 | "bower": "^1.4.1", 26 | "coffee-script": "^1.8.0", 27 | "gulp": "^3.8.11", 28 | "gulp-autoprefixer": "^2.1.0", 29 | "gulp-concat": "^2.4.3", 30 | "gulp-header": "^1.2.2", 31 | "gulp-jade": "^1.0.0", 32 | "gulp-jshint": "^1.9.2", 33 | "gulp-livereload": "^3.7.0", 34 | "gulp-minify-css": "^0.4.5", 35 | "gulp-ng-annotate": "^0.5.2", 36 | "gulp-plumber": "^1.0.0", 37 | "gulp-rename": "^1.2.0", 38 | "gulp-sass": "^2.1.1", 39 | "gulp-uglify": "^1.1.0", 40 | "gulp-util": "^3.0.3", 41 | "gulp-wrap": "^0.11.0", 42 | "jasmine-core": "^2.2.0", 43 | "karma": "^0.12.31", 44 | "karma-chrome-launcher": "^0.1.8", 45 | "karma-jasmine": "^0.3.5" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /source/demo/drag_groups/demo.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 3 | var widgetGroups = [ 4 | [ 5 | { 6 | title:'Cats with Woks', 7 | class: 'woks' 8 | }, 9 | { 10 | title:'Socks on Sticks', 11 | class: 'socks' 12 | }, 13 | { 14 | title:'Mocks of Macs', 15 | class: 'mocks' 16 | }, 17 | { 18 | title:'Pops in Pumps', 19 | class: 'pops' 20 | }, 21 | { 22 | title:'Hocks of Rumps', 23 | class: 'hocks' 24 | } 25 | ], 26 | [ 27 | { 28 | title:'Hocks of Rumps', 29 | class: 'hocks' 30 | }, 31 | { 32 | title:'Cats with Woks', 33 | class: 'woks' 34 | }, 35 | { 36 | title:'Pops in Pumps', 37 | class: 'pops' 38 | } 39 | ] 40 | ]; 41 | 42 | angular.module('app', ['ng-draggable-widgets']) 43 | .controller('dragController', function($scope) { 44 | $scope.widgetGroups = widgetGroups; 45 | $scope.moveWidget = function(drag) { 46 | console.log(drag); 47 | } 48 | }); 49 | })(); 50 | -------------------------------------------------------------------------------- /source/demo/drag_groups/demo.scss: -------------------------------------------------------------------------------- 1 | $width: 100px; 2 | $height: 200px; 3 | $margin: 10px; 4 | 5 | html { 6 | box-sizing: border-box; 7 | } 8 | 9 | *, *:before, *:after { 10 | box-sizing: inherit; 11 | } 12 | 13 | body { 14 | width: $width * 8; 15 | margin:0 auto; 16 | font-family:sans-serif; 17 | color:#fff; 18 | font-size: 100%; 19 | } 20 | 21 | .group-one, .group-two { 22 | width:3*$width + 8*$margin; 23 | background:grey; 24 | margin:$margin; 25 | padding:$margin; 26 | } 27 | 28 | .group-one { 29 | float:left; 30 | } 31 | 32 | .group-two { 33 | float:right; 34 | } 35 | 36 | div[draggable-widget] { 37 | width:$width; 38 | height:$height; 39 | border-radius:5px; 40 | padding:10px; 41 | float:left; 42 | margin:$margin; 43 | &.woks { 44 | background:aqua; 45 | } 46 | &.mocks { 47 | background:cadetblue; 48 | } 49 | &.socks { 50 | background:red; 51 | width:2 * $width + $margin*2; 52 | } 53 | &.pops { 54 | background:darkseagreen; 55 | } 56 | &.hocks { 57 | background:darkred; 58 | } 59 | } 60 | div.widget-placeholder { 61 | border:1px solid red; 62 | float:left; 63 | height:$height; 64 | margin:$margin; 65 | } 66 | div[draggable-widget-handle] { 67 | border:3px solid rgba(255,255,255,1); 68 | padding:10px; 69 | background:rgba(0,0,0,0.3); 70 | border-radius:3px; 71 | color:white; 72 | margin-bottom:1em; 73 | cursor: move; 74 | } 75 | -------------------------------------------------------------------------------- /source/demo/drag_groups/index.jade: -------------------------------------------------------------------------------- 1 | doctype html 2 | html( ng-app='app' ) 3 | head 4 | title Widget dragging demo 5 | script( src='../../public/components/angular/angular.js' ) 6 | script( src='../../build/js/ng-draggable-widgets.js' ) 7 | script( src='./demo.js' ) 8 | link( rel="stylesheet" type='text/css' href='./demo.css' ) 9 | body( ng-controller='dragController' ) 10 | div.group-one( drag-group='widgetGroups[0]' ) 11 | div( ng-repeat='widget in widgetGroups[0]' draggable-widget='widget' draggable-widget-callback='moveWidget' class='{{widget.class}}' ) 12 | div( draggable-widget-handle ) drag 13 | p {{widget.title}} 14 | 15 | div.group-two( drag-group='widgetGroups[1]' ) 16 | div( ng-repeat='widget in widgetGroups[1]' draggable-widget='widget' draggable-widget-callback='moveWidget' class='{{widget.class}}' ) 17 | div( draggable-widget-handle ) drag 18 | p {{widget.title}} 19 | -------------------------------------------------------------------------------- /source/demo/nested_divs/demo.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 3 | var widgets = [ 4 | { 5 | title:'Cats with Woks', 6 | class: 'woks' 7 | }, 8 | { 9 | title:'Socks on Sticks', 10 | class: 'socks' 11 | }, 12 | { 13 | title:'Mocks of Macs', 14 | class: 'mocks' 15 | }, 16 | { 17 | title:'Pops in Pumps', 18 | class: 'pops' 19 | }, 20 | { 21 | title:'Hocks of Rumps', 22 | class: 'hocks' 23 | } 24 | ]; 25 | 26 | angular.module('app', ['ng-draggable-widgets']) 27 | .controller('dragController', function($scope) { 28 | $scope.widgets = widgets; 29 | $scope.moveWidget = function(drag) { 30 | console.log(drag); 31 | } 32 | }); 33 | })(); 34 | -------------------------------------------------------------------------------- /source/demo/nested_divs/demo.scss: -------------------------------------------------------------------------------- 1 | $width: 200px; 2 | $height: 300px; 3 | $margin: 10px; 4 | 5 | html { 6 | box-sizing: border-box; 7 | } 8 | *, *:before, *:after { 9 | box-sizing: inherit; 10 | } 11 | 12 | body { 13 | font-family:sans-serif; 14 | color:#fff; 15 | font-size: 100%; 16 | width:4*$width + 10*$margin; 17 | margin:0 auto; 18 | } 19 | 20 | div[draggable-widget] { 21 | width:$width; 22 | height:$height; 23 | border-radius:5px; 24 | padding:10px; 25 | float:left; 26 | margin:$margin; 27 | &.woks { 28 | background:aqua; 29 | } 30 | &.mocks { 31 | background:cadetblue; 32 | } 33 | &.socks { 34 | background:red; 35 | width:2 * $width + $margin*2; 36 | } 37 | &.pops { 38 | background:darkseagreen; 39 | } 40 | &.hocks { 41 | background:darkred; 42 | } 43 | } 44 | div.widget-placeholder { 45 | border:1px solid red; 46 | float:left; 47 | height:$height; 48 | margin:$margin; 49 | } 50 | div[draggable-widget-handle] { 51 | border:3px solid rgba(255,255,255,1); 52 | padding:10px; 53 | background:rgba(0,0,0,0.3); 54 | border-radius:3px; 55 | color:white; 56 | margin-bottom:1em; 57 | cursor: move; 58 | } 59 | -------------------------------------------------------------------------------- /source/demo/nested_divs/index.jade: -------------------------------------------------------------------------------- 1 | doctype html 2 | html( ng-app='app' ) 3 | head 4 | title Widget dragging demo 5 | script( src='../../public/components/angular/angular.js' ) 6 | script( src='../../build/js/ng-draggable-widgets.js' ) 7 | script( src='./demo.js' ) 8 | link( rel="stylesheet" type='text/css' href='./demo.css' ) 9 | body( ng-controller='dragController' ) 10 | div( drag-group='widgets' ) 11 | div 12 | div 13 | div( ng-repeat='widget in widgets' draggable-widget='widget' draggable-widget-callback='moveWidget' class='{{widget.class}}' ) 14 | div 15 | div 16 | div( draggable-widget-handle ) drag 17 | p {{widget.title}} 18 | -------------------------------------------------------------------------------- /source/demo/percentage_widths/demo.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 3 | var widgets = [ 4 | { 5 | title:'Cats with Woks', 6 | class: 'woks' 7 | }, 8 | { 9 | title:'Socks on Sticks', 10 | class: 'socks' 11 | }, 12 | { 13 | title:'Mocks of Macs', 14 | class: 'mocks' 15 | }, 16 | { 17 | title:'Pops in Pumps', 18 | class: 'pops' 19 | }, 20 | { 21 | title:'Hocks of Rumps', 22 | class: 'hocks' 23 | } 24 | ]; 25 | 26 | angular.module('app', ['ng-draggable-widgets']) 27 | .controller('dragController', function($scope) { 28 | $scope.widgets = widgets; 29 | $scope.moveWidget = function(drag) { 30 | console.log(drag); 31 | } 32 | }); 33 | })(); 34 | -------------------------------------------------------------------------------- /source/demo/percentage_widths/demo.scss: -------------------------------------------------------------------------------- 1 | $width: 200px; 2 | $height: 300px; 3 | $margin: 10px; 4 | 5 | html { 6 | box-sizing: border-box; 7 | } 8 | *, *:before, *:after { 9 | box-sizing: inherit; 10 | } 11 | 12 | body { 13 | font-family:sans-serif; 14 | color:#fff; 15 | font-size: 100%; 16 | width:4*$width + 10*$margin; 17 | margin:0 auto; 18 | } 19 | 20 | div[draggable-widget] { 21 | width:33.333%; 22 | height:$height; 23 | padding:10px; 24 | float:left; 25 | &.woks { 26 | background:aqua; 27 | } 28 | &.mocks { 29 | background:cadetblue; 30 | } 31 | &.socks { 32 | background:red; 33 | width:66.6666%; 34 | } 35 | &.pops { 36 | background:darkseagreen; 37 | } 38 | &.hocks { 39 | background:darkred; 40 | } 41 | } 42 | div.widget-placeholder { 43 | background:rgba(0,0,0,0.2); 44 | float:left; 45 | height:$height; 46 | } 47 | div[draggable-widget-handle] { 48 | padding:10px; 49 | background:rgba(0,0,0,0.3); 50 | border-radius:3px; 51 | color:white; 52 | margin-bottom:1em; 53 | cursor: move; 54 | } 55 | -------------------------------------------------------------------------------- /source/demo/percentage_widths/index.jade: -------------------------------------------------------------------------------- 1 | doctype html 2 | html( ng-app='app' ) 3 | head 4 | title Widget dragging demo 5 | script( src='../../public/components/angular/angular.js' ) 6 | script( src='../../build/js/ng-draggable-widgets.js' ) 7 | script( src='./demo.js' ) 8 | link( rel="stylesheet" type='text/css' href='./demo.css' ) 9 | body( ng-controller='dragController' ) 10 | div( drag-group='widgets' ) 11 | div( ng-repeat='widget in widgets' draggable-widget='widget' draggable-widget-callback='moveWidget' class='{{widget.class}}' ) 12 | div( draggable-widget-handle ) drag 13 | p {{widget.title}} 14 | -------------------------------------------------------------------------------- /source/demo/simple_dragging/demo.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 3 | var widgets = [ 4 | { 5 | title:'Cats with Woks', 6 | class: 'woks' 7 | }, 8 | { 9 | title:'Socks on Sticks', 10 | class: 'socks' 11 | }, 12 | { 13 | title:'Mocks of Macs', 14 | class: 'mocks' 15 | }, 16 | { 17 | title:'Pops in Pumps', 18 | class: 'pops' 19 | }, 20 | { 21 | title:'Hocks of Rumps', 22 | class: 'hocks' 23 | } 24 | ]; 25 | 26 | angular.module('app', ['ng-draggable-widgets']) 27 | .controller('dragController', function($scope) { 28 | $scope.widgets = widgets; 29 | $scope.moveWidget = function(drag) { 30 | console.log(drag); 31 | } 32 | }); 33 | })(); 34 | -------------------------------------------------------------------------------- /source/demo/simple_dragging/demo.scss: -------------------------------------------------------------------------------- 1 | $width: 200px; 2 | $height: 300px; 3 | $margin: 10px; 4 | 5 | html { 6 | box-sizing: border-box; 7 | } 8 | *, *:before, *:after { 9 | box-sizing: inherit; 10 | } 11 | 12 | body { 13 | font-family:sans-serif; 14 | color:#fff; 15 | font-size: 100%; 16 | width:4*$width + 10*$margin; 17 | margin:0 auto; 18 | } 19 | 20 | div[draggable-widget] { 21 | width:$width; 22 | height:$height; 23 | border-radius:5px; 24 | padding:10px; 25 | float:left; 26 | margin:$margin; 27 | &.woks { 28 | background:aqua; 29 | } 30 | &.mocks { 31 | background:cadetblue; 32 | } 33 | &.socks { 34 | background:red; 35 | width:2 * $width + $margin*2; 36 | } 37 | &.pops { 38 | background:darkseagreen; 39 | } 40 | &.hocks { 41 | background:darkred; 42 | } 43 | } 44 | div.widget-placeholder { 45 | border:1px solid red; 46 | float:left; 47 | height:$height; 48 | margin:$margin; 49 | } 50 | div[draggable-widget-handle] { 51 | border:3px solid rgba(255,255,255,1); 52 | padding:10px; 53 | background:rgba(0,0,0,0.3); 54 | border-radius:3px; 55 | color:white; 56 | margin-bottom:1em; 57 | cursor: move; 58 | } 59 | -------------------------------------------------------------------------------- /source/demo/simple_dragging/index.jade: -------------------------------------------------------------------------------- 1 | doctype html 2 | html( ng-app='app' ) 3 | head 4 | title Widget dragging demo 5 | script( src='../../public/components/angular/angular.js' ) 6 | script( src='../../build/js/ng-draggable-widgets.js' ) 7 | script( src='./demo.js' ) 8 | link( rel="stylesheet" type='text/css' href='./demo.css' ) 9 | body( ng-controller='dragController' ) 10 | div( drag-group='widgets' ) 11 | div( ng-repeat='widget in widgets' draggable-widget='widget' draggable-widget-callback='moveWidget' class='{{widget.class}}' ) 12 | div( draggable-widget-handle ) drag 13 | p {{widget.title}} 14 | -------------------------------------------------------------------------------- /source/js/ng-draggable-widgets.js: -------------------------------------------------------------------------------- 1 | // Hitzones are positioned on the left and right of widgets 2 | // We call check to see if the current drag location is in a hitzone 3 | // If so we move the placeholder and update the drag destination. 4 | var hitZones = { 5 | size: 10, 6 | check: function(e) { 7 | var i, groups, widgets, widget, rect, width; 8 | groups = document.querySelectorAll('[drag-group]'); 9 | for (var j = 0; j < groups.length; j++) { 10 | widgets = groups[j].querySelectorAll('[draggable-widget]:not(.dragging)'); 11 | for (i = 0; i < widgets.length; i++) { 12 | widget = widgets[i]; 13 | rect = widget.getBoundingClientRect(); 14 | width = widget.offsetWidth; 15 | if ((e.pageY > rect.top) && 16 | (e.pageY < rect.bottom) && 17 | (e.pageX > rect.left) && 18 | (e.pageX < rect.right - (width / 2))) { 19 | placeholder.insertBefore(widget); 20 | console.log(i); 21 | return { 22 | group: angular.element(widget).scope().dragGroup, 23 | index: i 24 | }; 25 | } 26 | if ((e.pageY > rect.top) && 27 | (e.pageY < rect.bottom) && 28 | (e.pageX > rect.left + (width / 2)) && 29 | (e.pageX < rect.right)) { 30 | placeholder.insertAfter(widget); 31 | console.log(i+1); 32 | return { 33 | group: angular.element(widget).scope().dragGroup, 34 | index: i+1 35 | }; 36 | } 37 | } 38 | } 39 | } 40 | }; 41 | 42 | var widget = { 43 | initDrag: function(el) { 44 | el.addClass('dragging'); 45 | el.css({ 46 | position:'absolute', 47 | 'z-index':100000 48 | }); 49 | }, 50 | setPosition: function(el, x, y, offsetX, offsetY) { 51 | el.css({ 52 | left: x - offsetX - 20 + 'px', 53 | top: y - offsetY - 20 + 'px' 54 | }); 55 | }, 56 | stopDrag: function(el) { 57 | el.removeClass('dragging'); 58 | el.css({ 59 | position:'', 60 | left: '', 61 | top: '', 62 | 'z-index': '' 63 | }); 64 | }, 65 | lockSize: function(el) { 66 | el.css({ 67 | width: el[0].offsetWidth + 'px', 68 | height: el[0].offsetHeight + 'px' 69 | }); 70 | }, 71 | unlockSize: function(el) { 72 | el.css({ 73 | width: '', 74 | height: '' 75 | }); 76 | } 77 | }; 78 | 79 | // The placeholder object is a little rectangle with the same dimensions as the dragged widget 80 | // It gives the user a preview of page layout. 81 | // We can show or hide it, and insert it before or after another widget. 82 | var placeholder = { 83 | el: angular.element([ 84 | '
', 85 | '
' 86 | ].join('')), 87 | show: function(el) { 88 | placeholder.el[0].hidden = false; 89 | placeholder.el.css({ 90 | width: el[0].clientWidth +'px', 91 | height: el[0].clientHeight +'px' 92 | }); 93 | el.after(placeholder.el); 94 | }, 95 | hide: function() { 96 | placeholder.el[0].hidden = true; 97 | }, 98 | insertBefore: function(el) { 99 | el.parentElement.insertBefore(placeholder.el[0], el); 100 | }, 101 | insertAfter: function(el) { 102 | angular.element(el).after(placeholder.el); 103 | } 104 | }; 105 | 106 | // controller for the drag directive. 107 | // initialises the drag 108 | var dragController = /*@ngInject*/function($scope) { 109 | var drag = $scope.drag = {}; 110 | drag.start = function(e) { 111 | drag.initDrag(e); 112 | }; 113 | drag.end = function() { 114 | drag.destroyDrag(); 115 | }; 116 | }; 117 | 118 | angular.module('ng-draggable-widgets', []) 119 | .directive('dragGroup', function($parse) { 120 | return { 121 | scope:true, 122 | restrict: 'A', 123 | link: { 124 | pre: function(scope, el, attrs) { 125 | scope.dragGroup = {}; 126 | scope.dragGroup = $parse(attrs.dragGroup)(scope); 127 | } 128 | } 129 | }; 130 | }) 131 | 132 | .directive('draggableWidget', function($rootScope) { 133 | return { 134 | scope:true, 135 | restrict: 'A', 136 | controller: dragController, 137 | link: { 138 | pre: function(scope, el, attrs) { 139 | var drag = scope.drag; 140 | 141 | drag.callback = attrs.draggableWidgetCallback; 142 | 143 | // Set up the DOM for dragging 144 | drag.initDrag = function(e) { 145 | var dest; 146 | console.log('start drag'); 147 | drag.offsetX = e.offsetX; 148 | drag.offsetY = e.offsetY; 149 | widget.lockSize(el); 150 | widget.initDrag(el); 151 | widget.setPosition(el, e.pageX, e.pageY, scope.drag.offsetX, scope.drag.offsetY); 152 | placeholder.show(el); 153 | scope.drag.source = { 154 | group: scope.dragGroup, 155 | index: scope.$index 156 | }; 157 | angular.element(window).on('mousemove', function(e) { 158 | widget.setPosition(el, e.pageX, e.pageY, scope.drag.offsetX, scope.drag.offsetY); 159 | dest = hitZones.check(e); 160 | if (dest) { 161 | scope.drag.dest = dest; 162 | console.log('dest', dest); 163 | } 164 | }); 165 | }; 166 | 167 | // Unset the DOM for dragging 168 | scope.drag.destroyDrag = function() { 169 | var obj; 170 | console.log('stop drag'); 171 | angular.element(window).off('mousemove'); 172 | widget.stopDrag(el); 173 | widget.unlockSize(el); 174 | placeholder.hide(); 175 | if (scope.drag.dest) { 176 | obj = scope.drag.source.group[scope.drag.source.index]; 177 | scope.drag.source.group.splice(scope.drag.source.index, 1); 178 | scope.drag.dest.group.splice(scope.drag.dest.index, 0, obj); 179 | } 180 | if (drag.callback) { 181 | scope[drag.callback](scope.drag); 182 | } 183 | $rootScope.$apply(); 184 | }; 185 | } 186 | } 187 | }; 188 | }) 189 | 190 | .directive('draggableWidgetHandle', function() { 191 | return { 192 | scope:true, 193 | restrict: 'A', 194 | link: { 195 | pre: function(scope, el) { 196 | el.on('mousedown', function(e) { 197 | scope.drag.initDrag(e); 198 | angular.element(document).one('mouseup', function(e) { 199 | scope.drag.destroyDrag(e); 200 | }); 201 | }); 202 | } 203 | } 204 | }; 205 | }); 206 | --------------------------------------------------------------------------------