├── .gitignore ├── .gitmodules ├── CHANGELOG.md ├── Gruntfile.js ├── README.md ├── README_RU.md ├── bower.json ├── css └── stylesheet.css ├── demo ├── demo.css ├── head.html ├── index.html ├── lasso.html └── sortable.html ├── dist ├── angular-dnd.js └── angular-dnd.min.js ├── index.js ├── package.json └── src ├── core.js ├── directives ├── dndContainment.js ├── dndDraggable.js ├── dndDroppable.js ├── dndFittext.js ├── dndLassoArea.js ├── dndModel.js ├── dndRect.js ├── dndResizable.js ├── dndRotatable.js ├── dndSelectable.js ├── dndSortable.js └── dndkeyModel.js └── services ├── EventEmitter.js ├── dndKey.js └── dndLasso.js /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .idea 3 | node_modules 4 | sh 5 | npm-debug.log 6 | .idea 7 | ._* 8 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "latest"] 2 | path = latest 3 | url = https://github.com/Tuch/angular-dnd 4 | branch = master 5 | [submodule "0.1.5"] 6 | path = 0.1.5 7 | url = https://github.com/Tuch/angular-dnd 8 | branch = 0.1.5 9 | [submodule "0.1.6"] 10 | path = 0.1.6 11 | url = https://github.com/Tuch/angular-dnd 12 | branch = 0.1.6 13 | [submodule "0.1.7"] 14 | path = 0.1.7 15 | url = https://github.com/Tuch/angular-dnd 16 | branch = 0.1.7 17 | [submodule "dev"] 18 | path = dev 19 | url = https://github.com/Tuch/angular-dnd.git 20 | branch = dev 21 | [submodule "0.1.9"] 22 | path = 0.1.9 23 | url = https://github.com/Tuch/angular-dnd.git 24 | branch = 0.1.9 25 | [submodule "0.1.10"] 26 | path = 0.1.10 27 | url = https://github.com/Tuch/angular-dnd.git 28 | branch = 0.1.10 29 | [submodule "0.1.11"] 30 | path = 0.1.11 31 | url = https://github.com/Tuch/angular-dnd.git 32 | branch = 0.1.11 33 | [submodule "0.1.12"] 34 | path = 0.1.12 35 | url = https://github.com/Tuch/angular-dnd.git 36 | branch = 0.1.12 37 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # [Changes] 2 | 3 | ## v0.1.25 4 | * [#52]https://github.com/Tuch/angular-dnd/pull/52 (@Petah) 5 | * ** 6 | 7 | ## v0.1.24 8 | * [#43]https://github.com/Tuch/angular-dnd/pull/43 (@Jobularity) 9 | * ** 10 | 11 | ## v0.1.23 12 | * [#38]https://github.com/Tuch/angular-dnd/pull/38) dnd-draggable don't allow touch scroll (@liollury) 13 | * ** 14 | 15 | ## v0.1.22 16 | * Hitfix bug with scope.$apply. 17 | * ** 18 | 19 | ## v0.1.21 20 | * Hitfix bug with scope.$apply. 21 | * ** 22 | 23 | ## v0.1.20 24 | * Fix bug with scope.$apply. 25 | * ** 26 | 27 | ## v0.1.19 28 | * Fix bug with dnd-droppable element. 29 | * ** 30 | 31 | ## v0.1.18 32 | * **dndLassoArea 33 | * Fix bug with dnd-on-lassoend property 34 | * **move to npm packages 35 | * ** 36 | 37 | ## v0.1.17 38 | * **dnd-sortable-list 39 | * implement directive for handling sortable list 40 | * ** 41 | 42 | ## v0.1.16 43 | * **angular.element(...).dndCloneByStyles 44 | * Fix bug with ignore text nodes 45 | * **dnd-rect 46 | * Fix bug if dnd-rect attr is getter function 47 | * ** 48 | 49 | ## v0.1.15 50 | * **dnd-selectable 51 | * In case if function return false the value will remain the same. - DEPRECATED 52 | * ** 53 | 54 | ## v0.1.14 55 | * **bug-fixes 56 | * Fixed bug with touch manipulator and dnd-rect directive 57 | * ** 58 | 59 | ## v0.1.13 60 | * **bug-fixes 61 | * Fixed a bug with the sequence of events call (leave -> enter -> over) 62 | * ** 63 | 64 | ## v0.1.12 65 | * **bug-fixes 66 | * Fix bug with child elements of dnd-pointer-none 67 | * Fix bug with drag 'tempateUrl' elements 68 | * Toggle off angular.dnd.debug.mode 69 | * Implement angular.element.dndClosest instead angular.element.dndParent 70 | * ** 71 | 72 | ## v0.1.11 73 | * **dnd-containment 74 | * **API BREAKING CHANGE!** implement dnd-containment instead dnd-container and restrictTheMovement options 75 | * ** 76 | 77 | ## v0.1.10 78 | * **Draggable 79 | * **API BREAKING CHANGE!** handle - новая опция 80 | * **Examples 81 | * Add new dnd-draggable example 82 | * ** 83 | 84 | ## v0.1.9 85 | 86 | * **Draggable 87 | * **API BREAKING CHANGE!** restrictTheMovement переименован в restrictMovement 88 | * ** 89 | 90 | * **Sortable 91 | * **API BREAKING CHANGE!** restrictTheMovement переименован в restrictMovement 92 | * ** 93 | 94 | * Implement IE 10+ supporting 95 | 96 | ## v0.1.7 97 | * **Draggable helper 98 | * **API BREAKING CHANGE!** теперь у dnd-draggable-opts есть новое свойство helper - может принимать либо значение 'clone' либо какой либо шаблон по типу ng-include и т.п. 99 | * **API BREAKING CHANGE!** для большего соответствия действительности ns переименован в layer 100 | * **API BREAKING CHANGE!** в draggable-opts добавлено свойство restrictTheMovement[boolean] - позволяет включать либо отключать зависимость от dnd-container 101 | * ** 102 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | var srcFiles = [ 2 | 'src/core.js', 3 | 'src/directives/dndDraggable.js', 4 | 'src/directives/dndDroppable.js', 5 | 'src/directives/dndRotatable.js', 6 | 'src/directives/dndResizable.js', 7 | 'src/directives/dndSortable.js', 8 | 'src/directives/dndSelectable.js', 9 | 'src/directives/dndRect.js', 10 | 'src/directives/dndModel.js', 11 | 'src/directives/dndLassoArea.js', 12 | 'src/directives/dndFittext.js', 13 | 'src/directives/dndKeyModel.js', 14 | 'src/directives/dndContainment.js', 15 | 'src/services/dndKey.js', 16 | 'src/services/dndLasso.js', 17 | 'src/services/EventEmitter.js' 18 | ]; 19 | 20 | module.exports = function (grunt) { 21 | grunt.initConfig({ 22 | concat: { 23 | options: { 24 | separator: ';\n\n', 25 | }, 26 | dist: { 27 | src: srcFiles, 28 | dest: 'dist/angular-dnd.js', 29 | }, 30 | }, 31 | wrap: { 32 | basic: { 33 | src: ['dist/angular-dnd.js'], 34 | dest: 'dist/angular-dnd.js', 35 | options: { 36 | wrapper: [ 37 | ';(function(angular, undefined, window, document){\n', 38 | '\n})(angular, undefined, window, document);' 39 | ] 40 | } 41 | } 42 | }, 43 | uglify: { 44 | build: { 45 | src: 'dist/angular-dnd.js', 46 | dest: 'dist/angular-dnd.min.js' 47 | } 48 | }, 49 | watch: { 50 | files: srcFiles, 51 | tasks: [ 52 | "concat", 53 | "wrap", 54 | "uglify" 55 | ] 56 | }, 57 | connect: { 58 | def: { 59 | options: { 60 | port: 3000, 61 | } 62 | }, 63 | }, 64 | }); 65 | 66 | function loadNpmTasks(tasks) { 67 | tasks.forEach(function (task, i) { 68 | grunt.loadNpmTasks(task); 69 | }); 70 | } 71 | 72 | loadNpmTasks([ 73 | 'grunt-contrib-uglify', 74 | 'grunt-wrap', 75 | 'grunt-contrib-concat', 76 | 'grunt-contrib-connect', 77 | 'grunt-contrib-watch', 78 | ]); 79 | 80 | grunt.registerTask('default', [ 81 | "concat", 82 | "wrap", 83 | "uglify", 84 | "connect", 85 | 'watch' 86 | ]); 87 | }; 88 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Join the chat at https://gitter.im/Tuch/angular-dnd](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/Tuch/angular-dnd?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 2 | 3 | Angular-DND 0.1.25 4 | ========= 5 | 6 | Features: 7 | --------- 8 | - Not jQueryUI wrapper 9 | - Support touch events 10 | 11 | Directives: 12 | --------- 13 | 14 | ### dnd-draggable [expression] 15 | Provides movement ability to the element. Parameter 'false' disables the directive. 16 | 17 | #### dnd-draggable-opts [object] - directive options: 18 | - layer[string]: What would droppable elements react to draggable elements, they must be in a single layer. By default, layer = 'common' 19 | - useAsPoint[boolean]: Droppable area will interact with the item only if the manipulator cursor will be within the droppable element 20 | - helper[string]: 'clone' or templateUrl - allow to move to use helper, instead of the element 21 | - handle[string]: Selector of handle element to be used for pulling element 22 | 23 | #### watching attributes: 24 | - dnd-on-dragstart [function()]: Triggered at the start of drag element 25 | - dnd-on-drag [function()]: Triggered at the process of drag element 26 | - dnd-on-dragend [function()]: Triggered at the end of drag element 27 | 28 | #### scope: 29 | - $dragged [boolean] - Register that lets you know whether the movement element during the last cycle of events (the last ~ 5ms). Convenient to use for example in ng-click (see. Example). 30 | - $dropmodel [string] - Droppable element model defined in the dnd-model directive 31 | 32 | ### dnd-pointer-none 33 | Attribute operates similar to the pointer-events: none - ignoring the event, but in relation to dnd- directives (see Example 2 sortable directive - the text field) 34 | 35 | 36 | 37 | 38 | ### dnd-droppable [expression] 39 | Create targets for draggable elements. Parameter 'false' disables the directive. 40 | 41 | #### dnd-droppable-opts [object]: 42 | - layer[string]: What would droppable elements react to draggable elements, they must be in a single layer. By default, layer = 'common' 43 | 44 | #### watching attributes: 45 | - dnd-on-dragenter [function()]: Triggered when hit draggable within the droppable 46 | - dnd-on-dragover [function()]: Triggered when an accepted draggable is dragged over the droppable 47 | - dnd-on-dragleave [function()]: Triggered when leave draggable within the droppable 48 | - dnd-on-drop [function()]: Triggered when an accepted draggable is dropped on the droppable 49 | 50 | #### scope: 51 | - $dragmodel [string] Draggable element model defined in the dnd-model directive 52 | 53 | 54 | 55 | 56 | 57 | ### dnd-rotatable [expression] 58 | Provides rotate ability to the element. Parameter 'false' disables the directive. 59 | 60 | #### watching attributes: 61 | - dnd-on-dragstart [function()]: Triggered at the start of element rotation 62 | - dnd-on-drag [function()]: Triggered at the process of element rotation 63 | - dnd-on-dragend [function()]: Triggered at the end of element rotation 64 | 65 | #### scope: 66 | - $rotated [boolean] - Register that lets you know whether the element rotation during the last cycle of events (the last ~ 5ms). Convenient to use for example in ng-click (see. Example). 67 | 68 | 69 | 70 | 71 | 72 | ### dnd-resizable [expression] 73 | Provides resize ability to the element. Parameter 'false' disables the directive. 74 | 75 | #### watching attributes: 76 | - dnd-on-dragstart [function()]: Triggered at the start of resizing element 77 | - dnd-on-drag [function()]: Triggered at the process of resizing element 78 | - dnd-on-dragend [function()]: Triggered at the end of resizing element 79 | 80 | #### scope: 81 | - $resized [boolean] - Register that lets you know whether the element resizing during the last cycle of events (the last ~ 5ms). Convenient to use for example in ng-click (see. Example). 82 | 83 | 84 | ### dnd-sortable-list [expression] 85 | List of sortable items 86 | 87 | ### dnd-sortable [expression] 88 | Reorder elements in a list or grid. 89 | 90 | #### watching attributes: 91 | - dnd-on-sortstart [function()]: Triggered at the start of sorting element 92 | - dnd-on-sort [function()]: Triggered at the process of sorting element 93 | - dnd-on-sortchange [function()]: This event is triggered during sorting, but only when the DOM position has changed 94 | - dnd-on-sortend [function()]: Triggered at the end of sorting element 95 | 96 | 97 | 98 | ### dnd-lasso-area [expression] 99 | This Directive is to create rect models with lasso tool. Also, this directive can work with a selectable directive (as a container) (see. Example). Parameter 'false' disables the directive. 100 | 101 | #### watching attributes: 102 | - dnd-on-lassostart [function()]: Triggered at the start of lasso change size 103 | - dnd-on-lasso [function([rect])]: Triggered at process of lasso change size 104 | - dnd-on-lassoend [function([rect])]: Triggered at end of lasso change size 105 | 106 | #### scope: 107 | - $dragged [boolean] - Register that lets you know whether the movement leasso during the last cycle of events (the last ~ 5ms). Convenient to use for example in ng-click (see. Example). 108 | 109 | 110 | 111 | 112 | 113 | ### dnd-selectable [expression] 114 | Use manipulator to select elements, individually or in a group. 115 | 116 | #### Requirements: 117 | - dnd-lasso-area как родительский элемент 118 | 119 | #### watching attributes: 120 | - dnd-on-selected [function([$keyPressed])]: interrupt after changing selected value (dnd-model-selected) from false to true. 121 | - dnd-on-unselected [function([$keyPressed])]: interrupt after changing selected value (dnd-model-selected) from true на false. 122 | - dnd-on-selecting [function([$keyPressed])]: interrupt after changing selecting value (dnd-model-selecting) from false на true. 123 | - dnd-on-unselecting [function([$keyPressed])]: interrupt after changing selecting value (dnd-model-selecting) from true на false. 124 | 125 | #### model attributes: 126 | - dnd-model-selected: varibale name of selected state 127 | - dnd-model-selecting: varibale name of selecting state 128 | 129 | #### scope: 130 | - $keypressed - Register that indicates whether the button is pressed ctrl, shift or cmd during the event 131 | 132 | #### Sequence of events: 133 | - if click - selected(true/false). 134 | - If not click - selecting(true) -> selected(true/false) -> selecting(false) 135 | 136 | 137 | 138 | 139 | ### dnd-rect (string) 140 | Model of element rectangle (top, left, width, height, transform). Core directive. 141 | Use with dnd-draggable, dnd-resizable, dnd-rotatable, dnd-fittext, dnd-sortable, and also dnd-selectable(optional) work with dnd-rect. 142 | 143 | 144 | 145 | 146 | 147 | ### dnd-containment(string, selector) 148 | Containment work area of draggable/resizable/rotatable elements. By default containment is body. 149 | 150 | 151 | 152 | 153 | 154 | ### dnd-fittext (mix) 155 | Directive for text fitting under element sizes. 156 | 157 | #### dnd-fittext parameter: 158 | - Value, change which triggers resizing text. (ex: {width: rect.width, height: rect.height, text: rect.text}. see example) 159 | 160 | - dnd-fittext-min - min font size in px 161 | - dnd-fittext-max - max font size in px 162 | 163 | 164 | 165 | 166 | 167 | 168 | Services: 169 | --------- 170 | 171 | ### DndLasso 172 | Service-Class to provide directives interface of the rectangular lasso tool 173 | 174 | 175 | 176 | 177 | 178 | ### dndKey 179 | Service for control key pressing 180 | 181 | #### Methods 182 | - get(): get array of pressed keys 183 | - isset(code): check key state by key code 184 | 185 | 186 | 187 | 188 | 189 | Examples 190 | --------- 191 | 192 | - All examples inside demo folder 193 | - Also available [link](http://tuch.github.io/angular-dnd/demo) 194 | 195 | 196 | 197 | 198 | 199 | Plunkers 200 | --------- 201 | - [many drop zones](http://embed.plnkr.co/tdBHyg032OEK3Kn8ZQZw/preview) 202 | - [custom sortables](http://embed.plnkr.co/ElwZjFIQh3N2HHe18Gse/preview) 203 | - [useAsPointExample](http://plnkr.co/edit/sQqhYVlZwd2VxGTGoEA8?p=preview) 204 | 205 | 206 | TODO: 207 | --------- 208 | 209 | - **more options** 210 | - ensure rapid expandability options 211 | - **supporting touch specific events (rotate, resize)** 212 | - **supporting dynamic options** 213 | - **supporting ng-animate** 214 | - **supporting HTML5 dnd events** 215 | 216 | 217 | PS: 218 | -------- 219 | Sorry for bad translation. 220 | Waiting for your pull requests. 221 | 222 | [![Join the chat at https://gitter.im/Tuch/angular-dnd](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/Tuch/angular-dnd?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 223 | -------------------------------------------------------------------------------- /README_RU.md: -------------------------------------------------------------------------------- 1 | [![Join the chat at https://gitter.im/Tuch/angular-dnd](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/Tuch/angular-dnd?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 2 | 3 | Angular-DND 0.1.25 4 | ========= 5 | 6 | Особенности: 7 | --------- 8 | - Не jQueryUI обертка 9 | - Поддержка touch событий 10 | 11 | Директивы: 12 | --------- 13 | 14 | ### dnd-draggable [expression] 15 | Обеспечивает возможность перемещения элемент. Параметр 'false' отключает директиву. 16 | 17 | #### dnd-draggable-opts [object] - опции директивы: 18 | - layer[string]: Имя layer. Что бы droppable-элементы реагировали на draggable-элементы они должны находиться на одном layer. По умолчанию layer = 'common' 19 | - useAsPoint[boolean]: Droppable область будет взаимодействовать с элементом, только если курсор манипулятора окажется в пределах droppable элемента 20 | - helper[string]: 'clone' или templateUrl - позволяют для перемещения использовать helper, а не сам элемент 21 | - handle[string]: селектор хэндла, который будет использован для перетягивания элемента 22 | 23 | #### watching attributes: 24 | - dnd-on-dragstart [function()]: срабатывающая в начале движения элемента 25 | - dnd-on-drag [function()]: срабатывающая при движении элемента 26 | - dnd-on-dragend [function()]: срабатывающая в конце движения элемента 27 | 28 | #### scope: 29 | - $dragged [boolean] - флаг, который позволяют узнать было ли движение элемента в течении последнего цикла событий (последние ~5мс). Удобно использовать например в ng-click (см. пример). 30 | - $dropmodel [string] - модель droppable элемента, заданная в директиве dnd-model 31 | 32 | ### dnd-pointer-none 33 | Атрибут работает по аналогии с pointer-events: none - игнорирование событий, но по отношению к dnd- директивам (см.пример 2 sortable директивы - поле для ввода текста) 34 | 35 | 36 | 37 | 38 | ### dnd-droppable [expression] 39 | Позволяет определить droppable-элемент, который будет реагировать на draggable-элементы. Параметр 'false' отключает директиву. 40 | 41 | #### dnd-droppable-opts [object]: 42 | - layer[string]: Имя layer. Что бы droppable-элементы реагировали на draggable-элементы они должны находитья на одном layer. По умолчанию layer = 'common' 43 | 44 | #### watching attributes: 45 | - dnd-on-dragenter [function()]: срабатывающая при попадании draggable-элемента в пределы droppable-элемента 46 | - dnd-on-dragover [function()]: срабатывающая при движении draggable-элемента внутри droppable-элемента 47 | - dnd-on-dragleave [function()]: срабатывающая при выходе draggable-элемента за пределы droppable-элемента 48 | - dnd-on-drop [function()]: срабатывающая если отпустить draggable-элемент внутри границ droppable-элемента 49 | 50 | #### scope: 51 | - $dragmodel [string] модель draggable элемента, заданая в директиве dnd-model 52 | 53 | 54 | 55 | 56 | 57 | ### dnd-rotatable [expression] 58 | Обеспечивает возможность вращения элемента. Параметр 'false' отключает директиву. 59 | 60 | #### watching attributes: 61 | - dnd-on-dragstart [function()]: срабатывает в начале вращения элемента 62 | - dnd-on-drag [function()]: срабатывает при вращении элемента 63 | - dnd-on-dragend [function()]: срабатывает в конце вращения элемента 64 | 65 | #### scope: 66 | - $rotated [boolean] - флаг, который позволяют узнать вращался ли элемент в течении последнего цикла событий (последние ~5мс). Удобно использовать например в ng-click (см. пример). 67 | 68 | 69 | 70 | 71 | 72 | ### dnd-resizable [expression] 73 | Обеспечивает возможность изменения размеров элемента. Параметр 'false' отключает директиву. 74 | 75 | #### watching attributes: 76 | - dnd-on-dragstart [function()]: срабатывает в начале изменения размеров элемента 77 | - dnd-on-drag [function()]: срабатывает при изменении размеров элемента 78 | - dnd-on-dragend [function()]: срабатывает в конце изменения размеров элемента 79 | 80 | #### scope: 81 | - $resized [boolean] - флаг, который позволяют узнать было ли изменение размеров элемента в течении последнего цикла событий (последние ~5мс). Удобно использовать например в ng-click (см. пример). 82 | 83 | 84 | 85 | 86 | ### dnd-sortable-list [expression] 87 | Список элементов для сортировки (он же может быть указан в ng-repeat) 88 | 89 | 90 | 91 | 92 | ### dnd-sortable [expression] 93 | Позволяет организовать сортировку списка 94 | 95 | #### watching attributes: 96 | - dnd-on-dragstart [function()]: срабатывает в начале изменения размеров элемента 97 | - dnd-on-drag [function()]: срабатывает при изменении размеров элемента 98 | - dnd-on-dragchange [function()]: срабатывает при изменении позиции элемента 99 | - dnd-on-dragend [function()]: срабатывает в конце изменения размеров элемента 100 | 101 | 102 | 103 | 104 | 105 | ### dnd-lasso-area [expression] 106 | Директива, предназначенная для создания rect моделей с помощью инструмента lasso. Также эта директива работает в паре с selectable директивой (в роли контейнера) (см. пример). Параметр 'false' отключает директиву. 107 | 108 | #### watching attributes: 109 | - dnd-on-lassostart [function()]: срабатывает в начале изменения размеров lasso. 110 | - dnd-on-lasso [function([rect])]: срабатывает при изменении размеров lasso 111 | - dnd-on-lassoend [function([rect])]: срабатывает при окончании изменения размеров lasso 112 | 113 | #### scope: 114 | - $dragged [boolean] - флаг, который позволяют узнать было ли движение lasso в течении последнего цикла событий (последние ~5мс). Удобно использовать например в ng-click (см. пример). 115 | 116 | 117 | 118 | 119 | 120 | ### dnd-selectable [expression] 121 | Директива, позволяющая выделять объекты. Как инструмент выделения используется lasso (см. пример). Параметр 'false' отключает директиву. 122 | 123 | #### Требования: 124 | - dnd-lasso-area как родительский элемент 125 | 126 | #### watching attributes: 127 | - dnd-on-selected [function([$keyPressed])]: срабатывает после изменением значения selected (dnd-model-selected) c false на true. 128 | - dnd-on-unselected [function([$keyPressed])]: срабатывает после изменениеми значения selected (dnd-model-selected) c true на false. 129 | - dnd-on-selecting [function([$keyPressed])]: срабатывает после изменением значения selecting (dnd-model-selecting) c false на true. 130 | - dnd-on-unselecting [function([$keyPressed])]: срабатывает после изменением значения selecting (dnd-model-selecting) c true на false. 131 | 132 | #### model attributes: 133 | - dnd-model-selected: указывается име переменной в scope, в которой хранится состояние selected 134 | - dnd-model-selecting: указывается име переменной в scope, в которой хранится состояние selecting 135 | 136 | #### scope: 137 | - $keypressed - флаг, который указывает, была ли нажата клавиша ctrl, shift или cmd во время события 138 | 139 | #### Последовательность событий: 140 | - Если click - selected(true/false). 141 | - Если не click - selecting(true) -> selected(true/false) -> selecting(false) 142 | 143 | 144 | 145 | 146 | 147 | ### dnd-rect (string) 148 | Директива, которая представляет собой модель элемента, описывающую его координаты относительно верхнего левого угла, размеры и матрицу преобразования (top, left, width, height, transform). 149 | Директивы dnd-draggable, dnd-resizable, dnd-rotatable, dnd-fittext, а также dnd-selectable(опционально) работают в связке с dnd-rect. 150 | 151 | 152 | 153 | 154 | 155 | ### dnd-containment(string, selector) 156 | Ограничивает область действия draggable/resizable/rotatable элементов. По умолчанию контейнером служит body. 157 | 158 | 159 | 160 | 161 | 162 | ### dnd-fittext 163 | Отличная директива для подгонки текста под размер блока (удобно с resizable элементами), в котором этот текст находится. 164 | за единственный аргумент функция принимает объект rect, содержащий в себе 165 | На основе этих параметров идет расчет высоты шрифта. 166 | Также директва поддерживает дополнительные атрибуты-настройки: dnd-fittext-max и dnd-fittext-min, которые позволяют задать максимальное и минимальное соответственно значения шрифта в пикселях. 167 | 168 | #### Параметр: 169 | - имя переменной scope изменение величины которой служит тригером (может быть объектом, например {width: rect.width, height: rect.height} или строкой, например rect.height, где rect это модель, определенная в атрибуте dnd-rect , см. пример). 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | Сервисы: 178 | --------- 179 | 180 | ### DndLasso 181 | Сервис-класс, предназначенный для обеспечения директивам интерфейса к одноименному инструменту прямоугольного lasso 182 | 183 | 184 | 185 | 186 | 187 | ### dndKey 188 | Сервис для отслеживания нажатых на клавиатуре клавиш 189 | 190 | #### Методы 191 | - get(): Получить массив нажатых клавиш 192 | - isset(code): Проверить состояние клавиши по коду клавиши 193 | 194 | 195 | 196 | 197 | 198 | Примеры 199 | --------- 200 | 201 | Все примеры внутри в папке Demo. 202 | Также доступна [ссылка](http://tuch.github.io/angular-dnd/demo) 203 | 204 | 205 | TODO: 206 | --------- 207 | 208 | - **more options** 209 | - ensure rapid expandability options 210 | - **supporting touch specific events (rotate, resize)** 211 | - **supporting dynamic options** 212 | - **supporting ng-animate** 213 | - **supporting HTML5 dnd events** 214 | 215 | [![Join the chat at https://gitter.im/Tuch/angular-dnd](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/Tuch/angular-dnd?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 216 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-dnd-module", 3 | "version": "0.1.25", 4 | "authors": [ 5 | "Afonin A.V. " 6 | ], 7 | "description": "Drag and Drop module for Super heroic AngularJS. No jQueryUI. Touch supporting", 8 | "main": "./dist/angular-dnd.min.js", 9 | "dependencies": { 10 | "angular": "^1.4.5" 11 | }, 12 | "_source": "git://github.com/angular/bower-angular.git" 13 | } 14 | -------------------------------------------------------------------------------- /css/stylesheet.css: -------------------------------------------------------------------------------- 1 | .angular-dnd-resizable-handle { 2 | position: absolute; 3 | display: block; 4 | } 5 | .angular-dnd-disabled .ui-resizable-handle, 6 | .angular-dnd-autohide .ui-resizable-handle { 7 | display: none; 8 | } 9 | .angular-dnd-resizable-handle-n { 10 | cursor: pointer; 11 | height: 7px; 12 | width: 100%; 13 | top: 0px; 14 | left: 0; 15 | } 16 | .angular-dnd-resizable-handle-s { 17 | cursor: pointer; 18 | height: 7px; 19 | width: 100%; 20 | bottom: 0px; 21 | left: 0; 22 | } 23 | .angular-dnd-resizable-handle-e { 24 | cursor: pointer; 25 | width: 7px; 26 | right: 0px; 27 | top: 0; 28 | height: 100%; 29 | } 30 | .angular-dnd-resizable-handle-w { 31 | cursor: pointer; 32 | width: 7px; 33 | left: 0px; 34 | top: 0; 35 | height: 100%; 36 | } 37 | .angular-dnd-resizable-handle-se { 38 | cursor: pointer; 39 | width: 12px; 40 | height: 12px; 41 | right: 0px; 42 | bottom: 0px; 43 | } 44 | .angular-dnd-resizable-handle-sw { 45 | cursor: pointer; 46 | width: 12px; 47 | height: 12px; 48 | left: 0px; 49 | bottom: 0px; 50 | } 51 | 52 | .angular-dnd-resizable-handle-nw { 53 | cursor: pointer; 54 | width: 12px; 55 | height: 12px; 56 | left: 0px; 57 | top: 0px; 58 | } 59 | 60 | .angular-dnd-resizable-handle-ne { 61 | cursor: pointer; 62 | width: 12px; 63 | height: 12px; 64 | right: 0px; 65 | top: 0px; 66 | } 67 | 68 | .angular-dnd-rotatable-handle { 69 | display: block; 70 | position: absolute; 71 | top:-10px; 72 | left:-10px; 73 | width:10px; 74 | height:10px; 75 | background:#ccc; 76 | border-radius:50%; 77 | } 78 | 79 | .angular-dnd-lasso { 80 | position: absolute; 81 | border: 1px solid rgba(108, 152, 197, 0.73); 82 | background: rgba(108, 152, 197, 0.27); 83 | box-sizing: border-box; 84 | } 85 | 86 | .angular-dnd-draggable-helper { 87 | opacity: .5; 88 | } 89 | 90 | .angular-dnd-placeholder { 91 | opacity: .5; 92 | } 93 | -------------------------------------------------------------------------------- /demo/demo.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | padding:0; 3 | margin:0; 4 | font-family: Verdana; 5 | } 6 | 7 | * { 8 | box-sizing: border-box; 9 | } 10 | 11 | .title { 12 | padding: 10px; 13 | text-transform: uppercase; 14 | font-size: 35px; 15 | color: #777; 16 | padding-bottom: 20px; 17 | } 18 | 19 | .draggable { 20 | border: 1px solid #aaa; 21 | background-color: #eee; 22 | width:100px; 23 | height:100px; 24 | text-align: center; 25 | text-transform: uppercase; 26 | vertical-align: middle; 27 | } 28 | 29 | .handle { 30 | cursor: pointer; 31 | } 32 | 33 | .dropzone { 34 | margin:10px; 35 | padding:7px; 36 | width: 200px; 37 | height: 200px; 38 | line-height: 184px; 39 | border:1px solid #aaa; 40 | text-align: center; 41 | text-transform: uppercase; 42 | } 43 | 44 | .dropzone.active { 45 | background: #67AFAC; 46 | } 47 | 48 | .dropzone.dropped { 49 | background: #ddffdd; 50 | } 51 | 52 | .dnd-container { 53 | padding: 10px; 54 | background: #ffffdd; 55 | position: relative; 56 | } 57 | 58 | input { 59 | margin:10px; 60 | padding:7px; 61 | display: block; 62 | width: 200px; 63 | border:1px solid #aaa; 64 | } 65 | 66 | input[type=radio] { 67 | display: inline-block; 68 | width: auto; 69 | } 70 | 71 | .menu .button { 72 | display: inline-block; 73 | background: #ddd; 74 | border-radius: 10px; 75 | margin: 10px; 76 | padding: 10px; 77 | } 78 | 79 | .menu .button:link { 80 | text-decoration: none; 81 | color: #555; 82 | } 83 | 84 | .menu .button:visited { 85 | text-decoration: none; 86 | color: #555; 87 | } 88 | 89 | .menu .button:hover { 90 | color: #222; 91 | background: #eee; 92 | } 93 | 94 | .lasso-area { 95 | width: 300px; 96 | height: 300px; 97 | background: #eee; 98 | border: 1px solid #ddd; 99 | margin: 10px; 100 | display: inline-block; 101 | position: relative; 102 | } 103 | 104 | .article { 105 | position: absolute; 106 | bottom: 100%; 107 | padding-bottom: 5px; 108 | color: #555; 109 | 110 | } 111 | 112 | .lasso-area-creature { 113 | position: absolute; 114 | background: #ffff33; 115 | border-radius: 3px; 116 | } 117 | 118 | .lasso-area-creature.selected { 119 | background: #bbffbb; 120 | } 121 | 122 | .lasso-area-creature.selecting { 123 | background: #ffffbb; 124 | } 125 | 126 | button { 127 | margin:10px; 128 | font-size:20px; 129 | cursor: pointer; 130 | padding: 5px 20px; 131 | background:#eee; 132 | border: 1px solid #ddd; 133 | } 134 | 135 | button:hover { 136 | background:#f7f7f7; 137 | } 138 | 139 | #section2 { 140 | padding:10px; 141 | } 142 | 143 | .sortable { 144 | list-style: none; 145 | padding:0; 146 | } 147 | .sortable-1,.sortable-2 { 148 | width: 200px; 149 | 150 | background: #eee; 151 | padding: 30px; 152 | } 153 | 154 | .sortable li { 155 | background: #5F9EDF; 156 | margin:5px; 157 | padding:2px; 158 | color:#fff; 159 | cursor: pointer; 160 | } 161 | 162 | 163 | .container { 164 | padding: 10px; 165 | position: relative; 166 | } 167 | 168 | #sortable-exmaple2 .sortable { 169 | padding: 0; 170 | } 171 | 172 | #sortable-exmaple2 .sortable li { 173 | margin: 0; 174 | } 175 | 176 | #sortable-exmaple2 .sortable input { 177 | width: auto; 178 | } 179 | 180 | #sortable-exmaple3 .sortable-3{ 181 | padding: 10px; 182 | background: #ccc; 183 | } 184 | -------------------------------------------------------------------------------- /demo/head.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Angular-DnD Basic Example 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 76 | 77 | 78 | 79 | 80 | 81 | 82 |
83 |
84 | 85 |
Basic example
Draggable, Droppable, Rotatable, Resizable, Fittext...
86 | 87 | top: 88 | left: 89 | height: 90 | width: 91 | transform: 92 | 93 | 94 | 95 | 101 | 102 |
103 |
127 | 128 | Drag me 129 | 130 |
131 |
145 | 146 | Drag me with helper 147 | 148 |
149 | 150 |
162 | 163 |
166 | 167 | Drage me with handle 168 |
169 |
170 | O 171 |
172 | 173 |
174 |
188 | 189 | Drag me with clone helper 190 |
191 | 192 |
205 | 206 | 207 |
208 | 209 |







































