├── .gitignore ├── js ├── arrive │ ├── .npmignore │ ├── tests │ │ ├── lib │ │ │ └── jasmine-2.0.0 │ │ │ │ ├── jasmine_favicon.png │ │ │ │ ├── MIT.LICENSE │ │ │ │ ├── jasmine.css │ │ │ │ ├── console.js │ │ │ │ ├── boot.js │ │ │ │ └── jasmine-html.js │ │ ├── withoutjQuery.js │ │ ├── SpecRunner.html │ │ └── spec │ │ │ └── arriveSpec.js │ ├── bower.json │ ├── LICENSE │ ├── package.json │ ├── minified │ │ └── arrive.min.js │ ├── README.md │ └── src │ │ └── arrive.js ├── jquery │ ├── .gitignore │ ├── README.md │ ├── package.json │ ├── bower.json │ ├── component.json │ └── composer.json └── sortablejs │ ├── package.json │ ├── Sortable.min.js │ ├── README.md │ └── Sortable.js ├── src ├── .DS_Store ├── bg │ └── background.js └── inject │ ├── inject.js │ └── inject.bak.js ├── icons ├── icon128.png ├── icon16.png ├── icon19.png └── icon48.png ├── package.json └── manifest.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/* -------------------------------------------------------------------------------- /js/arrive/.npmignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | -------------------------------------------------------------------------------- /js/jquery/.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | jquery-migrate.js 3 | jquery-migrate.min.js 4 | -------------------------------------------------------------------------------- /js/jquery/README.md: -------------------------------------------------------------------------------- 1 | jQuery Component 2 | ================ 3 | 4 | Shim repository for jQuery. 5 | -------------------------------------------------------------------------------- /src/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kyleladd/podio-field-organizer/master/src/.DS_Store -------------------------------------------------------------------------------- /icons/icon128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kyleladd/podio-field-organizer/master/icons/icon128.png -------------------------------------------------------------------------------- /icons/icon16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kyleladd/podio-field-organizer/master/icons/icon16.png -------------------------------------------------------------------------------- /icons/icon19.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kyleladd/podio-field-organizer/master/icons/icon19.png -------------------------------------------------------------------------------- /icons/icon48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kyleladd/podio-field-organizer/master/icons/icon48.png -------------------------------------------------------------------------------- /js/arrive/tests/lib/jasmine-2.0.0/jasmine_favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kyleladd/podio-field-organizer/master/js/arrive/tests/lib/jasmine-2.0.0/jasmine_favicon.png -------------------------------------------------------------------------------- /js/jquery/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "components-jquery", 3 | "version": "2.0.0", 4 | "description": "jQuery component", 5 | "keywords": ["jquery"], 6 | "main": "./jquery.js" 7 | } 8 | -------------------------------------------------------------------------------- /js/jquery/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jquery", 3 | "version": "2.0.0", 4 | "description": "jQuery component", 5 | "keywords": [ 6 | "jquery", 7 | "component" 8 | ], 9 | "scripts": [ 10 | "jquery.js" 11 | ], 12 | "license": "MIT" 13 | } 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "podio-organizer", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "dependencies": { 7 | "arrive": "^2.4.1", 8 | "jquery": "^3.3.1", 9 | "sortablejs": "^1.7.0" 10 | }, 11 | "devDependencies": {}, 12 | "scripts": { 13 | "test": "echo \"Error: no test specified\" && exit 1" 14 | }, 15 | "author": "", 16 | "license": "ISC" 17 | } 18 | -------------------------------------------------------------------------------- /js/jquery/component.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jquery", 3 | "version": "2.0.0", 4 | "description": "jQuery component", 5 | "keywords": [ 6 | "jquery", 7 | "component" 8 | ], 9 | "scripts": [ 10 | "jquery.js" 11 | ], 12 | "license": "MIT", 13 | "gitHead": "46f8412bd1bb9b1b30b5b0eb88560e2d4196509c", 14 | "readme": "jQuery Component\n================\n\nShim repository for jQuery.\n", 15 | "readmeFilename": "README.md", 16 | "_id": "jquery@2.0.0", 17 | "repository": { 18 | "type": "git", 19 | "url": "git://github.com/components/jquery.git" 20 | } 21 | } -------------------------------------------------------------------------------- /js/arrive/tests/withoutjQuery.js: -------------------------------------------------------------------------------- 1 | function getParameterByName(name) { 2 | name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]"); 3 | var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"), 4 | results = regex.exec(location.search); 5 | return results == null ? "" : decodeURIComponent(results[1].replace(/\+/g, " ")); 6 | } 7 | 8 | if (getParameterByName("withoutjQuery") == "true") { 9 | // run tests without jQuery 10 | delete jQuery; 11 | j = function(selector) { 12 | return $(selector)[0]; 13 | }; 14 | 15 | $("#jQueryBtn").text("Run tests with jQuery").val("false"); 16 | } 17 | else { 18 | // run tests with jQuery 19 | j = $; 20 | } -------------------------------------------------------------------------------- /src/bg/background.js: -------------------------------------------------------------------------------- 1 | var settings = []; 2 | chrome.storage.sync.get(['fields'], function (obj) { 3 | settings = obj.fields; 4 | if(!settings){ 5 | settings = []; 6 | } 7 | }); 8 | 9 | chrome.extension.onMessage.addListener(function(request, sender, sendResponse) { 10 | console.log("request",request.action); 11 | console.log("bkgrd settings",settings); 12 | if(request.action === "get-settings"){ 13 | sendResponse(settings); 14 | return; 15 | } 16 | if(request.action === "set-settings"){ 17 | settings = request.settings; 18 | if(!request.settings){ 19 | settings = []; 20 | } 21 | chrome.storage.sync.set({"fields": settings}); 22 | sendResponse(settings); 23 | return; 24 | } 25 | }); -------------------------------------------------------------------------------- /js/arrive/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "arrive", 3 | "version": "2.4.1", 4 | "description": "Watch for DOM elements creation and removal.", 5 | "main": "src/arrive.js", 6 | "keywords": [ 7 | "watch", 8 | "listen", 9 | "DOM", 10 | "elements", 11 | "creation", 12 | "removal", 13 | "injected" 14 | ], 15 | "authors": [ 16 | { "name": "Uzair Farooq", "email": "uzair.farooq@outlook.com" } 17 | ], 18 | "repository": { 19 | "type": "git", 20 | "url": "git://github.com/uzairfarooq/arrive.git" 21 | }, 22 | "license": "/LICENSE", 23 | "homepage": "https://github.com/uzairfarooq/arrive", 24 | "ignore": [ 25 | "**/.*", 26 | "node_modules", 27 | "bower_components" 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Podio Organizer", 3 | "version": "0.0.1", 4 | "manifest_version": 2, 5 | "description": "Organizer podio task fields", 6 | "icons": { 7 | "16": "icons/icon16.png", 8 | "48": "icons/icon48.png", 9 | "128": "icons/icon128.png" 10 | }, 11 | "background": { 12 | "scripts": [ 13 | "src/bg/background.js" 14 | ], 15 | "persistent": false 16 | }, 17 | "permissions": [ 18 | "storage", 19 | "*://podio.com/*" 20 | ], 21 | "content_scripts": [ 22 | { 23 | "matches": [ 24 | "*://podio.com/*" 25 | ], 26 | "js": [ 27 | "js/jquery/jquery.min.js","js/arrive/minified/arrive.min.js","js/sortablejs/Sortable.min.js","src/inject/inject.js" 28 | ] 29 | } 30 | ] 31 | } -------------------------------------------------------------------------------- /js/jquery/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "components/jquery", 3 | "description": "jQuery JavaScript Library", 4 | "type": "component", 5 | "homepage": "http://jquery.com", 6 | "license": "MIT", 7 | "support": { 8 | "irc": "irc://irc.freenode.org/jquery", 9 | "issues": "http://bugs.jquery.com", 10 | "forum": "http://forum.jquery.com", 11 | "wiki": "http://docs.jquery.com/", 12 | "source": "https://github.com/jquery/jquery" 13 | }, 14 | "authors": [ 15 | { 16 | "name": "John Resig", 17 | "email": "jeresig@gmail.com" 18 | } 19 | ], 20 | "require": { 21 | "robloach/component-installer": "*" 22 | }, 23 | "extra": { 24 | "component": { 25 | "scripts": [ 26 | "jquery.js" 27 | ] 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /js/arrive/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014-2015 Uzair Farooq 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /js/arrive/tests/lib/jasmine-2.0.0/MIT.LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2008-2011 Pivotal Labs 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /js/arrive/tests/SpecRunner.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Jasmine Spec Runner v2.0.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /src/inject/inject.js: -------------------------------------------------------------------------------- 1 | var settings = []; 2 | chrome.extension.sendMessage({}, function(response) { 3 | $(function() { 4 | $(document).arrive(".app-fields-list", function() { 5 | $(document).unbindArrive(".app-fields-list"); 6 | var $container = $('.app-fields-list'); 7 | var $fields = $container.find('> li').detach(); 8 | chrome.extension.sendMessage({"action": "get-settings", "app":"app"}, 9 | function (response) { 10 | settings = response; 11 | $fields = $fields.sort(function(a, b) { 12 | var aVal = settings.indexOf($(a).attr('id')); 13 | var bVal = settings.indexOf($(b).attr('id')); 14 | if(aVal === -1 && bVal === -1){ 15 | return 0; 16 | } 17 | else if(aVal === -1){ 18 | return 1; 19 | } 20 | else if(bVal === -1){ 21 | return -1; 22 | } 23 | if(aVal < bVal) { return -1; } 24 | if(aVal > bVal) { return 1; } 25 | return 0; 26 | }); 27 | $container.html($fields); 28 | var sortable = Sortable.create($container.get(0),{ 29 | draggable: 'li', 30 | store: { 31 | get: function (sortable) { 32 | return []; 33 | }, 34 | set: function (sortable) { 35 | var order = $container.find('> li').map(function(){ 36 | return $(this).attr('id'); 37 | }).get(); 38 | chrome.extension.sendMessage({"action": "set-settings", "settings": order}, function(response){ 39 | }); 40 | } 41 | }, 42 | }); 43 | }); 44 | }); 45 | }); 46 | }); -------------------------------------------------------------------------------- /js/arrive/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "_from": "arrive@^2.4.1", 3 | "_id": "arrive@2.4.1", 4 | "_inBundle": false, 5 | "_integrity": "sha1-VkyH8gvAm4DeeBEk2UMWlQBLgCA=", 6 | "_location": "/arrive", 7 | "_phantomChildren": {}, 8 | "_requested": { 9 | "type": "range", 10 | "registry": true, 11 | "raw": "arrive@^2.4.1", 12 | "name": "arrive", 13 | "escapedName": "arrive", 14 | "rawSpec": "^2.4.1", 15 | "saveSpec": null, 16 | "fetchSpec": "^2.4.1" 17 | }, 18 | "_requiredBy": [ 19 | "#USER", 20 | "/" 21 | ], 22 | "_resolved": "https://registry.npmjs.org/arrive/-/arrive-2.4.1.tgz", 23 | "_shasum": "564c87f20bc09b80de781124d9431695004b8020", 24 | "_spec": "arrive@^2.4.1", 25 | "_where": "C:\\Users\\kladd\\Documents\\Programming\\Javascript\\Chrome Extensions\\podio-organizer\\ext", 26 | "author": { 27 | "name": "Uzair Farooq", 28 | "email": "uzair.farooq@outlook.com" 29 | }, 30 | "bugs": { 31 | "url": "https://github.com/uzairfarooq/arrive/issues" 32 | }, 33 | "bundleDependencies": false, 34 | "deprecated": false, 35 | "description": "arrive.js provides events to watch for DOM elements creation and removal. It makes use of Mutation Observers internally.", 36 | "directories": { 37 | "test": "tests" 38 | }, 39 | "homepage": "https://github.com/uzairfarooq/arrive#readme", 40 | "keywords": [ 41 | "jquery", 42 | "javascript", 43 | "js", 44 | "watch", 45 | "dynamic", 46 | "creation", 47 | "new", 48 | "element", 49 | "insertion", 50 | "listen" 51 | ], 52 | "license": "MIT", 53 | "main": "src/arrive.js", 54 | "name": "arrive", 55 | "repository": { 56 | "type": "git", 57 | "url": "git+https://github.com/uzairfarooq/arrive.git" 58 | }, 59 | "scripts": { 60 | "test": "open ./tests/SpecRunner.html" 61 | }, 62 | "version": "2.4.1" 63 | } 64 | -------------------------------------------------------------------------------- /js/sortablejs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "_from": "sortablejs", 3 | "_id": "sortablejs@1.7.0", 4 | "_inBundle": false, 5 | "_integrity": "sha1-gKKyNwq9Vo4c7IwnETHvMKkE+ig=", 6 | "_location": "/sortablejs", 7 | "_phantomChildren": {}, 8 | "_requested": { 9 | "type": "tag", 10 | "registry": true, 11 | "raw": "sortablejs", 12 | "name": "sortablejs", 13 | "escapedName": "sortablejs", 14 | "rawSpec": "", 15 | "saveSpec": null, 16 | "fetchSpec": "latest" 17 | }, 18 | "_requiredBy": [ 19 | "#USER", 20 | "/" 21 | ], 22 | "_resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.7.0.tgz", 23 | "_shasum": "80a2b2370abd568e1cec8c271131ef30a904fa28", 24 | "_spec": "sortablejs", 25 | "_where": "C:\\Users\\kladd\\Documents\\Programming\\Javascript\\Chrome Extensions\\podio-organizer\\ext", 26 | "author": { 27 | "name": "Konstantin Lebedev", 28 | "email": "ibnRubaXa@gmail.com" 29 | }, 30 | "bugs": { 31 | "url": "https://github.com/rubaxa/Sortable/issues" 32 | }, 33 | "bundleDependencies": false, 34 | "deprecated": false, 35 | "description": "Minimalist JavaScript library for reorderable drag-and-drop lists on modern browsers and touch devices. No jQuery. Supports AngularJS and any CSS library, e.g. Bootstrap.", 36 | "devDependencies": { 37 | "grunt": "*", 38 | "grunt-contrib-jshint": "*", 39 | "grunt-contrib-uglify": "*", 40 | "grunt-testcafe": "^0.15.0", 41 | "grunt-version": "*", 42 | "http-server": "^0.9.0", 43 | "testcafe": "^0.16.0" 44 | }, 45 | "exportName": "Sortable", 46 | "files": [ 47 | "Sortable.js", 48 | "Sortable.min.js" 49 | ], 50 | "homepage": "https://github.com/rubaxa/Sortable#readme", 51 | "keywords": [ 52 | "sortable", 53 | "reorder", 54 | "drag", 55 | "meteor", 56 | "angular", 57 | "ng-sortable", 58 | "react", 59 | "mixin" 60 | ], 61 | "license": "MIT", 62 | "main": "Sortable.js", 63 | "name": "sortablejs", 64 | "repository": { 65 | "type": "git", 66 | "url": "git://github.com/rubaxa/Sortable.git" 67 | }, 68 | "scripts": { 69 | "http-server": "http-server -s ./", 70 | "prepublish": "./node_modules/grunt/bin/grunt", 71 | "test": "./node_modules/grunt/bin/grunt" 72 | }, 73 | "spm": { 74 | "main": "Sortable.js", 75 | "ignore": [ 76 | "meteor", 77 | "st" 78 | ] 79 | }, 80 | "version": "1.7.0" 81 | } 82 | -------------------------------------------------------------------------------- /src/inject/inject.bak.js: -------------------------------------------------------------------------------- 1 | var settings = []; 2 | chrome.extension.sendMessage({"action": "get-settings", "app":"app"}, 3 | function (response) { 4 | console.log("response",response); 5 | settings = response; 6 | }); 7 | chrome.extension.sendMessage({}, function(response) { 8 | $(function() { 9 | $(document).arrive(".app-fields-list > li", function() { 10 | console.log("arrived"); 11 | $(document).unbindArrive(".app-fields-list > li"); 12 | var $container = $('.app-fields-list'); 13 | // $container.find('> li').each(function(){ 14 | // // debugger; 15 | // var $that = $(this); 16 | // $that.data("sortableid",$that.attr("id")); 17 | // console.log($that.data()); 18 | // }); 19 | var $fields = $container.find('> li').detach(); 20 | 21 | console.log("container",$container); 22 | console.log("FIELDS",$fields); 23 | $fields = $fields.sort(function(a, b) { 24 | // var aVal = $(a).find('.label-content-wrapper .label-content').text().replace(/\*/g, '').replace(/^\s+|\s+$/g, '').toLowerCase(); 25 | // var bVal = $(b).find('.label-content-wrapper .label-content').text().replace(/\*/g, '').replace(/^\s+|\s+$/g, '').toLowerCase(); 26 | var aVal = settings.indexOf($(a).attr('id')); 27 | var bVal = settings.indexOf($(b).attr('id')); 28 | // console.log("A",aVal,"B",bVal); 29 | if(aVal === -1 && bVal === -1){ 30 | return 0; 31 | } 32 | else if(aVal === -1){ 33 | return 1; 34 | } 35 | else if(bVal === -1){ 36 | return -1; 37 | } 38 | if(aVal < bVal) { return -1; } 39 | if(aVal > bVal) { return 1; } 40 | return 0; 41 | }); 42 | console.log("$fields",$fields); 43 | $container.html($fields); 44 | console.log("container",$container.get(0)); 45 | $container.sortable({ 46 | update: function( event, ui ) { 47 | console.log("UPDATE",$container); 48 | } 49 | }); 50 | 51 | // var sortable = Sortable.create($container.get(0),{ 52 | // dataIdAttr: 'data-sortableid', 53 | // draggable: 'li', 54 | // store: { 55 | // get: function (sortable) { 56 | // // var order = localStorage.getItem(sortable.options.group); 57 | // // return order ? order.split('|') : []; 58 | // chrome.extension.sendMessage({"action": "get-settings", "app":"app"}, function (response) { 59 | // console.log("settings response",response); 60 | // return response; 61 | // }); 62 | // }, 63 | // set: function (sortable) { 64 | // var order = sortable.toArray(); 65 | // chrome.extension.sendMessage({"action": "set-settings", "settings": order}, function(response){ 66 | // console.log("response",response); 67 | // }); 68 | // // var order = sortable.toArray(); 69 | // // console.log(order, 'baaaaaaaaaaaaaaaaaaaaaaaaar'); 70 | // // $('.visuaplayoutCol02').attr('value', order); 71 | // } 72 | // }, 73 | // // Changed sorting within list 74 | // // onUpdate: function (evt) { 75 | // // // same properties as onEnd 76 | // // console.log("sortable - onUpdate", $fields); 77 | // // // console.log("array",this.toArray()) 78 | // // console.log("new setting", $fields.map(function(){ 79 | // // return $(this).attr('id'); 80 | // // }).get()); 81 | // // debugger; 82 | // // chrome.extension.sendMessage({"action": "set-settings", "settings": $fields.map(function(){ 83 | // // return $(this).attr('id'); 84 | // // }).get()}, 85 | // // function (response) { 86 | // // console.log("response",response); 87 | // // }); 88 | // // } 89 | // }); 90 | }); 91 | }); 92 | }); 93 | -------------------------------------------------------------------------------- /js/arrive/tests/lib/jasmine-2.0.0/jasmine.css: -------------------------------------------------------------------------------- 1 | body { background-color: #eeeeee; padding: 0; margin: 5px; overflow-y: scroll; } 2 | 3 | .html-reporter { font-size: 11px; font-family: Monaco, "Lucida Console", monospace; line-height: 14px; color: #333333; } 4 | .html-reporter a { text-decoration: none; } 5 | .html-reporter a:hover { text-decoration: underline; } 6 | .html-reporter p, .html-reporter h1, .html-reporter h2, .html-reporter h3, .html-reporter h4, .html-reporter h5, .html-reporter h6 { margin: 0; line-height: 14px; } 7 | .html-reporter .banner, .html-reporter .symbol-summary, .html-reporter .summary, .html-reporter .result-message, .html-reporter .spec .description, .html-reporter .spec-detail .description, .html-reporter .alert .bar, .html-reporter .stack-trace { padding-left: 9px; padding-right: 9px; } 8 | .html-reporter .banner .version { margin-left: 14px; } 9 | .html-reporter #jasmine_content { position: fixed; right: 100%; } 10 | .html-reporter .version { color: #aaaaaa; } 11 | .html-reporter .banner { margin-top: 14px; } 12 | .html-reporter .duration { color: #aaaaaa; float: right; } 13 | .html-reporter .symbol-summary { overflow: hidden; *zoom: 1; margin: 14px 0; } 14 | .html-reporter .symbol-summary li { display: inline-block; height: 8px; width: 14px; font-size: 16px; } 15 | .html-reporter .symbol-summary li.passed { font-size: 14px; } 16 | .html-reporter .symbol-summary li.passed:before { color: #5e7d00; content: "\02022"; } 17 | .html-reporter .symbol-summary li.failed { line-height: 9px; } 18 | .html-reporter .symbol-summary li.failed:before { color: #b03911; content: "x"; font-weight: bold; margin-left: -1px; } 19 | .html-reporter .symbol-summary li.disabled { font-size: 14px; } 20 | .html-reporter .symbol-summary li.disabled:before { color: #bababa; content: "\02022"; } 21 | .html-reporter .symbol-summary li.pending { line-height: 17px; } 22 | .html-reporter .symbol-summary li.pending:before { color: #ba9d37; content: "*"; } 23 | .html-reporter .exceptions { color: #fff; float: right; margin-top: 5px; margin-right: 5px; } 24 | .html-reporter .bar { line-height: 28px; font-size: 14px; display: block; color: #eee; } 25 | .html-reporter .bar.failed { background-color: #b03911; } 26 | .html-reporter .bar.passed { background-color: #a6b779; } 27 | .html-reporter .bar.skipped { background-color: #bababa; } 28 | .html-reporter .bar.menu { background-color: #fff; color: #aaaaaa; } 29 | .html-reporter .bar.menu a { color: #333333; } 30 | .html-reporter .bar a { color: white; } 31 | .html-reporter.spec-list .bar.menu.failure-list, .html-reporter.spec-list .results .failures { display: none; } 32 | .html-reporter.failure-list .bar.menu.spec-list, .html-reporter.failure-list .summary { display: none; } 33 | .html-reporter .running-alert { background-color: #666666; } 34 | .html-reporter .results { margin-top: 14px; } 35 | .html-reporter.showDetails .summaryMenuItem { font-weight: normal; text-decoration: inherit; } 36 | .html-reporter.showDetails .summaryMenuItem:hover { text-decoration: underline; } 37 | .html-reporter.showDetails .detailsMenuItem { font-weight: bold; text-decoration: underline; } 38 | .html-reporter.showDetails .summary { display: none; } 39 | .html-reporter.showDetails #details { display: block; } 40 | .html-reporter .summaryMenuItem { font-weight: bold; text-decoration: underline; } 41 | .html-reporter .summary { margin-top: 14px; } 42 | .html-reporter .summary ul { list-style-type: none; margin-left: 14px; padding-top: 0; padding-left: 0; } 43 | .html-reporter .summary ul.suite { margin-top: 7px; margin-bottom: 7px; } 44 | .html-reporter .summary li.passed a { color: #5e7d00; } 45 | .html-reporter .summary li.failed a { color: #b03911; } 46 | .html-reporter .summary li.pending a { color: #ba9d37; } 47 | .html-reporter .description + .suite { margin-top: 0; } 48 | .html-reporter .suite { margin-top: 14px; } 49 | .html-reporter .suite a { color: #333333; } 50 | .html-reporter .failures .spec-detail { margin-bottom: 28px; } 51 | .html-reporter .failures .spec-detail .description { background-color: #b03911; } 52 | .html-reporter .failures .spec-detail .description a { color: white; } 53 | .html-reporter .result-message { padding-top: 14px; color: #333333; white-space: pre; } 54 | .html-reporter .result-message span.result { display: block; } 55 | .html-reporter .stack-trace { margin: 5px 0 0 0; max-height: 224px; overflow: auto; line-height: 18px; color: #666666; border: 1px solid #ddd; background: white; white-space: pre; } 56 | -------------------------------------------------------------------------------- /js/arrive/minified/arrive.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | * arrive.js 3 | * v2.4.1 4 | * https://github.com/uzairfarooq/arrive 5 | * MIT licensed 6 | * 7 | * Copyright (c) 2014-2017 Uzair Farooq 8 | */ 9 | 10 | var Arrive=function(e,t,n){"use strict";function r(e,t,n){l.addMethod(t,n,e.unbindEvent),l.addMethod(t,n,e.unbindEventWithSelectorOrCallback),l.addMethod(t,n,e.unbindEventWithSelectorAndCallback)}function i(e){e.arrive=f.bindEvent,r(f,e,"unbindArrive"),e.leave=d.bindEvent,r(d,e,"unbindLeave")}if(e.MutationObserver&&"undefined"!=typeof HTMLElement){var o=0,l=function(){var t=HTMLElement.prototype.matches||HTMLElement.prototype.webkitMatchesSelector||HTMLElement.prototype.mozMatchesSelector||HTMLElement.prototype.msMatchesSelector;return{matchesSelector:function(e,n){return e instanceof HTMLElement&&t.call(e,n)},addMethod:function(e,t,r){var i=e[t];e[t]=function(){return r.length==arguments.length?r.apply(this,arguments):"function"==typeof i?i.apply(this,arguments):n}},callCallbacks:function(e,t){t&&t.options.onceOnly&&1==t.firedElems.length&&(e=[e[0]]);for(var n,r=0;n=e[r];r++)n&&n.callback&&n.callback.call(n.elem,n.elem);t&&t.options.onceOnly&&1==t.firedElems.length&&t.me.unbindEventWithSelectorAndCallback.call(t.target,t.selector,t.callback)},checkChildNodesRecursively:function(e,t,n,r){for(var i,o=0;i=e[o];o++)n(i,t,r)&&r.push({callback:t.callback,elem:i}),i.childNodes.length>0&&l.checkChildNodesRecursively(i.childNodes,t,n,r)},mergeArrays:function(e,t){var n,r={};for(n in e)e.hasOwnProperty(n)&&(r[n]=e[n]);for(n in t)t.hasOwnProperty(n)&&(r[n]=t[n]);return r},toElementsArray:function(t){return n===t||"number"==typeof t.length&&t!==e||(t=[t]),t}}}(),c=function(){var e=function(){this._eventsBucket=[],this._beforeAdding=null,this._beforeRemoving=null};return e.prototype.addEvent=function(e,t,n,r){var i={target:e,selector:t,options:n,callback:r,firedElems:[]};return this._beforeAdding&&this._beforeAdding(i),this._eventsBucket.push(i),i},e.prototype.removeEvent=function(e){for(var t,n=this._eventsBucket.length-1;t=this._eventsBucket[n];n--)if(e(t)){this._beforeRemoving&&this._beforeRemoving(t);var r=this._eventsBucket.splice(n,1);r&&r.length&&(r[0].callback=null)}},e.prototype.beforeAdding=function(e){this._beforeAdding=e},e.prototype.beforeRemoving=function(e){this._beforeRemoving=e},e}(),a=function(t,r){var i=new c,o=this,a={fireOnAttributesModification:!1};return i.beforeAdding(function(n){var i,l=n.target;(l===e.document||l===e)&&(l=document.getElementsByTagName("html")[0]),i=new MutationObserver(function(e){r.call(this,e,n)});var c=t(n.options);i.observe(l,c),n.observer=i,n.me=o}),i.beforeRemoving(function(e){e.observer.disconnect()}),this.bindEvent=function(e,t,n){t=l.mergeArrays(a,t);for(var r=l.toElementsArray(this),o=0;o0?l.checkChildNodesRecursively(n,t,r,o):"attributes"===e.type&&r(i,t,o)&&o.push({callback:t.callback,elem:i}),l.callCallbacks(o,t)})}function r(e,t){return l.matchesSelector(e,t.selector)&&(e._id===n&&(e._id=o++),-1==t.firedElems.indexOf(e._id))?(t.firedElems.push(e._id),!0):!1}var i={fireOnAttributesModification:!1,onceOnly:!1,existing:!1};f=new a(e,t);var c=f.bindEvent;return f.bindEvent=function(e,t,r){n===r?(r=t,t=i):t=l.mergeArrays(i,t);var o=l.toElementsArray(this);if(t.existing){for(var a=[],s=0;s0&&l.checkChildNodesRecursively(n,t,r,i),l.callCallbacks(i,t)})}function r(e,t){return l.matchesSelector(e,t.selector)}var i={};d=new a(e,t);var o=d.bindEvent;return d.bindEvent=function(e,t,r){n===r?(r=t,t=i):t=l.mergeArrays(i,t),o.call(this,e,t,r)},d},f=new s,d=new u;t&&i(t.fn),i(HTMLElement.prototype),i(NodeList.prototype),i(HTMLCollection.prototype),i(HTMLDocument.prototype),i(Window.prototype);var h={};return r(f,h,"unbindAllArrive"),r(d,h,"unbindAllLeave"),h}}(window,"undefined"==typeof jQuery?null:jQuery,void 0); -------------------------------------------------------------------------------- /js/arrive/tests/lib/jasmine-2.0.0/console.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2008-2013 Pivotal Labs 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining 5 | a copy of this software and associated documentation files (the 6 | "Software"), to deal in the Software without restriction, including 7 | without limitation the rights to use, copy, modify, merge, publish, 8 | distribute, sublicense, and/or sell copies of the Software, and to 9 | permit persons to whom the Software is furnished to do so, subject to 10 | the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 19 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 20 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 21 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | */ 23 | function getJasmineRequireObj() { 24 | if (typeof module !== "undefined" && module.exports) { 25 | return exports; 26 | } else { 27 | window.jasmineRequire = window.jasmineRequire || {}; 28 | return window.jasmineRequire; 29 | } 30 | } 31 | 32 | getJasmineRequireObj().console = function(jRequire, j$) { 33 | j$.ConsoleReporter = jRequire.ConsoleReporter(); 34 | }; 35 | 36 | getJasmineRequireObj().ConsoleReporter = function() { 37 | 38 | var noopTimer = { 39 | start: function(){}, 40 | elapsed: function(){ return 0; } 41 | }; 42 | 43 | function ConsoleReporter(options) { 44 | var print = options.print, 45 | showColors = options.showColors || false, 46 | onComplete = options.onComplete || function() {}, 47 | timer = options.timer || noopTimer, 48 | specCount, 49 | failureCount, 50 | failedSpecs = [], 51 | pendingCount, 52 | ansi = { 53 | green: '\x1B[32m', 54 | red: '\x1B[31m', 55 | yellow: '\x1B[33m', 56 | none: '\x1B[0m' 57 | }; 58 | 59 | this.jasmineStarted = function() { 60 | specCount = 0; 61 | failureCount = 0; 62 | pendingCount = 0; 63 | print("Started"); 64 | printNewline(); 65 | timer.start(); 66 | }; 67 | 68 | this.jasmineDone = function() { 69 | printNewline(); 70 | for (var i = 0; i < failedSpecs.length; i++) { 71 | specFailureDetails(failedSpecs[i]); 72 | } 73 | 74 | printNewline(); 75 | var specCounts = specCount + " " + plural("spec", specCount) + ", " + 76 | failureCount + " " + plural("failure", failureCount); 77 | 78 | if (pendingCount) { 79 | specCounts += ", " + pendingCount + " pending " + plural("spec", pendingCount); 80 | } 81 | 82 | print(specCounts); 83 | 84 | printNewline(); 85 | var seconds = timer.elapsed() / 1000; 86 | print("Finished in " + seconds + " " + plural("second", seconds)); 87 | 88 | printNewline(); 89 | 90 | onComplete(failureCount === 0); 91 | }; 92 | 93 | this.specDone = function(result) { 94 | specCount++; 95 | 96 | if (result.status == "pending") { 97 | pendingCount++; 98 | print(colored("yellow", "*")); 99 | return; 100 | } 101 | 102 | if (result.status == "passed") { 103 | print(colored("green", '.')); 104 | return; 105 | } 106 | 107 | if (result.status == "failed") { 108 | failureCount++; 109 | failedSpecs.push(result); 110 | print(colored("red", 'F')); 111 | } 112 | }; 113 | 114 | return this; 115 | 116 | function printNewline() { 117 | print("\n"); 118 | } 119 | 120 | function colored(color, str) { 121 | return showColors ? (ansi[color] + str + ansi.none) : str; 122 | } 123 | 124 | function plural(str, count) { 125 | return count == 1 ? str : str + "s"; 126 | } 127 | 128 | function repeat(thing, times) { 129 | var arr = []; 130 | for (var i = 0; i < times; i++) { 131 | arr.push(thing); 132 | } 133 | return arr; 134 | } 135 | 136 | function indent(str, spaces) { 137 | var lines = (str || '').split("\n"); 138 | var newArr = []; 139 | for (var i = 0; i < lines.length; i++) { 140 | newArr.push(repeat(" ", spaces).join("") + lines[i]); 141 | } 142 | return newArr.join("\n"); 143 | } 144 | 145 | function specFailureDetails(result) { 146 | printNewline(); 147 | print(result.fullName); 148 | 149 | for (var i = 0; i < result.failedExpectations.length; i++) { 150 | var failedExpectation = result.failedExpectations[i]; 151 | printNewline(); 152 | print(indent(failedExpectation.stack, 2)); 153 | } 154 | 155 | printNewline(); 156 | } 157 | } 158 | 159 | return ConsoleReporter; 160 | }; 161 | -------------------------------------------------------------------------------- /js/arrive/tests/lib/jasmine-2.0.0/boot.js: -------------------------------------------------------------------------------- 1 | /** 2 | Starting with version 2.0, this file "boots" Jasmine, performing all of the necessary initialization before executing the loaded environment and all of a project's specs. This file should be loaded after `jasmine.js`, but before any project source files or spec files are loaded. Thus this file can also be used to customize Jasmine for a project. 3 | 4 | If a project is using Jasmine via the standalone distribution, this file can be customized directly. If a project is using Jasmine via the [Ruby gem][jasmine-gem], this file can be copied into the support directory via `jasmine copy_boot_js`. Other environments (e.g., Python) will have different mechanisms. 5 | 6 | The location of `boot.js` can be specified and/or overridden in `jasmine.yml`. 7 | 8 | [jasmine-gem]: http://github.com/pivotal/jasmine-gem 9 | */ 10 | 11 | (function() { 12 | 13 | /** 14 | * ## Require & Instantiate 15 | * 16 | * Require Jasmine's core files. Specifically, this requires and attaches all of Jasmine's code to the `jasmine` reference. 17 | */ 18 | window.jasmine = jasmineRequire.core(jasmineRequire); 19 | 20 | /** 21 | * Since this is being run in a browser and the results should populate to an HTML page, require the HTML-specific Jasmine code, injecting the same reference. 22 | */ 23 | jasmineRequire.html(jasmine); 24 | 25 | /** 26 | * Create the Jasmine environment. This is used to run all specs in a project. 27 | */ 28 | var env = jasmine.getEnv(); 29 | 30 | /** 31 | * ## The Global Interface 32 | * 33 | * Build up the functions that will be exposed as the Jasmine public interface. A project can customize, rename or alias any of these functions as desired, provided the implementation remains unchanged. 34 | */ 35 | var jasmineInterface = { 36 | describe: function(description, specDefinitions) { 37 | return env.describe(description, specDefinitions); 38 | }, 39 | 40 | xdescribe: function(description, specDefinitions) { 41 | return env.xdescribe(description, specDefinitions); 42 | }, 43 | 44 | it: function(desc, func) { 45 | return env.it(desc, func); 46 | }, 47 | 48 | xit: function(desc, func) { 49 | return env.xit(desc, func); 50 | }, 51 | 52 | beforeEach: function(beforeEachFunction) { 53 | return env.beforeEach(beforeEachFunction); 54 | }, 55 | 56 | afterEach: function(afterEachFunction) { 57 | return env.afterEach(afterEachFunction); 58 | }, 59 | 60 | expect: function(actual) { 61 | return env.expect(actual); 62 | }, 63 | 64 | pending: function() { 65 | return env.pending(); 66 | }, 67 | 68 | spyOn: function(obj, methodName) { 69 | return env.spyOn(obj, methodName); 70 | }, 71 | 72 | jsApiReporter: new jasmine.JsApiReporter({ 73 | timer: new jasmine.Timer() 74 | }) 75 | }; 76 | 77 | /** 78 | * Add all of the Jasmine global/public interface to the proper global, so a project can use the public interface directly. For example, calling `describe` in specs instead of `jasmine.getEnv().describe`. 79 | */ 80 | if (typeof window == "undefined" && typeof exports == "object") { 81 | extend(exports, jasmineInterface); 82 | } else { 83 | extend(window, jasmineInterface); 84 | } 85 | 86 | /** 87 | * Expose the interface for adding custom equality testers. 88 | */ 89 | jasmine.addCustomEqualityTester = function(tester) { 90 | env.addCustomEqualityTester(tester); 91 | }; 92 | 93 | /** 94 | * Expose the interface for adding custom expectation matchers 95 | */ 96 | jasmine.addMatchers = function(matchers) { 97 | return env.addMatchers(matchers); 98 | }; 99 | 100 | /** 101 | * Expose the mock interface for the JavaScript timeout functions 102 | */ 103 | jasmine.clock = function() { 104 | return env.clock; 105 | }; 106 | 107 | /** 108 | * ## Runner Parameters 109 | * 110 | * More browser specific code - wrap the query string in an object and to allow for getting/setting parameters from the runner user interface. 111 | */ 112 | 113 | var queryString = new jasmine.QueryString({ 114 | getWindowLocation: function() { return window.location; } 115 | }); 116 | 117 | var catchingExceptions = queryString.getParam("catch"); 118 | env.catchExceptions(typeof catchingExceptions === "undefined" ? true : catchingExceptions); 119 | 120 | /** 121 | * ## Reporters 122 | * The `HtmlReporter` builds all of the HTML UI for the runner page. This reporter paints the dots, stars, and x's for specs, as well as all spec names and all failures (if any). 123 | */ 124 | var htmlReporter = new jasmine.HtmlReporter({ 125 | env: env, 126 | onRaiseExceptionsClick: function() { queryString.setParam("catch", !env.catchingExceptions()); }, 127 | getContainer: function() { return document.body; }, 128 | createElement: function() { return document.createElement.apply(document, arguments); }, 129 | createTextNode: function() { return document.createTextNode.apply(document, arguments); }, 130 | timer: new jasmine.Timer() 131 | }); 132 | 133 | /** 134 | * The `jsApiReporter` also receives spec results, and is used by any environment that needs to extract the results from JavaScript. 135 | */ 136 | env.addReporter(jasmineInterface.jsApiReporter); 137 | env.addReporter(htmlReporter); 138 | 139 | /** 140 | * Filter which specs will be run by matching the start of the full name against the `spec` query param. 141 | */ 142 | var specFilter = new jasmine.HtmlSpecFilter({ 143 | filterString: function() { return queryString.getParam("spec"); } 144 | }); 145 | 146 | env.specFilter = function(spec) { 147 | return specFilter.matches(spec.getFullName()); 148 | }; 149 | 150 | /** 151 | * Setting up timing functions to be able to be overridden. Certain browsers (Safari, IE 8, phantomjs) require this hack. 152 | */ 153 | window.setTimeout = window.setTimeout; 154 | window.setInterval = window.setInterval; 155 | window.clearTimeout = window.clearTimeout; 156 | window.clearInterval = window.clearInterval; 157 | 158 | /** 159 | * ## Execution 160 | * 161 | * Replace the browser window's `onload`, ensure it's called, and then run all of the loaded specs. This includes initializing the `HtmlReporter` instance and then executing the loaded Jasmine environment. All of this will happen after all of the specs are loaded. 162 | */ 163 | var currentWindowOnload = window.onload; 164 | 165 | window.onload = function() { 166 | if (currentWindowOnload) { 167 | currentWindowOnload(); 168 | } 169 | htmlReporter.initialize(); 170 | env.execute(); 171 | }; 172 | 173 | /** 174 | * Helper function for readability above. 175 | */ 176 | function extend(destination, source) { 177 | for (var property in source) destination[property] = source[property]; 178 | return destination; 179 | } 180 | 181 | }()); 182 | -------------------------------------------------------------------------------- /js/arrive/README.md: -------------------------------------------------------------------------------- 1 | # arrive.js 2 | 3 | arrive.js provides events to watch for DOM elements creation and removal. It makes use of [Mutation Observers](https://developer.mozilla.org/en/docs/Web/API/MutationObserver) internally. 4 | 5 | Download [arrive.min.js](https://raw.githubusercontent.com/uzairfarooq/arrive/master/minified/arrive.min.js) (latest) 6 | 7 | or use [Bower](http://bower.io/) to install: 8 | 9 | ```bash 10 | # install arrive.js and add it to bower.json dependencies 11 | $ bower install arrive --save 12 | ``` 13 | 14 | ##### Node.js / NPM 15 | Node.js users can install using npm: 16 | 17 | ```bash 18 | $ npm install arrive --save 19 | ``` 20 | 21 | ## Usage 22 | **The library does not depend on jQuery, you can replace jQuery elements in the examples below with pure javascript elements and it would work fine.** 23 | ### Watch for elements creation 24 | Use `arrive` event to watch for elements creation: 25 | ```javascript 26 | // watch for creation of an element which satisfies the selector ".test-elem" 27 | $(document).arrive(".test-elem", function() { 28 | // 'this' refers to the newly created element 29 | var $newElem = $(this); 30 | }); 31 | 32 | // the above event would watch for creation of element in whole document 33 | // it's better to be more specific whenever possible, for example 34 | $(".container-1").arrive(".test-elem", function() { 35 | var $newElem = $(this); 36 | }); 37 | 38 | // as of v2.3.2, new element is also passed as argument to the callback function. 39 | // This is to support arrow functions as 'this' is not bindable in arrow functions. 40 | $(document).arrive(".test-elem", function(newElem) { 41 | var $newElem = $(newElem); 42 | }); 43 | ``` 44 | 45 | In pure javascript you can call the function on `document`, `window`, any `HTMLElement` or `NodeList`, like this: 46 | ```javascript 47 | // watch for element creation in the whole HTML document 48 | document.arrive(".test-elem", function() { 49 | // 'this' refers to the newly created element 50 | }); 51 | 52 | // this will attach arrive event to all elements in the NodeList 53 | document.getElementsByClass(".container-1").arrive(".test-elem", function() { 54 | // 'this' refers to the newly created element 55 | }); 56 | ``` 57 | 58 | Make sure to remove listeners when they are no longer needed, it's better for performance: 59 | ```javascript 60 | // unbind all arrive events on document element 61 | $(document).unbindArrive(); 62 | 63 | // unbind all arrive events on document element which are watching for ".test-elem" selector 64 | $(document).unbindArrive(".test-elem"); 65 | 66 | // unbind only a specific callback 67 | $(document).unbindArrive(callbackFunc); 68 | 69 | // unbind only a specific callback on ".test-elem" selector 70 | $(document).unbindArrive(".test-elem", callbackFunc); 71 | 72 | // unbind all arrive events 73 | Arrive.unbindAllArrive(); 74 | ``` 75 | 76 | #### Options 77 | As of v2.0 `arrive` event accepts an optional `options` object as 2nd argument. Options object consists of following: 78 | ```javascript 79 | var options = { 80 | fireOnAttributesModification: boolean, // Defaults to false. Setting it to true would make arrive event fire on existing elements which start to satisfy selector after some modification in DOM attributes (an arrive event won't fire twice for a single element even if the option is true). If false, it'd only fire for newly created elements. 81 | onceOnly: boolean // Defaults to false. Setting it to true would ensure that registered callbacks fire only once. No need to unbind the event if the attribute is set to true, it'll automatically unbind after firing once. 82 | existing: boolean // Defaults to false. Setting it to true would ensure that the registered callback is fired for the elements that already exist in the DOM and match the selector. If options.onceOnly is set, the callback is only called once with the first element matching the selector. 83 | }; 84 | ``` 85 | Example: 86 | ```javascript 87 | $(document).arrive(".test-elem", {fireOnAttributesModification: true}, function() { 88 | // 'this' refers to the newly created element 89 | var $newElem = $(this); 90 | }); 91 | ``` 92 | 93 | ### Watch for elements removal 94 | Use `leave` event to watch for elements removal. 95 | The first arugument to leave must not be a [descendent](https://developer.mozilla.org/en-US/docs/Web/CSS/Descendant_selectors) or [child](https://developer.mozilla.org/en-US/docs/Web/CSS/Child_selectors) selector i.e. you cannot pass `.page .test-elem`, instead, pass `.test-elem`. It's because of a limitation in MutationObserver's api. 96 | 97 | ```javascript 98 | // watch for removal of an element which satisfies the selector ".test-elem" 99 | $(".container-1").leave(".test-elem", function() { 100 | var $removedElem = $(this); 101 | }); 102 | ``` 103 | 104 | You can unbind the `leave` event in the same way as `arrive` event, using `unbindLeave` function i.e: 105 | 106 | ```javascript 107 | // unbind all leave events on document element 108 | $(document).unbindLeave(); 109 | 110 | // unbind all leave events 111 | Arrive.unbindAllLeave(); 112 | ``` 113 | 114 | 115 | ## Browser Support 116 | arrive.js is built over [Mutation Observers](https://developer.mozilla.org/en/docs/Web/API/MutationObserver) which is introduced in DOM4. It's supported in latest versions of all popular browsers. 117 | 118 | | Browser | Supported Versions 119 | | ------------------|:-----------------:| 120 | | Google Chrome | 27.0+ | 121 | | Firefox | 14.0+ | 122 | | Safari | 6.1+ | 123 | | Internet Explorer | 11.0+ | 124 | | Opera | 14.0+ | 125 | 126 | ## Contributing 127 | #### Report a bug / Request a feature 128 | If you want to report a bug or request a feature, use the [Issues](https://github.com/uzairfarooq/arrive/issues) section. Before creating a new issue, search the existing ones to make sure that you're not creating a duplicate. When reporting a bug, be sure to include OS/browser version and steps/code to reproduce the bug, a [JSFiddle](http://jsfiddle.net/) would be great. 129 | 130 | #### Development 131 | If you want to contribute to arrive, here is the workflow you should use: 132 | 133 | 1. Fork the repository. 134 | 2. Clone the forked repository locally. 135 | 3. From the `dev` branch, create and checkout a new feature branch to work upon. (If you want to work on some minor bug fix, you can skip this step and continue to work in `dev` branch) 136 | 4. Make your changes in that branch (the actual source file is `/src/arrive.js`). 137 | 5. If sensible, add some jasmine tests in `/tests/spec/arriveSpec.js` file. 138 | 6. Make sure there are no regressions by executing the unit tests by opening the file `/tests/SpecRunner.html` in a browser. There is a button 'Run tests without jQuery' at the top left of th page, click that button to make sure that the tests passes without jQuery. Run the test cases in all major browsers. 139 | 7. Push the changes to your github repository. 140 | 8. Submit a pull request from your repo back to the original repository. 141 | 9. Once it is accepted, remember to pull those changes back into your develop branch! 142 | 143 | 144 | **Keywords** 145 | 146 | javascript, js, jquery, node.js, watch, listen, creation, dynamically, removal, new, elements, DOM, dynamic, detect, insertions, event, bind, live, livequery 147 | -------------------------------------------------------------------------------- /js/arrive/tests/lib/jasmine-2.0.0/jasmine-html.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2008-2013 Pivotal Labs 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining 5 | a copy of this software and associated documentation files (the 6 | "Software"), to deal in the Software without restriction, including 7 | without limitation the rights to use, copy, modify, merge, publish, 8 | distribute, sublicense, and/or sell copies of the Software, and to 9 | permit persons to whom the Software is furnished to do so, subject to 10 | the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 19 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 20 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 21 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | */ 23 | jasmineRequire.html = function(j$) { 24 | j$.ResultsNode = jasmineRequire.ResultsNode(); 25 | j$.HtmlReporter = jasmineRequire.HtmlReporter(j$); 26 | j$.QueryString = jasmineRequire.QueryString(); 27 | j$.HtmlSpecFilter = jasmineRequire.HtmlSpecFilter(); 28 | }; 29 | 30 | jasmineRequire.HtmlReporter = function(j$) { 31 | 32 | var noopTimer = { 33 | start: function() {}, 34 | elapsed: function() { return 0; } 35 | }; 36 | 37 | function HtmlReporter(options) { 38 | var env = options.env || {}, 39 | getContainer = options.getContainer, 40 | createElement = options.createElement, 41 | createTextNode = options.createTextNode, 42 | onRaiseExceptionsClick = options.onRaiseExceptionsClick || function() {}, 43 | timer = options.timer || noopTimer, 44 | results = [], 45 | specsExecuted = 0, 46 | failureCount = 0, 47 | pendingSpecCount = 0, 48 | htmlReporterMain, 49 | symbols; 50 | 51 | this.initialize = function() { 52 | htmlReporterMain = createDom("div", {className: "html-reporter"}, 53 | createDom("div", {className: "banner"}, 54 | createDom("span", {className: "title"}, "Jasmine"), 55 | createDom("span", {className: "version"}, j$.version) 56 | ), 57 | createDom("ul", {className: "symbol-summary"}), 58 | createDom("div", {className: "alert"}), 59 | createDom("div", {className: "results"}, 60 | createDom("div", {className: "failures"}) 61 | ) 62 | ); 63 | getContainer().appendChild(htmlReporterMain); 64 | 65 | symbols = find(".symbol-summary"); 66 | }; 67 | 68 | var totalSpecsDefined; 69 | this.jasmineStarted = function(options) { 70 | totalSpecsDefined = options.totalSpecsDefined || 0; 71 | timer.start(); 72 | }; 73 | 74 | var summary = createDom("div", {className: "summary"}); 75 | 76 | var topResults = new j$.ResultsNode({}, "", null), 77 | currentParent = topResults; 78 | 79 | this.suiteStarted = function(result) { 80 | currentParent.addChild(result, "suite"); 81 | currentParent = currentParent.last(); 82 | }; 83 | 84 | this.suiteDone = function(result) { 85 | if (currentParent == topResults) { 86 | return; 87 | } 88 | 89 | currentParent = currentParent.parent; 90 | }; 91 | 92 | this.specStarted = function(result) { 93 | currentParent.addChild(result, "spec"); 94 | }; 95 | 96 | var failures = []; 97 | this.specDone = function(result) { 98 | if (result.status != "disabled") { 99 | specsExecuted++; 100 | } 101 | 102 | symbols.appendChild(createDom("li", { 103 | className: result.status, 104 | id: "spec_" + result.id, 105 | title: result.fullName 106 | } 107 | )); 108 | 109 | if (result.status == "failed") { 110 | failureCount++; 111 | 112 | var failure = 113 | createDom("div", {className: "spec-detail failed"}, 114 | createDom("div", {className: "description"}, 115 | createDom("a", {title: result.fullName, href: specHref(result)}, result.fullName) 116 | ), 117 | createDom("div", {className: "messages"}) 118 | ); 119 | var messages = failure.childNodes[1]; 120 | 121 | for (var i = 0; i < result.failedExpectations.length; i++) { 122 | var expectation = result.failedExpectations[i]; 123 | messages.appendChild(createDom("div", {className: "result-message"}, expectation.message)); 124 | messages.appendChild(createDom("div", {className: "stack-trace"}, expectation.stack)); 125 | } 126 | 127 | failures.push(failure); 128 | } 129 | 130 | if (result.status == "pending") { 131 | pendingSpecCount++; 132 | } 133 | }; 134 | 135 | this.jasmineDone = function() { 136 | var banner = find(".banner"); 137 | banner.appendChild(createDom("span", {className: "duration"}, "finished in " + timer.elapsed() / 1000 + "s")); 138 | 139 | var alert = find(".alert"); 140 | 141 | alert.appendChild(createDom("span", { className: "exceptions" }, 142 | createDom("label", { className: "label", 'for': "raise-exceptions" }, "raise exceptions"), 143 | createDom("input", { 144 | className: "raise", 145 | id: "raise-exceptions", 146 | type: "checkbox" 147 | }) 148 | )); 149 | var checkbox = find("input"); 150 | 151 | checkbox.checked = !env.catchingExceptions(); 152 | checkbox.onclick = onRaiseExceptionsClick; 153 | 154 | if (specsExecuted < totalSpecsDefined) { 155 | var skippedMessage = "Ran " + specsExecuted + " of " + totalSpecsDefined + " specs - run all"; 156 | alert.appendChild( 157 | createDom("span", {className: "bar skipped"}, 158 | createDom("a", {href: "?", title: "Run all specs"}, skippedMessage) 159 | ) 160 | ); 161 | } 162 | var statusBarMessage = "" + pluralize("spec", specsExecuted) + ", " + pluralize("failure", failureCount); 163 | if (pendingSpecCount) { statusBarMessage += ", " + pluralize("pending spec", pendingSpecCount); } 164 | 165 | var statusBarClassName = "bar " + ((failureCount > 0) ? "failed" : "passed"); 166 | alert.appendChild(createDom("span", {className: statusBarClassName}, statusBarMessage)); 167 | 168 | var results = find(".results"); 169 | results.appendChild(summary); 170 | 171 | summaryList(topResults, summary); 172 | 173 | function summaryList(resultsTree, domParent) { 174 | var specListNode; 175 | for (var i = 0; i < resultsTree.children.length; i++) { 176 | var resultNode = resultsTree.children[i]; 177 | if (resultNode.type == "suite") { 178 | var suiteListNode = createDom("ul", {className: "suite", id: "suite-" + resultNode.result.id}, 179 | createDom("li", {className: "suite-detail"}, 180 | createDom("a", {href: specHref(resultNode.result)}, resultNode.result.description) 181 | ) 182 | ); 183 | 184 | summaryList(resultNode, suiteListNode); 185 | domParent.appendChild(suiteListNode); 186 | } 187 | if (resultNode.type == "spec") { 188 | if (domParent.getAttribute("class") != "specs") { 189 | specListNode = createDom("ul", {className: "specs"}); 190 | domParent.appendChild(specListNode); 191 | } 192 | specListNode.appendChild( 193 | createDom("li", { 194 | className: resultNode.result.status, 195 | id: "spec-" + resultNode.result.id 196 | }, 197 | createDom("a", {href: specHref(resultNode.result)}, resultNode.result.description) 198 | ) 199 | ); 200 | } 201 | } 202 | } 203 | 204 | if (failures.length) { 205 | alert.appendChild( 206 | createDom('span', {className: "menu bar spec-list"}, 207 | createDom("span", {}, "Spec List | "), 208 | createDom('a', {className: "failures-menu", href: "#"}, "Failures"))); 209 | alert.appendChild( 210 | createDom('span', {className: "menu bar failure-list"}, 211 | createDom('a', {className: "spec-list-menu", href: "#"}, "Spec List"), 212 | createDom("span", {}, " | Failures "))); 213 | 214 | find(".failures-menu").onclick = function() { 215 | setMenuModeTo('failure-list'); 216 | }; 217 | find(".spec-list-menu").onclick = function() { 218 | setMenuModeTo('spec-list'); 219 | }; 220 | 221 | setMenuModeTo('failure-list'); 222 | 223 | var failureNode = find(".failures"); 224 | for (var i = 0; i < failures.length; i++) { 225 | failureNode.appendChild(failures[i]); 226 | } 227 | } 228 | }; 229 | 230 | return this; 231 | 232 | function find(selector) { 233 | return getContainer().querySelector(selector); 234 | } 235 | 236 | function createDom(type, attrs, childrenVarArgs) { 237 | var el = createElement(type); 238 | 239 | for (var i = 2; i < arguments.length; i++) { 240 | var child = arguments[i]; 241 | 242 | if (typeof child === 'string') { 243 | el.appendChild(createTextNode(child)); 244 | } else { 245 | if (child) { 246 | el.appendChild(child); 247 | } 248 | } 249 | } 250 | 251 | for (var attr in attrs) { 252 | if (attr == "className") { 253 | el[attr] = attrs[attr]; 254 | } else { 255 | el.setAttribute(attr, attrs[attr]); 256 | } 257 | } 258 | 259 | return el; 260 | } 261 | 262 | function pluralize(singular, count) { 263 | var word = (count == 1 ? singular : singular + "s"); 264 | 265 | return "" + count + " " + word; 266 | } 267 | 268 | function specHref(result) { 269 | return "?spec=" + encodeURIComponent(result.fullName); 270 | } 271 | 272 | function setMenuModeTo(mode) { 273 | htmlReporterMain.setAttribute("class", "html-reporter " + mode); 274 | } 275 | } 276 | 277 | return HtmlReporter; 278 | }; 279 | 280 | jasmineRequire.HtmlSpecFilter = function() { 281 | function HtmlSpecFilter(options) { 282 | var filterString = options && options.filterString() && options.filterString().replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&"); 283 | var filterPattern = new RegExp(filterString); 284 | 285 | this.matches = function(specName) { 286 | return filterPattern.test(specName); 287 | }; 288 | } 289 | 290 | return HtmlSpecFilter; 291 | }; 292 | 293 | jasmineRequire.ResultsNode = function() { 294 | function ResultsNode(result, type, parent) { 295 | this.result = result; 296 | this.type = type; 297 | this.parent = parent; 298 | 299 | this.children = []; 300 | 301 | this.addChild = function(result, type) { 302 | this.children.push(new ResultsNode(result, type, this)); 303 | }; 304 | 305 | this.last = function() { 306 | return this.children[this.children.length - 1]; 307 | }; 308 | } 309 | 310 | return ResultsNode; 311 | }; 312 | 313 | jasmineRequire.QueryString = function() { 314 | function QueryString(options) { 315 | 316 | this.setParam = function(key, value) { 317 | var paramMap = queryStringToParamMap(); 318 | paramMap[key] = value; 319 | options.getWindowLocation().search = toQueryString(paramMap); 320 | }; 321 | 322 | this.getParam = function(key) { 323 | return queryStringToParamMap()[key]; 324 | }; 325 | 326 | return this; 327 | 328 | function toQueryString(paramMap) { 329 | var qStrPairs = []; 330 | for (var prop in paramMap) { 331 | qStrPairs.push(encodeURIComponent(prop) + "=" + encodeURIComponent(paramMap[prop])); 332 | } 333 | return "?" + qStrPairs.join('&'); 334 | } 335 | 336 | function queryStringToParamMap() { 337 | var paramStr = options.getWindowLocation().search.substring(1), 338 | params = [], 339 | paramMap = {}; 340 | 341 | if (paramStr.length > 0) { 342 | params = paramStr.split('&'); 343 | for (var i = 0; i < params.length; i++) { 344 | var p = params[i].split('='); 345 | var value = decodeURIComponent(p[1]); 346 | if (value === "true" || value === "false") { 347 | value = JSON.parse(value); 348 | } 349 | paramMap[decodeURIComponent(p[0])] = value; 350 | } 351 | } 352 | 353 | return paramMap; 354 | } 355 | 356 | } 357 | 358 | return QueryString; 359 | }; 360 | -------------------------------------------------------------------------------- /js/arrive/tests/spec/arriveSpec.js: -------------------------------------------------------------------------------- 1 | jasmine.DEFAULT_TIMEOUT_INTERVAL = 500; 2 | 3 | describe("Arrive", function() { 4 | 5 | beforeEach(function() { 6 | // clear all binded events before running each test case 7 | Arrive.unbindAllArrive(); 8 | Arrive.unbindAllLeave(); 9 | }); 10 | 11 | describe("Arrive Event Tests", function() { 12 | 13 | describe("Binding events to different element types:", function() { 14 | var selector = ".test-elem"; 15 | 16 | it("event should be fired when 'arrive' event is binded to window", function(done) { 17 | var $appendedElem = $("
"); 18 | 19 | j(window).arrive(selector, function() { 20 | expect(this).toBe($appendedElem[0]); 21 | done(); 22 | }); 23 | $("body").append($appendedElem); 24 | }); 25 | }); 26 | 27 | describe("Selector involving single element:", function() { 28 | var selector = ".test-elem"; 29 | 30 | it("event should be fired when element with specified class is injected to DOM", function(done) { 31 | var $appendedElem = $("
"); 32 | j(document).arrive(selector, function() { 33 | expect(this).toBe($appendedElem[0]); 34 | done(); 35 | }); 36 | $("body").append($appendedElem); 37 | }); 38 | }); 39 | 40 | describe("Selector involving nested elements: div.container1 .container2 .btn.red", function() { 41 | var selector = "div.container1 .container2 .btn.red"; 42 | $("body").append("
"); 43 | 44 | it("event should be fired when a tree is inserted and it contains an element which satisfy the selector", function(done) { 45 | var $appendedElem = $("
"), 46 | $redBtn = $appendedElem.find(".btn.red"); 47 | 48 | j(document).arrive(selector, function() { 49 | expect(this).toBe($redBtn[0]); 50 | done(); 51 | }); 52 | 53 | $("body .container1").append($appendedElem); 54 | }); 55 | 56 | it("event should be fired when target element is directly injected in DOM", function(done) { 57 | $("body .container1").children().remove(); 58 | 59 | var $redBtn = $(""); 60 | 61 | j(document).arrive(selector, function() { 62 | expect(this).toBe($redBtn[0]); 63 | done(); 64 | }); 65 | 66 | $("body .container1").append($("
")); 67 | $("body .container1 .container2").append($redBtn); 68 | }); 69 | 70 | }); 71 | 72 | describe("Arrive event on attribute modification of an element.", function() { 73 | var $elem = $("
"), 74 | $btn = $elem.find(".btn"); 75 | 76 | $("body").append($elem); 77 | 78 | it("Event should be fired when a class is added to an element and the element starts to satisfies event selector", function(done) { 79 | j(document).arrive(".container5 .btn.red", { fireOnAttributesModification: true }, function() { 80 | expect(this).toBe($btn[0]); 81 | done(); 82 | }); 83 | $btn.addClass("red"); 84 | }); 85 | 86 | it("Event should be fired when tooltip is added to an element and the element starts to satisfies event selector", function(done) { 87 | j(document).arrive(".container5 .btn[title='it works!']", { fireOnAttributesModification: true } , function() { 88 | expect(this).toBe($btn[0]); 89 | done(); 90 | }); 91 | $btn.attr("title", "it works!"); 92 | }); 93 | 94 | it("Event should be not fired when a class is added to an element and the element starts to satisfies event selector but fireOnAttributesModification option is false", function(done) { 95 | var eventFired = false; 96 | j(document).arrive(".container5 .btn.red", function() { 97 | eventFired = true; 98 | }); 99 | $btn.addClass("red"); 100 | 101 | setTimeout(function() { 102 | expect(eventFired).not.toBeTruthy(); 103 | done(); 104 | }, 400); 105 | }); 106 | 107 | it("Event should be not fired when tooltip is added to an element and the element starts to satisfies event selector but fireOnAttributesModification option is false", function(done) { 108 | var eventFired = false; 109 | j(document).arrive(".container5 .btn[title='it works!']", function() { 110 | eventFired = true; 111 | }); 112 | $btn.attr("title", "it works!"); 113 | 114 | setTimeout(function() { 115 | expect(eventFired).not.toBeTruthy(); 116 | done(); 117 | }, 400); 118 | }); 119 | }); 120 | 121 | describe("Event unbinding tests", function() { 122 | var eventFired, 123 | selector = ".test-elem", 124 | callback = function() { 125 | eventFired = true; 126 | }; 127 | 128 | 129 | beforeEach(function() { 130 | eventFired = false; 131 | j(document).arrive(selector, callback); 132 | }); 133 | 134 | it("arrive event should not be fired when unbind is called", function(done) { 135 | j(document).unbindArrive(); 136 | $("body").append($("
")); 137 | 138 | setTimeout(function() { 139 | expect(eventFired).not.toBeTruthy(); 140 | done(); 141 | }, 400); 142 | }); 143 | 144 | it("arrive event should not be fired when unbind is called with selector as an argument", function(done) { 145 | j(document).unbindArrive(selector); 146 | $("body").append($("
")); 147 | 148 | setTimeout(function() { 149 | expect(eventFired).not.toBeTruthy(); 150 | done(); 151 | }, 400); 152 | }); 153 | 154 | it("arrive event should not be fired when unbind is called with callback as an argument", function(done) { 155 | j(document).unbindArrive(callback); 156 | $("body").append($("
")); 157 | 158 | setTimeout(function() { 159 | expect(eventFired).not.toBeTruthy(); 160 | done(); 161 | }, 400); 162 | }); 163 | 164 | it("arrive event should not be fired when unbind is called with selector and callback as arguments", function(done) { 165 | j(document).unbindArrive(selector, callback); 166 | $("body").append($("
")); 167 | 168 | setTimeout(function() { 169 | expect(eventFired).not.toBeTruthy(); 170 | done(); 171 | }, 400); 172 | }); 173 | 174 | it("arrive event should not be fired once unbind is called from within arrive callback", function(done) { 175 | 176 | var callbackCount = 0; 177 | j(document).arrive(".some-class", { existing: true }, function () { 178 | document.unbindArrive(".some-class"); 179 | callbackCount++; 180 | }); 181 | 182 | $("body").append($("
")); 183 | $("body").append($("
")); 184 | 185 | setTimeout(function() { 186 | expect(callbackCount).toBe(1); 187 | done(); 188 | }, 400); 189 | }); 190 | }); 191 | 192 | describe("Multiple events tests.", function() { 193 | var selector = ".test-elem", 194 | appendedElem = "
"; 195 | 196 | it("Callback should be called multiple times when multiple elements are injected", function(done) { 197 | var callCount = 0; 198 | 199 | j(document).arrive(selector, function() { 200 | callCount += 1; 201 | 202 | if (callCount >= 2) { 203 | expect(true).toBe(true); 204 | done(); 205 | } 206 | }); 207 | 208 | $("body").append(appendedElem); 209 | $("body").append(appendedElem); 210 | }); 211 | 212 | it("onceOnly argument should result in callback being called only once", function(done) { 213 | var callCount = 0; 214 | 215 | j(document).arrive(selector, {onceOnly: true}, function() { 216 | callCount += 1; 217 | }); 218 | 219 | $("body").append(appendedElem); 220 | $("body").append(appendedElem); 221 | 222 | setTimeout(function() { 223 | expect(callCount).toBe(1); 224 | done(); 225 | }, 400); 226 | 227 | }); 228 | }); 229 | 230 | describe("options.existing", function() { 231 | var selector = ".test-existing"; 232 | 233 | beforeEach(function() { 234 | $(".test-existing").remove(); 235 | }); 236 | 237 | it("callback should be called for existing element if options.existing is true", function(done) { 238 | var $existingElementA = $("
"); 239 | var $existingElementB = $existingElementA.clone(); 240 | $("body").append($existingElementA).append($existingElementB); 241 | 242 | var count = 0; 243 | j(document).arrive(selector, { existing: true }, function() { 244 | expect(this).toBe(!count ? $existingElementA[0] : $existingElementB[0]); 245 | if ((count += 1) === 2) { 246 | done(); 247 | } 248 | }); 249 | }); 250 | 251 | it("callback should be called for one existing element only if options.existing and options.onceOnly both are true", function(done) { 252 | var $existingElementA = $("
"); 253 | var $existingElementB = $existingElementA.clone(); 254 | $("body").append($existingElementA).append($existingElementB); 255 | 256 | j(document).arrive(selector, { existing: true, onceOnly: true }, function() { 257 | expect(this).toBe($existingElementA[0]); 258 | done(); 259 | }); 260 | }); 261 | }); 262 | }); 263 | 264 | describe("Leave Event Tests", function() { 265 | var selector = ".test-elem"; 266 | 267 | it("event should be fired when element with specified class is removed from DOM", function(done) { 268 | $(selector).remove(); // remove any previous test element in DOM 269 | 270 | var $toBeRemoved = $("
"), 271 | $testElem = $toBeRemoved.find(".test-elem"); 272 | $("body").append($toBeRemoved); 273 | 274 | j(document).leave(selector, function() { 275 | expect(this).toBe($testElem[0]); 276 | done(); 277 | }); 278 | 279 | $(selector).remove(); 280 | }); 281 | 282 | describe("Selector involving nested elements: div.container1 .container2 .btn.red", function() { 283 | var selector = ".btn.red", 284 | $redBtn = null; 285 | 286 | beforeEach(function() { 287 | $(".container1,.container5").remove(); 288 | var $container1 = $("
"); 289 | $redBtn = $container1.find(".btn.red"); 290 | $("body").append($container1); 291 | }); 292 | 293 | it("event should be fired when a tree is removed and it contains an element which satisfy the selector", function(done) { 294 | j(document).leave(selector, function() { 295 | expect(this).toBe($redBtn[0]); 296 | done(); 297 | }); 298 | $(".container2").remove(); 299 | }); 300 | 301 | it("event should be fired when target element is directly removed from DOM", function(done) { 302 | j(document).leave(selector, function() { 303 | expect(this).toBe($redBtn[0]); 304 | done(); 305 | }); 306 | $(".btn.red").remove(); 307 | }); 308 | 309 | }); 310 | 311 | describe("Calling arrive function on NodeList and HTMLElement", function() { 312 | it("arrive function should be callable on NodeList", function() { 313 | document.getElementsByTagName("body").arrive(".test", function() {}); 314 | expect(true).toBeTruthy(); 315 | }); 316 | 317 | it("arrive function should be callable on HTMLElement", function() { 318 | document.getElementsByTagName("body")[0].arrive(".test", function() {}); 319 | expect(true).toBeTruthy(); 320 | }); 321 | }); 322 | }); 323 | 324 | describe("ES2015 arrow function support", function() { 325 | var selector = ".test-elem"; 326 | it("Make sure the first argument equals `this` object", function(done) { 327 | var $appendedElem = $("
"); 328 | 329 | j(document).arrive(selector, function(elem) { 330 | expect(this).toBe(elem); 331 | done(); 332 | }); 333 | $("body").append($appendedElem); 334 | }); 335 | 336 | it("Make sure the first argument equals `this` object with `options.onceOnly` and `options.existing`", function(done) { 337 | j(document).arrive(selector, {onceOnly: true, existing: true}, function(elem) { 338 | expect(this).toBe(elem); 339 | done(); 340 | $(selector).remove(); 341 | }); 342 | }); 343 | }); 344 | }); 345 | -------------------------------------------------------------------------------- /js/sortablejs/Sortable.min.js: -------------------------------------------------------------------------------- 1 | /*! Sortable 1.7.0 - MIT | git://github.com/rubaxa/Sortable.git */ 2 | !function(a){"use strict";"function"==typeof define&&define.amd?define(a):"undefined"!=typeof module&&"undefined"!=typeof module.exports?module.exports=a():window.Sortable=a()}(function(){"use strict";function a(b,c){if(!b||!b.nodeType||1!==b.nodeType)throw"Sortable: `el` must be HTMLElement, and not "+{}.toString.call(b);this.el=b,this.options=c=t({},c),b[V]=this;var d={group:Math.random(),sort:!0,disabled:!1,store:null,handle:null,scroll:!0,scrollSensitivity:30,scrollSpeed:10,draggable:/[uo]l/i.test(b.nodeName)?"li":">*",ghostClass:"sortable-ghost",chosenClass:"sortable-chosen",dragClass:"sortable-drag",ignore:"a, img",filter:null,preventOnFilter:!0,animation:0,setData:function(a,b){a.setData("Text",b.textContent)},dropBubble:!1,dragoverBubble:!1,dataIdAttr:"data-id",delay:0,forceFallback:!1,fallbackClass:"sortable-fallback",fallbackOnBody:!1,fallbackTolerance:0,fallbackOffset:{x:0,y:0},supportPointer:a.supportPointer!==!1};for(var e in d)!(e in c)&&(c[e]=d[e]);ka(c);for(var g in this)"_"===g.charAt(0)&&"function"==typeof this[g]&&(this[g]=this[g].bind(this));this.nativeDraggable=!c.forceFallback&&ca,f(b,"mousedown",this._onTapStart),f(b,"touchstart",this._onTapStart),c.supportPointer&&f(b,"pointerdown",this._onTapStart),this.nativeDraggable&&(f(b,"dragover",this),f(b,"dragenter",this)),ia.push(this._onDragOver),c.store&&this.sort(c.store.get(this))}function b(a,b){"clone"!==a.lastPullMode&&(b=!0),B&&B.state!==b&&(i(B,"display",b?"none":""),b||B.state&&(a.options.group.revertClone?(C.insertBefore(B,D),a._animate(y,B)):C.insertBefore(B,y)),B.state=b)}function c(a,b,c){if(a){c=c||X;do if(">*"===b&&a.parentNode===c||r(a,b))return a;while(a=d(a))}return null}function d(a){var b=a.host;return b&&b.nodeType?b:a.parentNode}function e(a){a.dataTransfer&&(a.dataTransfer.dropEffect="move"),a.preventDefault()}function f(a,b,c){a.addEventListener(b,c,aa)}function g(a,b,c){a.removeEventListener(b,c,aa)}function h(a,b,c){if(a)if(a.classList)a.classList[c?"add":"remove"](b);else{var d=(" "+a.className+" ").replace(T," ").replace(" "+b+" "," ");a.className=(d+(c?" "+b:"")).replace(T," ")}}function i(a,b,c){var d=a&&a.style;if(d){if(void 0===c)return X.defaultView&&X.defaultView.getComputedStyle?c=X.defaultView.getComputedStyle(a,""):a.currentStyle&&(c=a.currentStyle),void 0===b?c:c[b];b in d||(b="-webkit-"+b),d[b]=c+("string"==typeof c?"":"px")}}function j(a,b,c){if(a){var d=a.getElementsByTagName(b),e=0,f=d.length;if(c)for(;e5||b.clientX-(d.left+d.width)>5}function p(a){for(var b=a.tagName+a.className+a.src+a.href+a.textContent,c=b.length,d=0;c--;)d+=b.charCodeAt(c);return d.toString(36)}function q(a,b){var c=0;if(!a||!a.parentNode)return-1;for(;a&&(a=a.previousElementSibling);)"TEMPLATE"===a.nodeName.toUpperCase()||">*"!==b&&!r(a,b)||c++;return c}function r(a,b){if(a){b=b.split(".");var c=b.shift().toUpperCase(),d=new RegExp("\\s("+b.join("|")+")(?=\\s)","g");return!(""!==c&&a.nodeName.toUpperCase()!=c||b.length&&((" "+a.className+" ").match(d)||[]).length!=b.length)}return!1}function s(a,b){var c,d;return function(){void 0===c&&(c=arguments,d=this,Z(function(){1===c.length?a.call(d,c[0]):a.apply(d,c),c=void 0},b))}}function t(a,b){if(a&&b)for(var c in b)b.hasOwnProperty(c)&&(a[c]=b[c]);return a}function u(a){return _&&_.dom?_.dom(a).cloneNode(!0):$?$(a).clone(!0)[0]:a.cloneNode(!0)}function v(a){for(var b=a.getElementsByTagName("input"),c=b.length;c--;){var d=b[c];d.checked&&ha.push(d)}}function w(a){return Z(a,0)}function x(a){return clearTimeout(a)}if("undefined"==typeof window||!window.document)return function(){throw new Error("Sortable.js requires a window with a document")};var y,z,A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S={},T=/\s+/g,U=/left|right|inline/,V="Sortable"+(new Date).getTime(),W=window,X=W.document,Y=W.parseInt,Z=W.setTimeout,$=W.jQuery||W.Zepto,_=W.Polymer,aa=!1,ba=!1,ca="draggable"in X.createElement("div"),da=function(a){return!navigator.userAgent.match(/(?:Trident.*rv[ :]?11\.|msie)/i)&&(a=X.createElement("x"),a.style.cssText="pointer-events:auto","auto"===a.style.pointerEvents)}(),ea=!1,fa=Math.abs,ga=Math.min,ha=[],ia=[],ja=s(function(a,b,c){if(c&&b.scroll){var d,e,f,g,h,i,j=c[V],k=b.scrollSensitivity,l=b.scrollSpeed,m=a.clientX,n=a.clientY,o=window.innerWidth,p=window.innerHeight;if(G!==c&&(F=b.scroll,G=c,H=b.scrollFn,F===!0)){F=c;do if(F.offsetWidth-1:e==a)}}var c={},d=a.group;d&&"object"==typeof d||(d={name:d}),c.name=d.name,c.checkPull=b(d.pull,!0),c.checkPut=b(d.put),c.revertClone=d.revertClone,a.group=c};try{window.addEventListener("test",null,Object.defineProperty({},"passive",{get:function(){ba=!1,aa={capture:!1,passive:ba}}}))}catch(a){}return a.prototype={constructor:a,_onTapStart:function(a){var b,d=this,e=this.el,f=this.options,g=f.preventOnFilter,h=a.type,i=a.touches&&a.touches[0],j=(i||a).target,l=a.target.shadowRoot&&a.path&&a.path[0]||j,m=f.filter;if(v(e),!y&&!(/mousedown|pointerdown/.test(h)&&0!==a.button||f.disabled)&&!l.isContentEditable&&(j=c(j,f.draggable,e),j&&E!==j)){if(b=q(j,f.draggable),"function"==typeof m){if(m.call(this,a,j,this))return k(d,l,"filter",j,e,e,b),void(g&&a.preventDefault())}else if(m&&(m=m.split(",").some(function(a){if(a=c(l,a.trim(),e))return k(d,a,"filter",j,e,e,b),!0})))return void(g&&a.preventDefault());f.handle&&!c(l,f.handle,e)||this._prepareDragStart(a,i,j,b)}},_prepareDragStart:function(a,b,c,d){var e,g=this,i=g.el,l=g.options,n=i.ownerDocument;c&&!y&&c.parentNode===i&&(P=a,C=i,y=c,z=y.parentNode,D=y.nextSibling,E=c,N=l.group,L=d,this._lastX=(b||a).clientX,this._lastY=(b||a).clientY,y.style["will-change"]="all",e=function(){g._disableDelayedDrag(),y.draggable=g.nativeDraggable,h(y,l.chosenClass,!0),g._triggerDragStart(a,b),k(g,C,"choose",y,C,C,L)},l.ignore.split(",").forEach(function(a){j(y,a.trim(),m)}),f(n,"mouseup",g._onDrop),f(n,"touchend",g._onDrop),f(n,"touchcancel",g._onDrop),f(n,"selectstart",g),l.supportPointer&&f(n,"pointercancel",g._onDrop),l.delay?(f(n,"mouseup",g._disableDelayedDrag),f(n,"touchend",g._disableDelayedDrag),f(n,"touchcancel",g._disableDelayedDrag),f(n,"mousemove",g._disableDelayedDrag),f(n,"touchmove",g._disableDelayedDrag),l.supportPointer&&f(n,"pointermove",g._disableDelayedDrag),g._dragStartTimer=Z(e,l.delay)):e())},_disableDelayedDrag:function(){var a=this.el.ownerDocument;clearTimeout(this._dragStartTimer),g(a,"mouseup",this._disableDelayedDrag),g(a,"touchend",this._disableDelayedDrag),g(a,"touchcancel",this._disableDelayedDrag),g(a,"mousemove",this._disableDelayedDrag),g(a,"touchmove",this._disableDelayedDrag),g(a,"pointermove",this._disableDelayedDrag)},_triggerDragStart:function(a,b){b=b||("touch"==a.pointerType?a:null),b?(P={target:y,clientX:b.clientX,clientY:b.clientY},this._onDragStart(P,"touch")):this.nativeDraggable?(f(y,"dragend",this),f(C,"dragstart",this._onDragStart)):this._onDragStart(P,!0);try{X.selection?w(function(){X.selection.empty()}):window.getSelection().removeAllRanges()}catch(a){}},_dragStarted:function(){if(C&&y){var b=this.options;h(y,b.ghostClass,!0),h(y,b.dragClass,!1),a.active=this,k(this,C,"start",y,C,C,L)}else this._nulling()},_emulateDragOver:function(){if(Q){if(this._lastX===Q.clientX&&this._lastY===Q.clientY)return;this._lastX=Q.clientX,this._lastY=Q.clientY,da||i(A,"display","none");var a=X.elementFromPoint(Q.clientX,Q.clientY),b=a,c=ia.length;if(a&&a.shadowRoot&&(a=a.shadowRoot.elementFromPoint(Q.clientX,Q.clientY),b=a),b)do{if(b[V]){for(;c--;)ia[c]({clientX:Q.clientX,clientY:Q.clientY,target:a,rootEl:b});break}a=b}while(b=b.parentNode);da||i(A,"display","")}},_onTouchMove:function(b){if(P){var c=this.options,d=c.fallbackTolerance,e=c.fallbackOffset,f=b.touches?b.touches[0]:b,g=f.clientX-P.clientX+e.x,h=f.clientY-P.clientY+e.y,j=b.touches?"translate3d("+g+"px,"+h+"px,0)":"translate("+g+"px,"+h+"px)";if(!a.active){if(d&&ga(fa(f.clientX-this._lastX),fa(f.clientY-this._lastY))y.offsetWidth,x=e.offsetHeight>y.offsetHeight,E=(v?(d.clientX-g.left)/t:(d.clientY-g.top)/u)>.5,F=e.nextElementSibling,G=!1;if(v){var H=y.offsetTop,L=e.offsetTop;G=H===L?e.previousElementSibling===y&&!w||E&&w:e.previousElementSibling===y||y.previousElementSibling===e?(d.clientY-g.top)/u>.5:L>H}else r||(G=F!==y&&!x||E&&x);var M=l(C,j,y,f,e,g,d,G);M!==!1&&(1!==M&&M!==-1||(G=1===M),ea=!0,Z(n,30),b(p,q),y.contains(j)||(G&&!F?j.appendChild(y):e.parentNode.insertBefore(y,G?F:e)),z=y.parentNode,this._animate(f,y),this._animate(g,e))}}},_animate:function(a,b){var c=this.options.animation;if(c){var d=b.getBoundingClientRect();1===a.nodeType&&(a=a.getBoundingClientRect()),i(b,"transition","none"),i(b,"transform","translate3d("+(a.left-d.left)+"px,"+(a.top-d.top)+"px,0)"),b.offsetWidth,i(b,"transition","all "+c+"ms"),i(b,"transform","translate3d(0,0,0)"),clearTimeout(b.animated),b.animated=Z(function(){i(b,"transition",""),i(b,"transform",""),b.animated=!1},c)}},_offUpEvents:function(){var a=this.el.ownerDocument;g(X,"touchmove",this._onTouchMove),g(X,"pointermove",this._onTouchMove),g(a,"mouseup",this._onDrop),g(a,"touchend",this._onDrop),g(a,"pointerup",this._onDrop),g(a,"touchcancel",this._onDrop),g(a,"pointercancel",this._onDrop),g(a,"selectstart",this)},_onDrop:function(b){var c=this.el,d=this.options;clearInterval(this._loopId),clearInterval(S.pid),clearTimeout(this._dragStartTimer),x(this._cloneId),x(this._dragStartId),g(X,"mouseover",this),g(X,"mousemove",this._onTouchMove),this.nativeDraggable&&(g(X,"drop",this),g(c,"dragstart",this._onDragStart)),this._offUpEvents(),b&&(R&&(b.preventDefault(),!d.dropBubble&&b.stopPropagation()),A&&A.parentNode&&A.parentNode.removeChild(A),C!==z&&"clone"===a.active.lastPullMode||B&&B.parentNode&&B.parentNode.removeChild(B),y&&(this.nativeDraggable&&g(y,"dragend",this),m(y),y.style["will-change"]="",h(y,this.options.ghostClass,!1),h(y,this.options.chosenClass,!1),k(this,C,"unchoose",y,z,C,L),C!==z?(M=q(y,d.draggable),M>=0&&(k(null,z,"add",y,z,C,L,M),k(this,C,"remove",y,z,C,L,M),k(null,z,"sort",y,z,C,L,M),k(this,C,"sort",y,z,C,L,M))):y.nextSibling!==D&&(M=q(y,d.draggable),M>=0&&(k(this,C,"update",y,z,C,L,M),k(this,C,"sort",y,z,C,L,M))),a.active&&(null!=M&&M!==-1||(M=L),k(this,C,"end",y,z,C,L,M),this.save()))),this._nulling()},_nulling:function(){C=y=z=A=D=B=E=F=G=P=Q=R=M=I=J=O=N=a.active=null,ha.forEach(function(a){a.checked=!0}),ha.length=0},handleEvent:function(a){switch(a.type){case"drop":case"dragend":this._onDrop(a);break;case"dragover":case"dragenter":y&&(this._onDragOver(a),e(a));break;case"mouseover":this._onDrop(a);break;case"selectstart":a.preventDefault()}},toArray:function(){for(var a,b=[],d=this.el.children,e=0,f=d.length,g=this.options;e 0) { 70 | utils.checkChildNodesRecursively(node.childNodes, registrationData, matchFunc, callbacksToBeCalled); 71 | } 72 | } 73 | }, 74 | mergeArrays: function(firstArr, secondArr){ 75 | // Overwrites default options with user-defined options. 76 | var options = {}, 77 | attrName; 78 | for (attrName in firstArr) { 79 | if (firstArr.hasOwnProperty(attrName)) { 80 | options[attrName] = firstArr[attrName]; 81 | } 82 | } 83 | for (attrName in secondArr) { 84 | if (secondArr.hasOwnProperty(attrName)) { 85 | options[attrName] = secondArr[attrName]; 86 | } 87 | } 88 | return options; 89 | }, 90 | toElementsArray: function (elements) { 91 | // check if object is an array (or array like object) 92 | // Note: window object has .length property but it's not array of elements so don't consider it an array 93 | if (typeof elements !== "undefined" && (typeof elements.length !== "number" || elements === window)) { 94 | elements = [elements]; 95 | } 96 | return elements; 97 | } 98 | }; 99 | })(); 100 | 101 | 102 | // Class to maintain state of all registered events of a single type 103 | var EventsBucket = (function() { 104 | var EventsBucket = function() { 105 | // holds all the events 106 | 107 | this._eventsBucket = []; 108 | // function to be called while adding an event, the function should do the event initialization/registration 109 | this._beforeAdding = null; 110 | // function to be called while removing an event, the function should do the event destruction 111 | this._beforeRemoving = null; 112 | }; 113 | 114 | EventsBucket.prototype.addEvent = function(target, selector, options, callback) { 115 | var newEvent = { 116 | target: target, 117 | selector: selector, 118 | options: options, 119 | callback: callback, 120 | firedElems: [] 121 | }; 122 | 123 | if (this._beforeAdding) { 124 | this._beforeAdding(newEvent); 125 | } 126 | 127 | this._eventsBucket.push(newEvent); 128 | return newEvent; 129 | }; 130 | 131 | EventsBucket.prototype.removeEvent = function(compareFunction) { 132 | for (var i=this._eventsBucket.length - 1, registeredEvent; (registeredEvent = this._eventsBucket[i]); i--) { 133 | if (compareFunction(registeredEvent)) { 134 | if (this._beforeRemoving) { 135 | this._beforeRemoving(registeredEvent); 136 | } 137 | 138 | // mark callback as null so that even if an event mutation was already triggered it does not call callback 139 | var removedEvents = this._eventsBucket.splice(i, 1); 140 | if (removedEvents && removedEvents.length) { 141 | removedEvents[0].callback = null; 142 | } 143 | } 144 | } 145 | }; 146 | 147 | EventsBucket.prototype.beforeAdding = function(beforeAdding) { 148 | this._beforeAdding = beforeAdding; 149 | }; 150 | 151 | EventsBucket.prototype.beforeRemoving = function(beforeRemoving) { 152 | this._beforeRemoving = beforeRemoving; 153 | }; 154 | 155 | return EventsBucket; 156 | })(); 157 | 158 | 159 | /** 160 | * @constructor 161 | * General class for binding/unbinding arrive and leave events 162 | */ 163 | var MutationEvents = function(getObserverConfig, onMutation) { 164 | var eventsBucket = new EventsBucket(), 165 | me = this; 166 | 167 | var defaultOptions = { 168 | fireOnAttributesModification: false 169 | }; 170 | 171 | // actual event registration before adding it to bucket 172 | eventsBucket.beforeAdding(function(registrationData) { 173 | var 174 | target = registrationData.target, 175 | observer; 176 | 177 | // mutation observer does not work on window or document 178 | if (target === window.document || target === window) { 179 | target = document.getElementsByTagName("html")[0]; 180 | } 181 | 182 | // Create an observer instance 183 | observer = new MutationObserver(function(e) { 184 | onMutation.call(this, e, registrationData); 185 | }); 186 | 187 | var config = getObserverConfig(registrationData.options); 188 | 189 | observer.observe(target, config); 190 | 191 | registrationData.observer = observer; 192 | registrationData.me = me; 193 | }); 194 | 195 | // cleanup/unregister before removing an event 196 | eventsBucket.beforeRemoving(function (eventData) { 197 | eventData.observer.disconnect(); 198 | }); 199 | 200 | this.bindEvent = function(selector, options, callback) { 201 | options = utils.mergeArrays(defaultOptions, options); 202 | 203 | var elements = utils.toElementsArray(this); 204 | 205 | for (var i = 0; i < elements.length; i++) { 206 | eventsBucket.addEvent(elements[i], selector, options, callback); 207 | } 208 | }; 209 | 210 | this.unbindEvent = function() { 211 | var elements = utils.toElementsArray(this); 212 | eventsBucket.removeEvent(function(eventObj) { 213 | for (var i = 0; i < elements.length; i++) { 214 | if (this === undefined || eventObj.target === elements[i]) { 215 | return true; 216 | } 217 | } 218 | return false; 219 | }); 220 | }; 221 | 222 | this.unbindEventWithSelectorOrCallback = function(selector) { 223 | var elements = utils.toElementsArray(this), 224 | callback = selector, 225 | compareFunction; 226 | 227 | if (typeof selector === "function") { 228 | compareFunction = function(eventObj) { 229 | for (var i = 0; i < elements.length; i++) { 230 | if ((this === undefined || eventObj.target === elements[i]) && eventObj.callback === callback) { 231 | return true; 232 | } 233 | } 234 | return false; 235 | }; 236 | } 237 | else { 238 | compareFunction = function(eventObj) { 239 | for (var i = 0; i < elements.length; i++) { 240 | if ((this === undefined || eventObj.target === elements[i]) && eventObj.selector === selector) { 241 | return true; 242 | } 243 | } 244 | return false; 245 | }; 246 | } 247 | eventsBucket.removeEvent(compareFunction); 248 | }; 249 | 250 | this.unbindEventWithSelectorAndCallback = function(selector, callback) { 251 | var elements = utils.toElementsArray(this); 252 | eventsBucket.removeEvent(function(eventObj) { 253 | for (var i = 0; i < elements.length; i++) { 254 | if ((this === undefined || eventObj.target === elements[i]) && eventObj.selector === selector && eventObj.callback === callback) { 255 | return true; 256 | } 257 | } 258 | return false; 259 | }); 260 | }; 261 | 262 | return this; 263 | }; 264 | 265 | 266 | /** 267 | * @constructor 268 | * Processes 'arrive' events 269 | */ 270 | var ArriveEvents = function() { 271 | // Default options for 'arrive' event 272 | var arriveDefaultOptions = { 273 | fireOnAttributesModification: false, 274 | onceOnly: false, 275 | existing: false 276 | }; 277 | 278 | function getArriveObserverConfig(options) { 279 | var config = { 280 | attributes: false, 281 | childList: true, 282 | subtree: true 283 | }; 284 | 285 | if (options.fireOnAttributesModification) { 286 | config.attributes = true; 287 | } 288 | 289 | return config; 290 | } 291 | 292 | function onArriveMutation(mutations, registrationData) { 293 | mutations.forEach(function( mutation ) { 294 | var newNodes = mutation.addedNodes, 295 | targetNode = mutation.target, 296 | callbacksToBeCalled = [], 297 | node; 298 | 299 | // If new nodes are added 300 | if( newNodes !== null && newNodes.length > 0 ) { 301 | utils.checkChildNodesRecursively(newNodes, registrationData, nodeMatchFunc, callbacksToBeCalled); 302 | } 303 | else if (mutation.type === "attributes") { 304 | if (nodeMatchFunc(targetNode, registrationData, callbacksToBeCalled)) { 305 | callbacksToBeCalled.push({ callback: registrationData.callback, elem: targetNode }); 306 | } 307 | } 308 | 309 | utils.callCallbacks(callbacksToBeCalled, registrationData); 310 | }); 311 | } 312 | 313 | function nodeMatchFunc(node, registrationData, callbacksToBeCalled) { 314 | // check a single node to see if it matches the selector 315 | if (utils.matchesSelector(node, registrationData.selector)) { 316 | if(node._id === undefined) { 317 | node._id = arriveUniqueId++; 318 | } 319 | // make sure the arrive event is not already fired for the element 320 | if (registrationData.firedElems.indexOf(node._id) == -1) { 321 | registrationData.firedElems.push(node._id); 322 | 323 | return true; 324 | } 325 | } 326 | 327 | return false; 328 | } 329 | 330 | arriveEvents = new MutationEvents(getArriveObserverConfig, onArriveMutation); 331 | 332 | var mutationBindEvent = arriveEvents.bindEvent; 333 | 334 | // override bindEvent function 335 | arriveEvents.bindEvent = function(selector, options, callback) { 336 | 337 | if (typeof callback === "undefined") { 338 | callback = options; 339 | options = arriveDefaultOptions; 340 | } else { 341 | options = utils.mergeArrays(arriveDefaultOptions, options); 342 | } 343 | 344 | var elements = utils.toElementsArray(this); 345 | 346 | if (options.existing) { 347 | var existing = []; 348 | 349 | for (var i = 0; i < elements.length; i++) { 350 | var nodes = elements[i].querySelectorAll(selector); 351 | for (var j = 0; j < nodes.length; j++) { 352 | existing.push({ callback: callback, elem: nodes[j] }); 353 | } 354 | } 355 | 356 | // no need to bind event if the callback has to be fired only once and we have already found the element 357 | if (options.onceOnly && existing.length) { 358 | return callback.call(existing[0].elem, existing[0].elem); 359 | } 360 | 361 | setTimeout(utils.callCallbacks, 1, existing); 362 | } 363 | 364 | mutationBindEvent.call(this, selector, options, callback); 365 | }; 366 | 367 | return arriveEvents; 368 | }; 369 | 370 | 371 | /** 372 | * @constructor 373 | * Processes 'leave' events 374 | */ 375 | var LeaveEvents = function() { 376 | // Default options for 'leave' event 377 | var leaveDefaultOptions = {}; 378 | 379 | function getLeaveObserverConfig() { 380 | var config = { 381 | childList: true, 382 | subtree: true 383 | }; 384 | 385 | return config; 386 | } 387 | 388 | function onLeaveMutation(mutations, registrationData) { 389 | mutations.forEach(function( mutation ) { 390 | var removedNodes = mutation.removedNodes, 391 | callbacksToBeCalled = []; 392 | 393 | if( removedNodes !== null && removedNodes.length > 0 ) { 394 | utils.checkChildNodesRecursively(removedNodes, registrationData, nodeMatchFunc, callbacksToBeCalled); 395 | } 396 | 397 | utils.callCallbacks(callbacksToBeCalled, registrationData); 398 | }); 399 | } 400 | 401 | function nodeMatchFunc(node, registrationData) { 402 | return utils.matchesSelector(node, registrationData.selector); 403 | } 404 | 405 | leaveEvents = new MutationEvents(getLeaveObserverConfig, onLeaveMutation); 406 | 407 | var mutationBindEvent = leaveEvents.bindEvent; 408 | 409 | // override bindEvent function 410 | leaveEvents.bindEvent = function(selector, options, callback) { 411 | 412 | if (typeof callback === "undefined") { 413 | callback = options; 414 | options = leaveDefaultOptions; 415 | } else { 416 | options = utils.mergeArrays(leaveDefaultOptions, options); 417 | } 418 | 419 | mutationBindEvent.call(this, selector, options, callback); 420 | }; 421 | 422 | return leaveEvents; 423 | }; 424 | 425 | 426 | var arriveEvents = new ArriveEvents(), 427 | leaveEvents = new LeaveEvents(); 428 | 429 | function exposeUnbindApi(eventObj, exposeTo, funcName) { 430 | // expose unbind function with function overriding 431 | utils.addMethod(exposeTo, funcName, eventObj.unbindEvent); 432 | utils.addMethod(exposeTo, funcName, eventObj.unbindEventWithSelectorOrCallback); 433 | utils.addMethod(exposeTo, funcName, eventObj.unbindEventWithSelectorAndCallback); 434 | } 435 | 436 | /*** expose APIs ***/ 437 | function exposeApi(exposeTo) { 438 | exposeTo.arrive = arriveEvents.bindEvent; 439 | exposeUnbindApi(arriveEvents, exposeTo, "unbindArrive"); 440 | 441 | exposeTo.leave = leaveEvents.bindEvent; 442 | exposeUnbindApi(leaveEvents, exposeTo, "unbindLeave"); 443 | } 444 | 445 | if ($) { 446 | exposeApi($.fn); 447 | } 448 | exposeApi(HTMLElement.prototype); 449 | exposeApi(NodeList.prototype); 450 | exposeApi(HTMLCollection.prototype); 451 | exposeApi(HTMLDocument.prototype); 452 | exposeApi(Window.prototype); 453 | 454 | var Arrive = {}; 455 | // expose functions to unbind all arrive/leave events 456 | exposeUnbindApi(arriveEvents, Arrive, "unbindAllArrive"); 457 | exposeUnbindApi(leaveEvents, Arrive, "unbindAllLeave"); 458 | 459 | return Arrive; 460 | 461 | })(window, typeof jQuery === 'undefined' ? null : jQuery, undefined); -------------------------------------------------------------------------------- /js/sortablejs/README.md: -------------------------------------------------------------------------------- 1 | # Sortable 2 | Sortable is a minimalist JavaScript library for reorderable drag-and-drop lists. 3 | 4 | Demo: http://rubaxa.github.io/Sortable/ 5 | 6 | 7 | ## Features 8 | 9 | * Supports touch devices and [modern](http://caniuse.com/#search=drag) browsers (including IE9) 10 | * Can drag from one list to another or within the same list 11 | * CSS animation when moving items 12 | * Supports drag handles *and selectable text* (better than voidberg's html5sortable) 13 | * Smart auto-scrolling 14 | * Built using native HTML5 drag and drop API 15 | * Supports 16 | * [Meteor](https://github.com/SortableJS/meteor-sortablejs) 17 | * AngularJS 18 | * [2.0+](https://github.com/SortableJS/angular-sortablejs) 19 | * [1.*](https://github.com/SortableJS/angular-legacy-sortablejs) 20 | * React 21 | * [ES2015+](https://github.com/SortableJS/react-sortablejs) 22 | * [Mixin](https://github.com/SortableJS/react-mixin-sortablejs) 23 | * [Knockout](https://github.com/SortableJS/knockout-sortablejs) 24 | * [Polymer](https://github.com/SortableJS/polymer-sortablejs) 25 | * [Vue](https://github.com/SortableJS/Vue.Draggable) 26 | * Supports any CSS library, e.g. [Bootstrap](#bs) 27 | * Simple API 28 | * [CDN](#cdn) 29 | * No jQuery (but there is [support](#jq)) 30 | 31 | 32 |
33 | 34 | 35 | ### Articles 36 | 37 | * [Sortable v1.0 — New capabilities](https://github.com/RubaXa/Sortable/wiki/Sortable-v1.0-—-New-capabilities/) (December 22, 2014) 38 | * [Sorting with the help of HTML5 Drag'n'Drop API](https://github.com/RubaXa/Sortable/wiki/Sorting-with-the-help-of-HTML5-Drag'n'Drop-API/) (December 23, 2013) 39 | 40 | 41 |
42 | 43 | ### Install 44 | 45 | Via npm 46 | 47 | ```bash 48 | $ npm install sortablejs --save 49 | ``` 50 | 51 | Via bower: 52 | 53 | ```bash 54 | $ bower install --save sortablejs 55 | ``` 56 | 57 |
58 | 59 | ### Usage 60 | ```html 61 |
    62 |
  • item 1
  • 63 |
  • item 2
  • 64 |
  • item 3
  • 65 |
66 | ``` 67 | 68 | ```js 69 | var el = document.getElementById('items'); 70 | var sortable = Sortable.create(el); 71 | ``` 72 | 73 | You can use any element for the list and its elements, not just `ul`/`li`. Here is an [example with `div`s](http://jsbin.com/qumuwe/edit?html,js,output). 74 | 75 | 76 | --- 77 | 78 | 79 | ### Options 80 | ```js 81 | var sortable = new Sortable(el, { 82 | group: "name", // or { name: "...", pull: [true, false, clone], put: [true, false, array] } 83 | sort: true, // sorting inside list 84 | delay: 0, // time in milliseconds to define when the sorting should start 85 | disabled: false, // Disables the sortable if set to true. 86 | store: null, // @see Store 87 | animation: 150, // ms, animation speed moving items when sorting, `0` — without animation 88 | handle: ".my-handle", // Drag handle selector within list items 89 | filter: ".ignore-elements", // Selectors that do not lead to dragging (String or Function) 90 | preventOnFilter: true, // Call `event.preventDefault()` when triggered `filter` 91 | draggable: ".item", // Specifies which items inside the element should be draggable 92 | ghostClass: "sortable-ghost", // Class name for the drop placeholder 93 | chosenClass: "sortable-chosen", // Class name for the chosen item 94 | dragClass: "sortable-drag", // Class name for the dragging item 95 | dataIdAttr: 'data-id', 96 | 97 | forceFallback: false, // ignore the HTML5 DnD behaviour and force the fallback to kick in 98 | 99 | fallbackClass: "sortable-fallback", // Class name for the cloned DOM Element when using forceFallback 100 | fallbackOnBody: false, // Appends the cloned DOM Element into the Document's Body 101 | fallbackTolerance: 0, // Specify in pixels how far the mouse should move before it's considered as a drag. 102 | 103 | scroll: true, // or HTMLElement 104 | scrollFn: function(offsetX, offsetY, originalEvent) { ... }, // if you have custom scrollbar scrollFn may be used for autoscrolling 105 | scrollSensitivity: 30, // px, how near the mouse must be to an edge to start scrolling. 106 | scrollSpeed: 10, // px 107 | 108 | setData: function (/** DataTransfer */dataTransfer, /** HTMLElement*/dragEl) { 109 | dataTransfer.setData('Text', dragEl.textContent); // `dataTransfer` object of HTML5 DragEvent 110 | }, 111 | 112 | // Element is chosen 113 | onChoose: function (/**Event*/evt) { 114 | evt.oldIndex; // element index within parent 115 | }, 116 | 117 | // Element dragging started 118 | onStart: function (/**Event*/evt) { 119 | evt.oldIndex; // element index within parent 120 | }, 121 | 122 | // Element dragging ended 123 | onEnd: function (/**Event*/evt) { 124 | var itemEl = evt.item; // dragged HTMLElement 125 | evt.to; // target list 126 | evt.from; // previous list 127 | evt.oldIndex; // element's old index within old parent 128 | evt.newIndex; // element's new index within new parent 129 | }, 130 | 131 | // Element is dropped into the list from another list 132 | onAdd: function (/**Event*/evt) { 133 | // same properties as onEnd 134 | }, 135 | 136 | // Changed sorting within list 137 | onUpdate: function (/**Event*/evt) { 138 | // same properties as onEnd 139 | }, 140 | 141 | // Called by any change to the list (add / update / remove) 142 | onSort: function (/**Event*/evt) { 143 | // same properties as onEnd 144 | }, 145 | 146 | // Element is removed from the list into another list 147 | onRemove: function (/**Event*/evt) { 148 | // same properties as onEnd 149 | }, 150 | 151 | // Attempt to drag a filtered element 152 | onFilter: function (/**Event*/evt) { 153 | var itemEl = evt.item; // HTMLElement receiving the `mousedown|tapstart` event. 154 | }, 155 | 156 | // Event when you move an item in the list or between lists 157 | onMove: function (/**Event*/evt, /**Event*/originalEvent) { 158 | // Example: http://jsbin.com/tuyafe/1/edit?js,output 159 | evt.dragged; // dragged HTMLElement 160 | evt.draggedRect; // TextRectangle {left, top, right и bottom} 161 | evt.related; // HTMLElement on which have guided 162 | evt.relatedRect; // TextRectangle 163 | originalEvent.clientY; // mouse position 164 | // return false; — for cancel 165 | }, 166 | 167 | // Called when creating a clone of element 168 | onClone: function (/**Event*/evt) { 169 | var origEl = evt.item; 170 | var cloneEl = evt.clone; 171 | } 172 | }); 173 | ``` 174 | 175 | 176 | --- 177 | 178 | 179 | #### `group` option 180 | To drag elements from one list into another, both lists must have the same `group` value. 181 | You can also define whether lists can give away, give and keep a copy (`clone`), and receive elements. 182 | 183 | * name: `String` — group name 184 | * pull: `true|false|'clone'|function` — ability to move from the list. `clone` — copy the item, rather than move. 185 | * put: `true|false|["foo", "bar"]|function` — whether elements can be added from other lists, or an array of group names from which elements can be taken. 186 | * revertClone: `boolean` — revert cloned element to initial position after moving to a another list. 187 | 188 | 189 | Demo: 190 | - http://jsbin.com/naduvo/edit?js,output 191 | - http://jsbin.com/rusuvot/edit?js,output — use of complex logic in the `pull` and` put` 192 | - http://jsbin.com/magogub/edit?js,output — use `revertClone: true` 193 | 194 | 195 | --- 196 | 197 | 198 | #### `sort` option 199 | Sorting inside list. 200 | 201 | Demo: http://jsbin.com/videzob/edit?html,js,output 202 | 203 | 204 | --- 205 | 206 | 207 | #### `delay` option 208 | Time in milliseconds to define when the sorting should start. 209 | 210 | Demo: http://jsbin.com/xizeh/edit?html,js,output 211 | 212 | 213 | --- 214 | 215 | 216 | #### `disabled` options 217 | Disables the sortable if set to `true`. 218 | 219 | Demo: http://jsbin.com/xiloqu/edit?html,js,output 220 | 221 | ```js 222 | var sortable = Sortable.create(list); 223 | 224 | document.getElementById("switcher").onclick = function () { 225 | var state = sortable.option("disabled"); // get 226 | 227 | sortable.option("disabled", !state); // set 228 | }; 229 | ``` 230 | 231 | 232 | --- 233 | 234 | 235 | #### `handle` option 236 | To make list items draggable, Sortable disables text selection by the user. 237 | That's not always desirable. To allow text selection, define a drag handler, 238 | which is an area of every list element that allows it to be dragged around. 239 | 240 | Demo: http://jsbin.com/newize/edit?html,js,output 241 | 242 | ```js 243 | Sortable.create(el, { 244 | handle: ".my-handle" 245 | }); 246 | ``` 247 | 248 | ```html 249 |
    250 |
  • :: list item text one 251 |
  • :: list item text two 252 |
253 | ``` 254 | 255 | ```css 256 | .my-handle { 257 | cursor: move; 258 | cursor: -webkit-grabbing; 259 | } 260 | ``` 261 | 262 | 263 | --- 264 | 265 | 266 | #### `filter` option 267 | 268 | 269 | ```js 270 | Sortable.create(list, { 271 | filter: ".js-remove, .js-edit", 272 | onFilter: function (evt) { 273 | var item = evt.item, 274 | ctrl = evt.target; 275 | 276 | if (Sortable.utils.is(ctrl, ".js-remove")) { // Click on remove button 277 | item.parentNode.removeChild(item); // remove sortable item 278 | } 279 | else if (Sortable.utils.is(ctrl, ".js-edit")) { // Click on edit link 280 | // ... 281 | } 282 | } 283 | }) 284 | ``` 285 | 286 | 287 | --- 288 | 289 | 290 | #### `ghostClass` option 291 | Class name for the drop placeholder (default `sortable-ghost`). 292 | 293 | Demo: http://jsbin.com/hunifu/4/edit?css,js,output 294 | 295 | ```css 296 | .ghost { 297 | opacity: 0.4; 298 | } 299 | ``` 300 | 301 | ```js 302 | Sortable.create(list, { 303 | ghostClass: "ghost" 304 | }); 305 | ``` 306 | 307 | 308 | --- 309 | 310 | 311 | #### `chosenClass` option 312 | Class name for the chosen item (default `sortable-chosen`). 313 | 314 | Demo: http://jsbin.com/hunifu/3/edit?html,css,js,output 315 | 316 | ```css 317 | .chosen { 318 | color: #fff; 319 | background-color: #c00; 320 | } 321 | ``` 322 | 323 | ```js 324 | Sortable.create(list, { 325 | delay: 500, 326 | chosenClass: "chosen" 327 | }); 328 | ``` 329 | 330 | 331 | --- 332 | 333 | 334 | #### `forceFallback` option 335 | If set to `true`, the Fallback for non HTML5 Browser will be used, even if we are using an HTML5 Browser. 336 | This gives us the possibility to test the behaviour for older Browsers even in newer Browser, or make the Drag 'n Drop feel more consistent between Desktop , Mobile and old Browsers. 337 | 338 | On top of that, the Fallback always generates a copy of that DOM Element and appends the class `fallbackClass` defined in the options. This behaviour controls the look of this 'dragged' Element. 339 | 340 | Demo: http://jsbin.com/yacuqib/edit?html,css,js,output 341 | 342 | 343 | --- 344 | 345 | 346 | #### `fallbackTolerance` option 347 | Emulates the native drag threshold. Specify in pixels how far the mouse should move before it's considered as a drag. 348 | Useful if the items are also clickable like in a list of links. 349 | 350 | When the user clicks inside a sortable element, it's not uncommon for your hand to move a little between the time you press and the time you release. 351 | Dragging only starts if you move the pointer past a certain tolerance, so that you don't accidentally start dragging every time you click. 352 | 353 | 3 to 5 are probably good values. 354 | 355 | 356 | --- 357 | 358 | 359 | #### `scroll` option 360 | If set to `true`, the page (or sortable-area) scrolls when coming to an edge. 361 | 362 | Demo: 363 | - `window`: http://jsbin.com/tutuzeh/edit?html,js,output 364 | - `overflow: hidden`: http://jsbin.com/kolisu/edit?html,js,output 365 | 366 | 367 | --- 368 | 369 | 370 | #### `scrollFn` option 371 | Defines function that will be used for autoscrolling. el.scrollTop/el.scrollLeft is used by default. 372 | Useful when you have custom scrollbar with dedicated scroll function. 373 | 374 | 375 | --- 376 | 377 | 378 | #### `scrollSensitivity` option 379 | Defines how near the mouse must be to an edge to start scrolling. 380 | 381 | 382 | --- 383 | 384 | 385 | #### `scrollSpeed` option 386 | The speed at which the window should scroll once the mouse pointer gets within the `scrollSensitivity` distance. 387 | 388 | 389 | --- 390 | 391 | 392 | ### Event object ([demo](http://jsbin.com/xedusu/edit?js,output)) 393 | 394 | - to:`HTMLElement` — list, in which moved element. 395 | - from:`HTMLElement` — previous list 396 | - item:`HTMLElement` — dragged element 397 | - clone:`HTMLElement` 398 | - oldIndex:`Number|undefined` — old index within parent 399 | - newIndex:`Number|undefined` — new index within parent 400 | 401 | 402 | #### `move` event object 403 | - to:`HTMLElement` 404 | - from:`HTMLElement` 405 | - dragged:`HTMLElement` 406 | - draggedRect:` TextRectangle` 407 | - related:`HTMLElement` — element on which have guided 408 | - relatedRect:` TextRectangle` 409 | 410 | 411 | --- 412 | 413 | 414 | ### Method 415 | 416 | 417 | ##### option(name:`String`[, value:`*`]):`*` 418 | Get or set the option. 419 | 420 | 421 | 422 | ##### closest(el:`String`[, selector:`HTMLElement`]):`HTMLElement|null` 423 | For each element in the set, get the first element that matches the selector by testing the element itself and traversing up through its ancestors in the DOM tree. 424 | 425 | 426 | ##### toArray():`String[]` 427 | Serializes the sortable's item `data-id`'s (`dataIdAttr` option) into an array of string. 428 | 429 | 430 | ##### sort(order:`String[]`) 431 | Sorts the elements according to the array. 432 | 433 | ```js 434 | var order = sortable.toArray(); 435 | sortable.sort(order.reverse()); // apply 436 | ``` 437 | 438 | 439 | ##### save() 440 | Save the current sorting (see [store](#store)) 441 | 442 | 443 | ##### destroy() 444 | Removes the sortable functionality completely. 445 | 446 | 447 | --- 448 | 449 | 450 | 451 | ### Store 452 | Saving and restoring of the sort. 453 | 454 | ```html 455 |
    456 |
  • order
  • 457 |
  • save
  • 458 |
  • restore
  • 459 |
460 | ``` 461 | 462 | ```js 463 | Sortable.create(el, { 464 | group: "localStorage-example", 465 | store: { 466 | /** 467 | * Get the order of elements. Called once during initialization. 468 | * @param {Sortable} sortable 469 | * @returns {Array} 470 | */ 471 | get: function (sortable) { 472 | var order = localStorage.getItem(sortable.options.group.name); 473 | return order ? order.split('|') : []; 474 | }, 475 | 476 | /** 477 | * Save the order of elements. Called onEnd (when the item is dropped). 478 | * @param {Sortable} sortable 479 | */ 480 | set: function (sortable) { 481 | var order = sortable.toArray(); 482 | localStorage.setItem(sortable.options.group.name, order.join('|')); 483 | } 484 | } 485 | }) 486 | ``` 487 | 488 | 489 | --- 490 | 491 | 492 | 493 | ### Bootstrap 494 | Demo: http://jsbin.com/qumuwe/edit?html,js,output 495 | 496 | ```html 497 | 498 | 499 | 500 | 501 | 502 | 503 | 504 | 505 | 506 |
    507 |
  • This is Sortable
  • 508 |
  • It works with Bootstrap...
  • 509 |
  • ...out of the box.
  • 510 |
  • It has support for touch devices.
  • 511 |
  • Just drag some elements around.
  • 512 |
513 | 514 | 518 | ``` 519 | 520 | 521 | --- 522 | 523 | 524 | ### Static methods & properties 525 | 526 | 527 | 528 | ##### Sortable.create(el:`HTMLElement`[, options:`Object`]):`Sortable` 529 | Create new instance. 530 | 531 | 532 | --- 533 | 534 | 535 | ##### Sortable.active:`Sortable` 536 | Link to the active instance. 537 | 538 | 539 | --- 540 | 541 | 542 | ##### Sortable.utils 543 | * on(el`:HTMLElement`, event`:String`, fn`:Function`) — attach an event handler function 544 | * off(el`:HTMLElement`, event`:String`, fn`:Function`) — remove an event handler 545 | * css(el`:HTMLElement`)`:Object` — get the values of all the CSS properties 546 | * css(el`:HTMLElement`, prop`:String`)`:Mixed` — get the value of style properties 547 | * css(el`:HTMLElement`, prop`:String`, value`:String`) — set one CSS properties 548 | * css(el`:HTMLElement`, props`:Object`) — set more CSS properties 549 | * find(ctx`:HTMLElement`, tagName`:String`[, iterator`:Function`])`:Array` — get elements by tag name 550 | * bind(ctx`:Mixed`, fn`:Function`)`:Function` — Takes a function and returns a new one that will always have a particular context 551 | * is(el`:HTMLElement`, selector`:String`)`:Boolean` — check the current matched set of elements against a selector 552 | * closest(el`:HTMLElement`, selector`:String`[, ctx`:HTMLElement`])`:HTMLElement|Null` — for each element in the set, get the first element that matches the selector by testing the element itself and traversing up through its ancestors in the DOM tree 553 | * clone(el`:HTMLElement`)`:HTMLElement` — create a deep copy of the set of matched elements 554 | * toggleClass(el`:HTMLElement`, name`:String`, state`:Boolean`) — add or remove one classes from each element 555 | 556 | 557 | --- 558 | 559 | 560 | 561 | ### CDN 562 | 563 | ```html 564 | 565 | 566 | 567 | 568 | 569 | 570 | ``` 571 | 572 | 573 | --- 574 | 575 | 576 | 577 | ### jQuery compatibility 578 | To assemble plugin for jQuery, perform the following steps: 579 | 580 | ```bash 581 | cd Sortable 582 | npm install 583 | grunt jquery 584 | ``` 585 | 586 | Now you can use `jquery.fn.sortable.js`:
587 | (or `jquery.fn.sortable.min.js` if you run `grunt jquery:min`) 588 | 589 | ```js 590 | $("#list").sortable({ /* options */ }); // init 591 | 592 | $("#list").sortable("widget"); // get Sortable instance 593 | 594 | $("#list").sortable("destroy"); // destroy Sortable instance 595 | 596 | $("#list").sortable("{method-name}"); // call an instance method 597 | 598 | $("#list").sortable("{method-name}", "foo", "bar"); // call an instance method with parameters 599 | ``` 600 | 601 | And `grunt jquery:mySortableFunc` → `jquery.fn.mySortableFunc.js` 602 | 603 | --- 604 | 605 | 606 | ### Contributing (Issue/PR) 607 | 608 | Please, [read this](CONTRIBUTING.md). 609 | 610 | 611 | --- 612 | 613 | 614 | ## MIT LICENSE 615 | Copyright 2013-2017 Lebedev Konstantin 616 | http://rubaxa.github.io/Sortable/ 617 | 618 | Permission is hereby granted, free of charge, to any person obtaining 619 | a copy of this software and associated documentation files (the 620 | "Software"), to deal in the Software without restriction, including 621 | without limitation the rights to use, copy, modify, merge, publish, 622 | distribute, sublicense, and/or sell copies of the Software, and to 623 | permit persons to whom the Software is furnished to do so, subject to 624 | the following conditions: 625 | 626 | The above copyright notice and this permission notice shall be 627 | included in all copies or substantial portions of the Software. 628 | 629 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 630 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 631 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 632 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 633 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 634 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 635 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 636 | 637 | -------------------------------------------------------------------------------- /js/sortablejs/Sortable.js: -------------------------------------------------------------------------------- 1 | /**! 2 | * Sortable 3 | * @author RubaXa 4 | * @license MIT 5 | */ 6 | 7 | (function sortableModule(factory) { 8 | "use strict"; 9 | 10 | if (typeof define === "function" && define.amd) { 11 | define(factory); 12 | } 13 | else if (typeof module != "undefined" && typeof module.exports != "undefined") { 14 | module.exports = factory(); 15 | } 16 | else { 17 | /* jshint sub:true */ 18 | window["Sortable"] = factory(); 19 | } 20 | })(function sortableFactory() { 21 | "use strict"; 22 | 23 | if (typeof window === "undefined" || !window.document) { 24 | return function sortableError() { 25 | throw new Error("Sortable.js requires a window with a document"); 26 | }; 27 | } 28 | 29 | var dragEl, 30 | parentEl, 31 | ghostEl, 32 | cloneEl, 33 | rootEl, 34 | nextEl, 35 | lastDownEl, 36 | 37 | scrollEl, 38 | scrollParentEl, 39 | scrollCustomFn, 40 | 41 | lastEl, 42 | lastCSS, 43 | lastParentCSS, 44 | 45 | oldIndex, 46 | newIndex, 47 | 48 | activeGroup, 49 | putSortable, 50 | 51 | autoScroll = {}, 52 | 53 | tapEvt, 54 | touchEvt, 55 | 56 | moved, 57 | 58 | /** @const */ 59 | R_SPACE = /\s+/g, 60 | R_FLOAT = /left|right|inline/, 61 | 62 | expando = 'Sortable' + (new Date).getTime(), 63 | 64 | win = window, 65 | document = win.document, 66 | parseInt = win.parseInt, 67 | setTimeout = win.setTimeout, 68 | 69 | $ = win.jQuery || win.Zepto, 70 | Polymer = win.Polymer, 71 | 72 | captureMode = false, 73 | passiveMode = false, 74 | 75 | supportDraggable = ('draggable' in document.createElement('div')), 76 | supportCssPointerEvents = (function (el) { 77 | // false when IE11 78 | if (!!navigator.userAgent.match(/(?:Trident.*rv[ :]?11\.|msie)/i)) { 79 | return false; 80 | } 81 | el = document.createElement('x'); 82 | el.style.cssText = 'pointer-events:auto'; 83 | return el.style.pointerEvents === 'auto'; 84 | })(), 85 | 86 | _silent = false, 87 | 88 | abs = Math.abs, 89 | min = Math.min, 90 | 91 | savedInputChecked = [], 92 | touchDragOverListeners = [], 93 | 94 | _autoScroll = _throttle(function (/**Event*/evt, /**Object*/options, /**HTMLElement*/rootEl) { 95 | // Bug: https://bugzilla.mozilla.org/show_bug.cgi?id=505521 96 | if (rootEl && options.scroll) { 97 | var _this = rootEl[expando], 98 | el, 99 | rect, 100 | sens = options.scrollSensitivity, 101 | speed = options.scrollSpeed, 102 | 103 | x = evt.clientX, 104 | y = evt.clientY, 105 | 106 | winWidth = window.innerWidth, 107 | winHeight = window.innerHeight, 108 | 109 | vx, 110 | vy, 111 | 112 | scrollOffsetX, 113 | scrollOffsetY 114 | ; 115 | 116 | // Delect scrollEl 117 | if (scrollParentEl !== rootEl) { 118 | scrollEl = options.scroll; 119 | scrollParentEl = rootEl; 120 | scrollCustomFn = options.scrollFn; 121 | 122 | if (scrollEl === true) { 123 | scrollEl = rootEl; 124 | 125 | do { 126 | if ((scrollEl.offsetWidth < scrollEl.scrollWidth) || 127 | (scrollEl.offsetHeight < scrollEl.scrollHeight) 128 | ) { 129 | break; 130 | } 131 | /* jshint boss:true */ 132 | } while (scrollEl = scrollEl.parentNode); 133 | } 134 | } 135 | 136 | if (scrollEl) { 137 | el = scrollEl; 138 | rect = scrollEl.getBoundingClientRect(); 139 | vx = (abs(rect.right - x) <= sens) - (abs(rect.left - x) <= sens); 140 | vy = (abs(rect.bottom - y) <= sens) - (abs(rect.top - y) <= sens); 141 | } 142 | 143 | 144 | if (!(vx || vy)) { 145 | vx = (winWidth - x <= sens) - (x <= sens); 146 | vy = (winHeight - y <= sens) - (y <= sens); 147 | 148 | /* jshint expr:true */ 149 | (vx || vy) && (el = win); 150 | } 151 | 152 | 153 | if (autoScroll.vx !== vx || autoScroll.vy !== vy || autoScroll.el !== el) { 154 | autoScroll.el = el; 155 | autoScroll.vx = vx; 156 | autoScroll.vy = vy; 157 | 158 | clearInterval(autoScroll.pid); 159 | 160 | if (el) { 161 | autoScroll.pid = setInterval(function () { 162 | scrollOffsetY = vy ? vy * speed : 0; 163 | scrollOffsetX = vx ? vx * speed : 0; 164 | 165 | if ('function' === typeof(scrollCustomFn)) { 166 | return scrollCustomFn.call(_this, scrollOffsetX, scrollOffsetY, evt); 167 | } 168 | 169 | if (el === win) { 170 | win.scrollTo(win.pageXOffset + scrollOffsetX, win.pageYOffset + scrollOffsetY); 171 | } else { 172 | el.scrollTop += scrollOffsetY; 173 | el.scrollLeft += scrollOffsetX; 174 | } 175 | }, 24); 176 | } 177 | } 178 | } 179 | }, 30), 180 | 181 | _prepareGroup = function (options) { 182 | function toFn(value, pull) { 183 | if (value === void 0 || value === true) { 184 | value = group.name; 185 | } 186 | 187 | if (typeof value === 'function') { 188 | return value; 189 | } else { 190 | return function (to, from) { 191 | var fromGroup = from.options.group.name; 192 | 193 | return pull 194 | ? value 195 | : value && (value.join 196 | ? value.indexOf(fromGroup) > -1 197 | : (fromGroup == value) 198 | ); 199 | }; 200 | } 201 | } 202 | 203 | var group = {}; 204 | var originalGroup = options.group; 205 | 206 | if (!originalGroup || typeof originalGroup != 'object') { 207 | originalGroup = {name: originalGroup}; 208 | } 209 | 210 | group.name = originalGroup.name; 211 | group.checkPull = toFn(originalGroup.pull, true); 212 | group.checkPut = toFn(originalGroup.put); 213 | group.revertClone = originalGroup.revertClone; 214 | 215 | options.group = group; 216 | } 217 | ; 218 | 219 | // Detect support a passive mode 220 | try { 221 | window.addEventListener('test', null, Object.defineProperty({}, 'passive', { 222 | get: function () { 223 | // `false`, because everything starts to work incorrectly and instead of d'n'd, 224 | // begins the page has scrolled. 225 | passiveMode = false; 226 | captureMode = { 227 | capture: false, 228 | passive: passiveMode 229 | }; 230 | } 231 | })); 232 | } catch (err) {} 233 | 234 | /** 235 | * @class Sortable 236 | * @param {HTMLElement} el 237 | * @param {Object} [options] 238 | */ 239 | function Sortable(el, options) { 240 | if (!(el && el.nodeType && el.nodeType === 1)) { 241 | throw 'Sortable: `el` must be HTMLElement, and not ' + {}.toString.call(el); 242 | } 243 | 244 | this.el = el; // root element 245 | this.options = options = _extend({}, options); 246 | 247 | 248 | // Export instance 249 | el[expando] = this; 250 | 251 | // Default options 252 | var defaults = { 253 | group: Math.random(), 254 | sort: true, 255 | disabled: false, 256 | store: null, 257 | handle: null, 258 | scroll: true, 259 | scrollSensitivity: 30, 260 | scrollSpeed: 10, 261 | draggable: /[uo]l/i.test(el.nodeName) ? 'li' : '>*', 262 | ghostClass: 'sortable-ghost', 263 | chosenClass: 'sortable-chosen', 264 | dragClass: 'sortable-drag', 265 | ignore: 'a, img', 266 | filter: null, 267 | preventOnFilter: true, 268 | animation: 0, 269 | setData: function (dataTransfer, dragEl) { 270 | dataTransfer.setData('Text', dragEl.textContent); 271 | }, 272 | dropBubble: false, 273 | dragoverBubble: false, 274 | dataIdAttr: 'data-id', 275 | delay: 0, 276 | forceFallback: false, 277 | fallbackClass: 'sortable-fallback', 278 | fallbackOnBody: false, 279 | fallbackTolerance: 0, 280 | fallbackOffset: {x: 0, y: 0}, 281 | supportPointer: Sortable.supportPointer !== false 282 | }; 283 | 284 | 285 | // Set default options 286 | for (var name in defaults) { 287 | !(name in options) && (options[name] = defaults[name]); 288 | } 289 | 290 | _prepareGroup(options); 291 | 292 | // Bind all private methods 293 | for (var fn in this) { 294 | if (fn.charAt(0) === '_' && typeof this[fn] === 'function') { 295 | this[fn] = this[fn].bind(this); 296 | } 297 | } 298 | 299 | // Setup drag mode 300 | this.nativeDraggable = options.forceFallback ? false : supportDraggable; 301 | 302 | // Bind events 303 | _on(el, 'mousedown', this._onTapStart); 304 | _on(el, 'touchstart', this._onTapStart); 305 | options.supportPointer && _on(el, 'pointerdown', this._onTapStart); 306 | 307 | if (this.nativeDraggable) { 308 | _on(el, 'dragover', this); 309 | _on(el, 'dragenter', this); 310 | } 311 | 312 | touchDragOverListeners.push(this._onDragOver); 313 | 314 | // Restore sorting 315 | options.store && this.sort(options.store.get(this)); 316 | } 317 | 318 | 319 | Sortable.prototype = /** @lends Sortable.prototype */ { 320 | constructor: Sortable, 321 | 322 | _onTapStart: function (/** Event|TouchEvent */evt) { 323 | var _this = this, 324 | el = this.el, 325 | options = this.options, 326 | preventOnFilter = options.preventOnFilter, 327 | type = evt.type, 328 | touch = evt.touches && evt.touches[0], 329 | target = (touch || evt).target, 330 | originalTarget = evt.target.shadowRoot && (evt.path && evt.path[0]) || target, 331 | filter = options.filter, 332 | startIndex; 333 | 334 | _saveInputCheckedState(el); 335 | 336 | 337 | // Don't trigger start event when an element is been dragged, otherwise the evt.oldindex always wrong when set option.group. 338 | if (dragEl) { 339 | return; 340 | } 341 | 342 | if (/mousedown|pointerdown/.test(type) && evt.button !== 0 || options.disabled) { 343 | return; // only left button or enabled 344 | } 345 | 346 | // cancel dnd if original target is content editable 347 | if (originalTarget.isContentEditable) { 348 | return; 349 | } 350 | 351 | target = _closest(target, options.draggable, el); 352 | 353 | if (!target) { 354 | return; 355 | } 356 | 357 | if (lastDownEl === target) { 358 | // Ignoring duplicate `down` 359 | return; 360 | } 361 | 362 | // Get the index of the dragged element within its parent 363 | startIndex = _index(target, options.draggable); 364 | 365 | // Check filter 366 | if (typeof filter === 'function') { 367 | if (filter.call(this, evt, target, this)) { 368 | _dispatchEvent(_this, originalTarget, 'filter', target, el, el, startIndex); 369 | preventOnFilter && evt.preventDefault(); 370 | return; // cancel dnd 371 | } 372 | } 373 | else if (filter) { 374 | filter = filter.split(',').some(function (criteria) { 375 | criteria = _closest(originalTarget, criteria.trim(), el); 376 | 377 | if (criteria) { 378 | _dispatchEvent(_this, criteria, 'filter', target, el, el, startIndex); 379 | return true; 380 | } 381 | }); 382 | 383 | if (filter) { 384 | preventOnFilter && evt.preventDefault(); 385 | return; // cancel dnd 386 | } 387 | } 388 | 389 | if (options.handle && !_closest(originalTarget, options.handle, el)) { 390 | return; 391 | } 392 | 393 | // Prepare `dragstart` 394 | this._prepareDragStart(evt, touch, target, startIndex); 395 | }, 396 | 397 | _prepareDragStart: function (/** Event */evt, /** Touch */touch, /** HTMLElement */target, /** Number */startIndex) { 398 | var _this = this, 399 | el = _this.el, 400 | options = _this.options, 401 | ownerDocument = el.ownerDocument, 402 | dragStartFn; 403 | 404 | if (target && !dragEl && (target.parentNode === el)) { 405 | tapEvt = evt; 406 | 407 | rootEl = el; 408 | dragEl = target; 409 | parentEl = dragEl.parentNode; 410 | nextEl = dragEl.nextSibling; 411 | lastDownEl = target; 412 | activeGroup = options.group; 413 | oldIndex = startIndex; 414 | 415 | this._lastX = (touch || evt).clientX; 416 | this._lastY = (touch || evt).clientY; 417 | 418 | dragEl.style['will-change'] = 'all'; 419 | 420 | dragStartFn = function () { 421 | // Delayed drag has been triggered 422 | // we can re-enable the events: touchmove/mousemove 423 | _this._disableDelayedDrag(); 424 | 425 | // Make the element draggable 426 | dragEl.draggable = _this.nativeDraggable; 427 | 428 | // Chosen item 429 | _toggleClass(dragEl, options.chosenClass, true); 430 | 431 | // Bind the events: dragstart/dragend 432 | _this._triggerDragStart(evt, touch); 433 | 434 | // Drag start event 435 | _dispatchEvent(_this, rootEl, 'choose', dragEl, rootEl, rootEl, oldIndex); 436 | }; 437 | 438 | // Disable "draggable" 439 | options.ignore.split(',').forEach(function (criteria) { 440 | _find(dragEl, criteria.trim(), _disableDraggable); 441 | }); 442 | 443 | _on(ownerDocument, 'mouseup', _this._onDrop); 444 | _on(ownerDocument, 'touchend', _this._onDrop); 445 | _on(ownerDocument, 'touchcancel', _this._onDrop); 446 | _on(ownerDocument, 'selectstart', _this); 447 | options.supportPointer && _on(ownerDocument, 'pointercancel', _this._onDrop); 448 | 449 | if (options.delay) { 450 | // If the user moves the pointer or let go the click or touch 451 | // before the delay has been reached: 452 | // disable the delayed drag 453 | _on(ownerDocument, 'mouseup', _this._disableDelayedDrag); 454 | _on(ownerDocument, 'touchend', _this._disableDelayedDrag); 455 | _on(ownerDocument, 'touchcancel', _this._disableDelayedDrag); 456 | _on(ownerDocument, 'mousemove', _this._disableDelayedDrag); 457 | _on(ownerDocument, 'touchmove', _this._disableDelayedDrag); 458 | options.supportPointer && _on(ownerDocument, 'pointermove', _this._disableDelayedDrag); 459 | 460 | _this._dragStartTimer = setTimeout(dragStartFn, options.delay); 461 | } else { 462 | dragStartFn(); 463 | } 464 | 465 | 466 | } 467 | }, 468 | 469 | _disableDelayedDrag: function () { 470 | var ownerDocument = this.el.ownerDocument; 471 | 472 | clearTimeout(this._dragStartTimer); 473 | _off(ownerDocument, 'mouseup', this._disableDelayedDrag); 474 | _off(ownerDocument, 'touchend', this._disableDelayedDrag); 475 | _off(ownerDocument, 'touchcancel', this._disableDelayedDrag); 476 | _off(ownerDocument, 'mousemove', this._disableDelayedDrag); 477 | _off(ownerDocument, 'touchmove', this._disableDelayedDrag); 478 | _off(ownerDocument, 'pointermove', this._disableDelayedDrag); 479 | }, 480 | 481 | _triggerDragStart: function (/** Event */evt, /** Touch */touch) { 482 | touch = touch || (evt.pointerType == 'touch' ? evt : null); 483 | 484 | if (touch) { 485 | // Touch device support 486 | tapEvt = { 487 | target: dragEl, 488 | clientX: touch.clientX, 489 | clientY: touch.clientY 490 | }; 491 | 492 | this._onDragStart(tapEvt, 'touch'); 493 | } 494 | else if (!this.nativeDraggable) { 495 | this._onDragStart(tapEvt, true); 496 | } 497 | else { 498 | _on(dragEl, 'dragend', this); 499 | _on(rootEl, 'dragstart', this._onDragStart); 500 | } 501 | 502 | try { 503 | if (document.selection) { 504 | // Timeout neccessary for IE9 505 | _nextTick(function () { 506 | document.selection.empty(); 507 | }); 508 | } else { 509 | window.getSelection().removeAllRanges(); 510 | } 511 | } catch (err) { 512 | } 513 | }, 514 | 515 | _dragStarted: function () { 516 | if (rootEl && dragEl) { 517 | var options = this.options; 518 | 519 | // Apply effect 520 | _toggleClass(dragEl, options.ghostClass, true); 521 | _toggleClass(dragEl, options.dragClass, false); 522 | 523 | Sortable.active = this; 524 | 525 | // Drag start event 526 | _dispatchEvent(this, rootEl, 'start', dragEl, rootEl, rootEl, oldIndex); 527 | } else { 528 | this._nulling(); 529 | } 530 | }, 531 | 532 | _emulateDragOver: function () { 533 | if (touchEvt) { 534 | if (this._lastX === touchEvt.clientX && this._lastY === touchEvt.clientY) { 535 | return; 536 | } 537 | 538 | this._lastX = touchEvt.clientX; 539 | this._lastY = touchEvt.clientY; 540 | 541 | if (!supportCssPointerEvents) { 542 | _css(ghostEl, 'display', 'none'); 543 | } 544 | 545 | var target = document.elementFromPoint(touchEvt.clientX, touchEvt.clientY); 546 | var parent = target; 547 | var i = touchDragOverListeners.length; 548 | 549 | if (target && target.shadowRoot) { 550 | target = target.shadowRoot.elementFromPoint(touchEvt.clientX, touchEvt.clientY); 551 | parent = target; 552 | } 553 | 554 | if (parent) { 555 | do { 556 | if (parent[expando]) { 557 | while (i--) { 558 | touchDragOverListeners[i]({ 559 | clientX: touchEvt.clientX, 560 | clientY: touchEvt.clientY, 561 | target: target, 562 | rootEl: parent 563 | }); 564 | } 565 | 566 | break; 567 | } 568 | 569 | target = parent; // store last element 570 | } 571 | /* jshint boss:true */ 572 | while (parent = parent.parentNode); 573 | } 574 | 575 | if (!supportCssPointerEvents) { 576 | _css(ghostEl, 'display', ''); 577 | } 578 | } 579 | }, 580 | 581 | 582 | _onTouchMove: function (/**TouchEvent*/evt) { 583 | if (tapEvt) { 584 | var options = this.options, 585 | fallbackTolerance = options.fallbackTolerance, 586 | fallbackOffset = options.fallbackOffset, 587 | touch = evt.touches ? evt.touches[0] : evt, 588 | dx = (touch.clientX - tapEvt.clientX) + fallbackOffset.x, 589 | dy = (touch.clientY - tapEvt.clientY) + fallbackOffset.y, 590 | translate3d = evt.touches ? 'translate3d(' + dx + 'px,' + dy + 'px,0)' : 'translate(' + dx + 'px,' + dy + 'px)'; 591 | 592 | // only set the status to dragging, when we are actually dragging 593 | if (!Sortable.active) { 594 | if (fallbackTolerance && 595 | min(abs(touch.clientX - this._lastX), abs(touch.clientY - this._lastY)) < fallbackTolerance 596 | ) { 597 | return; 598 | } 599 | 600 | this._dragStarted(); 601 | } 602 | 603 | // as well as creating the ghost element on the document body 604 | this._appendGhost(); 605 | 606 | moved = true; 607 | touchEvt = touch; 608 | 609 | _css(ghostEl, 'webkitTransform', translate3d); 610 | _css(ghostEl, 'mozTransform', translate3d); 611 | _css(ghostEl, 'msTransform', translate3d); 612 | _css(ghostEl, 'transform', translate3d); 613 | 614 | evt.preventDefault(); 615 | } 616 | }, 617 | 618 | _appendGhost: function () { 619 | if (!ghostEl) { 620 | var rect = dragEl.getBoundingClientRect(), 621 | css = _css(dragEl), 622 | options = this.options, 623 | ghostRect; 624 | 625 | ghostEl = dragEl.cloneNode(true); 626 | 627 | _toggleClass(ghostEl, options.ghostClass, false); 628 | _toggleClass(ghostEl, options.fallbackClass, true); 629 | _toggleClass(ghostEl, options.dragClass, true); 630 | 631 | _css(ghostEl, 'top', rect.top - parseInt(css.marginTop, 10)); 632 | _css(ghostEl, 'left', rect.left - parseInt(css.marginLeft, 10)); 633 | _css(ghostEl, 'width', rect.width); 634 | _css(ghostEl, 'height', rect.height); 635 | _css(ghostEl, 'opacity', '0.8'); 636 | _css(ghostEl, 'position', 'fixed'); 637 | _css(ghostEl, 'zIndex', '100000'); 638 | _css(ghostEl, 'pointerEvents', 'none'); 639 | 640 | options.fallbackOnBody && document.body.appendChild(ghostEl) || rootEl.appendChild(ghostEl); 641 | 642 | // Fixing dimensions. 643 | ghostRect = ghostEl.getBoundingClientRect(); 644 | _css(ghostEl, 'width', rect.width * 2 - ghostRect.width); 645 | _css(ghostEl, 'height', rect.height * 2 - ghostRect.height); 646 | } 647 | }, 648 | 649 | _onDragStart: function (/**Event*/evt, /**boolean*/useFallback) { 650 | var _this = this; 651 | var dataTransfer = evt.dataTransfer; 652 | var options = _this.options; 653 | 654 | _this._offUpEvents(); 655 | 656 | if (activeGroup.checkPull(_this, _this, dragEl, evt)) { 657 | cloneEl = _clone(dragEl); 658 | 659 | cloneEl.draggable = false; 660 | cloneEl.style['will-change'] = ''; 661 | 662 | _css(cloneEl, 'display', 'none'); 663 | _toggleClass(cloneEl, _this.options.chosenClass, false); 664 | 665 | // #1143: IFrame support workaround 666 | _this._cloneId = _nextTick(function () { 667 | rootEl.insertBefore(cloneEl, dragEl); 668 | _dispatchEvent(_this, rootEl, 'clone', dragEl); 669 | }); 670 | } 671 | 672 | _toggleClass(dragEl, options.dragClass, true); 673 | 674 | if (useFallback) { 675 | if (useFallback === 'touch') { 676 | // Bind touch events 677 | _on(document, 'touchmove', _this._onTouchMove); 678 | _on(document, 'touchend', _this._onDrop); 679 | _on(document, 'touchcancel', _this._onDrop); 680 | 681 | if (options.supportPointer) { 682 | _on(document, 'pointermove', _this._onTouchMove); 683 | _on(document, 'pointerup', _this._onDrop); 684 | } 685 | } else { 686 | // Old brwoser 687 | _on(document, 'mousemove', _this._onTouchMove); 688 | _on(document, 'mouseup', _this._onDrop); 689 | } 690 | 691 | _this._loopId = setInterval(_this._emulateDragOver, 50); 692 | } 693 | else { 694 | if (dataTransfer) { 695 | dataTransfer.effectAllowed = 'move'; 696 | options.setData && options.setData.call(_this, dataTransfer, dragEl); 697 | } 698 | 699 | _on(document, 'drop', _this); 700 | 701 | // #1143: Бывает элемент с IFrame внутри блокирует `drop`, 702 | // поэтому если вызвался `mouseover`, значит надо отменять весь d'n'd. 703 | // Breaking Chrome 62+ 704 | // _on(document, 'mouseover', _this); 705 | 706 | _this._dragStartId = _nextTick(_this._dragStarted); 707 | } 708 | }, 709 | 710 | _onDragOver: function (/**Event*/evt) { 711 | var el = this.el, 712 | target, 713 | dragRect, 714 | targetRect, 715 | revert, 716 | options = this.options, 717 | group = options.group, 718 | activeSortable = Sortable.active, 719 | isOwner = (activeGroup === group), 720 | isMovingBetweenSortable = false, 721 | canSort = options.sort; 722 | 723 | if (evt.preventDefault !== void 0) { 724 | evt.preventDefault(); 725 | !options.dragoverBubble && evt.stopPropagation(); 726 | } 727 | 728 | if (dragEl.animated) { 729 | return; 730 | } 731 | 732 | moved = true; 733 | 734 | if (activeSortable && !options.disabled && 735 | (isOwner 736 | ? canSort || (revert = !rootEl.contains(dragEl)) // Reverting item into the original list 737 | : ( 738 | putSortable === this || 739 | ( 740 | (activeSortable.lastPullMode = activeGroup.checkPull(this, activeSortable, dragEl, evt)) && 741 | group.checkPut(this, activeSortable, dragEl, evt) 742 | ) 743 | ) 744 | ) && 745 | (evt.rootEl === void 0 || evt.rootEl === this.el) // touch fallback 746 | ) { 747 | // Smart auto-scrolling 748 | _autoScroll(evt, options, this.el); 749 | 750 | if (_silent) { 751 | return; 752 | } 753 | 754 | target = _closest(evt.target, options.draggable, el); 755 | dragRect = dragEl.getBoundingClientRect(); 756 | 757 | if (putSortable !== this) { 758 | putSortable = this; 759 | isMovingBetweenSortable = true; 760 | } 761 | 762 | if (revert) { 763 | _cloneHide(activeSortable, true); 764 | parentEl = rootEl; // actualization 765 | 766 | if (cloneEl || nextEl) { 767 | rootEl.insertBefore(dragEl, cloneEl || nextEl); 768 | } 769 | else if (!canSort) { 770 | rootEl.appendChild(dragEl); 771 | } 772 | 773 | return; 774 | } 775 | 776 | 777 | if ((el.children.length === 0) || (el.children[0] === ghostEl) || 778 | (el === evt.target) && (_ghostIsLast(el, evt)) 779 | ) { 780 | //assign target only if condition is true 781 | if (el.children.length !== 0 && el.children[0] !== ghostEl && el === evt.target) { 782 | target = el.lastElementChild; 783 | } 784 | 785 | if (target) { 786 | if (target.animated) { 787 | return; 788 | } 789 | 790 | targetRect = target.getBoundingClientRect(); 791 | } 792 | 793 | _cloneHide(activeSortable, isOwner); 794 | 795 | if (_onMove(rootEl, el, dragEl, dragRect, target, targetRect, evt) !== false) { 796 | if (!dragEl.contains(el)) { 797 | el.appendChild(dragEl); 798 | parentEl = el; // actualization 799 | } 800 | 801 | this._animate(dragRect, dragEl); 802 | target && this._animate(targetRect, target); 803 | } 804 | } 805 | else if (target && !target.animated && target !== dragEl && (target.parentNode[expando] !== void 0)) { 806 | if (lastEl !== target) { 807 | lastEl = target; 808 | lastCSS = _css(target); 809 | lastParentCSS = _css(target.parentNode); 810 | } 811 | 812 | targetRect = target.getBoundingClientRect(); 813 | 814 | var width = targetRect.right - targetRect.left, 815 | height = targetRect.bottom - targetRect.top, 816 | floating = R_FLOAT.test(lastCSS.cssFloat + lastCSS.display) 817 | || (lastParentCSS.display == 'flex' && lastParentCSS['flex-direction'].indexOf('row') === 0), 818 | isWide = (target.offsetWidth > dragEl.offsetWidth), 819 | isLong = (target.offsetHeight > dragEl.offsetHeight), 820 | halfway = (floating ? (evt.clientX - targetRect.left) / width : (evt.clientY - targetRect.top) / height) > 0.5, 821 | nextSibling = target.nextElementSibling, 822 | after = false 823 | ; 824 | 825 | if (floating) { 826 | var elTop = dragEl.offsetTop, 827 | tgTop = target.offsetTop; 828 | 829 | if (elTop === tgTop) { 830 | after = (target.previousElementSibling === dragEl) && !isWide || halfway && isWide; 831 | } 832 | else if (target.previousElementSibling === dragEl || dragEl.previousElementSibling === target) { 833 | after = (evt.clientY - targetRect.top) / height > 0.5; 834 | } else { 835 | after = tgTop > elTop; 836 | } 837 | } else if (!isMovingBetweenSortable) { 838 | after = (nextSibling !== dragEl) && !isLong || halfway && isLong; 839 | } 840 | 841 | var moveVector = _onMove(rootEl, el, dragEl, dragRect, target, targetRect, evt, after); 842 | 843 | if (moveVector !== false) { 844 | if (moveVector === 1 || moveVector === -1) { 845 | after = (moveVector === 1); 846 | } 847 | 848 | _silent = true; 849 | setTimeout(_unsilent, 30); 850 | 851 | _cloneHide(activeSortable, isOwner); 852 | 853 | if (!dragEl.contains(el)) { 854 | if (after && !nextSibling) { 855 | el.appendChild(dragEl); 856 | } else { 857 | target.parentNode.insertBefore(dragEl, after ? nextSibling : target); 858 | } 859 | } 860 | 861 | parentEl = dragEl.parentNode; // actualization 862 | 863 | this._animate(dragRect, dragEl); 864 | this._animate(targetRect, target); 865 | } 866 | } 867 | } 868 | }, 869 | 870 | _animate: function (prevRect, target) { 871 | var ms = this.options.animation; 872 | 873 | if (ms) { 874 | var currentRect = target.getBoundingClientRect(); 875 | 876 | if (prevRect.nodeType === 1) { 877 | prevRect = prevRect.getBoundingClientRect(); 878 | } 879 | 880 | _css(target, 'transition', 'none'); 881 | _css(target, 'transform', 'translate3d(' 882 | + (prevRect.left - currentRect.left) + 'px,' 883 | + (prevRect.top - currentRect.top) + 'px,0)' 884 | ); 885 | 886 | target.offsetWidth; // repaint 887 | 888 | _css(target, 'transition', 'all ' + ms + 'ms'); 889 | _css(target, 'transform', 'translate3d(0,0,0)'); 890 | 891 | clearTimeout(target.animated); 892 | target.animated = setTimeout(function () { 893 | _css(target, 'transition', ''); 894 | _css(target, 'transform', ''); 895 | target.animated = false; 896 | }, ms); 897 | } 898 | }, 899 | 900 | _offUpEvents: function () { 901 | var ownerDocument = this.el.ownerDocument; 902 | 903 | _off(document, 'touchmove', this._onTouchMove); 904 | _off(document, 'pointermove', this._onTouchMove); 905 | _off(ownerDocument, 'mouseup', this._onDrop); 906 | _off(ownerDocument, 'touchend', this._onDrop); 907 | _off(ownerDocument, 'pointerup', this._onDrop); 908 | _off(ownerDocument, 'touchcancel', this._onDrop); 909 | _off(ownerDocument, 'pointercancel', this._onDrop); 910 | _off(ownerDocument, 'selectstart', this); 911 | }, 912 | 913 | _onDrop: function (/**Event*/evt) { 914 | var el = this.el, 915 | options = this.options; 916 | 917 | clearInterval(this._loopId); 918 | clearInterval(autoScroll.pid); 919 | clearTimeout(this._dragStartTimer); 920 | 921 | _cancelNextTick(this._cloneId); 922 | _cancelNextTick(this._dragStartId); 923 | 924 | // Unbind events 925 | _off(document, 'mouseover', this); 926 | _off(document, 'mousemove', this._onTouchMove); 927 | 928 | if (this.nativeDraggable) { 929 | _off(document, 'drop', this); 930 | _off(el, 'dragstart', this._onDragStart); 931 | } 932 | 933 | this._offUpEvents(); 934 | 935 | if (evt) { 936 | if (moved) { 937 | evt.preventDefault(); 938 | !options.dropBubble && evt.stopPropagation(); 939 | } 940 | 941 | ghostEl && ghostEl.parentNode && ghostEl.parentNode.removeChild(ghostEl); 942 | 943 | if (rootEl === parentEl || Sortable.active.lastPullMode !== 'clone') { 944 | // Remove clone 945 | cloneEl && cloneEl.parentNode && cloneEl.parentNode.removeChild(cloneEl); 946 | } 947 | 948 | if (dragEl) { 949 | if (this.nativeDraggable) { 950 | _off(dragEl, 'dragend', this); 951 | } 952 | 953 | _disableDraggable(dragEl); 954 | dragEl.style['will-change'] = ''; 955 | 956 | // Remove class's 957 | _toggleClass(dragEl, this.options.ghostClass, false); 958 | _toggleClass(dragEl, this.options.chosenClass, false); 959 | 960 | // Drag stop event 961 | _dispatchEvent(this, rootEl, 'unchoose', dragEl, parentEl, rootEl, oldIndex); 962 | 963 | if (rootEl !== parentEl) { 964 | newIndex = _index(dragEl, options.draggable); 965 | 966 | if (newIndex >= 0) { 967 | // Add event 968 | _dispatchEvent(null, parentEl, 'add', dragEl, parentEl, rootEl, oldIndex, newIndex); 969 | 970 | // Remove event 971 | _dispatchEvent(this, rootEl, 'remove', dragEl, parentEl, rootEl, oldIndex, newIndex); 972 | 973 | // drag from one list and drop into another 974 | _dispatchEvent(null, parentEl, 'sort', dragEl, parentEl, rootEl, oldIndex, newIndex); 975 | _dispatchEvent(this, rootEl, 'sort', dragEl, parentEl, rootEl, oldIndex, newIndex); 976 | } 977 | } 978 | else { 979 | if (dragEl.nextSibling !== nextEl) { 980 | // Get the index of the dragged element within its parent 981 | newIndex = _index(dragEl, options.draggable); 982 | 983 | if (newIndex >= 0) { 984 | // drag & drop within the same list 985 | _dispatchEvent(this, rootEl, 'update', dragEl, parentEl, rootEl, oldIndex, newIndex); 986 | _dispatchEvent(this, rootEl, 'sort', dragEl, parentEl, rootEl, oldIndex, newIndex); 987 | } 988 | } 989 | } 990 | 991 | if (Sortable.active) { 992 | /* jshint eqnull:true */ 993 | if (newIndex == null || newIndex === -1) { 994 | newIndex = oldIndex; 995 | } 996 | 997 | _dispatchEvent(this, rootEl, 'end', dragEl, parentEl, rootEl, oldIndex, newIndex); 998 | 999 | // Save sorting 1000 | this.save(); 1001 | } 1002 | } 1003 | 1004 | } 1005 | 1006 | this._nulling(); 1007 | }, 1008 | 1009 | _nulling: function() { 1010 | rootEl = 1011 | dragEl = 1012 | parentEl = 1013 | ghostEl = 1014 | nextEl = 1015 | cloneEl = 1016 | lastDownEl = 1017 | 1018 | scrollEl = 1019 | scrollParentEl = 1020 | 1021 | tapEvt = 1022 | touchEvt = 1023 | 1024 | moved = 1025 | newIndex = 1026 | 1027 | lastEl = 1028 | lastCSS = 1029 | 1030 | putSortable = 1031 | activeGroup = 1032 | Sortable.active = null; 1033 | 1034 | savedInputChecked.forEach(function (el) { 1035 | el.checked = true; 1036 | }); 1037 | savedInputChecked.length = 0; 1038 | }, 1039 | 1040 | handleEvent: function (/**Event*/evt) { 1041 | switch (evt.type) { 1042 | case 'drop': 1043 | case 'dragend': 1044 | this._onDrop(evt); 1045 | break; 1046 | 1047 | case 'dragover': 1048 | case 'dragenter': 1049 | if (dragEl) { 1050 | this._onDragOver(evt); 1051 | _globalDragOver(evt); 1052 | } 1053 | break; 1054 | 1055 | case 'mouseover': 1056 | this._onDrop(evt); 1057 | break; 1058 | 1059 | case 'selectstart': 1060 | evt.preventDefault(); 1061 | break; 1062 | } 1063 | }, 1064 | 1065 | 1066 | /** 1067 | * Serializes the item into an array of string. 1068 | * @returns {String[]} 1069 | */ 1070 | toArray: function () { 1071 | var order = [], 1072 | el, 1073 | children = this.el.children, 1074 | i = 0, 1075 | n = children.length, 1076 | options = this.options; 1077 | 1078 | for (; i < n; i++) { 1079 | el = children[i]; 1080 | if (_closest(el, options.draggable, this.el)) { 1081 | order.push(el.getAttribute(options.dataIdAttr) || _generateId(el)); 1082 | } 1083 | } 1084 | 1085 | return order; 1086 | }, 1087 | 1088 | 1089 | /** 1090 | * Sorts the elements according to the array. 1091 | * @param {String[]} order order of the items 1092 | */ 1093 | sort: function (order) { 1094 | var items = {}, rootEl = this.el; 1095 | 1096 | this.toArray().forEach(function (id, i) { 1097 | var el = rootEl.children[i]; 1098 | 1099 | if (_closest(el, this.options.draggable, rootEl)) { 1100 | items[id] = el; 1101 | } 1102 | }, this); 1103 | 1104 | order.forEach(function (id) { 1105 | if (items[id]) { 1106 | rootEl.removeChild(items[id]); 1107 | rootEl.appendChild(items[id]); 1108 | } 1109 | }); 1110 | }, 1111 | 1112 | 1113 | /** 1114 | * Save the current sorting 1115 | */ 1116 | save: function () { 1117 | var store = this.options.store; 1118 | store && store.set(this); 1119 | }, 1120 | 1121 | 1122 | /** 1123 | * For each element in the set, get the first element that matches the selector by testing the element itself and traversing up through its ancestors in the DOM tree. 1124 | * @param {HTMLElement} el 1125 | * @param {String} [selector] default: `options.draggable` 1126 | * @returns {HTMLElement|null} 1127 | */ 1128 | closest: function (el, selector) { 1129 | return _closest(el, selector || this.options.draggable, this.el); 1130 | }, 1131 | 1132 | 1133 | /** 1134 | * Set/get option 1135 | * @param {string} name 1136 | * @param {*} [value] 1137 | * @returns {*} 1138 | */ 1139 | option: function (name, value) { 1140 | var options = this.options; 1141 | 1142 | if (value === void 0) { 1143 | return options[name]; 1144 | } else { 1145 | options[name] = value; 1146 | 1147 | if (name === 'group') { 1148 | _prepareGroup(options); 1149 | } 1150 | } 1151 | }, 1152 | 1153 | 1154 | /** 1155 | * Destroy 1156 | */ 1157 | destroy: function () { 1158 | var el = this.el; 1159 | 1160 | el[expando] = null; 1161 | 1162 | _off(el, 'mousedown', this._onTapStart); 1163 | _off(el, 'touchstart', this._onTapStart); 1164 | _off(el, 'pointerdown', this._onTapStart); 1165 | 1166 | if (this.nativeDraggable) { 1167 | _off(el, 'dragover', this); 1168 | _off(el, 'dragenter', this); 1169 | } 1170 | 1171 | // Remove draggable attributes 1172 | Array.prototype.forEach.call(el.querySelectorAll('[draggable]'), function (el) { 1173 | el.removeAttribute('draggable'); 1174 | }); 1175 | 1176 | touchDragOverListeners.splice(touchDragOverListeners.indexOf(this._onDragOver), 1); 1177 | 1178 | this._onDrop(); 1179 | 1180 | this.el = el = null; 1181 | } 1182 | }; 1183 | 1184 | 1185 | function _cloneHide(sortable, state) { 1186 | if (sortable.lastPullMode !== 'clone') { 1187 | state = true; 1188 | } 1189 | 1190 | if (cloneEl && (cloneEl.state !== state)) { 1191 | _css(cloneEl, 'display', state ? 'none' : ''); 1192 | 1193 | if (!state) { 1194 | if (cloneEl.state) { 1195 | if (sortable.options.group.revertClone) { 1196 | rootEl.insertBefore(cloneEl, nextEl); 1197 | sortable._animate(dragEl, cloneEl); 1198 | } else { 1199 | rootEl.insertBefore(cloneEl, dragEl); 1200 | } 1201 | } 1202 | } 1203 | 1204 | cloneEl.state = state; 1205 | } 1206 | } 1207 | 1208 | 1209 | function _closest(/**HTMLElement*/el, /**String*/selector, /**HTMLElement*/ctx) { 1210 | if (el) { 1211 | ctx = ctx || document; 1212 | 1213 | do { 1214 | if ((selector === '>*' && el.parentNode === ctx) || _matches(el, selector)) { 1215 | return el; 1216 | } 1217 | /* jshint boss:true */ 1218 | } while (el = _getParentOrHost(el)); 1219 | } 1220 | 1221 | return null; 1222 | } 1223 | 1224 | 1225 | function _getParentOrHost(el) { 1226 | var parent = el.host; 1227 | 1228 | return (parent && parent.nodeType) ? parent : el.parentNode; 1229 | } 1230 | 1231 | 1232 | function _globalDragOver(/**Event*/evt) { 1233 | if (evt.dataTransfer) { 1234 | evt.dataTransfer.dropEffect = 'move'; 1235 | } 1236 | evt.preventDefault(); 1237 | } 1238 | 1239 | 1240 | function _on(el, event, fn) { 1241 | el.addEventListener(event, fn, captureMode); 1242 | } 1243 | 1244 | 1245 | function _off(el, event, fn) { 1246 | el.removeEventListener(event, fn, captureMode); 1247 | } 1248 | 1249 | 1250 | function _toggleClass(el, name, state) { 1251 | if (el) { 1252 | if (el.classList) { 1253 | el.classList[state ? 'add' : 'remove'](name); 1254 | } 1255 | else { 1256 | var className = (' ' + el.className + ' ').replace(R_SPACE, ' ').replace(' ' + name + ' ', ' '); 1257 | el.className = (className + (state ? ' ' + name : '')).replace(R_SPACE, ' '); 1258 | } 1259 | } 1260 | } 1261 | 1262 | 1263 | function _css(el, prop, val) { 1264 | var style = el && el.style; 1265 | 1266 | if (style) { 1267 | if (val === void 0) { 1268 | if (document.defaultView && document.defaultView.getComputedStyle) { 1269 | val = document.defaultView.getComputedStyle(el, ''); 1270 | } 1271 | else if (el.currentStyle) { 1272 | val = el.currentStyle; 1273 | } 1274 | 1275 | return prop === void 0 ? val : val[prop]; 1276 | } 1277 | else { 1278 | if (!(prop in style)) { 1279 | prop = '-webkit-' + prop; 1280 | } 1281 | 1282 | style[prop] = val + (typeof val === 'string' ? '' : 'px'); 1283 | } 1284 | } 1285 | } 1286 | 1287 | 1288 | function _find(ctx, tagName, iterator) { 1289 | if (ctx) { 1290 | var list = ctx.getElementsByTagName(tagName), i = 0, n = list.length; 1291 | 1292 | if (iterator) { 1293 | for (; i < n; i++) { 1294 | iterator(list[i], i); 1295 | } 1296 | } 1297 | 1298 | return list; 1299 | } 1300 | 1301 | return []; 1302 | } 1303 | 1304 | 1305 | 1306 | function _dispatchEvent(sortable, rootEl, name, targetEl, toEl, fromEl, startIndex, newIndex) { 1307 | sortable = (sortable || rootEl[expando]); 1308 | 1309 | var evt = document.createEvent('Event'), 1310 | options = sortable.options, 1311 | onName = 'on' + name.charAt(0).toUpperCase() + name.substr(1); 1312 | 1313 | evt.initEvent(name, true, true); 1314 | 1315 | evt.to = toEl || rootEl; 1316 | evt.from = fromEl || rootEl; 1317 | evt.item = targetEl || rootEl; 1318 | evt.clone = cloneEl; 1319 | 1320 | evt.oldIndex = startIndex; 1321 | evt.newIndex = newIndex; 1322 | 1323 | rootEl.dispatchEvent(evt); 1324 | 1325 | if (options[onName]) { 1326 | options[onName].call(sortable, evt); 1327 | } 1328 | } 1329 | 1330 | 1331 | function _onMove(fromEl, toEl, dragEl, dragRect, targetEl, targetRect, originalEvt, willInsertAfter) { 1332 | var evt, 1333 | sortable = fromEl[expando], 1334 | onMoveFn = sortable.options.onMove, 1335 | retVal; 1336 | 1337 | evt = document.createEvent('Event'); 1338 | evt.initEvent('move', true, true); 1339 | 1340 | evt.to = toEl; 1341 | evt.from = fromEl; 1342 | evt.dragged = dragEl; 1343 | evt.draggedRect = dragRect; 1344 | evt.related = targetEl || toEl; 1345 | evt.relatedRect = targetRect || toEl.getBoundingClientRect(); 1346 | evt.willInsertAfter = willInsertAfter; 1347 | 1348 | fromEl.dispatchEvent(evt); 1349 | 1350 | if (onMoveFn) { 1351 | retVal = onMoveFn.call(sortable, evt, originalEvt); 1352 | } 1353 | 1354 | return retVal; 1355 | } 1356 | 1357 | 1358 | function _disableDraggable(el) { 1359 | el.draggable = false; 1360 | } 1361 | 1362 | 1363 | function _unsilent() { 1364 | _silent = false; 1365 | } 1366 | 1367 | 1368 | /** @returns {HTMLElement|false} */ 1369 | function _ghostIsLast(el, evt) { 1370 | var lastEl = el.lastElementChild, 1371 | rect = lastEl.getBoundingClientRect(); 1372 | 1373 | // 5 — min delta 1374 | // abs — нельзя добавлять, а то глюки при наведении сверху 1375 | return (evt.clientY - (rect.top + rect.height) > 5) || 1376 | (evt.clientX - (rect.left + rect.width) > 5); 1377 | } 1378 | 1379 | 1380 | /** 1381 | * Generate id 1382 | * @param {HTMLElement} el 1383 | * @returns {String} 1384 | * @private 1385 | */ 1386 | function _generateId(el) { 1387 | var str = el.tagName + el.className + el.src + el.href + el.textContent, 1388 | i = str.length, 1389 | sum = 0; 1390 | 1391 | while (i--) { 1392 | sum += str.charCodeAt(i); 1393 | } 1394 | 1395 | return sum.toString(36); 1396 | } 1397 | 1398 | /** 1399 | * Returns the index of an element within its parent for a selected set of 1400 | * elements 1401 | * @param {HTMLElement} el 1402 | * @param {selector} selector 1403 | * @return {number} 1404 | */ 1405 | function _index(el, selector) { 1406 | var index = 0; 1407 | 1408 | if (!el || !el.parentNode) { 1409 | return -1; 1410 | } 1411 | 1412 | while (el && (el = el.previousElementSibling)) { 1413 | if ((el.nodeName.toUpperCase() !== 'TEMPLATE') && (selector === '>*' || _matches(el, selector))) { 1414 | index++; 1415 | } 1416 | } 1417 | 1418 | return index; 1419 | } 1420 | 1421 | function _matches(/**HTMLElement*/el, /**String*/selector) { 1422 | if (el) { 1423 | selector = selector.split('.'); 1424 | 1425 | var tag = selector.shift().toUpperCase(), 1426 | re = new RegExp('\\s(' + selector.join('|') + ')(?=\\s)', 'g'); 1427 | 1428 | return ( 1429 | (tag === '' || el.nodeName.toUpperCase() == tag) && 1430 | (!selector.length || ((' ' + el.className + ' ').match(re) || []).length == selector.length) 1431 | ); 1432 | } 1433 | 1434 | return false; 1435 | } 1436 | 1437 | function _throttle(callback, ms) { 1438 | var args, _this; 1439 | 1440 | return function () { 1441 | if (args === void 0) { 1442 | args = arguments; 1443 | _this = this; 1444 | 1445 | setTimeout(function () { 1446 | if (args.length === 1) { 1447 | callback.call(_this, args[0]); 1448 | } else { 1449 | callback.apply(_this, args); 1450 | } 1451 | 1452 | args = void 0; 1453 | }, ms); 1454 | } 1455 | }; 1456 | } 1457 | 1458 | function _extend(dst, src) { 1459 | if (dst && src) { 1460 | for (var key in src) { 1461 | if (src.hasOwnProperty(key)) { 1462 | dst[key] = src[key]; 1463 | } 1464 | } 1465 | } 1466 | 1467 | return dst; 1468 | } 1469 | 1470 | function _clone(el) { 1471 | if (Polymer && Polymer.dom) { 1472 | return Polymer.dom(el).cloneNode(true); 1473 | } 1474 | else if ($) { 1475 | return $(el).clone(true)[0]; 1476 | } 1477 | else { 1478 | return el.cloneNode(true); 1479 | } 1480 | } 1481 | 1482 | function _saveInputCheckedState(root) { 1483 | var inputs = root.getElementsByTagName('input'); 1484 | var idx = inputs.length; 1485 | 1486 | while (idx--) { 1487 | var el = inputs[idx]; 1488 | el.checked && savedInputChecked.push(el); 1489 | } 1490 | } 1491 | 1492 | function _nextTick(fn) { 1493 | return setTimeout(fn, 0); 1494 | } 1495 | 1496 | function _cancelNextTick(id) { 1497 | return clearTimeout(id); 1498 | } 1499 | 1500 | // Fixed #973: 1501 | _on(document, 'touchmove', function (evt) { 1502 | if (Sortable.active) { 1503 | evt.preventDefault(); 1504 | } 1505 | }); 1506 | 1507 | // Export utils 1508 | Sortable.utils = { 1509 | on: _on, 1510 | off: _off, 1511 | css: _css, 1512 | find: _find, 1513 | is: function (el, selector) { 1514 | return !!_closest(el, selector, el); 1515 | }, 1516 | extend: _extend, 1517 | throttle: _throttle, 1518 | closest: _closest, 1519 | toggleClass: _toggleClass, 1520 | clone: _clone, 1521 | index: _index, 1522 | nextTick: _nextTick, 1523 | cancelNextTick: _cancelNextTick 1524 | }; 1525 | 1526 | 1527 | /** 1528 | * Create sortable instance 1529 | * @param {HTMLElement} el 1530 | * @param {Object} [options] 1531 | */ 1532 | Sortable.create = function (el, options) { 1533 | return new Sortable(el, options); 1534 | }; 1535 | 1536 | 1537 | // Export 1538 | Sortable.version = '1.7.0'; 1539 | return Sortable; 1540 | }); 1541 | --------------------------------------------------------------------------------