210 | 211 |
212 | 213 |
214 | 215 | 216 | -------------------------------------------------------------------------------- /demo/lasso.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Angular-DnD Lasso example 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 |
78 | 79 |
Some lasso examples
80 | 81 |
88 |
Creating lasso
89 | 90 |
91 | 92 |
95 |
Selecting lasso
96 |
111 |
112 |
113 |












































114 | 115 | 116 | 117 | -------------------------------------------------------------------------------- /demo/sortable.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Angular-DnD Basic Example 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 91 | 92 | 93 | 94 | 95 | 96 | 97 |
98 |
99 | 100 |
101 | Sortable example
102 |
103 | 104 |

Example 1

105 |
106 | 107 |
    108 |
  • {{item.name}}
  • 116 |
117 | 118 |
119 | 120 |
    121 |
  • {{item.name}}
  • 122 |
123 | 124 |
125 | 126 |

Example 2

127 |
128 | 129 |
    130 |
  • 131 | 132 |
  • 133 |
134 | 135 |
136 | 137 |

Example 3

138 |
139 |
    140 |
  • 141 | {{ item1.name }} 142 |
      143 |
    • 144 | {{ item2.name }} 145 |
    • 146 |
    147 |
  • 148 |
149 | 150 |
151 | 152 |
153 | 154 | 155 | -------------------------------------------------------------------------------- /dist/angular-dnd.min.js: -------------------------------------------------------------------------------- 1 | !function(a,b,c,d){"use strict";function e(){}function f(){return!1}function g(){return!0}function h(a,b){return function(){b.apply(a,arguments)}}function i(a){return a*(Math.PI/180)}function j(a){return a*(180/Math.PI)}function k(a,b,c){return a>b?a:b>c?c:b}function l(a){var c=a.data("events");return c!==b?c:(c=t.data(a,"events"),c!==b?c:(c=t._data(a,"events"),c!==b?c:(c=t._data(a[0],"events"),c!==b?c:b)))}function m(a,b,c,d){3===arguments.length&&"boolean"!=typeof c&&(d=c,c=!1);var e;return function(){var f=arguments;d=d||this,c&&!e&&a.apply(d,f),clearTimeout(e),e=setTimeout(function(){c||a.apply(d,f),e=null},b)}}function n(a,b,c){var d,e;return function(){d||(e=arguments,c=c||this,a.apply(c,e),d=setTimeout(function(){d=null},b))}}function o(a,b){isNaN(b)&&(b=0);var c=Math.pow(10,b);return Math.round(a*c)/c}var p,q,r,s="0.1.25",t=a.element,u=(t(c),t(d)),v={mode:!1,helpers:{}},w=a.forEach,x=a.extend;!function(){c.console=c.console||{log:e,info:e,warn:e,error:e}}(),function(){var a=navigator.userAgent;/webkit\//i.test(a)?(p="-webkit-transform",q="-webkit-transform-origin",r="webkitMatchesSelector"):/gecko\//i.test(a)?(p="-moz-transform",q="-moz-transform-origin",r="mozMatchesSelector"):/trident\//i.test(a)?(p="-ms-transform",q="ms-transform-origin",r="msMatchesSelector"):/presto\//i.test(a)?(p="-o-transform",q="-o-transform-origin",r="oMatchesSelector"):(p="transform",q="transform-origin",r="matches")}();var y=function(){function a(a,b){"object"==typeof a&&(b=a.y||a.top,a=a.x||a.left),this.x=a||0,this.y=b||0}return a.prototype={equal:function(b){return b instanceof a||(b=new a(b)),this.x===b.x&&this.y===b.y},plus:function(b){return b instanceof a||(b=new a(b)),new a(this.x+b.x,this.y+b.y)},minus:function(b){return b instanceof a||(b=new a(b)),new a(this.x-b.x,this.y-b.y)},scale:function(b){return new a(this.x*b,this.y*b)},magnitude:function(){return this.distance(new a(0,0),this)},distance:function(b){return b instanceof a||(b=new a(b)),Math.sqrt(Math.pow(this.x-b.x,2)+Math.pow(this.y-b.y,2))},angle:function(a,b){var c=Math.atan2(a.y-this.y,a.x-this.x);return b===!0&&(c*=180/Math.PI),c},deltaAngle:function(a,c,d){c=c===b?{x:0,y:0}:c;var e=this.angle(c)-a.angle(c);return 0>e&&(e=2*Math.PI+e),d===!0&&(e*=180/Math.PI),e},transform:function(a){return a.transformPoint(this)},deltaTransform:function(a){return a.deltaTransformPoint(this)},rotate:function(a,b){var c=(new z).rotate(a,b);return this.transform(c)},getAsCss:function(){return{top:this.y,left:this.x}}},function(b,c){return new a(b,c)}}(),z=function(){function a(a,c,d,e,f,g){this.a=a!==b?a:1,this.b=c||0,this.c=d||0,this.d=e!==b?e:1,this.tx=f||0,this.ty=g||0}a.prototype={concat:function(b){return new a(this.a*b.a+this.c*b.b,this.b*b.a+this.d*b.b,this.a*b.c+this.c*b.d,this.b*b.c+this.d*b.d,this.a*b.tx+this.c*b.ty+this.tx,this.b*b.tx+this.d*b.ty+this.ty)},inverse:function(){var b=this.a*this.d-this.b*this.c;return new a(this.d/b,-this.b/b,-this.c/b,this.a/b,(this.c*this.ty-this.d*this.tx)/b,(this.b*this.tx-this.a*this.ty)/b)},rotate:function(b,c){var d=new a(Math.cos(b),Math.sin(b),-Math.sin(b),Math.cos(b));return c&&(d=this.translate(c.x,c.y).concat(d).translate(-c.x,-c.y)),this.concat(d)},scale:function(b,c,d){c=c||b;var e=new a(b,0,0,c);return d&&(e=e.translate(d.x,d.y).translate(-d.x,-d.y)),e},translate:function(b,c){var d=new a(1,0,0,1,b,c);return this.concat(d)},transformPoint:function(a){return new y(this.a*a.x+this.c*a.y+this.tx,this.b*a.x+this.d*a.y+this.ty)},deltaTransformPoint:function(a){return new y(this.a*a.x+this.c*a.y,this.b*a.x+this.d*a.y)},toStyle:function(){var a=o(this.a,3),b=o(this.b,3),c=o(this.c,3),d=o(this.d,3),e=o(this.tx,3),f=o(this.ty,3),g="matrix("+a+", "+b+", "+c+", "+d+", "+e+", "+f+")";return"matrix(1, 0, 0, 1, 0, 0)"===g?"none":g}};var c=function(b,c,d,e,f,g){return new a(b,c,d,e,f,g)};return c.IDENTITY=new a,c.HORIZONTAL_FLIP=new a(-1,0,0,1),c.VERTICAL_FLIP=new a(1,0,0,-1),c}(),A=function(){function a(a,b,c,d){this.tl=a,this.tr=b,this.bl=c,this.br=d}a.prototype={applyMatrix:function(c,d){var e,f,g,h,i=new z(1,0,0,1,d.x,d.y),j=new z(1,0,0,1,-d.x,-d.y);return d!==b?(e=this.tl.transform(j).transform(c).transform(i),f=this.tr.transform(j).transform(c).transform(i),g=this.bl.transform(j).transform(c).transform(i),h=this.br.transform(j).transform(c).transform(i)):(e=this.tl.transform(c),f=this.tr.transform(c),g=this.bl.transform(c),h=this.br.transform(c)),new a(e,f,g,h)},width:function(){var a=this.tl.x-this.tr.x,b=this.tl.y-this.tr.y;return Math.sqrt(a*a+b*b)},height:function(){var a=this.tl.x-this.bl.x,b=this.tl.y-this.bl.y;return Math.sqrt(a*a+b*b)},client:function(){var a=Math.min(this.tl.y,this.tr.y,this.bl.y,this.br.y),b=Math.min(this.tl.x,this.tr.x,this.bl.x,this.br.x),c=Math.max(this.tl.y,this.tr.y,this.bl.y,this.br.y)-a,d=Math.max(this.tl.x,this.tr.x,this.bl.x,this.br.x)-b;return{top:o(a,1),left:o(b,1),height:o(c,1),width:o(d,1),bottom:o(a+c,1),right:o(b+d,1)}},getAngle:function(a){var b=this.tl.y-this.tr.y,c=this.tl.x-this.tr.x;return 180*Math.tan(c/b)/Math.PI}};var c=function(b,c,d,e){var f=arguments;return"object"==typeof f[0]&&(c=f[0].top,b=f[0].left,d=f[0].width,e=f[0].height),new a(new y(b,c),new y(b+d,c),new y(b,c+e),new y(b+d,c+e))};return c.fromPoints=function(b,c,d,e){return new a(b,c,d,e)},c}();x(t.prototype,{dndDisableSelection:function(){this.on("dragstart selectstart",f).dndCss({"-moz-user-select":"none","-khtml-user-select":"none","-webkit-user-select":"none","-o-user-select":"none","-ms-user-select":"none","user-select":"none"})},dndEnableSelection:function(){this.off("dragstart selectstart",f).dndCss({"-moz-user-select":"auto","-khtml-user-select":"auto","-webkit-user-select":"auto","-o-user-select":"auto","-ms-user-select":"auto","user-select":"auto"})},dndClientRect:function(){if(this[0]){var a=this[0]===c?{top:0,bottom:0,left:0,right:0,width:0,height:0}:this[0].getBoundingClientRect();return{bottom:a.bottom,height:a.height,left:a.left,right:a.right,top:a.top,width:a.width}}},dndStyleRect:function(){var a=this.dndCss(["width","height","top","left"]),b=parseFloat(a.width),c=parseFloat(a.height),d="auto"===a.top?0:parseFloat(a.top),e="auto"===a.left?0:parseFloat(a.left);return{top:d,right:e+b,bottom:d+c,left:e,width:b,height:c}},dndGetParentScrollArea:function(){var a,b,e,f,g,h,i,j=[],k=this.dndClosest();d.documentElement;return w(k,function(c){i=t(c).dndCss(["padding-top","padding-right","padding-bottom","padding-left"]),a=c.scrollWidth,b=c.clientWidth,e=c.scrollHeight,f=c.clientHeight,h=parseFloat(i["padding-top"])+parseFloat(i["padding-bottom"]),g=parseFloat(i["padding-left"])+parseFloat(i["padding-right"]),(a-g!==b||e-h!==f)&&j.push(c)}),j.push(c),t(j)},dndGetFirstNotStaticParent:function(){var a,b,c=this.dndClosest();return w(c,function(c){return b=t(c).dndCss("position"),"absolute"===b||"relative"===b||"fixed"===b?(a=c,!1):void 0}),a||(a=d.documentElement),t(a)},dndClosest:function(a){a=a||"*";for(var b=this[0],c=[];b;)b[r](a)&&c.push(b),b=b.parentElement;return t(c)},dndGetAngle:function(a){var b=this.dndCss(p);if("none"===b||""===b)return 0;var c=b.split("(")[1].split(")")[0].split(","),d=c[0],e=c[1],f=Math.atan2(e,d);return f=0>f?f+=2*Math.PI:f,a?Math.round(180*f/Math.PI):f},dndCloneByStyles:function(){for(var b=[],d=0,e=this.length;e>d;d++){var f=this[d].cloneNode(),g=a.element(this[d].childNodes).dndCloneByStyles();a.element(f).append(g),1===f.nodeType&&(f.style.cssText=c.getComputedStyle(this[d],"").cssText),b.push(f)}return a.element(b)},dndCss:function(){function a(a){return a.replace(e,function(a,b,c,d){return d?c.toUpperCase():c}).replace(f,"Moz$1")}function d(){var a=arguments;if(1===a.length&&(a[0]instanceof Array||"string"==typeof a[0]))return j(this,a[0]);if(1===a.length&&"object"==typeof a[0])return i(this,a[0]);if(2===a.length){var b={};return b[a[0]]=a[1],i(this,b)}return this}var e=/([\:\-\_\.]+(.))/g,f=/^moz([A-Z])/,g={};!function(){var a={width:["paddingLeft","paddingRight","borderLeftWidth","borderRightWidth"],height:["paddingTop","paddingBottom","borderTopWidth","borderBottomWidth"]};w(a,function(a,b){g[b]={get:function(d){var e=c.getComputedStyle(d),f=e[b];if("border-box"!==e.boxSizing||"%"===f[f.length-1])return f;f=parseFloat(f);for(var g=0;g-1&&this.get().splice(b,1)},has:function(a){return this.get().indexOf(a)>-1},add:function(a){if(!this.has(a)){this.get().push(a);var b=this;t(a).on("$destroy",function(){b.remove(a)})}}},a}(),i=function(){function a(a,b){this.el=a,this.$el=t(a),this.listeners={dragstart:[],drag:[],dragend:[],dragenter:[],dragover:[],dragleave:[],drop:[]},this.regions=new g(b),this.layer=function(){return b},this.setCurrentManipulator(null)}var b=["dragstart","drag","dragend","dragenter","dragover","dragleave","drop"],h=["dragstart","drag","dragend"],i=["dragenter","dragover","dragleave","drop"];h.has=i.has=function(a){return this.indexOf(a)>-1};var j;return j="ontouchstart"in d.documentElement?{start:"touchstart",move:"touchmove",end:"touchend",cancel:"touchcancel"}:"pointerEnabled"in c.navigator?{start:"pointerdown",move:"pointermove",end:"pointerup",cancel:"pointercancel"}:"msPointerEnabled"in c.navigator?{start:"MSPointerDown",move:"MSPointerMove",end:"MSPointerUp",cancel:"MSPointerCancel"}:{start:"touchstart",move:"touchmove",end:"touchend",cancel:"touchcancel"},a.prototype={_isEmptyListeners:function(a){if(a instanceof Array){for(var b=0;b0)return!1;return!0},addListener:function(a,d){return-1===b.indexOf(a)?(console.error("jquery.dnd: invalid event name - ",a),this):(this.listeners[a].push(d),i.has(a)?this.regions.add(this.el):!h.has(a)||this.mouse||this.touch||("onmousedown"in c&&(this.mouse=new e(this)),("ontouchstart"in c||"onmsgesturechange"in c)&&(this.touch=new f(this,j))),this)},removeListener:function(a,b){var c=arguments;if(0===c.length)for(var d in this.listeners)this.listeners[d].length=0;else if(1===c.length)this.listeners[a].length=0;else for(var e=this.listeners[a],f=0;f-1&&c.splice(a,1)},getTarget:function(){return c[0]},isTarget:function(){return this.getTarget()===this},start:function(){this.started=!0,this.targets=[],this.asPoint=!1,this.api=new j(this),this.$scrollareas=this.dnd.$el.dndGetParentScrollArea(),this.$reference=this.dnd.$el.dndGetFirstNotStaticParent(),this.$scrollareas.on("scroll",this.onscroll),this.dnd.trigger("dragstart",this.api)},onscroll:function(){this.clearCache(),this.dnd.trigger("drag",this.api)},getCache:function(a){return this.cache[a]},setCache:function(a,b){return this.cache[a]=b},clearCache:function(){this.cache={}},stop:function(){if(this.$scrollareas.off("scroll",this.onscroll),this.targets.length)for(var a=0,b=this.targets.length;b>a;a++)t(this.targets[a]).data("dnd")[this.dnd.layer()].trigger("drop",this.api,this.dnd.el);this.dnd.trigger("dragend",this.api,this.targets)},prepareRegions:function(){for(var a=this.dnd.regions.get(),b=[],c=0;cb.left&&a.xb.top&&a.y=b.left&&a.right<=b.right&&(b.top<=a.bottom&&a.bottom<=b.bottom||b.bottom>=a.top&&a.top>=b.top||a.top<=b.top&&a.bottom>=b.bottom)||a.top>=b.top&&a.bottom<=b.bottom&&(b.left<=a.right&&a.right<=b.right||b.right>=a.left&&a.left>=b.left||a.left<=b.left&&a.right>=b.right)||a.top>=b.top&&a.right<=b.right&&a.bottom<=b.bottom&&a.left>=b.left},progress:function(b){this.event=b,this.started||this.start();var c=this.getCache("regions");c||(c=this.setCache("regions",this.prepareRegions()),v.mode&&this.showRegioins()),this.dnd.trigger("drag",this.api);for(var d=this.asPoint?this._isTriggerByPoint.bind(this,this.getBorderedAxis()):this._isTriggerByRect.bind(this,a.element(this.dnd.el).dndClientRect()),e=[],f=[],g=[],h=0;hf;f++){var h=b[f];v.helpers.renderRect(h.rect.left-e.left,h.rect.top-e.top,h.rect.width,h.rect.height)}},hideRegions:function(){for(var a=d.querySelectorAll(".dnd-debug-rect"),b=0,c=a.length;c>b;b++)a[b].remove()}},b}();e.prototype={getClientAxis:function(a){return new y(this.event.clientX+(a?a.x:0),this.event.clientY+(a?a.y:0))},mousedown:function(a){this.manipulator.begin(a)&&(u.on("mousemove",this.mousemove),u.on("mouseup",this.mouseup))},mousemove:function(a){this.manipulator.progress(a)},mouseup:function(a){this.manipulator.end(a),u.off("mousemove",this.mousemove),u.off("mouseup",this.mouseup),this.dnd.setCurrentManipulator(null)},destroy:function(){this.dnd.$el.off("mousedown",this.mousedown)}},f.prototype={getClientAxis:function(a){var b=this.event.originalEvent||this.event;return b.changedTouches?y(b.changedTouches[0].clientX+(a?a.x:0),b.changedTouches[0].clientY+(a?a.y:0)):y(b.clientX+(a?a.x:0),b.clientY+(a?a.y:0))},touchstart:function(a){this.manipulator.begin(a)&&(u.on(this.te.move,this.touchmove),u.on(this.te.end+" "+this.te.cancel,this.touchend))},touchmove:function(a){this.manipulator.isTarget()&&a.preventDefault(),this.manipulator.progress(a)},touchend:function(a){this.manipulator.end(a),u.off(this.te.move,this.touchmove),u.off(this.te.end+" "+this.te.cancel,this.touchend),this.dnd.setCurrentManipulator(null)},destroy:function(){this.dnd.$el.off(this.te.start,this.touchstart)}},t.prototype.dndBind=function(a,c){if(!this.length)return this;var d,e,f,g=[];if("object"==typeof a){e=a;for(var h in e){d=h.replace(/\s+/g," ").split(" ");for(var j=0;j').dndCss({position:"absolute"});return b.prototype={init:function(a){return delete this.start,delete this.element,this.api=a,this.ready=!1,"clone"===this.templateUrl?this.createElementByClone():this.createElementByTemplate(),this.wrap().appendTo(t(d.body)),this.scope.$root&&this.scope.$root.$$phase||this.scope.$apply(),a.setReferenceElement(d.body),this.initBorderOffset(),this},createTemplateByUrl:function(b){return b=a.isFunction(b)?b():b,f.get(b,{cache:i}).then(function(a){this.template=a.data,this._offset=y(),this.ready=!0}.bind(this))},createElementByClone:function(){return this.element=this.mainElement.dndCloneByStyles().dndCss("position","static"),this._offset=y(this.mainElement.dndClientRect()).minus(this.api.getBorderedAxis()),this.ready=!0,this},createElementByTemplate:function(){return this.element=g(this.template)(this.scope),this},wrap:function(){return c.html(""),c.append(this.element),this},appendTo:function(a){return a.append(c),this},initBorderOffset:function(){var a=this.api.getBorderedAxis();if("clone"===this.templateUrl){var b=this.mainElement.dndClientRect();this.borderOffset={top:a.y-b.top,left:a.x-b.left,bottom:a.y-b.top-b.height,right:a.x-b.left-b.width}}else{var b=c.dndClientRect();this.borderOffset={top:0,left:0,bottom:-b.height,right:-b.width}}},updatePosition:function(){var a=this.api.getRelBorderedAxis(this.borderOffset).plus(this._offset);c.dndCss(a.getAsCss())},destroy:function(){this.element.remove()}},b}();return{require:["?dndRect","?dndModel","?dndContainment"],scope:!0,link:k}}]),B.directive("dndDroppable",["$parse","$timeout",function(a,c){return{require:"?dndModel",scope:!0,link:function(c,d,e,f){function g(a){var d=a.droplocal={};a.dropmodel=f?f.get():f,d.droppable=l(c,{$dragmodel:a.dragmodel,$dropmodel:a.dropmodel,$api:a}),d.droppable=d.droppable===b?!0:d.droppable,d.droppable&&(n(c,{$dragmodel:a.dragmodel,$dropmodel:a.dropmodel,$api:a}),c.$root&&c.$root.$$phase||c.$apply())}function h(a){var b=a.droplocal;b.droppable&&(o(c,{$dragmodel:a.dragmodel,$dropmodel:a.dropmodel,$api:a}),c.$root&&c.$root.$$phase||c.$apply())}function i(a){var d=a.droplocal;d.droppable&&(p(c,{$dragmodel:a.dragmodel,$dropmodel:a.dropmodel,$api:a}),a.dropmodel=b,c.$root&&c.$root.$$phase||c.$apply())}function j(a){var b=a.droplocal;b.droppable&&q(c,{$dragmodel:a.dragmodel,$dropmodel:a.dropmodel,$api:a})}var k={layer:"common"},l=a(e.dndDroppable),m=x({},k,a(e.dndDroppableOpts)(c)||{}),n=a(e.dndOnDragenter),o=a(e.dndOnDragover),p=a(e.dndOnDragleave),q=a(e.dndOnDrop),r={};m.layer=m.layer||k.layer,r[m.layer+".dragenter"]=g,r[m.layer+".dragover"]=h,r[m.layer+".dragleave"]=i,r[m.layer+".drop"]=j,d.dndBind(r)}}}]),B.directive("dndRotatable",["$parse","$timeout",function(c,e){function f(f,g,h,k){function l(c){var e=c.local={};if(e.rotatable=r(f),e.rotatable=e.rotatable===b?!0:e.rotatable,e.rotatable||c.unTarget(),c.isTarget()){e.started=!0,c.setBounderElement(p?p.get():a.element(d.body));var h=c.getRelBorderedAxis();e.srect=g.dndStyleRect(),e.currAngle=g.dndGetAngle(),e.startPoint=y(h),e.borders=c.getBorders(),e.center=y(e.srect).plus(y(e.srect.width/2,e.srect.height/2)),f.$rotated=!0,t(f),f.$root&&f.$root.$$phase||f.$apply()}}function m(a){var b=a.local;if(b.started){var c=a.getRelBorderedAxis(),d=y(c).deltaAngle(b.startPoint,b.center),e=j(b.currAngle+d);e=Math.round(e/s.step)*s.step;var h=i(e),k=z().rotate(h),l=A(b.center.x-b.srect.width/2,b.center.y-b.srect.height/2,b.srect.width,b.srect.height).applyMatrix(k,b.center).client(),m=a.getReferencePoint();b.borders&&(l.left+m.xb.borders.right+1||l.top+m.y+l.height>b.borders.bottom+1)||(o?o.update("transform",k.toStyle()):g.dndCss("transform",k.toStyle()),u(f),f.$root&&f.$root.$$phase||f.$apply())}}function n(a){var b=a.local;b.started&&(v(f),e(function(){f.$rotated=!1}))}var o=k[0],p=k[1],q={step:5},r=c(h.dndRotatable),s=x({},q,c(h.dndRotatableOpts)(f)||{}),t=c(h.dndOnRotatestart),u=c(h.dndOnRotate),v=c(h.dndOnRotateend),w=g.dndCss("position");"fixed"!=w&&"absolute"!=w&&"relative"!=w&&(w="relative",g.dndCss("position",w));var B=a.element('
');g.append(B),f.$rotated=!1;var C={"$$rotatable.dragstart":l,"$$rotatable.drag":m,"$$rotatable.dragend":n};B.dndBind(C)}return{require:["?dndRect","?dndContainment"],scope:!0,link:f}}]),B.directive("dndResizable",["$parse","$timeout",function(c,e){function f(b){return a.element("
").addClass("angular-dnd-resizable-handle angular-dnd-resizable-handle-"+b)}function g(a,b){return b="object"==typeof b?b:{x:1,y:1},new y(a.left+a.width*b.x/2,a.top+a.height*b.y/2)}function h(h,i,j,l){function m(c){function f(c){var e=c.local={};if(e.resizable=q(h),e.resizable=e.resizable===b?!0:e.resizable,e.resizable||c.unTarget(),c.isTarget()){c.setBounderElement(o?o.get():a.element(d.body)),e.started=!0,e.$parent=i.parent(),e.rads=i.dndGetAngle(),e.rotateMatrix=z.IDENTITY.rotate(e.rads),e.inverseRotateMatrix=e.rotateMatrix.inverse(),e.parentRect=e.$parent.dndClientRect();var f=c.getBorderedAxis(),g=i.dndClientRect(),j=e.rect=i.dndStyleRect();e.borders=c.getBorders(),e.startAxis=f,e.minScaleX=r.minWidth/j.width,e.minScaleY=r.minHeight/j.height,e.maxScaleX=r.maxWidth/j.width,e.maxScaleY=r.maxHeight/j.height,e.deltaX=g.left-j.left+g.width/2-j.width/2,e.deltaY=g.top-j.top+g.height/2-j.height/2,h.$resized=!0,s(h),h.$root&&h.$root.$$phase||h.$apply()}}function j(a){var b=a.local;if(b.started){var d=a.getBorderedAxis(),e=y(d).minus(b.startAxis).transform(b.inverseRotateMatrix),f={x:1,y:1},j=b.rect.width,l=b.rect.height,m=b.rect.top,o=b.rect.left;switch(c){case"n":f.y=(l-e.y)/l;break;case"e":f.x=(j+e.x)/j;break;case"s":f.y=(l+e.y)/l;break;case"w":f.x=(j-e.x)/j;break;case"ne":f.x=(j+e.x)/j,f.y=(l-e.y)/l;break;case"se":f.x=(j+e.x)/j,f.y=(l+e.y)/l;break;case"sw":f.x=(j-e.x)/j,f.y=(l+e.y)/l;break;case"nw":f.x=(j-e.x)/j,f.y=(l-e.y)/l}f.x=k(b.minScaleX,f.x,b.maxScaleX),f.y=k(b.minScaleY,f.y,b.maxScaleY);var p,q=g(b.rect),r=g(b.rect,f);switch(c){case"n":p=y(o,m+l*f.y).rotate(b.rads,r).minus(y(o,m+l).rotate(b.rads,q));break;case"e":p=y(o,m).rotate(b.rads,r).minus(y(o,m).rotate(b.rads,q));break;case"s":p=y(o,m).rotate(b.rads,r).minus(y(o,m).rotate(b.rads,q));break;case"w":p=y(o+j*f.x,m).rotate(b.rads,r).minus(y(o+j,m).rotate(b.rads,q));break;case"ne":p=y(o,m+l*f.y).rotate(b.rads,r).minus(y(o,m+l).rotate(b.rads,q));break;case"se":p=y(o,m).rotate(b.rads,r).minus(y(o,m).rotate(b.rads,q));break;case"sw":p=y(o+j*f.x,m).rotate(b.rads,r).minus(y(o+j,m).rotate(b.rads,q));break;case"nw":p=y(o+j*f.x,m+l*f.y).rotate(b.rads,r).minus(y(o+j,m+l).rotate(b.rads,q))}var s={};s.width=j*f.x,s.height=l*f.y,s.left=o-p.x,s.top=m-p.y;var u=y(s.left+b.deltaX+s.width/2,s.top+b.deltaY+s.height/2),v=A(s.left+b.deltaX,s.top+b.deltaY,s.width,s.height).applyMatrix(b.rotateMatrix,u).client();b.borders&&(v.left+1b.borders.right||v.bottom-1>b.borders.bottom)||(n?n.update(s):i.dndCss(s),t(h),h.$root&&h.$root.$$phase||h.$apply())}}function l(a){var b=a.local;b.started&&(u(h),e(function(){h.$resized=!1}))}return{"$$resizable.dragstart":f,"$$resizable.drag":j,"$$resizable.dragend":l}}var n=l[0],o=l[1],p={handles:"ne, se, sw, nw, n, e, s, w",minWidth:20,minHeight:20,maxWidth:1e4,maxHeight:1e4},q=c(j.dndResizable),r=x({},p,c(j.dndResizableOpts)(h)||{}),s=c(j.dndOnResizestart),t=c(j.dndOnResize),u=c(j.dndOnResizeend),v=i.dndCss("position");"fixed"!==v&&"absolute"!==v&&"relative"!==v&&(v="relative",i.dndCss("position",v));for(var w=r.handles.replace(/\s/g,"").split(","),B=0;B0||(a.$sortable.model={list:i(c)},a.$sortable.insertBefore=!0,d.append(a.placeholder[0]))},d.dndBind(j)}function e(a,b,c){}return e.$inject=["$scope","$element","$attrs"],{scope:!0,link:d,controller:e}}]),B.directive("dndSortable",["$parse","$compile",function(b,c){function d(a,b,c){return Object.getOwnPropertyNames(a).map(function(c){return[c,a[c]].join(b)}).join(c)}function f(a){return"{"+d(a,":",",")+"}"}function g(a){return d(a,'="','" ')+'"'}function h(c,d){var e=c[0].nodeName.toLowerCase(),h=d.ngRepeat||"",i=h.match(j);if(!i)throw"dnd-sortable-item requires ng-repeat as dependence";var k=i[1],l=i[2];k=k.match(/^(?:(\s*[\$\w]+)|\(\s*([\$\w]+)\s*,\s*([\$\w]+)\s*\))$/);var m={item:"item: "+(k[3]||k[1])+", ",list:"list: "+l.replace(/\s\|(.)+$/g,"")+", ",index:"index: $index"},n=a.extend({layer:"'common'",handle:"''"},b(d.dndSortableOpts)()),o={"ng-transclude":"","dnd-draggable":"","dnd-draggable-opts":f({helper:"'clone'",useAsPoint:!0,layer:n.layer,handle:n.handle}),"dnd-droppable":"", 2 | "dnd-droppable-opts":f({layer:n.layer}),"dnd-on-dragstart":"$$onDragStart($api, $dropmodel, $dragmodel)","dnd-on-dragend":"$$onDragEnd($api, $dropmodel, $dragmodel)","dnd-on-dragover":"$$onDragOver($api, $dropmodel, $dragmodel)","dnd-on-drag":"$$onDrag($api, $dropmodel, $dragmodel)","dnd-model":"{"+m.item+m.list+m.index+"}"};return"<"+e+" "+g(o)+">"}function i(c,d,f,g){function h(a,b,c){var e=d.dndClientRect();return(l?(b.x-e.left)/e.width:(b.y-e.top)/e.height)>.5}function i(a,b,c,d){d=d||b,d.splice(c,0,b.splice(a,1)[0])}var j=(g[0],d.dndCss(["float","display"])),k=d[0].parentNode,l=/left|right|inline/.test(j["float"]+j.display),m=b(f.dndModel)||e,n=b(f.dndOnSortstart),o=b(f.dndOnSort),p=b(f.dndOnSortchange),q=b(f.dndOnSortend);b(f.dndOnSortenter),b(f.dndOnSortleave);c.$$onDragStart=function(a){n(c),a.placeholder=d.clone(),d.addClass("ng-hide"),a.placeholder.addClass("angular-dnd-placeholder"),k.insertBefore(a.placeholder[0],d[0]),a.$sortable={},a.clearCache(),c.$root&&c.$root.$$phase||c.$apply()},c.$$onDragOver=function(b,e,f){var g=a.isDefined(b.$sortable);if(g){var i=h(b.getDragTarget(),b.getBorderedAxis());i?k.insertBefore(b.placeholder[0],d[0].nextSibling):k.insertBefore(b.placeholder[0],d[0]);var j=m(c);p===a.noop||b.$sortable.model&&b.$sortable.model.index===j.index||(p(c),c.$root&&c.$root.$$phase||c.$apply()),b.$sortable.model=j,b.$sortable.insertBefore=!i,b.clearCache()}},c.$$onDragEnd=function(a){if(d.removeClass("ng-hide"),a.placeholder.remove(),a.$sortable.model){var b=c.$index,e=a.$sortable.model.index,f=m(c).list,g=a.$sortable.model.list;g===f?b>e?a.$sortable.insertBefore||e++:a.$sortable.insertBefore&&e--:a.$sortable.insertBefore||e++,i(b,f,e,g),a.clearCache(),q(c),c.$root&&c.$root.$$phase||c.$apply()}},o!==a.noop&&(c.$$onDrag=function(a){o(c),c.$root&&c.$root.$$phase||c.$apply()})}var j=/^\s*([\s\S]+?)\s+in\s+([\s\S]+?)(?:\s+as\s+([\s\S]+?))?(?:\s+track\s+by\s+([\s\S]+?))?\s*$/;return{scope:!0,transclude:!0,template:h,replace:!0,link:i,require:["^dndSortableList"]}}]),B.directive("dndSelectable",["$parse",function(a){function c(c,d,f){var g=a(d.dndModelSelecting),h=g.assign||e,i=a(d.dndModelSelected),j=i.assign||e,k=a(d.dndSelectable),l=(k.assign||e,a(d.dndOnSelected)),m=a(d.dndOnUnselected),n=a(d.dndOnSelecting),o=a(d.dndOnUnselecting);j(c,!1),h(c,!1),this.getElement=function(){return f},this.isSelected=function(){return i(c)},this.isSelecting=function(){return g(c)},this.isSelectable=function(){var a=k(c);return a===b||a},this.toggleSelected=function(a){return a=a===b?!this.isSelected():a,a?this.selected():this.unselected()},this.selecting=function(){return this.isSelectable()?(h(c,!0),n(c),this):this},this.unselecting=function(){return h(c,!1),o(c),this},this.selected=function(){return this.isSelectable()?(j(c,!0),l(c),this):this},this.unselected=function(){return j(c,!1),m(c),this},this.hit=function(a){var c=this.rectCtrl.getClient();for(var d in c)c[d]=parseFloat(c[d]);return c.bottom=c.bottom==b?c.top+c.height:c.bottom,c.right=c.right==b?c.left+c.width:c.right,a.bottom=a.bottom==b?a.top+a.height:a.bottom,a.right=a.right==b?a.left+a.width:a.right,a.top<=c.top&&c.top<=a.bottom&&(a.left<=c.left&&c.left<=a.right||a.left<=c.right&&c.right<=a.right)||a.top<=c.bottom&&c.bottom<=a.bottom&&(a.left<=c.left&&c.left<=a.right||a.left<=c.right&&c.right<=a.right)||a.left>=c.left&&a.right<=c.right&&(c.top<=a.bottom&&a.bottom<=c.bottom||c.bottom>=a.top&&a.top>=c.top||a.top<=c.top&&a.bottom>=c.bottom)||a.top>=c.top&&a.bottom<=c.bottom&&(c.left<=a.right&&a.right<=c.right||c.right>=a.left&&a.left>=c.left||a.left<=c.left&&a.right>=c.right)||a.top>=c.top&&a.right<=c.right&&a.bottom<=c.bottom&&a.left>=c.left}}function d(a){this.$element=a}return c.$inject=["$scope","$attrs","$element"],d.prototype={getClient:function(){return this.$element.dndClientRect()}},{restrict:"A",require:["dndSelectable","^dndLassoArea","?dndRect"],controller:c,scope:!0,link:function(a,b,c,e){function f(){e[1].remove(e[0]),a.$root&&a.$root.$$phase||a.$apply()}a.$dndSelectable=e[0];var g=e[2];e[0].rectCtrl=g?g:new d(b),e[1].add(e[0]),b.on("$destroy",f)}}}]),B.directive("dndRect",["$parse",function(a){function c(c,g,h){function i(a){var c;a="object"==typeof a?a:{};for(var e=0;e-1},c.$inject=["$scope","$attrs","$element"],{restrict:"A",controller:c}}]),B.directive("dndModel",["$parse",function(a){function b(b,c){var d=a(c.dndModel),e=d.assign;this.set=function(a){e(b,a)},this.get=function(){return d(b)}}return b.$inject=["$scope","$attrs"],{restrict:"A",controller:b}}]),B.directive("dndLassoArea",["DndLasso","$parse","$timeout","dndKey",function(a,c,d,e){function f(){var a=[],c={};this.data=function(){return c},this.add=function(b){a.push(b)},this.remove=function(b){for(var c=0;c").dndCss({position:"absolute",left:-99999,top:-99999,opacity:0,"z-index":-9999});return t(d.body).append(h),{restrict:"A",link:function(a,d,e){function h(c){c=c===b?{}:c;var h=d.dndCss(["font-size","font-family","font-weight","text-transform","border-top","border-right","border-bottom","border-left","padding-top","padding-right","padding-bottom","padding-left"]),i=c.text==b?d.text()||d.val():c.text,j=[];c.width===b&&j.push("width"),c.height===b&&j.push("height"),j.length&&(j=d.dndCss(j));for(var l in j){var m=j[l];if("%"==m[m.length-1])return;c[l]=j[l]}var n=f(i,h),o=g(d,0,0);if(!n.width||!n.height)return void d.dndCss("font-size","");o.width=parseFloat(c.width),o.height=parseFloat(c.height);var p=o.height/n.height,q=o.width/n.width,r=a.$eval(e.dndFittextMax),s=a.$eval(e.dndFittextMin);s==b&&(s=0),r==b&&(r=Number.POSITIVE_INFINITY);var t=q>p?p:q;if(t*=.85,!(t>.95&&1>=t||t>=1&&1.05>t)){var u=t*parseFloat(h["font-size"]);u=k(s,u,r),d.dndCss("font-size",u+"px")}}a.$watch(e.dndFittext,n(function(a){h(a)}),!0),t(c).on("resize",function(){h()})}}}]),B.directive("dndKeyModel",["$parse","dndKey",function(a,c){return{restrict:"A",link:function(d,e,f){var g=a(f.dndKeyModel),h=g.assign;d.$watch(function(){return c.get()},function(a,c){a!==b&&h(d,a)})}}}]),B.directive("dndContainment",["$parse",function(a){function b(b,c,d){var e=a(c.dndContainment);this.get=function(){var a=e(d);return a?b.dndClosest(a).eq(0):b.parent()}}return b.$inject=["$element","$attrs","$scope"],{restrict:"EAC",controller:b}}]),B.factory("dndKey",["$rootScope",function(a){function b(){}function c(b){var c=b.keyCode;f(b),e.indexOf(c)>-1||(e.push(c),a.$digest())}function d(b){var c=b.keyCode,d=e.indexOf(c);-1!==d&&(e.splice(d,1),a.$digest())}var e=[];b.prototype={get:function(){return e},isset:function(a){var b=e.indexOf(a);return-1!==b}};var f=m(d,1e3);return u.on("keydown",c),u.on("keyup",d),new b}]),B.factory("DndLasso",[function(){function a(a){this.getRect=function(){return this.isActive?a.rect:b},this.getClientRect=function(){return this.isActive?f.dndClientRect():b},this.isActive=function(){return a.active}}function c(b){var c=b.isTarget(),d=new a(this);this.isTarget=function(){return c},this.handler=function(){return d},this.getEvent=function(){return b.getEvent()}}function e(a){function b(b){var d=b.local=new c(b);return d.isTarget()?(d.active=!0,i.trigger("start",d.handler()),b.setReferenceElement(a.$el),b.setBounderElement(a.$el),d.startAxis=b.getRelBorderedAxis(),f.removeAttr("class style").removeClass("ng-hide").addClass(a.className),void a.$el.append(f)):void i.trigger("start",d.handler())}function e(b){var c=b.local;if(!c.active)return void i.trigger("drag",c.handler());var d=b.getRelBorderedAxis().minus(c.startAxis),e={top:c.startAxis.y,left:c.startAxis.x,width:d.x,height:d.y};e.width<0&&(e.width=-e.width,e.left=e.left-e.width),e.height<0&&(e.height=-e.height,e.top=e.top-e.height),c.rect=e,e.top+=a.offsetY,e.left+=a.offsetX,f.dndCss(e),i.trigger("drag",c.handler())}function h(a){var b=a.local;return b.active?(f.addClass("ng-hide"),t(d.body).append(f),void i.trigger("end",b.handler())):void i.trigger("end",b.handler())}var i=this;a=x({},g,a);var j={"$$lasso.dragstart":b,"$$lasso.drag":e,"$$lasso.dragend":h};a.$el.dndBind(j),this.destroy=function(){a.$el.dndUnbind()};var k={};this.on=function(a,b){k[a]=k[a]||[],k[a].push(b)},this.trigger=function(a,b){k[a]=k[a]||[],b=b||"string"==typeof b?[b]:[],k[a].forEach(function(a){a.apply(this,b)})}}var f=t("
").dndCss({position:"absolute"}),g={className:"angular-dnd-lasso",offsetX:0,offsetY:0};return e}]),B.factory("EventEmitter",[function(){function a(){var a={};this.on=function(b,c){a[b]=a[b]||[],a[b].push(c)},this.off=function(b,c){if(a[b])for(var d=0,e=a[b].length;e>d;d++)a[b][d]===c&&a[b].splice(d,1)},this.trigger=function(b,c){a[b]=a[b]||[],c=c||"string"==typeof c?[c]:[],a[b].forEach(function(a){a.apply(this,c)})}}return a}])}(angular,void 0,window,document); -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | require('./dist/angular-dnd'); 2 | module.exports = 'dnd'; 3 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-dnd-module", 3 | "version": "0.1.25", 4 | "author": "Alexander Afonin", 5 | "repository": { 6 | "type": "git", 7 | "url": "https://github.com/Tuch/angular-dnd.git" 8 | }, 9 | "dependencies": { 10 | "angular": "^1.4.6" 11 | }, 12 | "devDependencies": { 13 | "grunt": "^0.4.5", 14 | "grunt-contrib": "*", 15 | "grunt-contrib-concat": "*", 16 | "grunt-contrib-connect": "*", 17 | "grunt-contrib-uglify": "~0.4.0", 18 | "grunt-contrib-watch": "*", 19 | "grunt-wrap": "*" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/core.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * @license AngularJS-DND v0.1.25 4 | * (c) 2014-2015 Alexander Afonin (toafonin@gmail.com, http://github.com/Tuch) 5 | * License: MIT 6 | */ 7 | 8 | 'use strict'; 9 | 10 | /* 11 | 12 | ================= 13 | ANGULAR DND: 14 | ================= 15 | 16 | */ 17 | 18 | /* ENVIRONMENT VARIABLES */ 19 | 20 | var version = '0.1.25', 21 | $ = angular.element, $window = $(window), $document = $(document), body = 'body', TRANSFORM, TRANSFORMORIGIN, MATCHES_SELECTOR, 22 | debug = { 23 | mode: false, 24 | helpers: {} 25 | }, 26 | forEach = angular.forEach, 27 | extend = angular.extend; 28 | 29 | (function () { 30 | window.console = window.console || { 31 | log: noop, 32 | info: noop, 33 | warn: noop, 34 | error: noop 35 | }; 36 | })(); 37 | 38 | (function() { 39 | var agent = navigator.userAgent; 40 | 41 | if ( /webkit\//i.test(agent) ) { 42 | TRANSFORM = '-webkit-transform'; 43 | TRANSFORMORIGIN = '-webkit-transform-origin'; 44 | MATCHES_SELECTOR = 'webkitMatchesSelector'; 45 | } else if (/gecko\//i.test(agent)) { 46 | TRANSFORM = '-moz-transform'; 47 | TRANSFORMORIGIN = '-moz-transform-origin'; 48 | MATCHES_SELECTOR = 'mozMatchesSelector'; 49 | } else if (/trident\//i.test(agent)) { 50 | TRANSFORM = '-ms-transform'; 51 | TRANSFORMORIGIN = 'ms-transform-origin'; 52 | MATCHES_SELECTOR = 'msMatchesSelector'; 53 | } else if (/presto\//i.test(agent)) { 54 | TRANSFORM = '-o-transform'; 55 | TRANSFORMORIGIN = '-o-transform-origin'; 56 | MATCHES_SELECTOR = 'oMatchesSelector'; 57 | } else { 58 | TRANSFORM = 'transform'; 59 | TRANSFORMORIGIN = 'transform-origin'; 60 | MATCHES_SELECTOR = 'matches'; 61 | } 62 | 63 | })(); 64 | 65 | 66 | 67 | /* SOME HELPERS */ 68 | 69 | function noop() {} 70 | 71 | function doFalse() { 72 | return false; 73 | } 74 | 75 | function doTrue() { 76 | return true; 77 | } 78 | 79 | function proxy(context, fn) { 80 | return function() { 81 | fn.apply(context, arguments); 82 | }; 83 | } 84 | 85 | function degToRad(d) { 86 | return (d * (Math.PI / 180)); 87 | } 88 | 89 | function radToDeg(r) { 90 | return (r * (180 / Math.PI)); 91 | } 92 | 93 | function getNumFromSegment(min, curr, max) { 94 | return curr max ? max : curr; 95 | } 96 | 97 | function findEvents(element) { 98 | 99 | var events = element.data('events'); 100 | if (events !== undefined) { 101 | return events; 102 | } 103 | 104 | events = $.data(element, 'events'); 105 | if (events !== undefined) { 106 | return events; 107 | } 108 | 109 | events = $._data(element, 'events'); 110 | if (events !== undefined) { 111 | return events; 112 | } 113 | 114 | events = $._data(element[0], 'events'); 115 | if (events !== undefined) { 116 | return events; 117 | } 118 | 119 | return undefined; 120 | } 121 | 122 | function debounce(fn, timeout, invokeAsap, context) { 123 | if (arguments.length === 3 && typeof invokeAsap !== 'boolean') { 124 | context = invokeAsap; 125 | invokeAsap = false; 126 | } 127 | 128 | var timer; 129 | 130 | return function() { 131 | var args = arguments; 132 | context = context || this; 133 | 134 | if (invokeAsap && !timer) { 135 | fn.apply(context, args); 136 | } 137 | 138 | clearTimeout(timer); 139 | 140 | timer = setTimeout(function() { 141 | if (!invokeAsap) { 142 | fn.apply(context, args); 143 | } 144 | 145 | timer = null; 146 | 147 | }, timeout); 148 | 149 | }; 150 | } 151 | 152 | function throttle(fn, timeout, context) { 153 | var timer, args; 154 | 155 | return function() { 156 | if (timer) { 157 | return; 158 | } 159 | 160 | args = arguments; 161 | context = context || this; 162 | fn.apply(context, args); 163 | timer = setTimeout(function() { timer = null; }, timeout); 164 | }; 165 | } 166 | 167 | 168 | /* parsing like: ' a = fn1(), b = fn2()' into {a: 'fn1()', b: 'fn2()'} */ 169 | 170 | function parseStringAsVars(str) { 171 | if (!str) { 172 | return undefined; 173 | } 174 | 175 | var a = str.replace(/\s/g,'').split(','), ret = {}; 176 | 177 | for( var i = 0; i < a.length; i++ ) { 178 | a[i] = a[i].split('='); 179 | ret[a[i][0]] = a[i][1]; 180 | } 181 | 182 | return ret; 183 | } 184 | 185 | function avgPerf (fn1, timeout, context, callback) { 186 | context = context || this; 187 | timeout = timeout || 1000; 188 | callback = callback || function(val) { 189 | console.log(val); 190 | }; 191 | 192 | var time = []; 193 | 194 | var fn2 = debounce(function() { 195 | var sum = 0; 196 | 197 | for(var i=0; i < time.length; i++) { 198 | sum += time[i]; 199 | } 200 | 201 | callback( Math.round(sum / time.length) ); 202 | 203 | time = []; 204 | 205 | }, timeout); 206 | 207 | return function() { 208 | var start = Date.now(); 209 | 210 | fn1.apply(context, arguments); 211 | 212 | time.push(Date.now() - start); 213 | 214 | fn2(); 215 | 216 | }; 217 | } 218 | 219 | function roundNumber(number, n) { 220 | if (isNaN(n)) { 221 | n=0; 222 | } 223 | 224 | var m = Math.pow(10,n); 225 | return Math.round(number * m) / m; 226 | } 227 | 228 | 229 | /* POINT OBJECT */ 230 | 231 | var Point = (function() { 232 | function Point(x, y) { 233 | if (typeof x === 'object') { 234 | y = x.y || x.top; 235 | x = x.x || x.left; 236 | } 237 | 238 | this.x = x || 0; 239 | this.y = y || 0; 240 | } 241 | 242 | Point.prototype = { 243 | equal: function(other) { 244 | if (!(other instanceof Point)) { 245 | other = new Point(other); 246 | } 247 | 248 | return this.x === other.x && this.y === other.y; 249 | }, 250 | plus: function(other) { 251 | if (!(other instanceof Point)) { 252 | other = new Point(other); 253 | } 254 | 255 | return new Point(this.x + other.x, this.y + other.y); 256 | }, 257 | minus: function(other) { 258 | if (!(other instanceof Point)) { 259 | other = new Point(other); 260 | } 261 | 262 | return new Point(this.x - other.x, this.y - other.y); 263 | }, 264 | scale: function(scalar) { 265 | return new Point(this.x * scalar, this.y * scalar); 266 | }, 267 | magnitude: function() { 268 | return this.distance(new Point(0, 0), this); 269 | }, 270 | distance: function(other) { 271 | if (!(other instanceof Point)) { 272 | other = new Point(other); 273 | } 274 | 275 | return Math.sqrt(Math.pow(this.x - other.x, 2) + Math.pow(this.y - other.y, 2)); 276 | }, 277 | angle: function (other, isdegree) { 278 | var ret = Math.atan2( 279 | other.y - this.y, 280 | other.x - this.x 281 | ); 282 | 283 | if (isdegree===true) { 284 | ret *= 180/Math.PI; 285 | } 286 | 287 | return ret; 288 | }, 289 | deltaAngle: function(other, aboutPoint, isdegree) { 290 | aboutPoint = aboutPoint === undefined ? {x:0,y:0} : aboutPoint; 291 | var ret = this.angle(aboutPoint) - other.angle(aboutPoint); 292 | 293 | if (ret < 0) { 294 | ret = Math.PI*2 + ret; 295 | } 296 | 297 | if (isdegree===true) { 298 | ret *= 180/Math.PI; 299 | } 300 | 301 | return ret; 302 | }, 303 | transform: function(matrix) { 304 | return matrix.transformPoint(this); 305 | }, 306 | deltaTransform: function(matrix) { 307 | return matrix.deltaTransformPoint(this); 308 | }, 309 | rotate: function(rads, aboutPoint) { 310 | var matrix = (new Matrix()).rotate(rads, aboutPoint); 311 | 312 | return this.transform(matrix); 313 | }, 314 | getAsCss: function() { 315 | return { 316 | top: this.y, 317 | left: this.x 318 | }; 319 | } 320 | }; 321 | 322 | return function(x,y) { 323 | return new Point(x,y); 324 | }; 325 | })(); 326 | 327 | 328 | 329 | /* MATRIX OBJECT */ 330 | 331 | var Matrix = (function() { 332 | function Matrix(a, b, c, d, tx, ty) { 333 | this.a = a !== undefined ? a : 1; 334 | this.b = b || 0; 335 | this.c = c || 0; 336 | this.d = d !== undefined ? d : 1; 337 | this.tx = tx || 0; 338 | this.ty = ty || 0; 339 | } 340 | 341 | Matrix.prototype = { 342 | concat: function(other) { 343 | return new Matrix( 344 | this.a * other.a + this.c * other.b, 345 | this.b * other.a + this.d * other.b, 346 | this.a * other.c + this.c * other.d, 347 | this.b * other.c + this.d * other.d, 348 | this.a * other.tx + this.c * other.ty + this.tx, 349 | this.b * other.tx + this.d * other.ty + this.ty 350 | ); 351 | }, 352 | inverse: function() { 353 | var determinant = this.a * this.d - this.b * this.c; 354 | 355 | return new Matrix( 356 | this.d / determinant, 357 | -this.b / determinant, 358 | -this.c / determinant, 359 | this.a / determinant, 360 | (this.c * this.ty - this.d * this.tx) / determinant, 361 | (this.b * this.tx - this.a * this.ty) / determinant 362 | ); 363 | }, 364 | rotate: function(theta, aboutPoint) { 365 | var rotationMatrix = new Matrix( 366 | Math.cos(theta), 367 | Math.sin(theta), 368 | -Math.sin(theta), 369 | Math.cos(theta) 370 | ); 371 | 372 | if (aboutPoint) { 373 | rotationMatrix = this.translate(aboutPoint.x, aboutPoint.y).concat(rotationMatrix).translate(-aboutPoint.x, -aboutPoint.y); 374 | } 375 | 376 | return this.concat(rotationMatrix); 377 | }, 378 | scale: function(sx, sy, aboutPoint) { 379 | sy = sy || sx; 380 | 381 | var scaleMatrix = new Matrix(sx, 0, 0, sy); 382 | 383 | if (aboutPoint) { 384 | scaleMatrix = scaleMatrix.translate(aboutPoint.x, aboutPoint.y).translate(-aboutPoint.x, -aboutPoint.y); 385 | } 386 | 387 | return scaleMatrix; 388 | }, 389 | translate: function(tx, ty) { 390 | var translateMatrix = new Matrix(1, 0, 0, 1, tx, ty); 391 | 392 | return this.concat(translateMatrix); 393 | }, 394 | 395 | transformPoint: function(point) { 396 | return new Point( 397 | this.a * point.x + this.c * point.y + this.tx, 398 | this.b * point.x + this.d * point.y + this.ty 399 | ); 400 | }, 401 | deltaTransformPoint: function(point) { 402 | return new Point( 403 | this.a * point.x + this.c * point.y, 404 | this.b * point.x + this.d * point.y 405 | ); 406 | }, 407 | toStyle: function() { 408 | var a = roundNumber(this.a, 3), 409 | b = roundNumber(this.b, 3), 410 | c = roundNumber(this.c, 3), 411 | d = roundNumber(this.d, 3), 412 | tx = roundNumber(this.tx, 3), 413 | ty = roundNumber(this.ty, 3), 414 | result = 'matrix(' + a + ', ' + b + ', ' + c + ', ' + d + ', ' + tx +', ' + ty + ')'; 415 | 416 | return result === 'matrix(1, 0, 0, 1, 0, 0)' ? 'none' : result; 417 | } 418 | }; 419 | 420 | var fn = function(a, b, c, d, tx, ty) { 421 | return new Matrix(a, b, c, d, tx, ty); 422 | }; 423 | 424 | fn.IDENTITY = new Matrix(); 425 | fn.HORIZONTAL_FLIP = new Matrix(-1, 0, 0, 1); 426 | fn.VERTICAL_FLIP = new Matrix(1, 0, 0, -1); 427 | 428 | return fn; 429 | }()); 430 | 431 | /* RECT OBJECT */ 432 | 433 | var Rect = (function() { 434 | function Rect(tl, tr, bl, br) { 435 | this.tl = tl; 436 | this.tr = tr; 437 | this.bl = bl; 438 | this.br = br; 439 | } 440 | 441 | Rect.prototype = { 442 | applyMatrix: function(matrix, aboutPoint) { 443 | var tl, tr, bl, br, 444 | translateIn = new Matrix(1,0,0,1,aboutPoint.x,aboutPoint.y), 445 | translateOut = new Matrix(1,0,0,1,-aboutPoint.x,-aboutPoint.y); 446 | 447 | if (aboutPoint !== undefined) { 448 | tl = this.tl.transform(translateOut).transform(matrix).transform(translateIn); 449 | tr = this.tr.transform(translateOut).transform(matrix).transform(translateIn); 450 | bl = this.bl.transform(translateOut).transform(matrix).transform(translateIn); 451 | br = this.br.transform(translateOut).transform(matrix).transform(translateIn); 452 | } else { 453 | tl = this.tl.transform(matrix); 454 | tr = this.tr.transform(matrix); 455 | bl = this.bl.transform(matrix); 456 | br = this.br.transform(matrix); 457 | } 458 | 459 | return new Rect(tl, tr, bl, br); 460 | }, 461 | width: function() { 462 | var dx = this.tl.x - this.tr.x; 463 | var dy = this.tl.y - this.tr.y; 464 | return Math.sqrt(dx*dx+dy*dy); 465 | }, 466 | height: function() { 467 | var dx = this.tl.x - this.bl.x; 468 | var dy = this.tl.y - this.bl.y; 469 | return Math.sqrt(dx*dx+dy*dy); 470 | }, 471 | client: function() { 472 | var top = Math.min(this.tl.y, this.tr.y, this.bl.y, this.br.y); 473 | var left = Math.min(this.tl.x, this.tr.x, this.bl.x, this.br.x); 474 | var height = Math.max(this.tl.y, this.tr.y, this.bl.y, this.br.y)-top; 475 | var width = Math.max(this.tl.x, this.tr.x, this.bl.x, this.br.x)-left; 476 | 477 | return { 478 | top: roundNumber(top,1), 479 | left: roundNumber(left,1), 480 | height: roundNumber(height,1), 481 | width: roundNumber(width,1), 482 | bottom: roundNumber(top+height, 1), 483 | right: roundNumber(left+width, 1) 484 | }; 485 | }, 486 | getAngle: function(degs) { 487 | var y = this.tl.y-this.tr.y; 488 | var x = this.tl.x-this.tr.x; 489 | 490 | return Math.tan(x/y)*180/Math.PI; 491 | } 492 | }; 493 | 494 | var fn = function(left, top, width, height) { 495 | var args = arguments; 496 | 497 | if (typeof args[0] === 'object') { 498 | top = args[0].top; 499 | left = args[0].left; 500 | width = args[0].width; 501 | height = args[0].height; 502 | } 503 | 504 | return new Rect( 505 | new Point(left,top), 506 | new Point(left+width,top), 507 | new Point(left,top+height), 508 | new Point(left+width,top+height) 509 | ); 510 | }; 511 | 512 | fn.fromPoints = function(tl,tr,bl,br) { 513 | return new Rect( tl,tr,bl,br ); 514 | }; 515 | 516 | return fn; 517 | })(); 518 | 519 | /* JQLITE EXTENDING */ 520 | 521 | extend($.prototype, { 522 | 523 | dndDisableSelection: function() { 524 | this.on('dragstart selectstart', doFalse ).dndCss({ 525 | '-moz-user-select': 'none', 526 | '-khtml-user-select': 'none', 527 | '-webkit-user-select': 'none', 528 | '-o-user-select': 'none', 529 | '-ms-user-select': 'none', 530 | 'user-select': 'none' 531 | }); 532 | }, 533 | 534 | dndEnableSelection: function() { 535 | this.off('dragstart selectstart', doFalse ).dndCss({ 536 | '-moz-user-select': 'auto', 537 | '-khtml-user-select': 'auto', 538 | '-webkit-user-select': 'auto', 539 | '-o-user-select': 'auto', 540 | '-ms-user-select': 'auto', 541 | 'user-select': 'auto' 542 | }); 543 | }, 544 | 545 | dndClientRect: function() { 546 | if (!this[0]) { 547 | return; 548 | } 549 | 550 | var DOMRect = this[0] === window ? {top:0,bottom:0,left:0,right:0,width:0,height:0} : this[0].getBoundingClientRect(); 551 | 552 | return { 553 | bottom: DOMRect.bottom, 554 | height: DOMRect.height, 555 | left: DOMRect.left, 556 | right: DOMRect.right, 557 | top: DOMRect.top, 558 | width: DOMRect.width, 559 | }; 560 | }, 561 | 562 | dndStyleRect: function() { 563 | var styles = this.dndCss(['width','height','top','left']); 564 | 565 | var width = parseFloat(styles.width); 566 | var height = parseFloat(styles.height); 567 | 568 | var top = styles.top === 'auto' ? 0 : parseFloat(styles.top); 569 | var left = styles.left === 'auto' ? 0 : parseFloat(styles.left); 570 | 571 | return {top: top, right: left+width, bottom: top+height, left: left, width: width, height: height}; 572 | }, 573 | 574 | dndGetParentScrollArea: function() { 575 | var ret = [], parents = this.dndClosest(), scrollX, clientX, scrollY, clientY, paddingX, paddingY, paddings, htmlEl = document.documentElement; 576 | 577 | forEach(parents, function(element) { 578 | 579 | paddings = $(element).dndCss(['padding-top', 'padding-right', 'padding-bottom', 'padding-left']); 580 | 581 | scrollX = element.scrollWidth; 582 | clientX = element.clientWidth; 583 | scrollY = element.scrollHeight; 584 | clientY = element.clientHeight; 585 | 586 | paddingY = parseFloat(paddings['padding-top']) + parseFloat(paddings['padding-bottom']); 587 | paddingX = parseFloat(paddings['padding-left']) + parseFloat(paddings['padding-right']); 588 | 589 | if ( scrollX - paddingX !== clientX || scrollY - paddingY !== clientY ) { 590 | ret.push(element); 591 | } 592 | }); 593 | 594 | ret.push(window); 595 | 596 | return $(ret); 597 | }, 598 | 599 | dndGetFirstNotStaticParent: function() { 600 | var ret, position, parents = this.dndClosest(); 601 | 602 | forEach(parents, function(element) { 603 | 604 | position = $(element).dndCss('position'); 605 | 606 | if ( position === 'absolute' || position === 'relative' || position === 'fixed' ) { 607 | ret = element; 608 | return false; 609 | } 610 | }); 611 | 612 | if (!ret) { 613 | ret = document.documentElement; 614 | } 615 | 616 | return $(ret); 617 | }, 618 | 619 | dndClosest: function(selector) { 620 | selector = selector || '*'; 621 | var parent = this[0], ret = []; 622 | 623 | while(parent) { 624 | parent[MATCHES_SELECTOR](selector) && ret.push(parent); 625 | parent = parent.parentElement; 626 | } 627 | 628 | return $(ret); 629 | }, 630 | dndGetAngle: function (degs) { 631 | var matrix = this.dndCss(TRANSFORM); 632 | 633 | if (matrix === 'none' || matrix === '') { 634 | return 0; 635 | } 636 | 637 | var values = matrix.split('(')[1].split(')')[0].split(','), 638 | a = values[0], b = values[1], rads = Math.atan2(b, a); 639 | 640 | rads = rads < 0 ? rads +=Math.PI*2 : rads; 641 | 642 | return degs ? Math.round(rads * 180/Math.PI) : rads; 643 | }, 644 | 645 | dndCloneByStyles: function () { 646 | var ret = []; 647 | 648 | for (var i = 0, length = this.length; i < length; i++) { 649 | var node = this[i].cloneNode(); 650 | var childNodes = angular.element(this[i].childNodes).dndCloneByStyles(); 651 | 652 | angular.element(node).append(childNodes); 653 | 654 | if (node.nodeType === 1) { 655 | node.style.cssText = window.getComputedStyle(this[i], "").cssText; 656 | } 657 | 658 | ret.push(node); 659 | } 660 | 661 | return angular.element(ret); 662 | }, 663 | 664 | dndCss: (function() { 665 | var SPECIAL_CHARS_REGEXP = /([\:\-\_\.]+(.))/g, 666 | MOZ_HACK_REGEXP = /^moz([A-Z])/, hooks = {}; 667 | 668 | function toCamelCase(string) { 669 | return string.replace(SPECIAL_CHARS_REGEXP, function(_, separator, letter, offset) { 670 | return offset ? letter.toUpperCase() : letter; 671 | }).replace(MOZ_HACK_REGEXP, 'Moz$1'); 672 | } 673 | 674 | (function() { 675 | var arr = { 676 | width: ['paddingLeft','paddingRight','borderLeftWidth', 'borderRightWidth'], 677 | height: ['paddingTop','paddingBottom','borderTopWidth', 'borderBottomWidth'] 678 | }; 679 | 680 | forEach(arr, function(styles, prop) { 681 | 682 | hooks[prop] = { 683 | get: function(element) { 684 | var computed = window.getComputedStyle(element); 685 | var ret = computed[prop]; 686 | 687 | if ( computed.boxSizing !== 'border-box' || ret[ret.length-1] === '%') { 688 | return ret; 689 | } 690 | 691 | ret = parseFloat(ret); 692 | 693 | for(var i = 0; i < styles.length; i++) { 694 | ret -= parseFloat(computed[ styles[i] ]); 695 | } 696 | 697 | return ret + 'px'; 698 | } 699 | }; 700 | 701 | }); 702 | 703 | })(); 704 | 705 | var cssNumber = { 706 | 'columnCount': true, 707 | 'fillOpacity': true, 708 | 'fontWeight': true, 709 | 'lineHeight': true, 710 | 'opacity': true, 711 | 'order': true, 712 | 'orphans': true, 713 | 'widows': true, 714 | 'zIndex': true, 715 | 'zoom': true 716 | }; 717 | 718 | var setCss = function($element, obj) { 719 | var styles = {}; 720 | 721 | for(var key in obj) { 722 | var val = obj[key]; 723 | 724 | if ( typeof val === 'number' && !cssNumber[key] ) { 725 | val += 'px'; 726 | } 727 | 728 | styles[toCamelCase(key)] = val; 729 | } 730 | 731 | $element.css(styles); 732 | 733 | return $element; 734 | }; 735 | 736 | var getCss = function($element, arg) { 737 | var ret = {}; 738 | 739 | if (!$element[0]) { 740 | return undefined; 741 | } 742 | 743 | var style = $element[0].style; 744 | var computed = window.getComputedStyle( $element[0], null ); 745 | 746 | if (typeof arg === 'string') { 747 | if (style[arg]) { 748 | return style[arg]; 749 | } else { 750 | return hooks[arg] && 'get' in hooks[arg] ? hooks[arg].get($element[0]) : computed.getPropertyValue( arg ); 751 | } 752 | } 753 | 754 | for(var i=0; i < arg.length; i++) { 755 | if (style[arg[i]]) { 756 | ret[arg[i]] = style[ arg[i] ]; 757 | } else { 758 | ret[arg[i]] = hooks[arg[i]] && 'get' in hooks[arg[i]] ? hooks[arg[i]].get($element[0]) : computed.getPropertyValue( arg[i] ); 759 | } 760 | } 761 | 762 | return ret; 763 | }; 764 | 765 | function css() { 766 | var a = arguments; 767 | 768 | if ( (a.length === 1) && ((a[0] instanceof Array) || (typeof a[0] === 'string')) ) { 769 | return getCss(this, a[0]); 770 | } else if ( (a.length === 1) && (typeof a[0] === 'object') ) { 771 | return setCss(this, a[0]); 772 | } else if ( a.length === 2 ) { 773 | var obj = {}; 774 | 775 | obj[a[0]] = a[1]; 776 | 777 | return setCss(this, obj); 778 | } 779 | 780 | return this; 781 | 782 | } 783 | 784 | return css; 785 | })() 786 | }); 787 | 788 | /* INIT ANGULAR MODULE */ 789 | 790 | var module = angular.module('dnd', []); 791 | 792 | /* ANGULAR.ELEMENT DND PLUGIN - CORE OF ANGULAR-DND */ 793 | 794 | (function() { 795 | 796 | var Regions = (function() { 797 | var list = {}; 798 | 799 | function Regions(layer) { 800 | if (!list[layer]) { 801 | list[layer] = []; 802 | } 803 | 804 | this.layer = function() { 805 | return layer; 806 | }; 807 | } 808 | 809 | Regions.prototype = { 810 | get: function() { 811 | return list[this.layer()]; 812 | }, 813 | 814 | remove: function(el) { 815 | var index = this.get().indexOf(el); 816 | 817 | if (index > -1) { 818 | this.get().splice(index,1); 819 | } 820 | }, 821 | 822 | has: function(el) { 823 | return this.get().indexOf(el) > -1; 824 | }, 825 | 826 | add: function(el) { 827 | if (this.has(el)) { 828 | return; 829 | } 830 | 831 | this.get().push(el); 832 | 833 | var self = this; 834 | 835 | $(el).on('$destroy', function() { self.remove(el); }); 836 | }, 837 | 838 | }; 839 | 840 | return Regions; 841 | })(); 842 | 843 | var Dnd = (function() { 844 | 845 | var events = [ 'dragstart', 'drag', 'dragend', 'dragenter', 'dragover', 'dragleave', 'drop' ]; 846 | var draggables = [ 'dragstart', 'drag', 'dragend' ]; 847 | var droppables = [ 'dragenter', 'dragover', 'dragleave', 'drop' ]; 848 | var handled = false; 849 | 850 | draggables.has = droppables.has = function(event) { 851 | return this.indexOf(event) > -1; 852 | }; 853 | 854 | var touchevents; 855 | if ('ontouchstart' in document.documentElement) { 856 | touchevents = {start: 'touchstart', move: 'touchmove', end: 'touchend', cancel: 'touchcancel'}; 857 | } else if ('pointerEnabled' in window.navigator) { 858 | touchevents = {start: 'pointerdown', move: 'pointermove', end: 'pointerup', cancel: 'pointercancel'}; 859 | } else if ('msPointerEnabled' in window.navigator) { 860 | touchevents = {start: 'MSPointerDown', move: 'MSPointerMove', end: 'MSPointerUp', cancel: 'MSPointerCancel'}; 861 | } else { 862 | touchevents = {start: 'touchstart', move: 'touchmove', end: 'touchend', cancel: 'touchcancel'}; 863 | } 864 | 865 | function Dnd(el, layer) { 866 | this.el = el; 867 | this.$el = $(el); 868 | this.listeners = { 'dragstart':[], 'drag':[], 'dragend':[], 'dragenter':[], 'dragover':[], 'dragleave':[], 'drop':[] }; 869 | this.regions = new Regions(layer); 870 | this.layer = function() { 871 | return layer; 872 | }; 873 | this.setCurrentManipulator(null); 874 | } 875 | 876 | Dnd.prototype = { 877 | _isEmptyListeners: function(event) { 878 | if (event instanceof Array) { 879 | 880 | for(var i=0; i < event.length; i++ ) { 881 | if (!this._isEmptyListeners(event[i])) { 882 | return false; 883 | } 884 | } 885 | 886 | } else if (this.listeners[event].length > 0) { 887 | return false; 888 | } 889 | 890 | return true; 891 | }, 892 | 893 | addListener: function(event, handler) { 894 | if (events.indexOf(event) === -1) { 895 | console.error('jquery.dnd: invalid event name - ', event); 896 | return this; 897 | } 898 | this.listeners[event].push( handler); 899 | 900 | if ( droppables.has(event) ) { 901 | this.regions.add(this.el); 902 | } else if (draggables.has(event) && !this.mouse && !this.touch) { 903 | if ('onmousedown' in window) { 904 | this.mouse = new Mouse(this); 905 | } 906 | if ( ('ontouchstart' in window) || ('onmsgesturechange' in window) ) { 907 | this.touch = new Touch(this, touchevents); 908 | } 909 | 910 | } 911 | 912 | return this; 913 | }, 914 | 915 | removeListener: function(event, handler) { 916 | var args = arguments; 917 | 918 | if (args.length === 0) { 919 | for( var key in this.listeners ) { 920 | this.listeners[key].length = 0; 921 | } 922 | } else if (args.length === 1) { 923 | this.listeners[event].length = 0; 924 | } else { 925 | var listeners = this.listeners[event]; 926 | 927 | for(var i=0; i < listeners.length; i++) { 928 | if ( listeners[i] === handler ) listeners[event].splice(i,1); 929 | } 930 | 931 | } 932 | 933 | if ( this._isEmptyListeners(droppables) ) this.regions.remove(this.el); 934 | else if ( this._isEmptyListeners(draggables)) this.destroy(); 935 | 936 | return this; 937 | }, 938 | 939 | trigger: function(event, api, el) { 940 | for(var i=0; i < this.listeners[event].length; i++) { 941 | this.listeners[event][i].call(this.$el, api, el); 942 | } 943 | 944 | return this; 945 | }, 946 | 947 | destroy: function() { 948 | if ( this.mouse ) { 949 | this.mouse.destroy(); 950 | delete this.mouse; 951 | } 952 | 953 | if ( this.touch ) { 954 | this.touch.destroy(); 955 | delete this.touch; 956 | } 957 | 958 | return this; 959 | }, 960 | 961 | setCurrentManipulator: function (manipulator) { 962 | this._manipulator = manipulator; 963 | 964 | return this; 965 | }, 966 | 967 | getCurrentManipulator: function () { 968 | return this._manipulator; 969 | } 970 | }; 971 | 972 | 973 | return Dnd; 974 | })(); 975 | 976 | var Api = (function() { 977 | 978 | function Api(manipulator) { 979 | this._manipulator = manipulator; 980 | } 981 | 982 | Api.prototype = { 983 | getAxis: function() { 984 | return this._manipulator.getClientAxis.apply(this._manipulator, arguments); 985 | }, 986 | getBorderedAxis: function() { 987 | return this._manipulator.getBorderedAxis.apply(this._manipulator, arguments); 988 | }, 989 | getRelBorderedAxis: function() { 990 | return this._manipulator.getRelBorderedAxis.apply(this._manipulator, arguments); 991 | }, 992 | getDragTarget: function() { 993 | return this._manipulator.dnd.el; 994 | }, 995 | getDropTarget: function() { 996 | return this._manipulator.target; 997 | }, 998 | getEvent: function() { 999 | return this._manipulator.event; 1000 | }, 1001 | isTarget: function() { 1002 | return this._manipulator.isTarget.apply(this._manipulator, arguments); 1003 | }, 1004 | unTarget: function() { 1005 | this._manipulator.removeFromTargets(); 1006 | }, 1007 | useAsPoint: function(value) { 1008 | return this._manipulator.asPoint = !(value === false); 1009 | }, 1010 | setBounderElement: function(node) { 1011 | this._manipulator.$bounder = angular.element(node); 1012 | this.clearCache(); 1013 | }, 1014 | setReferenceElement: function(node) { 1015 | this._manipulator.$reference = angular.element(node); 1016 | }, 1017 | getBorders: function() { 1018 | return this._manipulator.getBorders.apply(this._manipulator, arguments); 1019 | }, 1020 | getReferencePoint: function() { 1021 | return this._manipulator.getReferencePoint.apply(this._manipulator, arguments); 1022 | }, 1023 | clearCache: function() { 1024 | this._manipulator.clearCache.apply(this._manipulator, arguments); 1025 | } 1026 | }; 1027 | 1028 | return Api; 1029 | 1030 | })(); 1031 | 1032 | var Manipulator = (function() { 1033 | var targets = []; 1034 | 1035 | function Manipulator(dnd) { 1036 | this.dnd = dnd; 1037 | this.onscroll = proxy(this, this.onscroll); 1038 | } 1039 | 1040 | Manipulator.prototype = { 1041 | 1042 | getBorders: function(offset) { 1043 | if (!this.$bounder) { 1044 | return; 1045 | } 1046 | 1047 | var borders = this.getCache('borders'); 1048 | 1049 | if (!borders) { 1050 | var rect = this.$bounder.dndClientRect(); 1051 | 1052 | borders = this.setCache('borders', { 1053 | top: rect.top, 1054 | left: rect.left, 1055 | right: rect.right, 1056 | bottom: rect.bottom 1057 | }); 1058 | } 1059 | 1060 | return { 1061 | top: borders.top + (offset ? offset.top : 0), 1062 | left: borders.left + (offset ? offset.left : 0), 1063 | right: borders.right + (offset ? offset.right : 0), 1064 | bottom: borders.bottom + (offset ? offset.bottom : 0) 1065 | }; 1066 | }, 1067 | 1068 | getReferencePoint: function() { 1069 | var referencePoint = this.getCache('referencePoint'); 1070 | 1071 | if (!referencePoint) { 1072 | var rect = this.$reference.dndClientRect(); 1073 | 1074 | referencePoint = this.setCache('referencePoint', new Point(rect.left, rect.top)); 1075 | } 1076 | 1077 | return referencePoint; 1078 | }, 1079 | 1080 | getBorderedAxis: function(borderOffset, axisOffset) { 1081 | var axis = this.getClientAxis(axisOffset); 1082 | var borders = this.getBorders(borderOffset); 1083 | 1084 | var result = borders ? new Point( 1085 | getNumFromSegment(borders.left, axis.x, borders.right), 1086 | getNumFromSegment(borders.top, axis.y, borders.bottom) 1087 | ) : axis; 1088 | 1089 | return result; 1090 | }, 1091 | 1092 | getRelBorderedAxis: function(borderOffset, axisOffset) { 1093 | return this.getBorderedAxis(borderOffset, axisOffset).minus( this.getReferencePoint() ); 1094 | }, 1095 | 1096 | addToTargets: function() { 1097 | targets.push(this); 1098 | }, 1099 | 1100 | removeFromTargets: function() { 1101 | var index; 1102 | 1103 | while(index !== -1) { 1104 | index = targets.indexOf(this); 1105 | if (index > -1) { 1106 | targets.splice(index, 1); 1107 | } 1108 | } 1109 | }, 1110 | 1111 | getTarget: function() { 1112 | return targets[0]; 1113 | }, 1114 | 1115 | isTarget: function() { 1116 | return this.getTarget() === this; 1117 | }, 1118 | 1119 | start: function() { 1120 | this.started = true; 1121 | this.targets = []; 1122 | this.asPoint = false; 1123 | this.api = new Api(this); 1124 | this.$scrollareas = this.dnd.$el.dndGetParentScrollArea(); 1125 | this.$reference = this.dnd.$el.dndGetFirstNotStaticParent(); 1126 | this.$scrollareas.on('scroll', this.onscroll); 1127 | this.dnd.trigger('dragstart', this.api); 1128 | }, 1129 | 1130 | onscroll: function() { 1131 | this.clearCache(); 1132 | this.dnd.trigger('drag', this.api); 1133 | }, 1134 | 1135 | getCache: function(key) { 1136 | return this.cache[key]; 1137 | }, 1138 | 1139 | setCache: function(key, value) { 1140 | return this.cache[key] = value; 1141 | }, 1142 | 1143 | clearCache: function() { 1144 | this.cache = {}; 1145 | }, 1146 | 1147 | stop: function() { 1148 | this.$scrollareas.off ('scroll', this.onscroll); 1149 | 1150 | if (this.targets.length) { 1151 | for(var i = 0, length = this.targets.length; i < length; i++) { 1152 | $(this.targets[i]).data('dnd')[this.dnd.layer()].trigger('drop', this.api, this.dnd.el); 1153 | } 1154 | } 1155 | 1156 | this.dnd.trigger('dragend', this.api, this.targets); 1157 | }, 1158 | 1159 | 1160 | prepareRegions: function() { 1161 | var regions = this.dnd.regions.get(); 1162 | 1163 | var ret = []; 1164 | 1165 | for(var key = 0; key < regions.length; key++) { 1166 | var dnd = $( regions[key] ).data('dnd')[this.dnd.layer()]; 1167 | var rect = dnd.$el.dndClientRect(); 1168 | 1169 | if (this.dnd === dnd) { 1170 | continue; 1171 | } 1172 | 1173 | ret.push({ 1174 | dnd: dnd, 1175 | rect: rect 1176 | }); 1177 | } 1178 | 1179 | return ret; 1180 | 1181 | }, 1182 | 1183 | begin: function (event) { 1184 | if (this.dnd.getCurrentManipulator() || 1185 | $(event.target).dndClosest('[dnd-pointer-none]').length) { 1186 | return false; 1187 | } 1188 | 1189 | this.dnd.setCurrentManipulator(this); 1190 | 1191 | this.addToTargets(); 1192 | this.event = event; 1193 | this.started = false; 1194 | this.clearCache(); 1195 | angular.element(document.body).dndDisableSelection(); 1196 | 1197 | return true; 1198 | }, 1199 | 1200 | _isTriggerByPoint: function (p, r) { 1201 | return (p.x > r.left) && (p.x < r.right) && (p.y > r.top) && (p.y < r.bottom); 1202 | }, 1203 | 1204 | _isTriggerByRect: function (a, b) { 1205 | return a.top <= b.top && b.top <= a.bottom && ( a.left <= b.left && b.left <= a.right || a.left <= b.right && b.right <= a.right ) || 1206 | a.top <= b.bottom && b.bottom <= a.bottom && ( a.left <= b.left && b.left <= a.right || a.left <= b.right && b.right <= a.right ) || 1207 | a.left >= b.left && a.right <= b.right && ( b.top <= a.bottom && a.bottom <= b.bottom || b.bottom >= a.top && a.top >= b.top || a.top <= b.top && a.bottom >= b.bottom) || 1208 | a.top >= b.top && a.bottom <= b.bottom && ( b.left <= a.right && a.right <= b.right || b.right >= a.left && a.left >= b.left || a.left <= b.left && a.right >= b.right) || 1209 | a.top >= b.top && a.right <= b.right && a.bottom <= b.bottom && a.left >= b.left 1210 | }, 1211 | 1212 | progress: function (event) { 1213 | this.event = event; 1214 | 1215 | if (!this.started) { 1216 | this.start(); 1217 | } 1218 | 1219 | var regions = this.getCache('regions'); 1220 | 1221 | if (!regions) { 1222 | regions = this.setCache('regions', this.prepareRegions()); 1223 | if (debug.mode) { 1224 | this.showRegioins(); 1225 | } 1226 | } 1227 | 1228 | this.dnd.trigger('drag', this.api); 1229 | 1230 | var isTrigger = this.asPoint ? this._isTriggerByPoint.bind(this, this.getBorderedAxis()) : 1231 | this._isTriggerByRect.bind(this, angular.element(this.dnd.el).dndClientRect()); 1232 | var dragenter = []; 1233 | var dragover = []; 1234 | var dragleave = []; 1235 | 1236 | for(var i = 0; i < regions.length; i++) { 1237 | var region = regions[i], 1238 | trigger = isTrigger(region.rect), 1239 | targetIndex = this.targets.indexOf(region.dnd.el); 1240 | 1241 | if ( trigger ) { 1242 | if (targetIndex === -1) { 1243 | this.targets.push(region.dnd.el); 1244 | dragenter.push(region.dnd); 1245 | } else { 1246 | dragover.push(region.dnd); 1247 | } 1248 | } else if (targetIndex !== -1) { 1249 | dragleave.push($(this.targets[targetIndex]).data('dnd')[this.dnd.layer()]); 1250 | this.targets.splice(targetIndex, 1); 1251 | } 1252 | } 1253 | 1254 | this._triggerArray(dragleave, 'dragleave'); 1255 | this._triggerArray(dragenter, 'dragenter'); 1256 | this._triggerArray(dragover, 'dragover'); 1257 | }, 1258 | 1259 | _triggerArray: function (arr, event) { 1260 | for (var i = 0; i < arr.length; i++) { 1261 | arr[i].trigger(event, this.api, this.dnd.el); 1262 | } 1263 | }, 1264 | 1265 | end: function (event) { 1266 | this.event = event; 1267 | 1268 | if (this.started) { 1269 | this.stop(); 1270 | } 1271 | 1272 | angular.element(document.body).dndEnableSelection(); 1273 | 1274 | this.removeFromTargets(); 1275 | 1276 | debug.mode && this.hideRegions(); 1277 | 1278 | this.dnd.setCurrentManipulator(null); 1279 | }, 1280 | 1281 | showRegioins: function () { 1282 | this.hideRegions(); 1283 | 1284 | var regions = this.getCache('regions'), 1285 | bodyElement = angular.element(document.body), 1286 | bodyClientRect = bodyElement.dndClientRect(); 1287 | 1288 | for (var i = 0, length = regions.length; i < length; i++) { 1289 | var region = regions[i]; 1290 | 1291 | debug.helpers.renderRect( 1292 | region.rect.left - bodyClientRect.left, 1293 | region.rect.top - bodyClientRect.top, 1294 | region.rect.width, 1295 | region.rect.height 1296 | ); 1297 | } 1298 | }, 1299 | 1300 | hideRegions: function () { 1301 | var nodes = document.querySelectorAll('.dnd-debug-rect'); 1302 | 1303 | for (var i = 0, length = nodes.length; i < length; i++) { 1304 | nodes[i].remove(); 1305 | } 1306 | } 1307 | }; 1308 | 1309 | return Manipulator; 1310 | })(); 1311 | 1312 | function Mouse(dnd) { 1313 | this.dnd = dnd; 1314 | this.manipulator = new Manipulator(dnd); 1315 | this.mousedown = proxy(this, this.mousedown); 1316 | this.mousemove = proxy(this, this.mousemove); 1317 | this.mouseup = proxy(this, this.mouseup); 1318 | this.manipulator.getClientAxis = this.getClientAxis; 1319 | 1320 | dnd.$el.on('mousedown', this.mousedown); 1321 | } 1322 | 1323 | Mouse.prototype = { 1324 | 1325 | getClientAxis: function(offset) { 1326 | return new Point(this.event.clientX + (offset ? offset.x : 0), this.event.clientY + (offset ? offset.y : 0)); 1327 | }, 1328 | 1329 | mousedown: function (event) { 1330 | if (!this.manipulator.begin(event)) { 1331 | return; 1332 | } 1333 | 1334 | $document.on('mousemove', this.mousemove ); 1335 | $document.on('mouseup', this.mouseup ); 1336 | }, 1337 | 1338 | mousemove: function(event) { 1339 | this.manipulator.progress(event); 1340 | }, 1341 | 1342 | mouseup: function(event) { 1343 | this.manipulator.end(event); 1344 | 1345 | $document.off('mousemove', this.mousemove ); 1346 | $document.off('mouseup', this.mouseup ); 1347 | 1348 | this.dnd.setCurrentManipulator(null); 1349 | }, 1350 | 1351 | destroy: function() { 1352 | this.dnd.$el.off('mousedown', this.mousedown); 1353 | }, 1354 | }; 1355 | 1356 | 1357 | function Touch(dnd, te) { 1358 | this.dnd = dnd; 1359 | this.te = te; 1360 | this.manipulator = new Manipulator(dnd); 1361 | this.touchstart = proxy(this, this.touchstart); 1362 | this.touchmove = proxy(this, this.touchmove); 1363 | this.touchend = proxy(this, this.touchend); 1364 | this.manipulator.getClientAxis = this.getClientAxis; 1365 | 1366 | dnd.$el.on(this.te.start, this.touchstart); 1367 | } 1368 | 1369 | Touch.prototype = { 1370 | 1371 | getClientAxis: function(offset) { 1372 | var event = this.event.originalEvent || this.event; 1373 | 1374 | return event.changedTouches ? 1375 | Point(event.changedTouches[0].clientX + (offset ? offset.x : 0), event.changedTouches[0].clientY + (offset ? offset.y : 0)) : 1376 | Point(event.clientX + (offset ? offset.x : 0), event.clientY + (offset ? offset.y : 0)); 1377 | }, 1378 | 1379 | touchstart: function (event) { 1380 | if (!this.manipulator.begin(event)) { 1381 | return; 1382 | } 1383 | 1384 | $document.on(this.te.move, this.touchmove ); 1385 | $document.on(this.te.end + ' ' + this.te.cancel, this.touchend ); 1386 | }, 1387 | 1388 | touchmove: function(event) { 1389 | if (this.manipulator.isTarget()) { 1390 | event.preventDefault(); 1391 | } 1392 | 1393 | this.manipulator.progress(event); 1394 | }, 1395 | 1396 | touchend: function(event) { 1397 | this.manipulator.end(event); 1398 | 1399 | $document.off(this.te.move, this.touchmove ); 1400 | $document.off(this.te.end + ' ' + this.te.cancel, this.touchend ); 1401 | this.dnd.setCurrentManipulator(null); 1402 | }, 1403 | 1404 | destroy: function() { 1405 | this.dnd.$el.off(this.te.start, this.touchstart); 1406 | } 1407 | }; 1408 | 1409 | /** 1410 | * @name angular.element.dndBind 1411 | * 1412 | * @description 1413 | * Аналог jQuery.fn.bind(), только для drag and drop событий 1414 | * 1415 | * События также могут быть в формате , 1416 | * но в отличие от jQuery.fn.bind() в нашем случае layer позволяет не только групировать обработчики, 1417 | * но также и отделять области для droppable и draggable элементов. Поясним. 1418 | * Дело в том, что при определении событий элемент не явным образом приписывается к определенной области видимости (layer), 1419 | * причем один элемент может одновременно находится в нескольких областях. 1420 | * Это означает, что для того, чтобы на элемент срабатывали droppable события, он должен находится в layer draggable элемента. 1421 | * По умолчанию, если layer не задан в наименовании обаботчика события, то эта область именуется 'common', 1422 | * т.е. события drop и common.drop идентичны и находятся в одной и той же области 1423 | * 1424 | * ! Элемент не явным образом считается draggable-элементом, если у него задано одно или несколько событий dragstart, drag или dragend 1425 | * ! Элемент не явным образом считается droppable-элементом, если у него задано одно или несколько событий dragenter, dragover, dragleave или drop 1426 | * 1427 | * @param {object|string} event 1428 | * Если object, то необходимо описать пары :. 1429 | * Если string, то определяется только причем возможно задать несколько событий через пробел, например 1430 | * @param {function} handler 1431 | * Если arg1 это string, то arg2 это callback, который будет вызван после наступления события. 1432 | * @returns {object} angular.element object. 1433 | */ 1434 | 1435 | $.prototype.dndBind = function ( event, handler ) { 1436 | 1437 | if (!this.length) { 1438 | return this; 1439 | } 1440 | 1441 | var opts = [], events, obj, layer, self = this; 1442 | 1443 | if (typeof event === 'object') { 1444 | obj = event; 1445 | 1446 | for(var key in obj) { 1447 | events = key.replace(/\s+/g, ' ').split(' '); 1448 | 1449 | for(var i=0; i < events.length; i++) { 1450 | opts.push({ 1451 | event: events[i], 1452 | handler: obj[key] 1453 | }); 1454 | } 1455 | } 1456 | 1457 | } else if (typeof event === 'string' && typeof handler === 'function') { 1458 | events = event.trim().replace(/\s+/g, ' ').split(' '); 1459 | 1460 | for(var i=0; i < events.length; i++) { 1461 | opts.push({ 1462 | event: events[i], 1463 | handler: handler 1464 | }); 1465 | } 1466 | 1467 | } else { 1468 | return this; 1469 | } 1470 | 1471 | if (!opts.length) { 1472 | return this; 1473 | } 1474 | 1475 | forEach(this, function(element) { 1476 | var data = $(element).data(); 1477 | 1478 | if (!data.dnd) { 1479 | data.dnd = {}; 1480 | } 1481 | 1482 | for(var i=0; i < opts.length; i++) { 1483 | event = opts[i].event; 1484 | handler = opts[i].handler; 1485 | 1486 | event = event.split('.'); 1487 | 1488 | if (event[1] === undefined) { 1489 | event[1] = event[0]; 1490 | event[0] = 'common'; 1491 | } 1492 | 1493 | layer = event[0]; 1494 | event = event[1]; 1495 | 1496 | if (!data.dnd[layer]) { 1497 | data.dnd[layer] = new Dnd(element, layer); 1498 | } 1499 | 1500 | data.dnd[layer].addListener(event, handler); 1501 | } 1502 | }); 1503 | 1504 | return this; 1505 | }; 1506 | 1507 | 1508 | /** 1509 | * @name angular.element.dndUnbind 1510 | * 1511 | * @description 1512 | * Аналог jQuery.fn.unbind(), только для drag and drop событий 1513 | * 1514 | * @param {(object=|string=)} arg1 Если не object и не string, то удаляются все обработчики с каждого слоя 1515 | * Если object, то будут удалены callbacks события которые заданы в виде ключа и 1516 | * @param {(function=|string=)} arg2 1517 | * Если arg1 это string, то arg2 это callback, который будет вызван после наступления события. 1518 | * Если arg1 это object, то arg2 это string которая определяет имя используемого слоя. 1519 | * @param {string=} arg3 1520 | * Если задан arg1 и arg2, то arg3 это string которая определяет имя используемого слоя 1521 | * @returns {object} angular.element object. 1522 | */ 1523 | 1524 | $.prototype.dndUnbind = function() { 1525 | 1526 | var args = arguments, events = [], default_layer = 'common'; 1527 | 1528 | if (!this.length) { 1529 | return this; 1530 | } 1531 | 1532 | if (typeof args[0] === 'string') { 1533 | 1534 | args[0] = args[0].trim().replace(/\s+/g, ' ').split(' '); 1535 | 1536 | if (typeof args[1] === 'function') { 1537 | 1538 | for(var i = 0; i < args[0].length; i++) { 1539 | events.push({ 1540 | event: args[0][i], 1541 | handler: args[1] 1542 | }); 1543 | } 1544 | 1545 | } else { 1546 | for(var i = 0; i < args[0].length; i++) { 1547 | events.push({ 1548 | event: args[0][i] 1549 | }); 1550 | } 1551 | } 1552 | 1553 | } else if ( typeof args[0] === 'object') { 1554 | 1555 | for(var key in args[0]) { 1556 | if (args[0].hasOwnProperty(key)) { 1557 | 1558 | events.push({ 1559 | event: key.trim(), 1560 | handler: args[0][key] 1561 | }); 1562 | 1563 | } 1564 | } 1565 | 1566 | } else if (args.length !== 0) { 1567 | return this; 1568 | } 1569 | 1570 | forEach(this, function(element) { 1571 | var data = $(element).data(); 1572 | 1573 | if (!data.dnd) { 1574 | return; 1575 | } 1576 | 1577 | if (args.length === 0) { 1578 | 1579 | for(var key in data.dnd) { 1580 | data.dnd[key].removeListener(); 1581 | } 1582 | 1583 | } else { 1584 | 1585 | for(var i = 0; i < events.length; i++) { 1586 | var obj = events[i]; 1587 | 1588 | obj.event = obj.event.split('.'); 1589 | 1590 | if (obj.event[1] === undefined) { 1591 | obj.event[1] = obj.event[0]; 1592 | obj.event[0] = default_layer; 1593 | } 1594 | 1595 | if (obj.event[0] === '*') { 1596 | for(var key in data.dnd) { 1597 | data.dnd[key].removeListener( obj.event[1] ); 1598 | } 1599 | 1600 | } else if (data.dnd[ obj.event[0] ]) { 1601 | obj.handler ? data.dnd[ obj.event[0] ].removeListener( obj.event[1], obj.handler ) : data.dnd[ obj.event[0] ].removeListener( obj.event[1] ); 1602 | } 1603 | } 1604 | } 1605 | }); 1606 | 1607 | return this; 1608 | }; 1609 | 1610 | })(); 1611 | 1612 | /* DEBUG HELPERS */ 1613 | 1614 | debug.helpers.renderPoint = function (point) { 1615 | var element = angular.element(document.createElement('div')); 1616 | 1617 | element.dndCss({ 1618 | position: 'absolute', 1619 | left: point.x, 1620 | top: point.y, 1621 | height: 3, 1622 | width: 3, 1623 | background: 'rgba(0, 0, 0, 0.5)', 1624 | 'pointer-events': 'none', 1625 | 'z-index': 100000 1626 | }); 1627 | 1628 | element.addClass('dnd-debug-point'); 1629 | 1630 | angular.element(document.body).append(element); 1631 | }; 1632 | 1633 | debug.helpers.renderRect = function (left, top, width, height) { 1634 | var element = angular.element(document.createElement('div')); 1635 | 1636 | element.dndCss({ 1637 | position: 'absolute', 1638 | left: left, 1639 | top: top, 1640 | height: height, 1641 | width: width, 1642 | background: 'rgba(249, 255, 0, 0.1)', 1643 | 'pointer-events': 'none', 1644 | 'z-index': 100000, 1645 | 'box-sizing': 'border-box', 1646 | 'border': '2px dotted #000' 1647 | }); 1648 | 1649 | element.addClass('dnd-debug-rect'); 1650 | 1651 | angular.element(document.body).append(element); 1652 | }; 1653 | 1654 | angular.dnd = { 1655 | version: version, 1656 | noop: noop, 1657 | doTrue: doTrue, 1658 | doFalse: doFalse, 1659 | proxy: proxy, 1660 | radToDeg: radToDeg, 1661 | degToRad: degToRad, 1662 | getNumFromSegment: getNumFromSegment, 1663 | findEvents: findEvents, 1664 | throttle: throttle, 1665 | debounce: debounce, 1666 | debug: debug 1667 | }; 1668 | -------------------------------------------------------------------------------- /src/directives/dndContainment.js: -------------------------------------------------------------------------------- 1 | module.directive('dndContainment', ['$parse', function($parse){ 2 | 3 | Controller.$inject = ['$element', '$attrs', '$scope']; 4 | function Controller( $element, $attrs, $scope){ 5 | var getterSelector = $parse($attrs.dndContainment); 6 | 7 | this.get = function () { 8 | var selector = getterSelector($scope); 9 | 10 | return selector ? $element.dndClosest(selector).eq(0) : $element.parent(); 11 | } 12 | } 13 | 14 | return { 15 | restrict: 'EAC', 16 | controller: Controller, 17 | } 18 | }]) 19 | -------------------------------------------------------------------------------- /src/directives/dndDraggable.js: -------------------------------------------------------------------------------- 1 | module.directive('dndDraggable', ['$timeout', '$parse', '$http', '$compile', '$q', '$templateCache', 'EventEmitter', 2 | function ($timeout, $parse, $http, $compile, $q, $templateCache, EventEmitter) { 3 | 4 | var ElementTarget = (function () { 5 | 6 | function ElementTarget(element, rect) { 7 | 8 | var cssPosition = element.dndCss('position'); 9 | 10 | if (cssPosition !== 'fixed' && cssPosition !== 'absolute' && cssPosition !== 'relative') { 11 | cssPosition = 'relative'; 12 | element.dndCss('position', cssPosition); 13 | } 14 | 15 | this.element = element; 16 | 17 | this.rect = rect; 18 | } 19 | 20 | ElementTarget.prototype = { 21 | initBorderOffset: function () { 22 | var axis = this.api.getBorderedAxis(); 23 | var crect = this.element.dndClientRect(); 24 | 25 | this.borderOffset = { 26 | top: axis.y - crect.top, 27 | left: axis.x - crect.left, 28 | bottom: axis.y - crect.top - crect.height, 29 | right: axis.x - crect.left - crect.width 30 | }; 31 | }, 32 | 33 | init: function (api) { 34 | this.api = api; 35 | delete this.start; 36 | this.initBorderOffset(); 37 | }, 38 | 39 | updatePosition: function () { 40 | var axis = this.api.getRelBorderedAxis(this.borderOffset); 41 | 42 | if (!this.start) { 43 | this.start = new Point(this.element.dndStyleRect()).minus(axis); 44 | } 45 | 46 | var position = new Point(this.start).plus(axis); 47 | 48 | this.rect ? this.rect.update( position.getAsCss() ) : this.element.dndCss( position.getAsCss() ); 49 | }, 50 | 51 | destroy: function () {}, 52 | }; 53 | 54 | return ElementTarget; 55 | })(); 56 | 57 | var HelperTarget = (function () { 58 | var wrapper = $('
').dndCss({position: 'absolute'}); 59 | 60 | function HelperTarget(mainNode, templateUrl, scope) { 61 | this.mainElement = angular.element(mainNode); 62 | this.scope = scope; 63 | this.templateUrl = templateUrl; 64 | 65 | if (templateUrl !== 'clone') { 66 | this.createTemplateByUrl(templateUrl); 67 | } else { 68 | this.ready = true; 69 | } 70 | } 71 | 72 | HelperTarget.prototype = { 73 | 74 | init: function (api) { 75 | delete this.start; 76 | delete this.element; 77 | this.api = api; 78 | this.ready = false; 79 | 80 | this.templateUrl === 'clone' ? this.createElementByClone() : this.createElementByTemplate(); 81 | 82 | this.wrap().appendTo($(document.body)); 83 | 84 | if (!this.scope.$root || !this.scope.$root.$$phase) { 85 | this.scope.$apply(); 86 | } 87 | 88 | api.setReferenceElement(document.body); 89 | this.initBorderOffset(); 90 | 91 | return this; 92 | }, 93 | 94 | createTemplateByUrl: function (templateUrl) { 95 | templateUrl = angular.isFunction (templateUrl) ? templateUrl() : templateUrl; 96 | 97 | return $http.get(templateUrl, {cache: $templateCache}).then(function (result) { 98 | this.template = result.data; 99 | this._offset = Point(); 100 | this.ready = true; 101 | }.bind(this)); 102 | }, 103 | 104 | createElementByClone: function () { 105 | this.element = this.mainElement.dndCloneByStyles().dndCss('position', 'static'); 106 | this._offset = Point(this.mainElement.dndClientRect()).minus(this.api.getBorderedAxis()); 107 | this.ready = true; 108 | 109 | return this; 110 | }, 111 | 112 | createElementByTemplate: function () { 113 | this.element = $compile(this.template)(this.scope); 114 | 115 | return this; 116 | }, 117 | 118 | wrap: function () { 119 | wrapper.html(''); 120 | wrapper.append(this.element); 121 | 122 | return this; 123 | }, 124 | 125 | appendTo: function (element) { 126 | element.append(wrapper); 127 | 128 | return this; 129 | }, 130 | 131 | initBorderOffset: function () { 132 | var axis = this.api.getBorderedAxis(); 133 | 134 | if (this.templateUrl === 'clone') { 135 | var crect = this.mainElement.dndClientRect(); 136 | 137 | this.borderOffset = { 138 | top: axis.y - crect.top, 139 | left: axis.x - crect.left, 140 | bottom: axis.y - crect.top - crect.height, 141 | right: axis.x - crect.left - crect.width 142 | }; 143 | } else { 144 | var crect = wrapper.dndClientRect(); 145 | 146 | this.borderOffset = { 147 | top: 0, 148 | left: 0, 149 | bottom: -crect.height, 150 | right: -crect.width 151 | }; 152 | } 153 | }, 154 | 155 | updatePosition: function () { 156 | var position = this.api.getRelBorderedAxis(this.borderOffset).plus(this._offset); 157 | 158 | wrapper.dndCss(position.getAsCss()); 159 | }, 160 | 161 | 162 | destroy: function () { 163 | this.element.remove(); 164 | }, 165 | }; 166 | 167 | return HelperTarget; 168 | })(); 169 | 170 | function link (scope, element, attrs, ctrls) { 171 | var rect = ctrls[0], 172 | model = ctrls[1], 173 | containment = ctrls[2]; 174 | 175 | var defaults = { 176 | layer: 'common', 177 | useAsPoint: true, 178 | helper: null, 179 | handle: '' 180 | }; 181 | 182 | var getterDraggable = $parse(attrs.dndDraggable); 183 | var opts = extend({}, defaults, $parse(attrs.dndDraggableOpts)(scope) || {}); 184 | var dragstartCallback = $parse(attrs.dndOnDragstart); 185 | var dragCallback = $parse(attrs.dndOnDrag); 186 | var dragendCallback = $parse(attrs.dndOnDragend); 187 | var draggable = opts.helper ? new HelperTarget(element, opts.helper, scope) : new ElementTarget(element, rect); 188 | var started, handle = opts.handle ? element[0].querySelector(opts.handle) : ''; 189 | 190 | function dragstart(api) { 191 | started = false; 192 | 193 | // определяем включен ли draggable элемент 194 | var enabled = getterDraggable(scope); 195 | enabled = enabled === undefined || enabled; 196 | 197 | // если draggable элемент выключен - отмечаем элемент как "не цель курсора" 198 | if (!enabled || (handle && handle !== api.getEvent().target)) { 199 | api.unTarget(); 200 | } 201 | 202 | // если элемент не является целью курсора (возможно есть другие draggable элементы внутри) - никак не реагируем на событие 203 | if (!api.isTarget()) { 204 | return; 205 | } 206 | 207 | draggable.init(api); 208 | 209 | // ставим флаг, что элемент начал двигаться 210 | started = true; 211 | 212 | // ставим флаг useAsPoint, что бы определить, является ли элемент полноразмерным или точкой. 213 | // В зависимости от этого флага по разному реагируют droppable зоны на этот элемент 214 | api.useAsPoint(opts.useAsPoint); 215 | 216 | // задаем модель данному элементу 217 | api.dragmodel = model ? model.get() : null; 218 | 219 | api.setBounderElement( containment ? containment.get() : angular.element(document.body) ); 220 | 221 | // ставим флаг, что процесс перемещения элемента начался 222 | scope.$dragged = true; 223 | 224 | // применяем пользовательский callback 225 | dragstartCallback(scope, {'$dragmodel':api.dragmodel, '$dropmodel': api.dropmodel, '$api': api}); 226 | 227 | // запускаем dirty-checking цикл 228 | if (!scope.$root || !scope.$root.$$phase) { 229 | scope.$apply(); 230 | } 231 | } 232 | 233 | function drag(api) { 234 | if (!started) { 235 | return; 236 | } 237 | 238 | draggable.updatePosition(); 239 | dragCallback(scope, {'$dragmodel':api.dragmodel, '$dropmodel': api.dropmodel, '$api': api}); 240 | 241 | if (!scope.$root || !scope.$root.$$phase) { 242 | scope.$apply() 243 | } 244 | } 245 | 246 | function dragend(api) { 247 | if (!started) { 248 | return; 249 | } 250 | 251 | draggable.destroy(); 252 | 253 | dragendCallback(scope, {'$dragmodel':api.dragmodel, '$dropmodel': api.dropmodel, '$api': api}); 254 | 255 | $timeout(function () { 256 | scope.$dragged = false; 257 | }); 258 | } 259 | 260 | var bindings = {}; 261 | 262 | opts.layer = opts.layer || defaults.layer; 263 | 264 | bindings[opts.layer+'.dragstart'] = dragstart; 265 | bindings[opts.layer+'.drag'] = drag; 266 | bindings[opts.layer+'.dragend'] = dragend; 267 | 268 | element.dndBind(bindings); 269 | 270 | scope.$dragged = false; 271 | } 272 | 273 | return { 274 | require: ['?dndRect', '?dndModel', '?dndContainment'], 275 | scope: true, 276 | link: link 277 | }; 278 | }]); 279 | -------------------------------------------------------------------------------- /src/directives/dndDroppable.js: -------------------------------------------------------------------------------- 1 | module.directive('dndDroppable', ['$parse', '$timeout', function( $parse, $timeout ){ 2 | return { 3 | require: '?dndModel', 4 | scope: true, 5 | link: function(scope, $el, attrs, model){ 6 | 7 | var defaults = { 8 | layer: 'common' 9 | }; 10 | 11 | var getterDroppable = $parse(attrs.dndDroppable); 12 | var opts = extend({}, defaults, $parse(attrs.dndDroppableOpts)(scope) || {}); 13 | var dragenterCallback = $parse(attrs.dndOnDragenter); 14 | var dragoverCallback = $parse(attrs.dndOnDragover); 15 | var dragleaveCallback = $parse(attrs.dndOnDragleave); 16 | var dropCallback = $parse(attrs.dndOnDrop); 17 | 18 | function dragenter(api){ 19 | var local = api.droplocal = {}; 20 | 21 | api.dropmodel = model ? model.get() : model; 22 | 23 | local.droppable = getterDroppable(scope, {'$dragmodel': api.dragmodel, '$dropmodel': api.dropmodel, '$api': api}); 24 | local.droppable = local.droppable === undefined ? true : local.droppable; 25 | 26 | if(!local.droppable) { 27 | return; 28 | } 29 | 30 | dragenterCallback(scope, {'$dragmodel':api.dragmodel, '$dropmodel': api.dropmodel, '$api': api}); 31 | 32 | if (!scope.$root || !scope.$root.$$phase) { 33 | scope.$apply(); 34 | } 35 | } 36 | 37 | function dragover(api){ 38 | var local = api.droplocal; 39 | 40 | if(!local.droppable) { 41 | return; 42 | } 43 | 44 | dragoverCallback(scope, {'$dragmodel':api.dragmodel, '$dropmodel': api.dropmodel, '$api': api}); 45 | 46 | if (!scope.$root || !scope.$root.$$phase) { 47 | scope.$apply(); 48 | } 49 | } 50 | 51 | function dragleave(api){ 52 | var local = api.droplocal; 53 | 54 | if(!local.droppable) { 55 | return; 56 | } 57 | 58 | dragleaveCallback(scope, {'$dragmodel':api.dragmodel, '$dropmodel': api.dropmodel, '$api': api}); 59 | api.dropmodel = undefined; 60 | 61 | if (!scope.$root || !scope.$root.$$phase) { 62 | scope.$apply(); 63 | } 64 | } 65 | 66 | function drop(api){ 67 | var local = api.droplocal; 68 | 69 | if(!local.droppable) { 70 | return; 71 | } 72 | 73 | dropCallback(scope, {'$dragmodel':api.dragmodel, '$dropmodel': api.dropmodel, '$api': api}); 74 | } 75 | 76 | 77 | var bindings = {}; 78 | 79 | opts.layer = opts.layer || defaults.layer; 80 | 81 | bindings[opts.layer+'.dragenter'] = dragenter; 82 | bindings[opts.layer+'.dragover'] = dragover; 83 | bindings[opts.layer+'.dragleave'] = dragleave; 84 | bindings[opts.layer+'.drop'] = drop; 85 | 86 | $el.dndBind( bindings ); 87 | 88 | } 89 | }; 90 | }]); 91 | -------------------------------------------------------------------------------- /src/directives/dndFittext.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @name dnd.fittext 3 | * 4 | * @description 5 | * Отличная функция для подгонки текста под размер блока, в котором этот текст находится. 6 | * за единственный аргумент функция принимает объект rect, содержащий в себе ширину (width) и высоту (height) элемента. 7 | * На основе этих параметров идет расчет высоты шрифта. 8 | * Также у директвы есть дополнительные атрибуты-настройки: dnd-fittext-max и dnd-fittext-min, 9 | * которые позволяют задать максимальное и минимальное соответственно значение шрифта. 10 | * 11 | */ 12 | 13 | module.directive('dndFittext', ['$timeout', '$window', function( $timeout, $window ){ 14 | var $span = $('').dndCss({'position':'absolute','left':-99999, 'top':-99999, 'opacity':0, 'z-index': -9999}); 15 | 16 | $(document.body).append( $span ); 17 | 18 | function encodeStr(val) { 19 | var val = $span.text(val).html().replace(/\s+/g, ' ') 20 | if($span[0].tagName == 'INPUT' || $span[0].tagName == 'TEXTAREA') val = val.replace(/\s/g,' '); 21 | 22 | return val; 23 | } 24 | 25 | function getRealSize(text, font) { 26 | $span.html( encodeStr(text) ).dndCss(font); 27 | var rect = $span[0].getBoundingClientRect(); 28 | 29 | return { 30 | width: parseFloat(rect.width), 31 | height: parseFloat(rect.height) 32 | } 33 | } 34 | 35 | function getCurrSize($el, offsetWidthPrct, offsetHeightPrct){ 36 | var rect = $el[0].getBoundingClientRect(); 37 | 38 | return { 39 | width: parseFloat(rect.width)*(100-offsetWidthPrct)/100, 40 | height: parseFloat(rect.height)*(100-offsetHeightPrct)/100 41 | } 42 | } 43 | 44 | return { 45 | restrict: 'A', 46 | link: function(scope, $el, attrs) { 47 | 48 | function updateSize(opts) { 49 | opts = opts === undefined ? {} : opts; 50 | var font = $el.dndCss( 51 | ['font-size','font-family','font-weight','text-transform','border-top','border-right','border-bottom','border-left','padding-top','padding-right','padding-bottom','padding-left'] 52 | ), text = opts.text == undefined ? $el.text() || $el.val() : opts.text; 53 | 54 | var sizes = []; 55 | if(opts.width === undefined) sizes.push('width'); 56 | if(opts.height === undefined) sizes.push('height'); 57 | 58 | if(sizes.length) sizes = $el.dndCss(sizes); 59 | 60 | for(var key in sizes){ 61 | var val = sizes[key]; 62 | 63 | if(val[val.length-1] == '%') return; 64 | opts[key] = sizes[key]; 65 | } 66 | 67 | var realSize = getRealSize(text, font), currSize = getCurrSize($el,0,0); 68 | if(!realSize.width || !realSize.height) { 69 | $el.dndCss('font-size', ''); 70 | return 71 | } 72 | 73 | currSize.width = parseFloat(opts.width); 74 | currSize.height = parseFloat(opts.height); 75 | 76 | var kof1 = currSize.height / realSize.height; 77 | var kof2 = currSize.width / realSize.width; 78 | 79 | var max = scope.$eval(attrs.dndFittextMax); 80 | var min = scope.$eval(attrs.dndFittextMin); 81 | 82 | if(min == undefined) min = 0; 83 | if(max == undefined) max = Number.POSITIVE_INFINITY; 84 | 85 | var kof = (kof1 < kof2 ? kof1 : kof2); 86 | 87 | //Корректировка плавности 88 | kof *= 0.85; 89 | if((kof > 0.95 && kof <= 1) || (kof >= 1 && kof < 1.05) ) return; 90 | 91 | var n = kof * parseFloat(font['font-size']); 92 | 93 | n = getNumFromSegment(min, n, max); 94 | 95 | $el.dndCss('font-size', n+'px'); 96 | } 97 | 98 | scope.$watch( attrs.dndFittext, throttle(function(opts){ 99 | updateSize(opts); 100 | }), true); 101 | 102 | $($window).on('resize', function(){ 103 | updateSize(); 104 | }); 105 | } 106 | }; 107 | }]) 108 | -------------------------------------------------------------------------------- /src/directives/dndLassoArea.js: -------------------------------------------------------------------------------- 1 | module.directive('dndLassoArea', ['DndLasso', '$parse', '$timeout', 'dndKey', function(DndLasso, $parse, $timeout, dndKey){ 2 | 3 | Controller.$inject = []; 4 | function Controller(){ 5 | var ctrls = [], data = {}; 6 | 7 | this.data = function(){ 8 | return data; 9 | }; 10 | 11 | this.add = function(ctrl){ 12 | ctrls.push(ctrl); 13 | }; 14 | 15 | this.remove = function(ctrl){ 16 | for(var i = 0; i < ctrls.length; i++){ 17 | if(ctrls[i] === ctrl) { 18 | ctrls.splice(i,1); 19 | return true; 20 | } 21 | } 22 | 23 | return false; 24 | }; 25 | 26 | this.getSelectable = function(element){ 27 | for(var i = 0; i < ctrls.length; i++){ 28 | if(ctrls[i].getElement()[0] == element) return ctrls[i]; 29 | } 30 | 31 | return undefined; 32 | }; 33 | 34 | this.empty = function(){ 35 | return !ctrls.length; 36 | }; 37 | 38 | this.get = function(i){ 39 | return i === undefined ? ctrls : ctrls[i]; 40 | } 41 | } 42 | 43 | var ctrls = []; 44 | 45 | ctrls.remove = function (ctrl){ 46 | for(var i = 0; i < this.length; i++){ 47 | if(this[i] === ctrl) { 48 | this.splice(i,1); 49 | return true; 50 | } 51 | } 52 | 53 | return false; 54 | } 55 | 56 | return { 57 | restrict: 'A', 58 | controller: Controller, 59 | require: 'dndLassoArea', 60 | /* отрицательный приоритет необходим для того, что бы post link function dnd-lasso-area запускался раньше post link function ng-click */ 61 | priority: -1, 62 | link: function(scope, $el, attrs, ctrl){ 63 | var defaults = { 64 | selectAdditionals: true 65 | }; 66 | 67 | var getterLassoArea = $parse(attrs.dndLassoArea); 68 | var opts = extend({}, defaults, $parse(attrs.dndLassoAreaOpts)(scope) || {}); 69 | var dragstartCallback = $parse(attrs.dndOnLassostart); 70 | var dragCallback = $parse(attrs.dndOnLasso); 71 | var dragendCallback = $parse(attrs.dndOnLassoend); 72 | var clickCallback = $parse(attrs.dndLassoOnclick); 73 | var lasso = new DndLasso({ $el:$el }), selectable, keyPressed; 74 | 75 | ctrls.push(ctrl); 76 | 77 | function onClick(event){ 78 | if(!ctrl.empty()) { 79 | 80 | if(keyPressed) { 81 | selectable.toggleSelected(); 82 | return 83 | } 84 | 85 | var s = ctrl.get(); 86 | 87 | for(var i = 0; i < s.length; i++){ 88 | s[i].unselected().unselecting(); 89 | } 90 | 91 | if(selectable) selectable.selected(); 92 | 93 | } 94 | 95 | clickCallback( scope, {$event: event}); 96 | 97 | if (!scope.$root || !scope.$root.$$phase) { 98 | scope.$apply(); 99 | } 100 | } 101 | 102 | function onStart(handler) { 103 | scope.$dragged = true; 104 | 105 | if(!handler.isActive()) return; 106 | 107 | dragstartCallback( scope ); 108 | 109 | if(!ctrl.empty() && !keyPressed) { 110 | 111 | var s = ctrl.get(); 112 | 113 | for(var i = 0; i < s.length; i++){ 114 | s[i].unselected().unselecting(); 115 | } 116 | 117 | } 118 | 119 | if (!scope.$root || !scope.$root.$$phase) { 120 | scope.$apply(); 121 | } 122 | } 123 | 124 | function onDrag(handler) { 125 | scope.$dragged = true; 126 | 127 | if(!handler.isActive()) return; 128 | 129 | if(!ctrl.empty()) { 130 | var s = ctrl.get(), rect = handler.getClientRect(); 131 | 132 | for(var i = 0; i < s.length; i++) { 133 | s[i].hit(rect) ? s[i].selecting() : s[i].unselecting(); 134 | } 135 | } 136 | 137 | dragCallback(scope, { $rect: handler.getRect() }); 138 | 139 | if (!scope.$root || !scope.$root.$$phase) { 140 | scope.$apply(); 141 | } 142 | } 143 | 144 | function onEnd(handler) { 145 | 146 | if(!handler.isActive()) return; 147 | 148 | var s = ctrl.get(); 149 | 150 | if(!ctrl.empty()) { 151 | 152 | for(var i = 0; i < s.length; i++){ 153 | if(s[i].isSelecting()) s[i].toggleSelected(); 154 | } 155 | 156 | for(var i = 0; i < s.length; i++){ 157 | s[i].unselecting(); 158 | } 159 | } 160 | 161 | dragendCallback(scope, { $rect: handler.getRect() }); 162 | 163 | if (!scope.$root || !scope.$root.$$phase) { 164 | scope.$apply(); 165 | } 166 | 167 | /* что бы события click/dblclick получили флаг $dragged === true, переключение флага происходит после их выполнения */ 168 | $timeout(function(){ scope.$dragged = false; }); 169 | } 170 | 171 | $el.on('mousedown touchstart', throttle(function (event){ 172 | 173 | scope.$dragged = false; 174 | 175 | //scope.$keypressed = keyPressed = ( dndKey.isset(16) || dndKey.isset(17) || dndKey.isset(18) ); 176 | scope.$keypressed = keyPressed = opts.selectAdditionals ? ( event.shiftKey || event.ctrlKey || event.metaKey ) : false; 177 | 178 | if(!ctrl.empty()) { 179 | selectable = ctrl.getSelectable(event.target); 180 | } 181 | 182 | if (!scope.$root || !scope.$root.$$phase) { 183 | scope.$apply(); 184 | } 185 | 186 | }, 300) ); 187 | 188 | $el.on('click', function(event){ 189 | 190 | if(!scope.$dragged) onClick(event); 191 | 192 | /* что бы события dnd-on-* получили флаг $keypressed, переключение флага происходит после их выполнения */ 193 | if(scope.$keypressed) $timeout(function(){ scope.$keypressed = false; }); 194 | } ); 195 | 196 | lasso.on('start', onStart); 197 | lasso.on('drag', onDrag); 198 | lasso.on('end', onEnd); 199 | 200 | $el.on('$destroy', function(){ 201 | ctrls.remove(ctrl); 202 | 203 | if (!scope.$root || !scope.$root.$$phase) { 204 | scope.$apply(); 205 | } 206 | }); 207 | 208 | scope.$dragged = false; 209 | } 210 | }; 211 | }]) 212 | -------------------------------------------------------------------------------- /src/directives/dndModel.js: -------------------------------------------------------------------------------- 1 | module.directive('dndModel', ['$parse', function($parse){ 2 | 3 | Controller.$inject = ['$scope', '$attrs']; 4 | function Controller( $scope, $attrs ){ 5 | var getter = $parse($attrs.dndModel), setter = getter.assign 6 | 7 | this.set = function(value){ 8 | setter($scope, value); 9 | }; 10 | 11 | this.get = function(){ 12 | return getter($scope); 13 | }; 14 | } 15 | 16 | return { 17 | restrict: 'A', 18 | controller: Controller 19 | } 20 | }]) 21 | -------------------------------------------------------------------------------- /src/directives/dndRect.js: -------------------------------------------------------------------------------- 1 | module.directive('dndRect', ['$parse', function($parse){ 2 | var setStyles = ['top','left','width','height', 'transform']; 3 | var getStyles = ['top','left','width','height', TRANSFORM]; 4 | 5 | setStyles.has = function(val){ 6 | return this.indexOf(val) > -1; 7 | } 8 | 9 | Controller.$inject = ['$scope', '$attrs', '$element']; 10 | function Controller( $scope, $attrs, $element ){ 11 | var getter = $parse($attrs.dndRect), setter = getter.assign || noop, lastRect; 12 | 13 | this.update = function(prop, value) { 14 | var values, rect = getter($scope) || {}; 15 | 16 | if (typeof prop != 'object') { 17 | values = {}; 18 | values[prop] = value; 19 | } else values = prop; 20 | 21 | for(var i = 0; i < setStyles.length; i++){ 22 | var style = setStyles[i]; 23 | 24 | if(values[style] !== undefined) rect[style] = values[style]; 25 | } 26 | 27 | setter($scope, rect); 28 | }; 29 | 30 | this.get = function(){ 31 | return getter($scope); 32 | }; 33 | 34 | this.getClient = function(){ 35 | return $element.dndClientRect(); 36 | }; 37 | 38 | function sanitize(rect){ 39 | var css; 40 | 41 | rect = typeof rect == 'object' ? rect : {}; 42 | 43 | for(var i = 0; i < setStyles.length; i++){ 44 | 45 | var style = setStyles[i]; 46 | 47 | if(rect[style] !== undefined) continue; 48 | 49 | if(!css) css = $element.dndCss(getStyles); 50 | 51 | rect[style] = (style === 'transform') ? css[TRANSFORM] : css[style]; 52 | } 53 | 54 | for(var key in rect){ 55 | rect[key.toLowerCase()] = rect[key]; 56 | } 57 | 58 | for(var key in rect) { 59 | if(setStyles.has(key)) { 60 | if(typeof rect[key] === 'number') rect[key] = rect[key]+'px'; 61 | } else delete rect[key]; 62 | } 63 | 64 | return rect; 65 | }; 66 | 67 | $scope.$parent.$watch(function(){ 68 | var rect = getter($scope.$parent); 69 | 70 | if(rect !== lastRect) setter($scope, rect); 71 | }); 72 | 73 | $scope.$watch($attrs.dndRect, function(n, o){ 74 | if(!n || typeof n != 'object') return; 75 | if(o == undefined) o = {}; 76 | 77 | var lastRect = n = sanitize(n); 78 | 79 | var css = {}; 80 | 81 | for(var val, i=0; i < setStyles.length; i++ ){ 82 | val = setStyles[i]; 83 | 84 | if(n[val] == undefined && o[val] != undefined) { 85 | css[val] = ''; 86 | } else if(n[val] != undefined) { 87 | css[val] = n[val]; 88 | } 89 | } 90 | 91 | if(css.transform) { 92 | css[TRANSFORM] = css.transform; 93 | } 94 | 95 | $element.dndCss(css); 96 | 97 | }, true); 98 | } 99 | 100 | return { 101 | restrict: 'A', 102 | controller: Controller 103 | }; 104 | }]) 105 | -------------------------------------------------------------------------------- /src/directives/dndResizable.js: -------------------------------------------------------------------------------- 1 | module.directive('dndResizable', ['$parse', '$timeout', function($parse, $timeout) { 2 | 3 | function createHandleElement(side) { 4 | return angular.element('
').addClass('angular-dnd-resizable-handle angular-dnd-resizable-handle-' + side); 5 | } 6 | 7 | function getCenterPoint(rect, scale) { 8 | scale = typeof scale === 'object' ? scale : {x:1,y:1}; 9 | 10 | return new Point(rect.left + rect.width*scale.x/2, rect.top + rect.height*scale.y/2); 11 | } 12 | 13 | function link (scope, $el, attrs, ctrls) { 14 | var rect = ctrls[0], 15 | containment = ctrls[1]; 16 | 17 | var defaults = { 18 | handles: 'ne, se, sw, nw, n, e, s, w', 19 | minWidth: 20, 20 | minHeight: 20, 21 | maxWidth: 10000, 22 | maxHeight: 10000 23 | }; 24 | 25 | var getterResizable = $parse(attrs.dndResizable); 26 | var opts = extend({}, defaults, $parse(attrs.dndResizableOpts)(scope) || {}); 27 | var dragstartCallback = $parse(attrs.dndOnResizestart); 28 | var dragCallback = $parse(attrs.dndOnResize); 29 | var dragendCallback = $parse(attrs.dndOnResizeend); 30 | 31 | function getBindings(side) { 32 | 33 | function dragstart(api) { 34 | var local = api.local = {}; 35 | 36 | local.resizable = getterResizable(scope); 37 | local.resizable = local.resizable === undefined ? true : local.resizable; 38 | 39 | if ( !local.resizable ) { 40 | api.unTarget(); 41 | } 42 | 43 | if ( !api.isTarget() ) { 44 | return; 45 | } 46 | 47 | api.setBounderElement( containment ? containment.get() : angular.element(document.body) ); 48 | local.started = true; 49 | local.$parent = $el.parent(); 50 | local.rads = $el.dndGetAngle(); 51 | local.rotateMatrix = Matrix.IDENTITY.rotate(local.rads); 52 | local.inverseRotateMatrix = local.rotateMatrix.inverse(); 53 | local.parentRect = local.$parent.dndClientRect(); 54 | 55 | var axis = api.getBorderedAxis(), crect = $el.dndClientRect(), srect = local.rect = $el.dndStyleRect(); 56 | 57 | local.borders = api.getBorders(); 58 | local.startAxis = axis; 59 | 60 | local.minScaleX = opts.minWidth / srect.width; 61 | local.minScaleY = opts.minHeight / srect.height; 62 | local.maxScaleX = opts.maxWidth / srect.width; 63 | local.maxScaleY = opts.maxHeight / srect.height; 64 | 65 | local.deltaX = crect.left - srect.left + crect.width / 2 - srect.width / 2; 66 | local.deltaY = crect.top - srect.top + crect.height / 2 - srect.height / 2; 67 | 68 | scope.$resized = true; 69 | 70 | dragstartCallback(scope); 71 | 72 | if (!scope.$root || !scope.$root.$$phase) { 73 | scope.$apply(); 74 | } 75 | } 76 | 77 | function drag(api) { 78 | var local = api.local; 79 | 80 | if (!local.started) return; 81 | 82 | var axis = api.getBorderedAxis(); 83 | var vector = Point(axis).minus(local.startAxis).transform(local.inverseRotateMatrix); 84 | 85 | var scale = {x:1,y:1}; 86 | 87 | var width = local.rect.width, 88 | height = local.rect.height, 89 | top = local.rect.top, 90 | left = local.rect.left; 91 | 92 | switch(side) { 93 | case 'n': scale.y = (height - vector.y) / height; break; 94 | case 'e': scale.x = (width + vector.x) / width; break; 95 | case 's': scale.y = (height + vector.y) / height; break; 96 | case 'w': scale.x = (width - vector.x) / width; break; 97 | case 'ne': scale.x = (width + vector.x) / width; scale.y = (height - vector.y) / height; break; 98 | case 'se': scale.x = (width + vector.x) / width; scale.y = (height + vector.y) / height; break; 99 | case 'sw': scale.x = (width - vector.x) / width; scale.y = (height + vector.y) / height; break; 100 | case 'nw': scale.x = (width - vector.x) / width; scale.y = (height - vector.y) / height; break; 101 | } 102 | 103 | scale.x = getNumFromSegment(local.minScaleX, scale.x, local.maxScaleX); 104 | scale.y = getNumFromSegment(local.minScaleY, scale.y, local.maxScaleY); 105 | 106 | var offset; 107 | var center = getCenterPoint(local.rect); 108 | var scaledCenter = getCenterPoint(local.rect, scale); 109 | 110 | switch(side) { 111 | case 'n': offset = Point(left,top+height*scale.y).rotate(local.rads, scaledCenter).minus( Point(left,top+height).rotate(local.rads, center) ); break; 112 | case 'e': offset = Point(left,top).rotate(local.rads, scaledCenter).minus( Point(left,top).rotate(local.rads, center) ); break; 113 | case 's': offset = Point(left,top).rotate(local.rads, scaledCenter).minus( Point(left,top).rotate(local.rads, center) ); break; 114 | case 'w': offset = Point(left+width*scale.x,top).rotate(local.rads, scaledCenter).minus( Point(left+width,top).rotate(local.rads, center) ); break; 115 | case 'ne': offset = Point(left,top+height*scale.y).rotate(local.rads, scaledCenter).minus( Point(left,top+height).rotate(local.rads, center) ); break; 116 | case 'se': offset = Point(left,top).rotate(local.rads, scaledCenter).minus( Point(left,top).rotate(local.rads, center) ); break; 117 | case 'sw': offset = Point(left+width*scale.x,top).rotate(local.rads, scaledCenter).minus( Point(left+width,top).rotate(local.rads, center) ); break; 118 | case 'nw': offset = Point(left+width*scale.x,top+height*scale.y).rotate(local.rads, scaledCenter).minus( Point(left+width,top+height).rotate(local.rads, center) ); break; 119 | }; 120 | 121 | var styles = {}; 122 | styles.width = width * scale.x; 123 | styles.height = height * scale.y; 124 | styles.left = left - offset.x; 125 | styles.top = top - offset.y; 126 | 127 | var realCenter = Point(styles.left+local.deltaX+styles.width/2, styles.top+local.deltaY+styles.height/2); 128 | var boundedRect = Rect(styles.left+local.deltaX, styles.top+local.deltaY, styles.width, styles.height).applyMatrix( local.rotateMatrix, realCenter ).client(); 129 | 130 | if (local.borders && (boundedRect.left+1 < local.borders.left || boundedRect.top+1 < local.borders.top || boundedRect.right-1 > local.borders.right || boundedRect.bottom-1 > local.borders.bottom)) { 131 | return; 132 | } 133 | 134 | if (rect) { 135 | rect.update(styles); 136 | } else { 137 | $el.dndCss(styles); 138 | } 139 | 140 | dragCallback(scope); 141 | 142 | if (!scope.$root || !scope.$root.$$phase) { 143 | scope.$apply(); 144 | } 145 | } 146 | 147 | function dragend(api) { 148 | var local = api.local; 149 | 150 | if (!local.started) { 151 | return; 152 | } 153 | 154 | dragendCallback(scope); 155 | 156 | 157 | $timeout(function() { scope.$resized = false; }); 158 | } 159 | 160 | return { 161 | '$$resizable.dragstart': dragstart, 162 | '$$resizable.drag': drag, 163 | '$$resizable.dragend': dragend 164 | }; 165 | 166 | } 167 | 168 | var cssPosition = $el.dndCss('position'); 169 | 170 | if (cssPosition !== 'fixed' && cssPosition !== 'absolute' && cssPosition !== 'relative') { 171 | cssPosition = 'relative'; 172 | $el.dndCss('position', cssPosition); 173 | } 174 | 175 | var sides = opts.handles.replace(/\s/g,'').split(','); 176 | 177 | for(var i=0; i < sides.length; i++) { 178 | $el.append( createHandleElement( sides[i] ).dndBind( getBindings( sides[i] ) ) ); 179 | } 180 | 181 | scope.$resized = false; 182 | } 183 | 184 | return { 185 | require: ['?dndRect', '?dndContainment'], 186 | scope: true, 187 | link: link 188 | }; 189 | }]); 190 | -------------------------------------------------------------------------------- /src/directives/dndRotatable.js: -------------------------------------------------------------------------------- 1 | module.directive('dndRotatable', ['$parse', '$timeout', function($parse, $timeout){ 2 | 3 | function link (scope, element, attrs, ctrls) { 4 | var rect = ctrls[0], 5 | containment = ctrls[1]; 6 | 7 | var defaults = { 8 | step: 5 9 | }; 10 | 11 | var getterRotatable = $parse(attrs.dndRotatable); 12 | var opts = extend({}, defaults, $parse(attrs.dndRotatableOpts)(scope) || {}); 13 | var dragstartCallback = $parse(attrs.dndOnRotatestart); 14 | var dragCallback = $parse(attrs.dndOnRotate); 15 | var dragendCallback = $parse(attrs.dndOnRotateend); 16 | 17 | var cssPosition = element.dndCss('position'); 18 | 19 | if(cssPosition != 'fixed' && cssPosition != 'absolute' && cssPosition != 'relative') { 20 | cssPosition = 'relative'; 21 | element.dndCss('position', cssPosition); 22 | } 23 | 24 | var handle = angular.element('
'); 25 | 26 | element.append(handle); 27 | 28 | function dragstart(api) { 29 | var local = api.local = {}; 30 | 31 | local.rotatable = getterRotatable(scope); 32 | local.rotatable = local.rotatable === undefined ? true : local.rotatable; 33 | 34 | if( !local.rotatable ) { 35 | api.unTarget(); 36 | } 37 | 38 | if(!api.isTarget()) { 39 | return; 40 | } 41 | 42 | local.started = true; 43 | 44 | api.setBounderElement( containment ? containment.get() : angular.element(document.body) ); 45 | 46 | var axis = api.getRelBorderedAxis(); 47 | 48 | local.srect = element.dndStyleRect(); 49 | 50 | local.currAngle = element.dndGetAngle(); 51 | 52 | local.startPoint = Point(axis); 53 | 54 | local.borders = api.getBorders(); 55 | 56 | local.center = Point(local.srect).plus(Point(local.srect.width / 2, local.srect.height / 2)); 57 | 58 | scope.$rotated = true; 59 | 60 | dragstartCallback(scope); 61 | 62 | if (!scope.$root || !scope.$root.$$phase) { 63 | scope.$apply(); 64 | } 65 | } 66 | 67 | function drag(api){ 68 | var local = api.local; 69 | 70 | if(!local.started) { 71 | return; 72 | } 73 | 74 | var axis = api.getRelBorderedAxis(); 75 | var angle = Point(axis).deltaAngle(local.startPoint, local.center); 76 | var degs = radToDeg(local.currAngle+angle); 77 | 78 | degs = Math.round(degs/opts.step)*opts.step; 79 | var rads = degToRad(degs); 80 | var matrix = Matrix().rotate(rads); 81 | 82 | var compute = Rect( local.center.x - local.srect.width/2, local.center.y - local.srect.height/2, local.srect.width, local.srect.height).applyMatrix( matrix, local.center ).client(); 83 | var rPoint = api.getReferencePoint(); 84 | 85 | if(local.borders && (compute.left + rPoint.x < local.borders.left-1 || compute.top + rPoint.y < local.borders.top-1 || (compute.left + rPoint.x + compute.width) > local.borders.right+1 || (compute.top + rPoint.y + compute.height) > local.borders.bottom+1)) { 86 | return; 87 | } 88 | 89 | if(rect) { 90 | rect.update('transform', matrix.toStyle()); 91 | } else { 92 | element.dndCss('transform', matrix.toStyle()); 93 | } 94 | 95 | dragCallback(scope); 96 | 97 | if (!scope.$root || !scope.$root.$$phase) { 98 | scope.$apply(); 99 | } 100 | } 101 | 102 | function dragend(api){ 103 | var local = api.local; 104 | 105 | if(!local.started) { 106 | return; 107 | } 108 | 109 | dragendCallback(scope); 110 | 111 | $timeout(function(){ 112 | scope.$rotated = false; 113 | }); 114 | } 115 | 116 | scope.$rotated = false; 117 | 118 | var bindings = { 119 | '$$rotatable.dragstart': dragstart, 120 | '$$rotatable.drag': drag, 121 | '$$rotatable.dragend': dragend 122 | }; 123 | 124 | handle.dndBind( bindings ); 125 | 126 | } 127 | 128 | return { 129 | require: ['?dndRect', '?dndContainment'], 130 | scope: true, 131 | link: link 132 | }; 133 | }]) 134 | -------------------------------------------------------------------------------- /src/directives/dndSelectable.js: -------------------------------------------------------------------------------- 1 | module.directive('dndSelectable', ['$parse', function($parse){ 2 | 3 | var defaults = {}; 4 | 5 | Controller.$inject = ['$scope', '$attrs', '$element']; 6 | function Controller($scope, $attrs, $element) { 7 | var getterSelecting = $parse($attrs.dndModelSelecting), setterSelecting = getterSelecting.assign || noop; 8 | var getterSelected = $parse($attrs.dndModelSelected), setterSelected = getterSelected.assign || noop; 9 | var getterSelectable = $parse($attrs.dndSelectable), setterSelectable = getterSelectable.assign || noop; 10 | var onSelected = $parse($attrs.dndOnSelected); 11 | var onUnselected = $parse($attrs.dndOnUnselected); 12 | var onSelecting = $parse($attrs.dndOnSelecting); 13 | var onUnselecting = $parse($attrs.dndOnUnselecting); 14 | 15 | setterSelected($scope, false); 16 | setterSelecting($scope, false); 17 | 18 | this.getElement = function(){ 19 | return $element; 20 | }; 21 | 22 | this.isSelected = function(){ 23 | return getterSelected($scope); 24 | }; 25 | 26 | this.isSelecting = function(){ 27 | return getterSelecting($scope); 28 | }; 29 | 30 | this.isSelectable = function(){ 31 | var selectable = getterSelectable($scope); 32 | return selectable === undefined || selectable; 33 | }; 34 | 35 | this.toggleSelected = function(val){ 36 | val = val === undefined ? !this.isSelected() : val; 37 | 38 | return val ? this.selected() : this.unselected(); 39 | }; 40 | 41 | this.selecting = function(){ 42 | if (!this.isSelectable()) { 43 | return this; 44 | } 45 | 46 | setterSelecting($scope, true); 47 | onSelecting($scope); 48 | 49 | return this; 50 | }; 51 | 52 | this.unselecting = function(){ 53 | setterSelecting($scope, false); 54 | onUnselecting($scope); 55 | 56 | return this; 57 | }; 58 | 59 | this.selected = function(){ 60 | if (!this.isSelectable()) { 61 | return this; 62 | } 63 | 64 | setterSelected($scope, true); 65 | onSelected($scope); 66 | 67 | return this; 68 | }; 69 | 70 | this.unselected = function(){ 71 | setterSelected($scope, false); 72 | onUnselected($scope); 73 | 74 | return this; 75 | }; 76 | 77 | this.hit = function(a){ 78 | var b = this.rectCtrl.getClient(); 79 | 80 | for(var key in b) { 81 | b[key] = parseFloat(b[key]); 82 | } 83 | 84 | b.bottom = b.bottom == undefined ? b.top + b.height : b.bottom; 85 | b.right = b.right == undefined ? b.left + b.width : b.right; 86 | a.bottom = a.bottom == undefined ? a.top + a.height : a.bottom; 87 | a.right = a.right == undefined ? a.left + a.width : a.right; 88 | 89 | return ( 90 | a.top <= b.top && b.top <= a.bottom && ( a.left <= b.left && b.left <= a.right || a.left <= b.right && b.right <= a.right ) || 91 | a.top <= b.bottom && b.bottom <= a.bottom && ( a.left <= b.left && b.left <= a.right || a.left <= b.right && b.right <= a.right ) || 92 | a.left >= b.left && a.right <= b.right && ( b.top <= a.bottom && a.bottom <= b.bottom || b.bottom >= a.top && a.top >= b.top || a.top <= b.top && a.bottom >= b.bottom) || 93 | a.top >= b.top && a.bottom <= b.bottom && ( b.left <= a.right && a.right <= b.right || b.right >= a.left && a.left >= b.left || a.left <= b.left && a.right >= b.right) || 94 | a.top >= b.top && a.right <= b.right && a.bottom <= b.bottom && a.left >= b.left 95 | ); 96 | }; 97 | 98 | } 99 | 100 | function LikeRectCtrl($element){ 101 | this.$element = $element; 102 | } 103 | 104 | LikeRectCtrl.prototype = { 105 | getClient: function(){ 106 | return this.$element.dndClientRect(); 107 | } 108 | }; 109 | 110 | return { 111 | restrict: 'A', 112 | require: ['dndSelectable', '^dndLassoArea', '?dndRect'], 113 | controller: Controller, 114 | scope: true, 115 | link: function(scope, $el, attrs, ctrls) { 116 | scope.$dndSelectable = ctrls[0]; 117 | 118 | var rectCtrl = ctrls[2]; 119 | 120 | ctrls[0].rectCtrl = rectCtrl ? rectCtrl : new LikeRectCtrl($el); 121 | 122 | ctrls[1].add(ctrls[0]); 123 | 124 | function ondestroy() { 125 | ctrls[1].remove(ctrls[0]); 126 | 127 | if (!scope.$root || !scope.$root.$$phase) { 128 | scope.$apply(); 129 | } 130 | } 131 | 132 | $el.on('$destroy', ondestroy); 133 | } 134 | }; 135 | }]); 136 | -------------------------------------------------------------------------------- /src/directives/dndSortable.js: -------------------------------------------------------------------------------- 1 | module.directive('dndSortableList', ['$parse', '$compile', function($parse, $compile) { 2 | function link(scope, element, attrs, ctrl) { 3 | var defaults = { 4 | layer: 'common' 5 | }; 6 | var opts = ctrl.options = extend({}, defaults, $parse(attrs.dndSortableOpts)(scope) || {}); 7 | var getter = ctrl.getter = $parse(attrs.dndSortableList) || angular.noop; 8 | var bindings = {}; 9 | 10 | bindings[opts.layer+'.dragover'] = function(api) { 11 | if(getter(scope).length > 0) { 12 | return; 13 | } 14 | 15 | api.$sortable.model = {list: getter(scope)}; 16 | api.$sortable.insertBefore = true; 17 | element.append(api.placeholder[0]); 18 | }; 19 | 20 | element.dndBind(bindings); 21 | } 22 | 23 | controller.$inject = ['$scope', '$element', '$attrs']; 24 | function controller (scope, element, attrs) { } 25 | 26 | return { 27 | scope: true, 28 | link: link, 29 | controller: controller 30 | }; 31 | }]); 32 | 33 | module.directive('dndSortable', ['$parse', '$compile', function($parse, $compile) { 34 | var placeholder, ngRepeatRegExp = /^\s*([\s\S]+?)\s+in\s+([\s\S]+?)(?:\s+as\s+([\s\S]+?))?(?:\s+track\s+by\s+([\s\S]+?))?\s*$/; 35 | var defaults = { 36 | layer: 'common' 37 | }; 38 | 39 | 40 | 41 | function join(obj, sep1, sep2) { 42 | return Object.getOwnPropertyNames(obj).map(function(key) { 43 | return [key, obj[key]].join(sep1); 44 | }).join(sep2); 45 | } 46 | 47 | function joinObj (obj) { 48 | return '{' + join(obj, ':', ',') + '}'; 49 | } 50 | 51 | function joinAttrs (attrs) { 52 | return join(attrs, '="', '" ') + '"'; 53 | } 54 | 55 | function template(element, tAttrs) { 56 | var tag = element[0].nodeName.toLowerCase(); 57 | var ngRepeat = tAttrs.ngRepeat || ''; 58 | var match = ngRepeat.match(ngRepeatRegExp); 59 | 60 | if(!match) { 61 | throw 'dnd-sortable-item requires ng-repeat as dependence'; 62 | } 63 | 64 | var lhs = match[1]; 65 | var rhs = match[2]; 66 | 67 | lhs = lhs.match(/^(?:(\s*[\$\w]+)|\(\s*([\$\w]+)\s*,\s*([\$\w]+)\s*\))$/); 68 | 69 | var model = { 70 | item: 'item: ' + (lhs[3] || lhs[1]) + ', ', 71 | list: 'list: ' + rhs.replace(/\s\|(.)+$/g,'') + ', ', 72 | index: 'index: ' + '$index' 73 | }; 74 | 75 | var opts = angular.extend({ 76 | layer: "'common'", 77 | handle: "''" 78 | }, $parse(tAttrs.dndSortableOpts)()); 79 | 80 | var attrs = { 81 | 'ng-transclude': '', 82 | 'dnd-draggable': '', 83 | 'dnd-draggable-opts': joinObj({ 84 | helper: "'clone'", 85 | useAsPoint: true, 86 | layer: opts.layer, 87 | handle: opts.handle 88 | }), 89 | 'dnd-droppable': '', 90 | 'dnd-droppable-opts': joinObj({ 91 | layer: opts.layer 92 | }), 93 | 'dnd-on-dragstart': '$$onDragStart($api, $dropmodel, $dragmodel)', 94 | 'dnd-on-dragend': '$$onDragEnd($api, $dropmodel, $dragmodel)', 95 | 'dnd-on-dragover': '$$onDragOver($api, $dropmodel, $dragmodel)', 96 | 'dnd-on-drag': '$$onDrag($api, $dropmodel, $dragmodel)', 97 | 'dnd-model': '{' + model.item + model.list + model.index +'}' 98 | }; 99 | 100 | return '<' + tag + ' ' + joinAttrs(attrs) + '>'; 101 | } 102 | 103 | function link(scope, element, attrs, ctrls) { 104 | var listCtrl = ctrls[0]; 105 | var css = element.dndCss(['float', 'display']); 106 | var parentNode = element[0].parentNode; 107 | var floating = /left|right|inline/.test(css.float + css.display); 108 | var getter = $parse(attrs.dndModel) || noop; 109 | var sortstartCallback = $parse(attrs.dndOnSortstart); 110 | var sortCallback = $parse(attrs.dndOnSort); 111 | var sortchangeCallback = $parse(attrs.dndOnSortchange); 112 | var sortendCallback = $parse(attrs.dndOnSortend); 113 | var sortenterCallback = $parse(attrs.dndOnSortenter); 114 | var sortleaveCallback = $parse(attrs.dndOnSortleave); 115 | 116 | 117 | function isHalfway(dragTarget, axis, dropmodel) { 118 | var rect = element.dndClientRect(); 119 | 120 | return (floating ? (axis.x - rect.left) / rect.width : (axis.y - rect.top) / rect.height) > 0.5; 121 | } 122 | 123 | function moveValue(fromIndex, fromList, toIndex, toList) { 124 | toList = toList || fromList; 125 | toList.splice(toIndex, 0, fromList.splice(fromIndex, 1)[0]); 126 | } 127 | 128 | scope.$$onDragStart = function(api) { 129 | sortstartCallback(scope); 130 | 131 | api.placeholder = element.clone(); 132 | element.addClass('ng-hide'); 133 | api.placeholder.addClass('angular-dnd-placeholder'); 134 | parentNode.insertBefore(api.placeholder[0], element[0]); 135 | api.$sortable = {}; 136 | api.clearCache(); 137 | 138 | if (!scope.$root || !scope.$root.$$phase) { 139 | scope.$apply(); 140 | } 141 | }; 142 | 143 | scope.$$onDragOver = function(api, dropmodel, dragmodel) { 144 | var isDraggingNow = angular.isDefined(api.$sortable); 145 | if (!isDraggingNow) { 146 | return; 147 | } 148 | var halfway = isHalfway(api.getDragTarget(), api.getBorderedAxis()); 149 | 150 | halfway ? parentNode.insertBefore(api.placeholder[0], element[0].nextSibling) : parentNode.insertBefore(api.placeholder[0], element[0]); 151 | 152 | var model = getter(scope); 153 | 154 | if (sortchangeCallback !== angular.noop && (!api.$sortable.model || api.$sortable.model.index !== model.index)) { 155 | sortchangeCallback(scope); 156 | 157 | if (!scope.$root || !scope.$root.$$phase) { 158 | scope.$apply(); 159 | } 160 | } 161 | 162 | api.$sortable.model = model; 163 | api.$sortable.insertBefore = !halfway; 164 | 165 | api.clearCache(); 166 | }; 167 | 168 | scope.$$onDragEnd = function(api) { 169 | element.removeClass('ng-hide'); 170 | api.placeholder.remove(); 171 | 172 | if(!api.$sortable.model) { 173 | return; 174 | } 175 | 176 | var fromIndex = scope.$index, 177 | toIndex = api.$sortable.model.index, 178 | fromList = getter(scope).list, 179 | toList = api.$sortable.model.list; 180 | 181 | if(toList === fromList) { 182 | if(toIndex < fromIndex) { 183 | if(!api.$sortable.insertBefore) toIndex++; 184 | } else { 185 | if(api.$sortable.insertBefore) toIndex--; 186 | } 187 | } else if(!api.$sortable.insertBefore) toIndex++; 188 | 189 | moveValue(fromIndex, fromList, toIndex, toList); 190 | 191 | api.clearCache(); 192 | 193 | sortendCallback(scope); 194 | 195 | if (!scope.$root || !scope.$root.$$phase) { 196 | scope.$apply(); 197 | } 198 | }; 199 | 200 | (sortCallback !== angular.noop) && (scope.$$onDrag = function(api) { 201 | sortCallback(scope); 202 | 203 | if (!scope.$root || !scope.$root.$$phase) { 204 | scope.$apply(); 205 | } 206 | }); 207 | } 208 | 209 | return { 210 | scope: true, 211 | transclude: true, 212 | template: template, 213 | replace: true, 214 | link: link, 215 | require: ['^dndSortableList'] 216 | }; 217 | }]); 218 | //TODO: 219 | //create - вызывается при создании списка 220 | 221 | //activate - начат процесс сортировки (вызывается у всех связанных списков) 222 | //start - начат процесс сортировки (вызывается только у списка инициатора) 223 | 224 | //sort - вызывается при любом движении манипулятора при сортировке 225 | //change - сортировка списка изменилась 226 | //out - манипулятор с элементом вынесен за пределы списка (а также, если было событие over и небыло out то и при окончании сортировки) 227 | //over - манипулятор с элементом внесен в пределы списка 228 | 229 | //beforeStop - будет вызвано у списка инициатора 230 | //update - будет вызвано если список изменился 231 | //deactivate - вызывается у всех связанных списков 232 | //stop - вызывается самым последним у списка инициатора 233 | 234 | //receive - элемент дропнулся ИЗ другого списка 235 | //remove - элмент дропнулся В другой список 236 | 237 | //example: http://jsfiddle.net/UAcC7/1441/ 238 | -------------------------------------------------------------------------------- /src/directives/dndkeyModel.js: -------------------------------------------------------------------------------- 1 | module.directive('dndKeyModel', ['$parse', 'dndKey', function($parse, dndKey){ 2 | return { 3 | restrict: 'A', 4 | link: function(scope, $el, attrs) { 5 | var getter = $parse(attrs.dndKeyModel), setter = getter.assign; 6 | 7 | scope.$watch(function(){ return dndKey.get() }, function(n,o){ 8 | if(n === undefined) return; 9 | setter(scope, n); 10 | }); 11 | } 12 | } 13 | }]) 14 | -------------------------------------------------------------------------------- /src/services/EventEmitter.js: -------------------------------------------------------------------------------- 1 | module.factory('EventEmitter', [function () { 2 | 3 | function EventEmitter() { 4 | var events = {}; 5 | 6 | this.on = function(name, fn) { 7 | events[name] = events[name] || []; 8 | events[name].push(fn); 9 | }; 10 | 11 | this.off = function(name, fn) { 12 | if(!events[name]) return; 13 | 14 | for(var i = 0, length = events[name].length; i < length; i++){ 15 | if(events[name][i] === fn) events[name].splice(i, 1); 16 | } 17 | }; 18 | 19 | this.trigger = function(name, args) { 20 | events[name] = events[name] || []; 21 | args = args || typeof args === 'string' ? [args] : []; 22 | events[name].forEach(function(fn) { 23 | fn.apply(this, args); 24 | }); 25 | } 26 | } 27 | 28 | return EventEmitter; 29 | }]) 30 | -------------------------------------------------------------------------------- /src/services/dndKey.js: -------------------------------------------------------------------------------- 1 | module.factory('dndKey', ['$rootScope', function ($rootScope) { 2 | var keys = []; 3 | 4 | function DndKey(){ 5 | 6 | }; 7 | 8 | DndKey.prototype = { 9 | get: function(){ 10 | return keys; 11 | }, 12 | isset: function(code){ 13 | var index = keys.indexOf(code); 14 | return (index !== -1); 15 | } 16 | }; 17 | 18 | function keydown(event){ 19 | var code = event.keyCode; 20 | debounceKeyup(event); 21 | if(keys.indexOf(code) > -1) return; 22 | 23 | keys.push(code); 24 | $rootScope.$digest(); 25 | } 26 | 27 | function keyup(event){ 28 | var code = event.keyCode, index = keys.indexOf(code); 29 | if(index === -1) return; 30 | 31 | keys.splice(index,1); 32 | $rootScope.$digest(); 33 | }; 34 | 35 | 36 | 37 | var debounceKeyup = debounce(keyup, 1000); 38 | $document.on('keydown', keydown); 39 | $document.on('keyup', keyup); 40 | 41 | return new DndKey; 42 | }]) 43 | -------------------------------------------------------------------------------- /src/services/dndLasso.js: -------------------------------------------------------------------------------- 1 | module.factory('DndLasso', [function () { 2 | var $div = $('
').dndCss({position: 'absolute'}); 3 | 4 | var defaults = { 5 | className: 'angular-dnd-lasso', 6 | offsetX: 0, 7 | offsetY: 0 8 | }; 9 | 10 | function Handler(local){ 11 | 12 | this.getRect = function(){ 13 | return this.isActive ? local.rect : undefined; 14 | } 15 | 16 | this.getClientRect = function(){ 17 | return this.isActive ? $div.dndClientRect() : undefined; 18 | } 19 | 20 | this.isActive = function(){ 21 | return local.active; 22 | } 23 | } 24 | 25 | function Local(api){ 26 | var isTarget = api.isTarget(), handler = new Handler(this); 27 | 28 | this.isTarget = function(){ 29 | return isTarget; 30 | } 31 | 32 | this.handler = function(){ 33 | return handler; 34 | } 35 | 36 | this.getEvent = function(){ 37 | return api.getEvent(); 38 | } 39 | } 40 | 41 | function Lasso(opts){ 42 | 43 | var self = this; 44 | 45 | opts = extend( {}, defaults, opts ); 46 | 47 | function dragstart(api) { 48 | var local = api.local = new Local(api); 49 | 50 | if( !local.isTarget() ) { 51 | self.trigger('start', local.handler() ); 52 | return; 53 | } 54 | 55 | local.active = true; 56 | 57 | self.trigger('start', local.handler() ); 58 | 59 | api.setReferenceElement(opts.$el); 60 | api.setBounderElement(opts.$el); 61 | 62 | local.startAxis = api.getRelBorderedAxis(); 63 | 64 | $div.removeAttr('class style').removeClass('ng-hide').addClass(opts.className); 65 | 66 | opts.$el.append( $div ); 67 | 68 | }; 69 | 70 | function drag(api) { 71 | var local = api.local; 72 | 73 | if( !local.active ) { 74 | self.trigger('drag', local.handler()); 75 | return; 76 | } 77 | 78 | var change = api.getRelBorderedAxis().minus(local.startAxis); 79 | 80 | var rect = { 81 | top: local.startAxis.y, 82 | left: local.startAxis.x, 83 | width: change.x, 84 | height: change.y 85 | }; 86 | 87 | if(rect.width < 0) { 88 | rect.width = - rect.width; 89 | rect.left = rect.left - rect.width; 90 | } 91 | 92 | if(rect.height < 0) { 93 | rect.height = - rect.height; 94 | rect.top = rect.top - rect.height; 95 | } 96 | 97 | local.rect = rect; 98 | 99 | rect.top += opts.offsetY; 100 | rect.left += opts.offsetX; 101 | 102 | $div.dndCss(rect); 103 | 104 | self.trigger('drag', local.handler() ); 105 | }; 106 | 107 | function dragend(api) { 108 | var local = api.local; 109 | 110 | if( !local.active ) { 111 | self.trigger('end', local.handler()); 112 | return; 113 | } 114 | 115 | $div.addClass('ng-hide'); 116 | 117 | $(document.body).append( $div ); 118 | 119 | self.trigger('end', local.handler() ); 120 | }; 121 | 122 | var bindings = { 123 | '$$lasso.dragstart': dragstart, 124 | '$$lasso.drag': drag, 125 | '$$lasso.dragend': dragend 126 | }; 127 | 128 | opts.$el.dndBind(bindings); 129 | 130 | this.destroy = function(){ 131 | opts.$el.dndUnbind(); 132 | }; 133 | 134 | var events = {}; 135 | 136 | this.on = function(name, fn) { 137 | events[name] = events[name] || []; 138 | events[name].push(fn); 139 | }; 140 | 141 | this.trigger = function(name, args) { 142 | events[name] = events[name] || []; 143 | args = args || typeof args === 'string' ? [args] : []; 144 | events[name].forEach(function(fn) { 145 | fn.apply(this, args); 146 | }); 147 | } 148 | } 149 | 150 | return Lasso; 151 | }]) 152 | --------------------------------------------------------------------------------