├── .gitattributes ├── .github └── FUNDING.yml ├── .gitignore ├── .huskyrc.json ├── LICENSE ├── README.md ├── dist ├── jkanban.css ├── jkanban.js ├── jkanban.min.css └── jkanban.min.js ├── example ├── drag_auto_scroll.js └── index.html ├── jest-unit-config.js ├── jest.config.js ├── jkanban.css ├── jkanban.js ├── jkanban.spec.js ├── package-lock.json └── package.json /.gitattributes: -------------------------------------------------------------------------------- 1 | dist/* linguist-generated=true -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [riktar] 4 | patreon: #riccardotartaglia 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | # custom: ['https://donorbox.org/jkanban'] # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # folder to ignore 2 | .idea 3 | node_modules 4 | #files to ignore 5 | *.iml 6 | /jkanban.dev.scss 7 | .DS_Store -------------------------------------------------------------------------------- /.huskyrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "hooks": { 3 | "pre-push": "npm run test" 4 | } 5 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # jKanban 2 | 3 | > Javascript plugin for Kanban boards 4 | 5 | jKanban allow you to create and manage Kanban Board in your project! 6 | 7 | Please try out the live [demo][1]! 8 | 9 | [1]: http://www.riccardotartaglia.it/jkanban/ 10 | 11 | ## Install 12 | Clone the repo and use the javascript and the css files in the `dist` folder. 13 | 14 | You have to include 15 | 16 | `` 17 | 18 | and 19 | 20 | `` 21 | 22 | in your page and you are done. 23 | 24 | ## Usage 25 | Init jKanban is a piece of cake! 26 | ### `var kanban = new jKanban(options)` 27 | 28 | Here's an **overview of the default values**. 29 | ```js 30 | var kanban = new jKanban({ 31 | element : '', // selector of the kanban container 32 | gutter : '15px', // gutter of the board 33 | widthBoard : '250px', // width of the board 34 | responsivePercentage: false, // if it is true I use percentage in the width of the boards and it is not necessary gutter and widthBoard 35 | dragItems : true, // if false, all items are not draggable 36 | boards : [], // json of boards 37 | dragBoards : true, // the boards are draggable, if false only item can be dragged 38 | itemAddOptions: { 39 | enabled: false, // add a button to board for easy item creation 40 | content: '+', // text or html content of the board button 41 | class: 'kanban-title-button btn btn-default btn-xs', // default class of the button 42 | footer: false // position the button on footer 43 | }, 44 | itemHandleOptions: { 45 | enabled : false, // if board item handle is enabled or not 46 | handleClass : "item_handle", // css class for your custom item handle 47 | customCssHandler : "drag_handler", // when customHandler is undefined, jKanban will use this property to set main handler class 48 | customCssIconHandler: "drag_handler_icon", // when customHandler is undefined, jKanban will use this property to set main icon handler class. If you want, you can use font icon libraries here 49 | customHandler : "+ %title% " // your entirely customized handler. Use %title% to position item title 50 | // any key's value included in item collection can be replaced with %key% 51 | }, 52 | click : function (el) {}, // callback when any board's item are clicked 53 | context : function (el, event) {}, // callback when any board's item are right clicked 54 | dragEl : function (el, source) {}, // callback when any board's item are dragged 55 | dragendEl : function (el) {}, // callback when any board's item stop drag 56 | dropEl : function (el, target, source, sibling) {}, // callback when any board's item drop in a board 57 | dragBoard : function (el, source) {}, // callback when any board stop drag 58 | dragendBoard : function (el) {}, // callback when any board stop drag 59 | buttonClick : function(el, boardId) {}, // callback when the board's button is clicked 60 | propagationHandlers: [], // the specified callback does not cancel the browser event. possible values: "click", "context" 61 | }) 62 | ``` 63 | 64 | Now take a look to the `boards` object 65 | ```js 66 | [ 67 | { 68 | "id" : "board-id-1", // id of the board 69 | "title" : "Board Title", // title of the board 70 | "class" : "class1,class2,...", // css classes to add at the title 71 | "dragTo": ['another-board-id',...], // array of ids of boards where items can be dropped (default: []) 72 | "item" : [ // item of this board 73 | { 74 | "id" : "item-id-1", // id of the item 75 | "title" : "Item 1" // title of the item 76 | "class" : ["myClass",...] // array of additional classes 77 | }, 78 | { 79 | "id" : "item-id-2", 80 | "title" : "Item 2" 81 | } 82 | ] 83 | }, 84 | { 85 | "id" : "board-id-2", 86 | "title" : "Board Title 2" 87 | } 88 | ] 89 | ``` 90 | **WARNING: all ids are unique!** 91 | 92 | ### About custom properties 93 | jKanban also support custom properties on items to improve your applications with html data- properties. You can define them at like: 94 | ```js 95 | [ 96 | { 97 | "id" : "board-id-1", 98 | "title" : "Board Title", 99 | "item" : [ 100 | { 101 | "id" : "item-id-1", 102 | "title" : "Item 1", 103 | "username": "username1" 104 | }, 105 | { 106 | "id" : "item-id-2", 107 | "title" : "Item 2", 108 | "username": "username2" 109 | } 110 | ] 111 | } 112 | ] 113 | ``` 114 | Which jKanban will convert to: 115 | ```html 116 |
117 |
Item 1
118 |
Item 2
119 |
120 | ``` 121 | 122 | ## API 123 | jKanban provides the easiest possible API to make your boards awesome! 124 | 125 | Method Name | Arguments | Description 126 | ----------------------|----------------------------------|------------------------------------------------------------------------------------------------------------------------------ 127 | `addElement` | `boardID, element, position` | Add `element` in the board with ID `boardID`, `element` is the standard format. If `position` is set, inserts at position starting from 0 128 | `addForm` | `boardID, formItem` | Add `formItem` as html element into the board with ID `boardID` 129 | `addBoards` | `boards` | Add one or more boards in the kanban, `boards` are in the standard format 130 | `findElement` | `id` | Find board's item by `id` 131 | `replaceElement` | `id, element` | Replace item by `id` with `element` JSON standard format 132 | `getParentBoardID` | `id` | Get board ID of item `id` passed 133 | `findBoard` | `id` | Find board by `id` 134 | `getBoardElements` | `id` | Get all item of a board 135 | `removeElement` | `id` | Remove a board's element by id 136 | `removeBoard` | `id` | Remove a board by id 137 | 138 | ## Example 139 | Clone the repo and look in the `example` folder 140 | 141 | ## Thanks 142 | jKanban use [dragula](https://github.com/bevacqua/dragula) for drag&drop 143 | 144 | ## Develop 145 | Clone the repo then use `npm install` for download all the dependencies then launch `npm run build` for build the project 146 | 147 | ### Pull Requests? 148 | I'd love them! 149 | 150 | ### Comments? 151 | Let's hear them! (The nice ones please!) 152 | 153 | ### Me? 154 | In case you're interested I'm [@riktarweb](http://twitter.com/riktarweb) 155 | 156 | -------------------------------------------------------------------------------- /dist/jkanban.css: -------------------------------------------------------------------------------- 1 | .kanban-container { 2 | position: relative; 3 | box-sizing: border-box; 4 | width: auto; 5 | } 6 | 7 | .kanban-container * { 8 | box-sizing: border-box; 9 | } 10 | 11 | .kanban-container:after { 12 | clear: both; 13 | display: block; 14 | content: ""; 15 | } 16 | 17 | .kanban-board { 18 | position: relative; 19 | float: left; 20 | background: #e2e4e6; 21 | transition: all 0.3s cubic-bezier(0.23, 1, 0.32, 1); 22 | } 23 | 24 | .kanban-board.disabled-board { 25 | opacity: 0.3; 26 | } 27 | 28 | .kanban-board.is-moving.gu-mirror { 29 | transform: rotate(3deg); 30 | } 31 | 32 | .kanban-board.is-moving.gu-mirror .kanban-drag { 33 | overflow: hidden; 34 | padding-right: 50px; 35 | } 36 | 37 | .kanban-board header { 38 | font-size: 16px; 39 | padding: 15px; 40 | } 41 | 42 | .kanban-board header .kanban-title-board { 43 | font-weight: 700; 44 | margin: 0; 45 | padding: 0; 46 | display: inline; 47 | } 48 | 49 | .kanban-board header .kanban-title-button { 50 | float: right; 51 | } 52 | 53 | .kanban-board .kanban-drag { 54 | min-height: 200px; 55 | padding: 20px; 56 | } 57 | 58 | .kanban-board:after { 59 | clear: both; 60 | display: block; 61 | content: ""; 62 | } 63 | 64 | .kanban-item { 65 | background: #fff; 66 | padding: 15px; 67 | margin-bottom: 20px; 68 | transition: all 0.3s cubic-bezier(0.23, 1, 0.32, 1); 69 | animation: append-animate 0.3s cubic-bezier(0.23, 1, 0.32, 1); 70 | } 71 | 72 | @keyframes append-animate { 73 | from { 74 | transform: translateY(-20px); 75 | } 76 | to { 77 | transform: translateY(0px); 78 | } 79 | } 80 | 81 | .kanban-item:hover { 82 | cursor: move; 83 | } 84 | 85 | .kanban-item:last-child { 86 | margin: 0; 87 | } 88 | 89 | .kanban-item.is-moving.gu-mirror { 90 | transform: rotate(3deg); 91 | height: auto !important; 92 | } 93 | 94 | /* Dragula CSS */ 95 | .gu-mirror { 96 | position: fixed !important; 97 | margin: 0 !important; 98 | z-index: 9999 !important; 99 | } 100 | 101 | .gu-hide { 102 | display: none !important; 103 | } 104 | 105 | .gu-unselectable { 106 | -webkit-user-select: none !important; 107 | -moz-user-select: none !important; 108 | -ms-user-select: none !important; 109 | user-select: none !important; 110 | } 111 | 112 | .gu-transit { 113 | opacity: 0.2 !important; 114 | transform: rotate(0deg) !important; 115 | } 116 | 117 | .drag_handler { 118 | background: #fff; 119 | border-radius: 50%; 120 | width: 24px; 121 | height: 24px; 122 | position: relative; 123 | float: left; 124 | top: -3px; 125 | margin-right: 4px; 126 | } 127 | 128 | .drag_handler:hover { 129 | cursor: move; 130 | } 131 | 132 | .drag_handler_icon { 133 | position: relative; 134 | display: block; 135 | background: #000; 136 | width: 24px; 137 | height: 2px; 138 | top: 12px; 139 | transition: .5s ease-in-out; 140 | } 141 | 142 | .drag_handler_icon:before, 143 | .drag_handler_icon:after { 144 | background: #000; 145 | content: ''; 146 | display: block; 147 | width: 100%; 148 | height: 100%; 149 | position: absolute; 150 | transition: .5s ease-in-out; 151 | } 152 | 153 | .drag_handler_icon:before { 154 | top: 6px; 155 | } 156 | 157 | .drag_handler_icon:after { 158 | bottom: 6px; 159 | } -------------------------------------------------------------------------------- /dist/jkanban.js: -------------------------------------------------------------------------------- 1 | (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i self.options.responsive) { 85 | //Init Drag Board 86 | self.drakeBoard = self 87 | .dragula([self.container], { 88 | moves: function (el, source, handle, sibling) { 89 | if (!self.options.dragBoards) return false 90 | return ( 91 | handle.classList.contains('kanban-board-header') || 92 | handle.classList.contains('kanban-title-board') 93 | ) 94 | }, 95 | accepts: function (el, target, source, sibling) { 96 | return target.classList.contains('kanban-container') 97 | }, 98 | revertOnSpill: true, 99 | direction: 'horizontal' 100 | }) 101 | .on('drag', function (el, source) { 102 | el.classList.add('is-moving') 103 | self.options.dragBoard(el, source) 104 | if (typeof el.dragfn === 'function') el.dragfn(el, source) 105 | }) 106 | .on('dragend', function (el) { 107 | __updateBoardsOrder() 108 | el.classList.remove('is-moving') 109 | self.options.dragendBoard(el) 110 | if (typeof el.dragendfn === 'function') el.dragendfn(el) 111 | }) 112 | .on('drop', function (el, target, source, sibling) { 113 | el.classList.remove('is-moving') 114 | self.options.dropBoard(el, target, source, sibling) 115 | if (typeof el.dropfn === 'function') 116 | el.dropfn(el, target, source, sibling) 117 | }) 118 | 119 | //Init Drag Item 120 | self.drake = self 121 | .dragula(self.boardContainer, { 122 | moves: function (el, source, handle, sibling) { 123 | return self.__getCanMove(handle) 124 | }, 125 | revertOnSpill: true 126 | }) 127 | .on('cancel', function (el, container, source) { 128 | self.enableAllBoards() 129 | }) 130 | .on('drag', function (el, source) { 131 | var elClass = el.getAttribute('class') 132 | if (elClass !== '' && elClass.indexOf('not-draggable') > -1) { 133 | self.drake.cancel(true) 134 | return 135 | } 136 | 137 | el.classList.add('is-moving') 138 | 139 | self.options.dragEl(el, source) 140 | 141 | var boardJSON = __findBoardJSON(source.parentNode.dataset.id) 142 | if (boardJSON.dragTo !== undefined) { 143 | self.options.boards.map(function (board) { 144 | if ( 145 | boardJSON.dragTo.indexOf(board.id) === -1 && 146 | board.id !== source.parentNode.dataset.id 147 | ) { 148 | self.findBoard(board.id).classList.add('disabled-board') 149 | } 150 | }) 151 | } 152 | 153 | if (el !== null && typeof el.dragfn === 'function') 154 | el.dragfn(el, source) 155 | }) 156 | .on('dragend', function (el) { 157 | self.options.dragendEl(el) 158 | if (el !== null && typeof el.dragendfn === 'function') 159 | el.dragendfn(el) 160 | }) 161 | .on('drop', function (el, target, source, sibling) { 162 | self.enableAllBoards() 163 | 164 | var boardJSON = __findBoardJSON(source.parentNode.dataset.id) 165 | if (boardJSON.dragTo !== undefined) { 166 | if ( 167 | boardJSON.dragTo.indexOf(target.parentNode.dataset.id) === -1 && 168 | target.parentNode.dataset.id !== source.parentNode.dataset.id 169 | ) { 170 | self.drake.cancel(true) 171 | } 172 | } 173 | if (el !== null) { 174 | var result = self.options.dropEl(el, target, source, sibling) 175 | if (result === false) { 176 | self.drake.cancel(true) 177 | } 178 | el.classList.remove('is-moving') 179 | if (typeof el.dropfn === 'function') 180 | el.dropfn(el, target, source, sibling) 181 | } 182 | }) 183 | } 184 | } 185 | 186 | this.enableAllBoards = function () { 187 | var allB = document.querySelectorAll('.kanban-board') 188 | if (allB.length > 0 && allB !== undefined) { 189 | for (var i = 0; i < allB.length; i++) { 190 | allB[i].classList.remove('disabled-board') 191 | } 192 | } 193 | } 194 | 195 | this.addElement = function (boardID, element, position) { 196 | if (typeof position === 'undefined') { 197 | position = -1 198 | } 199 | var board = self.element.querySelector( 200 | '[data-id="' + boardID + '"] .kanban-drag' 201 | ) 202 | var refElement = board.childNodes[position] 203 | var nodeItem = document.createElement('div') 204 | nodeItem.classList.add('kanban-item') 205 | if (typeof element.id !== 'undefined' && element.id !== '') { 206 | nodeItem.setAttribute('data-eid', element.id) 207 | } 208 | if (element.class && Array.isArray(element.class)) { 209 | element.class.forEach(function (cl) { 210 | nodeItem.classList.add(cl) 211 | }) 212 | } 213 | nodeItem.innerHTML = __buildItemCard(element) 214 | //add function 215 | nodeItem.clickfn = element.click 216 | nodeItem.contextfn = element.context; 217 | nodeItem.dragfn = element.drag 218 | nodeItem.dragendfn = element.dragend 219 | nodeItem.dropfn = element.drop 220 | __appendCustomProperties(nodeItem, element) 221 | __onclickHandler(nodeItem) 222 | __onContextHandler(nodeItem) 223 | if (self.options.itemHandleOptions.enabled) { 224 | nodeItem.style.cursor = 'default' 225 | } 226 | board.insertBefore(nodeItem, refElement) 227 | return self 228 | } 229 | 230 | this.addForm = function (boardID, formItem) { 231 | var board = self.element.querySelector( 232 | '[data-id="' + boardID + '"] .kanban-drag' 233 | ) 234 | var _attribute = formItem.getAttribute('class') 235 | formItem.setAttribute('class', _attribute + ' not-draggable') 236 | board.appendChild(formItem) 237 | return self 238 | } 239 | 240 | this.addBoards = function (boards, isInit) { 241 | if (self.options.responsivePercentage) { 242 | self.container.style.width = '100%' 243 | self.options.gutter = '1%' 244 | if (window.innerWidth > self.options.responsive) { 245 | var boardWidth = (100 - boards.length * 2) / boards.length 246 | } else { 247 | var boardWidth = 100 - boards.length * 2 248 | } 249 | } else { 250 | var boardWidth = self.options.widthBoard 251 | } 252 | var addButton = self.options.itemAddOptions.enabled 253 | var buttonContent = self.options.itemAddOptions.content 254 | var buttonClass = self.options.itemAddOptions.class 255 | var buttonFooter = self.options.itemAddOptions.footer 256 | 257 | //for on all the boards 258 | for (var boardkey in boards) { 259 | // single board 260 | var board = boards[boardkey] 261 | if (!isInit) { 262 | self.options.boards.push(board) 263 | } 264 | 265 | if (!self.options.responsivePercentage) { 266 | //add width to container 267 | if (self.container.style.width === '') { 268 | self.container.style.width = 269 | parseInt(boardWidth) + parseInt(self.options.gutter) * 2 + 'px' 270 | } else { 271 | self.container.style.width = 272 | parseInt(self.container.style.width) + 273 | parseInt(boardWidth) + 274 | parseInt(self.options.gutter) * 2 + 275 | 'px' 276 | } 277 | } 278 | //create node 279 | var boardNode = document.createElement('div') 280 | boardNode.dataset.id = board.id 281 | boardNode.dataset.order = self.container.childNodes.length + 1 282 | boardNode.classList.add('kanban-board') 283 | //set style 284 | if (self.options.responsivePercentage) { 285 | boardNode.style.width = boardWidth + '%' 286 | } else { 287 | boardNode.style.width = boardWidth 288 | } 289 | boardNode.style.marginLeft = self.options.gutter 290 | boardNode.style.marginRight = self.options.gutter 291 | // header board 292 | var headerBoard = document.createElement('header') 293 | if (board.class !== '' && board.class !== undefined) 294 | var allClasses = board.class.split(',') 295 | else allClasses = [] 296 | headerBoard.classList.add('kanban-board-header') 297 | allClasses.map(function (value) { 298 | // Remove empty spaces 299 | value = value.replace(/^[ ]+/g, '') 300 | headerBoard.classList.add(value) 301 | }) 302 | headerBoard.innerHTML = 303 | '
' + board.title + '
' 304 | //content board 305 | var contentBoard = document.createElement('main') 306 | contentBoard.classList.add('kanban-drag') 307 | if (board.bodyClass !== '' && board.bodyClass !== undefined) 308 | var bodyClasses = board.bodyClass.split(',') 309 | else bodyClasses = [] 310 | bodyClasses.map(function (value) { 311 | contentBoard.classList.add(value) 312 | }) 313 | //add drag to array for dragula 314 | self.boardContainer.push(contentBoard) 315 | for (var itemkey in board.item) { 316 | //create item 317 | var itemKanban = board.item[itemkey] 318 | var nodeItem = document.createElement('div') 319 | nodeItem.classList.add('kanban-item') 320 | if (itemKanban.id) { 321 | nodeItem.dataset.eid = itemKanban.id 322 | } 323 | if (itemKanban.class && Array.isArray(itemKanban.class)) { 324 | itemKanban.class.forEach(function (cl) { 325 | nodeItem.classList.add(cl) 326 | }) 327 | } 328 | nodeItem.innerHTML = __buildItemCard(itemKanban) 329 | //add function 330 | nodeItem.clickfn = itemKanban.click 331 | nodeItem.contextfn = itemKanban.context 332 | nodeItem.dragfn = itemKanban.drag 333 | nodeItem.dragendfn = itemKanban.dragend 334 | nodeItem.dropfn = itemKanban.drop 335 | __appendCustomProperties(nodeItem, itemKanban) 336 | //add click handler of item 337 | __onclickHandler(nodeItem) 338 | __onContextHandler(nodeItem) 339 | if (self.options.itemHandleOptions.enabled) { 340 | nodeItem.style.cursor = 'default' 341 | } 342 | contentBoard.appendChild(nodeItem) 343 | } 344 | //footer board 345 | var footerBoard = document.createElement('footer') 346 | // if add button is true, add button to the board 347 | if (addButton) { 348 | var btn = document.createElement('BUTTON') 349 | var t = document.createTextNode(buttonContent ? buttonContent : '+') 350 | btn.setAttribute( 351 | 'class', 352 | buttonClass ? buttonClass : 'kanban-title-button btn btn-default btn-xs' 353 | ) 354 | btn.appendChild(t) 355 | //var buttonHtml = '' 356 | if (buttonFooter) { 357 | footerBoard.appendChild(btn) 358 | } else { 359 | headerBoard.appendChild(btn) 360 | } 361 | __onButtonClickHandler(btn, board.id) 362 | } 363 | //board assembly 364 | boardNode.appendChild(headerBoard) 365 | boardNode.appendChild(contentBoard) 366 | boardNode.appendChild(footerBoard) 367 | //board add 368 | self.container.appendChild(boardNode) 369 | } 370 | return self 371 | } 372 | 373 | this.findBoard = function (id) { 374 | var el = self.element.querySelector('[data-id="' + id + '"]') 375 | return el 376 | } 377 | 378 | this.getParentBoardID = function (el) { 379 | if (typeof el === 'string') { 380 | el = self.element.querySelector('[data-eid="' + el + '"]') 381 | } 382 | if (el === null) { 383 | return null 384 | } 385 | return el.parentNode.parentNode.dataset.id 386 | } 387 | 388 | this.moveElement = function (targetBoardID, elementID, element) { 389 | if (targetBoardID === this.getParentBoardID(elementID)) { 390 | return 391 | } 392 | 393 | this.removeElement(elementID) 394 | return this.addElement(targetBoardID, element) 395 | } 396 | 397 | this.replaceElement = function (el, element) { 398 | var nodeItem = el 399 | if (typeof nodeItem === 'string') { 400 | nodeItem = self.element.querySelector('[data-eid="' + el + '"]') 401 | } 402 | nodeItem.innerHTML = __buildItemCard(element) 403 | // add function 404 | nodeItem.clickfn = element.click 405 | nodeItem.contextfn = element.context 406 | nodeItem.dragfn = element.drag 407 | nodeItem.dragendfn = element.dragend 408 | nodeItem.dropfn = element.drop 409 | __appendCustomProperties(nodeItem, element) 410 | __onclickHandler(nodeItem) 411 | __onContextHandler(nodeItem) 412 | return self 413 | } 414 | 415 | this.findElement = function (id) { 416 | var el = self.element.querySelector('[data-eid="' + id + '"]') 417 | return el 418 | } 419 | 420 | this.getBoardElements = function (id) { 421 | var board = self.element.querySelector( 422 | '[data-id="' + id + '"] .kanban-drag' 423 | ) 424 | return board.childNodes 425 | } 426 | 427 | this.removeElement = function (el) { 428 | if (typeof el === 'string') 429 | el = self.element.querySelector('[data-eid="' + el + '"]') 430 | if (el !== null) { 431 | //fallback for IE 432 | if (typeof el.remove == 'function') { 433 | el.remove() 434 | } else { 435 | el.parentNode.removeChild(el) 436 | } 437 | } 438 | return self 439 | } 440 | 441 | this.removeBoard = function (board) { 442 | var boardElement = null 443 | if (typeof board === 'string') 444 | boardElement = self.element.querySelector('[data-id="' + board + '"]') 445 | if (boardElement !== null) { 446 | //fallback for IE 447 | if (typeof boardElement.remove == 'function') { 448 | boardElement.remove() 449 | } else { 450 | boardElement.parentNode.removeChild(boardElement) 451 | } 452 | } 453 | 454 | // remove thboard in options.boards 455 | for (var i = 0; i < self.options.boards.length; i++) { 456 | if (self.options.boards[i].id === board) { 457 | self.options.boards.splice(i, 1) 458 | break 459 | } 460 | } 461 | 462 | return self 463 | } 464 | 465 | // board button on click function 466 | this.onButtonClick = function (el) {} 467 | 468 | //PRIVATE FUNCTION 469 | function __extendDefaults (source, properties) { 470 | var property 471 | for (property in properties) { 472 | if (properties.hasOwnProperty(property)) { 473 | source[property] = properties[property] 474 | } 475 | } 476 | return source 477 | } 478 | 479 | function __setBoard () { 480 | self.element = document.querySelector(self.options.element) 481 | //create container 482 | var boardContainer = document.createElement('div') 483 | boardContainer.classList.add('kanban-container') 484 | self.container = boardContainer 485 | //add boards 486 | 487 | if (document.querySelector(self.options.element).dataset.hasOwnProperty('board')) { 488 | url = document.querySelector(self.options.element).dataset.board 489 | window.fetch(url, { 490 | method: 'GET', 491 | headers: { 'Content-Type': 'application/json' } 492 | }) 493 | .then(function (response) { 494 | // log response text 495 | response.json().then(function (data) { 496 | self.options.boards = data 497 | self.addBoards(self.options.boards, true) 498 | }) 499 | 500 | }) 501 | .catch(function (error) { 502 | console.log('Error: ', error) 503 | }) 504 | } else { 505 | self.addBoards(self.options.boards, true) 506 | } 507 | 508 | //appends to container 509 | self.element.appendChild(self.container) 510 | } 511 | 512 | function __onclickHandler (nodeItem, clickfn) { 513 | nodeItem.addEventListener('click', function (e) { 514 | if (!self.options.propagationHandlers.includes('click')) e.preventDefault() 515 | self.options.click(this) 516 | if (typeof this.clickfn === 'function') this.clickfn(this) 517 | }) 518 | } 519 | 520 | function __onContextHandler(nodeItem, contextfn) { 521 | if (nodeItem.addEventListener) { 522 | nodeItem.addEventListener('contextmenu', function (e) { 523 | if (!self.options.propagationHandlers.includes('context')) e.preventDefault() 524 | self.options.context(this, e) 525 | if (typeof this.contextfn === 'function') this.contextfn(this, e) 526 | }, false) 527 | } else { 528 | nodeItem.attachEvent('oncontextmenu', function () { 529 | self.options.context(this) 530 | if (typeof this.contextfn === 'function') this.contextfn(this) 531 | if (!self.options.propagationHandlers.includes('context')) window.event.returnValue = false 532 | }) 533 | } 534 | } 535 | 536 | function __onButtonClickHandler (nodeItem, boardId) { 537 | nodeItem.addEventListener('click', function (e) { 538 | e.preventDefault() 539 | self.options.buttonClick(this, boardId) 540 | // if(typeof(this.clickfn) === 'function') 541 | // this.clickfn(this); 542 | }) 543 | } 544 | 545 | function __findBoardJSON (id) { 546 | var el = [] 547 | self.options.boards.map(function (board) { 548 | if (board.id === id) { 549 | return el.push(board) 550 | } 551 | }) 552 | return el[0] 553 | } 554 | 555 | function __appendCustomProperties (element, parentObject) { 556 | for (var propertyName in parentObject) { 557 | if (self._disallowedItemProperties.indexOf(propertyName) > -1) { 558 | continue 559 | } 560 | 561 | element.setAttribute( 562 | 'data-' + propertyName, 563 | parentObject[propertyName] 564 | ) 565 | } 566 | } 567 | 568 | function __updateBoardsOrder () { 569 | var index = 1 570 | for (var i = 0; i < self.container.childNodes.length; i++) { 571 | self.container.childNodes[i].dataset.order = index++ 572 | } 573 | } 574 | 575 | function __buildItemCard(item) { 576 | var result = 'title' in item ? item.title : ''; 577 | 578 | if (self.options.itemHandleOptions.enabled) { 579 | if ((self.options.itemHandleOptions.customHandler || undefined) === undefined) { 580 | var customCssHandler = self.options.itemHandleOptions.customCssHandler 581 | var customCssIconHandler = self.options.itemHandleOptions.customCssIconHandler 582 | var customItemLayout = self.options.itemHandleOptions.customItemLayout 583 | if ((customCssHandler || undefined) === undefined) { 584 | customCssHandler = 'drag_handler'; 585 | } 586 | 587 | if ((customCssIconHandler || undefined) === undefined) { 588 | customCssIconHandler = customCssHandler + '_icon'; 589 | } 590 | 591 | if ((customItemLayout || undefined) === undefined) { 592 | customItemLayout = ''; 593 | } 594 | 595 | result = '
' + result + '
' 596 | } else { 597 | result = '
' + self.options.itemHandleOptions.customHandler.replace(/%([^%]+)%/g, function (match, key) 598 | { return item[key] !== undefined ? item[key] : '' }) + '
' 599 | return result 600 | } 601 | } 602 | 603 | return result 604 | } 605 | 606 | //init plugin 607 | this.init() 608 | } 609 | })() 610 | 611 | },{"dragula":9}],2:[function(require,module,exports){ 612 | module.exports = function atoa (a, n) { return Array.prototype.slice.call(a, n); } 613 | 614 | },{}],3:[function(require,module,exports){ 615 | 'use strict'; 616 | 617 | var ticky = require('ticky'); 618 | 619 | module.exports = function debounce (fn, args, ctx) { 620 | if (!fn) { return; } 621 | ticky(function run () { 622 | fn.apply(ctx || null, args || []); 623 | }); 624 | }; 625 | 626 | },{"ticky":11}],4:[function(require,module,exports){ 627 | 'use strict'; 628 | 629 | var atoa = require('atoa'); 630 | var debounce = require('./debounce'); 631 | 632 | module.exports = function emitter (thing, options) { 633 | var opts = options || {}; 634 | var evt = {}; 635 | if (thing === undefined) { thing = {}; } 636 | thing.on = function (type, fn) { 637 | if (!evt[type]) { 638 | evt[type] = [fn]; 639 | } else { 640 | evt[type].push(fn); 641 | } 642 | return thing; 643 | }; 644 | thing.once = function (type, fn) { 645 | fn._once = true; // thing.off(fn) still works! 646 | thing.on(type, fn); 647 | return thing; 648 | }; 649 | thing.off = function (type, fn) { 650 | var c = arguments.length; 651 | if (c === 1) { 652 | delete evt[type]; 653 | } else if (c === 0) { 654 | evt = {}; 655 | } else { 656 | var et = evt[type]; 657 | if (!et) { return thing; } 658 | et.splice(et.indexOf(fn), 1); 659 | } 660 | return thing; 661 | }; 662 | thing.emit = function () { 663 | var args = atoa(arguments); 664 | return thing.emitterSnapshot(args.shift()).apply(this, args); 665 | }; 666 | thing.emitterSnapshot = function (type) { 667 | var et = (evt[type] || []).slice(0); 668 | return function () { 669 | var args = atoa(arguments); 670 | var ctx = this || thing; 671 | if (type === 'error' && opts.throws !== false && !et.length) { throw args.length === 1 ? args[0] : args; } 672 | et.forEach(function emitter (listen) { 673 | if (opts.async) { debounce(listen, args, ctx); } else { listen.apply(ctx, args); } 674 | if (listen._once) { thing.off(type, listen); } 675 | }); 676 | return thing; 677 | }; 678 | }; 679 | return thing; 680 | }; 681 | 682 | },{"./debounce":3,"atoa":2}],5:[function(require,module,exports){ 683 | (function (global){(function (){ 684 | 'use strict'; 685 | 686 | var customEvent = require('custom-event'); 687 | var eventmap = require('./eventmap'); 688 | var doc = global.document; 689 | var addEvent = addEventEasy; 690 | var removeEvent = removeEventEasy; 691 | var hardCache = []; 692 | 693 | if (!global.addEventListener) { 694 | addEvent = addEventHard; 695 | removeEvent = removeEventHard; 696 | } 697 | 698 | module.exports = { 699 | add: addEvent, 700 | remove: removeEvent, 701 | fabricate: fabricateEvent 702 | }; 703 | 704 | function addEventEasy (el, type, fn, capturing) { 705 | return el.addEventListener(type, fn, capturing); 706 | } 707 | 708 | function addEventHard (el, type, fn) { 709 | return el.attachEvent('on' + type, wrap(el, type, fn)); 710 | } 711 | 712 | function removeEventEasy (el, type, fn, capturing) { 713 | return el.removeEventListener(type, fn, capturing); 714 | } 715 | 716 | function removeEventHard (el, type, fn) { 717 | var listener = unwrap(el, type, fn); 718 | if (listener) { 719 | return el.detachEvent('on' + type, listener); 720 | } 721 | } 722 | 723 | function fabricateEvent (el, type, model) { 724 | var e = eventmap.indexOf(type) === -1 ? makeCustomEvent() : makeClassicEvent(); 725 | if (el.dispatchEvent) { 726 | el.dispatchEvent(e); 727 | } else { 728 | el.fireEvent('on' + type, e); 729 | } 730 | function makeClassicEvent () { 731 | var e; 732 | if (doc.createEvent) { 733 | e = doc.createEvent('Event'); 734 | e.initEvent(type, true, true); 735 | } else if (doc.createEventObject) { 736 | e = doc.createEventObject(); 737 | } 738 | return e; 739 | } 740 | function makeCustomEvent () { 741 | return new customEvent(type, { detail: model }); 742 | } 743 | } 744 | 745 | function wrapperFactory (el, type, fn) { 746 | return function wrapper (originalEvent) { 747 | var e = originalEvent || global.event; 748 | e.target = e.target || e.srcElement; 749 | e.preventDefault = e.preventDefault || function preventDefault () { e.returnValue = false; }; 750 | e.stopPropagation = e.stopPropagation || function stopPropagation () { e.cancelBubble = true; }; 751 | e.which = e.which || e.keyCode; 752 | fn.call(el, e); 753 | }; 754 | } 755 | 756 | function wrap (el, type, fn) { 757 | var wrapper = unwrap(el, type, fn) || wrapperFactory(el, type, fn); 758 | hardCache.push({ 759 | wrapper: wrapper, 760 | element: el, 761 | type: type, 762 | fn: fn 763 | }); 764 | return wrapper; 765 | } 766 | 767 | function unwrap (el, type, fn) { 768 | var i = find(el, type, fn); 769 | if (i) { 770 | var wrapper = hardCache[i].wrapper; 771 | hardCache.splice(i, 1); // free up a tad of memory 772 | return wrapper; 773 | } 774 | } 775 | 776 | function find (el, type, fn) { 777 | var i, item; 778 | for (i = 0; i < hardCache.length; i++) { 779 | item = hardCache[i]; 780 | if (item.element === el && item.type === type && item.fn === fn) { 781 | return i; 782 | } 783 | } 784 | } 785 | 786 | }).call(this)}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) 787 | },{"./eventmap":6,"custom-event":7}],6:[function(require,module,exports){ 788 | (function (global){(function (){ 789 | 'use strict'; 790 | 791 | var eventmap = []; 792 | var eventname = ''; 793 | var ron = /^on/; 794 | 795 | for (eventname in global) { 796 | if (ron.test(eventname)) { 797 | eventmap.push(eventname.slice(2)); 798 | } 799 | } 800 | 801 | module.exports = eventmap; 802 | 803 | }).call(this)}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) 804 | },{}],7:[function(require,module,exports){ 805 | (function (global){(function (){ 806 | 807 | var NativeCustomEvent = global.CustomEvent; 808 | 809 | function useNative () { 810 | try { 811 | var p = new NativeCustomEvent('cat', { detail: { foo: 'bar' } }); 812 | return 'cat' === p.type && 'bar' === p.detail.foo; 813 | } catch (e) { 814 | } 815 | return false; 816 | } 817 | 818 | /** 819 | * Cross-browser `CustomEvent` constructor. 820 | * 821 | * https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent.CustomEvent 822 | * 823 | * @public 824 | */ 825 | 826 | module.exports = useNative() ? NativeCustomEvent : 827 | 828 | // IE >= 9 829 | 'undefined' !== typeof document && 'function' === typeof document.createEvent ? function CustomEvent (type, params) { 830 | var e = document.createEvent('CustomEvent'); 831 | if (params) { 832 | e.initCustomEvent(type, params.bubbles, params.cancelable, params.detail); 833 | } else { 834 | e.initCustomEvent(type, false, false, void 0); 835 | } 836 | return e; 837 | } : 838 | 839 | // IE <= 8 840 | function CustomEvent (type, params) { 841 | var e = document.createEventObject(); 842 | e.type = type; 843 | if (params) { 844 | e.bubbles = Boolean(params.bubbles); 845 | e.cancelable = Boolean(params.cancelable); 846 | e.detail = params.detail; 847 | } else { 848 | e.bubbles = false; 849 | e.cancelable = false; 850 | e.detail = void 0; 851 | } 852 | return e; 853 | } 854 | 855 | }).call(this)}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) 856 | },{}],8:[function(require,module,exports){ 857 | 'use strict'; 858 | 859 | var cache = {}; 860 | var start = '(?:^|\\s)'; 861 | var end = '(?:\\s|$)'; 862 | 863 | function lookupClass (className) { 864 | var cached = cache[className]; 865 | if (cached) { 866 | cached.lastIndex = 0; 867 | } else { 868 | cache[className] = cached = new RegExp(start + className + end, 'g'); 869 | } 870 | return cached; 871 | } 872 | 873 | function addClass (el, className) { 874 | var current = el.className; 875 | if (!current.length) { 876 | el.className = className; 877 | } else if (!lookupClass(className).test(current)) { 878 | el.className += ' ' + className; 879 | } 880 | } 881 | 882 | function rmClass (el, className) { 883 | el.className = el.className.replace(lookupClass(className), ' ').trim(); 884 | } 885 | 886 | module.exports = { 887 | add: addClass, 888 | rm: rmClass 889 | }; 890 | 891 | },{}],9:[function(require,module,exports){ 892 | (function (global){(function (){ 893 | 'use strict'; 894 | 895 | var emitter = require('contra/emitter'); 896 | var crossvent = require('crossvent'); 897 | var classes = require('./classes'); 898 | var doc = document; 899 | var documentElement = doc.documentElement; 900 | 901 | function dragula (initialContainers, options) { 902 | var len = arguments.length; 903 | if (len === 1 && Array.isArray(initialContainers) === false) { 904 | options = initialContainers; 905 | initialContainers = []; 906 | } 907 | var _mirror; // mirror image 908 | var _source; // source container 909 | var _item; // item being dragged 910 | var _offsetX; // reference x 911 | var _offsetY; // reference y 912 | var _moveX; // reference move x 913 | var _moveY; // reference move y 914 | var _initialSibling; // reference sibling when grabbed 915 | var _currentSibling; // reference sibling now 916 | var _copy; // item used for copying 917 | var _renderTimer; // timer for setTimeout renderMirrorImage 918 | var _lastDropTarget = null; // last container item was over 919 | var _grabbed; // holds mousedown context until first mousemove 920 | 921 | var o = options || {}; 922 | if (o.moves === void 0) { o.moves = always; } 923 | if (o.accepts === void 0) { o.accepts = always; } 924 | if (o.invalid === void 0) { o.invalid = invalidTarget; } 925 | if (o.containers === void 0) { o.containers = initialContainers || []; } 926 | if (o.isContainer === void 0) { o.isContainer = never; } 927 | if (o.copy === void 0) { o.copy = false; } 928 | if (o.copySortSource === void 0) { o.copySortSource = false; } 929 | if (o.revertOnSpill === void 0) { o.revertOnSpill = false; } 930 | if (o.removeOnSpill === void 0) { o.removeOnSpill = false; } 931 | if (o.direction === void 0) { o.direction = 'vertical'; } 932 | if (o.ignoreInputTextSelection === void 0) { o.ignoreInputTextSelection = true; } 933 | if (o.mirrorContainer === void 0) { o.mirrorContainer = doc.body; } 934 | 935 | var drake = emitter({ 936 | containers: o.containers, 937 | start: manualStart, 938 | end: end, 939 | cancel: cancel, 940 | remove: remove, 941 | destroy: destroy, 942 | canMove: canMove, 943 | dragging: false 944 | }); 945 | 946 | if (o.removeOnSpill === true) { 947 | drake.on('over', spillOver).on('out', spillOut); 948 | } 949 | 950 | events(); 951 | 952 | return drake; 953 | 954 | function isContainer (el) { 955 | return drake.containers.indexOf(el) !== -1 || o.isContainer(el); 956 | } 957 | 958 | function events (remove) { 959 | var op = remove ? 'remove' : 'add'; 960 | touchy(documentElement, op, 'mousedown', grab); 961 | touchy(documentElement, op, 'mouseup', release); 962 | } 963 | 964 | function eventualMovements (remove) { 965 | var op = remove ? 'remove' : 'add'; 966 | touchy(documentElement, op, 'mousemove', startBecauseMouseMoved); 967 | } 968 | 969 | function movements (remove) { 970 | var op = remove ? 'remove' : 'add'; 971 | crossvent[op](documentElement, 'selectstart', preventGrabbed); // IE8 972 | crossvent[op](documentElement, 'click', preventGrabbed); 973 | } 974 | 975 | function destroy () { 976 | events(true); 977 | release({}); 978 | } 979 | 980 | function preventGrabbed (e) { 981 | if (_grabbed) { 982 | e.preventDefault(); 983 | } 984 | } 985 | 986 | function grab (e) { 987 | _moveX = e.clientX; 988 | _moveY = e.clientY; 989 | 990 | var ignore = whichMouseButton(e) !== 1 || e.metaKey || e.ctrlKey; 991 | if (ignore) { 992 | return; // we only care about honest-to-god left clicks and touch events 993 | } 994 | var item = e.target; 995 | var context = canStart(item); 996 | if (!context) { 997 | return; 998 | } 999 | _grabbed = context; 1000 | eventualMovements(); 1001 | if (e.type === 'mousedown') { 1002 | if (isInput(item)) { // see also: https://github.com/bevacqua/dragula/issues/208 1003 | item.focus(); // fixes https://github.com/bevacqua/dragula/issues/176 1004 | } else { 1005 | e.preventDefault(); // fixes https://github.com/bevacqua/dragula/issues/155 1006 | } 1007 | } 1008 | } 1009 | 1010 | function startBecauseMouseMoved (e) { 1011 | if (!_grabbed) { 1012 | return; 1013 | } 1014 | if (whichMouseButton(e) === 0) { 1015 | release({}); 1016 | return; // when text is selected on an input and then dragged, mouseup doesn't fire. this is our only hope 1017 | } 1018 | 1019 | // truthy check fixes #239, equality fixes #207, fixes #501 1020 | if ((e.clientX !== void 0 && Math.abs(e.clientX - _moveX) <= (o.slideFactorX || 0)) && 1021 | (e.clientY !== void 0 && Math.abs(e.clientY - _moveY) <= (o.slideFactorY || 0))) { 1022 | return; 1023 | } 1024 | 1025 | if (o.ignoreInputTextSelection) { 1026 | var clientX = getCoord('clientX', e) || 0; 1027 | var clientY = getCoord('clientY', e) || 0; 1028 | var elementBehindCursor = doc.elementFromPoint(clientX, clientY); 1029 | if (isInput(elementBehindCursor)) { 1030 | return; 1031 | } 1032 | } 1033 | 1034 | var grabbed = _grabbed; // call to end() unsets _grabbed 1035 | eventualMovements(true); 1036 | movements(); 1037 | end(); 1038 | start(grabbed); 1039 | 1040 | var offset = getOffset(_item); 1041 | _offsetX = getCoord('pageX', e) - offset.left; 1042 | _offsetY = getCoord('pageY', e) - offset.top; 1043 | 1044 | classes.add(_copy || _item, 'gu-transit'); 1045 | renderMirrorImage(); 1046 | drag(e); 1047 | } 1048 | 1049 | function canStart (item) { 1050 | if (drake.dragging && _mirror) { 1051 | return; 1052 | } 1053 | if (isContainer(item)) { 1054 | return; // don't drag container itself 1055 | } 1056 | var handle = item; 1057 | while (getParent(item) && isContainer(getParent(item)) === false) { 1058 | if (o.invalid(item, handle)) { 1059 | return; 1060 | } 1061 | item = getParent(item); // drag target should be a top element 1062 | if (!item) { 1063 | return; 1064 | } 1065 | } 1066 | var source = getParent(item); 1067 | if (!source) { 1068 | return; 1069 | } 1070 | if (o.invalid(item, handle)) { 1071 | return; 1072 | } 1073 | 1074 | var movable = o.moves(item, source, handle, nextEl(item)); 1075 | if (!movable) { 1076 | return; 1077 | } 1078 | 1079 | return { 1080 | item: item, 1081 | source: source 1082 | }; 1083 | } 1084 | 1085 | function canMove (item) { 1086 | return !!canStart(item); 1087 | } 1088 | 1089 | function manualStart (item) { 1090 | var context = canStart(item); 1091 | if (context) { 1092 | start(context); 1093 | } 1094 | } 1095 | 1096 | function start (context) { 1097 | if (isCopy(context.item, context.source)) { 1098 | _copy = context.item.cloneNode(true); 1099 | drake.emit('cloned', _copy, context.item, 'copy'); 1100 | } 1101 | 1102 | _source = context.source; 1103 | _item = context.item; 1104 | _initialSibling = _currentSibling = nextEl(context.item); 1105 | 1106 | drake.dragging = true; 1107 | drake.emit('drag', _item, _source); 1108 | } 1109 | 1110 | function invalidTarget () { 1111 | return false; 1112 | } 1113 | 1114 | function end () { 1115 | if (!drake.dragging) { 1116 | return; 1117 | } 1118 | var item = _copy || _item; 1119 | drop(item, getParent(item)); 1120 | } 1121 | 1122 | function ungrab () { 1123 | _grabbed = false; 1124 | eventualMovements(true); 1125 | movements(true); 1126 | } 1127 | 1128 | function release (e) { 1129 | ungrab(); 1130 | 1131 | if (!drake.dragging) { 1132 | return; 1133 | } 1134 | var item = _copy || _item; 1135 | var clientX = getCoord('clientX', e) || 0; 1136 | var clientY = getCoord('clientY', e) || 0; 1137 | var elementBehindCursor = getElementBehindPoint(_mirror, clientX, clientY); 1138 | var dropTarget = findDropTarget(elementBehindCursor, clientX, clientY); 1139 | if (dropTarget && ((_copy && o.copySortSource) || (!_copy || dropTarget !== _source))) { 1140 | drop(item, dropTarget); 1141 | } else if (o.removeOnSpill) { 1142 | remove(); 1143 | } else { 1144 | cancel(); 1145 | } 1146 | } 1147 | 1148 | function drop (item, target) { 1149 | var parent = getParent(item); 1150 | if (_copy && o.copySortSource && target === _source) { 1151 | parent.removeChild(_item); 1152 | } 1153 | if (isInitialPlacement(target)) { 1154 | drake.emit('cancel', item, _source, _source); 1155 | } else { 1156 | drake.emit('drop', item, target, _source, _currentSibling); 1157 | } 1158 | cleanup(); 1159 | } 1160 | 1161 | function remove () { 1162 | if (!drake.dragging) { 1163 | return; 1164 | } 1165 | var item = _copy || _item; 1166 | var parent = getParent(item); 1167 | if (parent) { 1168 | parent.removeChild(item); 1169 | } 1170 | drake.emit(_copy ? 'cancel' : 'remove', item, parent, _source); 1171 | cleanup(); 1172 | } 1173 | 1174 | function cancel (revert) { 1175 | if (!drake.dragging) { 1176 | return; 1177 | } 1178 | var reverts = arguments.length > 0 ? revert : o.revertOnSpill; 1179 | var item = _copy || _item; 1180 | var parent = getParent(item); 1181 | var initial = isInitialPlacement(parent); 1182 | if (initial === false && reverts) { 1183 | if (_copy) { 1184 | if (parent) { 1185 | parent.removeChild(_copy); 1186 | } 1187 | } else { 1188 | _source.insertBefore(item, _initialSibling); 1189 | } 1190 | } 1191 | if (initial || reverts) { 1192 | drake.emit('cancel', item, _source, _source); 1193 | } else { 1194 | drake.emit('drop', item, parent, _source, _currentSibling); 1195 | } 1196 | cleanup(); 1197 | } 1198 | 1199 | function cleanup () { 1200 | var item = _copy || _item; 1201 | ungrab(); 1202 | removeMirrorImage(); 1203 | if (item) { 1204 | classes.rm(item, 'gu-transit'); 1205 | } 1206 | if (_renderTimer) { 1207 | clearTimeout(_renderTimer); 1208 | } 1209 | drake.dragging = false; 1210 | if (_lastDropTarget) { 1211 | drake.emit('out', item, _lastDropTarget, _source); 1212 | } 1213 | drake.emit('dragend', item); 1214 | _source = _item = _copy = _initialSibling = _currentSibling = _renderTimer = _lastDropTarget = null; 1215 | } 1216 | 1217 | function isInitialPlacement (target, s) { 1218 | var sibling; 1219 | if (s !== void 0) { 1220 | sibling = s; 1221 | } else if (_mirror) { 1222 | sibling = _currentSibling; 1223 | } else { 1224 | sibling = nextEl(_copy || _item); 1225 | } 1226 | return target === _source && sibling === _initialSibling; 1227 | } 1228 | 1229 | function findDropTarget (elementBehindCursor, clientX, clientY) { 1230 | var target = elementBehindCursor; 1231 | while (target && !accepted()) { 1232 | target = getParent(target); 1233 | } 1234 | return target; 1235 | 1236 | function accepted () { 1237 | var droppable = isContainer(target); 1238 | if (droppable === false) { 1239 | return false; 1240 | } 1241 | 1242 | var immediate = getImmediateChild(target, elementBehindCursor); 1243 | var reference = getReference(target, immediate, clientX, clientY); 1244 | var initial = isInitialPlacement(target, reference); 1245 | if (initial) { 1246 | return true; // should always be able to drop it right back where it was 1247 | } 1248 | return o.accepts(_item, target, _source, reference); 1249 | } 1250 | } 1251 | 1252 | function drag (e) { 1253 | if (!_mirror) { 1254 | return; 1255 | } 1256 | e.preventDefault(); 1257 | 1258 | var clientX = getCoord('clientX', e) || 0; 1259 | var clientY = getCoord('clientY', e) || 0; 1260 | var x = clientX - _offsetX; 1261 | var y = clientY - _offsetY; 1262 | 1263 | _mirror.style.left = x + 'px'; 1264 | _mirror.style.top = y + 'px'; 1265 | 1266 | var item = _copy || _item; 1267 | var elementBehindCursor = getElementBehindPoint(_mirror, clientX, clientY); 1268 | var dropTarget = findDropTarget(elementBehindCursor, clientX, clientY); 1269 | var changed = dropTarget !== null && dropTarget !== _lastDropTarget; 1270 | if (changed || dropTarget === null) { 1271 | out(); 1272 | _lastDropTarget = dropTarget; 1273 | over(); 1274 | } 1275 | var parent = getParent(item); 1276 | if (dropTarget === _source && _copy && !o.copySortSource) { 1277 | if (parent) { 1278 | parent.removeChild(item); 1279 | } 1280 | return; 1281 | } 1282 | var reference; 1283 | var immediate = getImmediateChild(dropTarget, elementBehindCursor); 1284 | if (immediate !== null) { 1285 | reference = getReference(dropTarget, immediate, clientX, clientY); 1286 | } else if (o.revertOnSpill === true && !_copy) { 1287 | reference = _initialSibling; 1288 | dropTarget = _source; 1289 | } else { 1290 | if (_copy && parent) { 1291 | parent.removeChild(item); 1292 | } 1293 | return; 1294 | } 1295 | if ( 1296 | (reference === null && changed) || 1297 | reference !== item && 1298 | reference !== nextEl(item) 1299 | ) { 1300 | _currentSibling = reference; 1301 | dropTarget.insertBefore(item, reference); 1302 | drake.emit('shadow', item, dropTarget, _source); 1303 | } 1304 | function moved (type) { drake.emit(type, item, _lastDropTarget, _source); } 1305 | function over () { if (changed) { moved('over'); } } 1306 | function out () { if (_lastDropTarget) { moved('out'); } } 1307 | } 1308 | 1309 | function spillOver (el) { 1310 | classes.rm(el, 'gu-hide'); 1311 | } 1312 | 1313 | function spillOut (el) { 1314 | if (drake.dragging) { classes.add(el, 'gu-hide'); } 1315 | } 1316 | 1317 | function renderMirrorImage () { 1318 | if (_mirror) { 1319 | return; 1320 | } 1321 | var rect = _item.getBoundingClientRect(); 1322 | _mirror = _item.cloneNode(true); 1323 | _mirror.style.width = getRectWidth(rect) + 'px'; 1324 | _mirror.style.height = getRectHeight(rect) + 'px'; 1325 | classes.rm(_mirror, 'gu-transit'); 1326 | classes.add(_mirror, 'gu-mirror'); 1327 | o.mirrorContainer.appendChild(_mirror); 1328 | touchy(documentElement, 'add', 'mousemove', drag); 1329 | classes.add(o.mirrorContainer, 'gu-unselectable'); 1330 | drake.emit('cloned', _mirror, _item, 'mirror'); 1331 | } 1332 | 1333 | function removeMirrorImage () { 1334 | if (_mirror) { 1335 | classes.rm(o.mirrorContainer, 'gu-unselectable'); 1336 | touchy(documentElement, 'remove', 'mousemove', drag); 1337 | getParent(_mirror).removeChild(_mirror); 1338 | _mirror = null; 1339 | } 1340 | } 1341 | 1342 | function getImmediateChild (dropTarget, target) { 1343 | var immediate = target; 1344 | while (immediate !== dropTarget && getParent(immediate) !== dropTarget) { 1345 | immediate = getParent(immediate); 1346 | } 1347 | if (immediate === documentElement) { 1348 | return null; 1349 | } 1350 | return immediate; 1351 | } 1352 | 1353 | function getReference (dropTarget, target, x, y) { 1354 | var horizontal = o.direction === 'horizontal'; 1355 | var reference = target !== dropTarget ? inside() : outside(); 1356 | return reference; 1357 | 1358 | function outside () { // slower, but able to figure out any position 1359 | var len = dropTarget.children.length; 1360 | var i; 1361 | var el; 1362 | var rect; 1363 | for (i = 0; i < len; i++) { 1364 | el = dropTarget.children[i]; 1365 | rect = el.getBoundingClientRect(); 1366 | if (horizontal && (rect.left + rect.width / 2) > x) { return el; } 1367 | if (!horizontal && (rect.top + rect.height / 2) > y) { return el; } 1368 | } 1369 | return null; 1370 | } 1371 | 1372 | function inside () { // faster, but only available if dropped inside a child element 1373 | var rect = target.getBoundingClientRect(); 1374 | if (horizontal) { 1375 | return resolve(x > rect.left + getRectWidth(rect) / 2); 1376 | } 1377 | return resolve(y > rect.top + getRectHeight(rect) / 2); 1378 | } 1379 | 1380 | function resolve (after) { 1381 | return after ? nextEl(target) : target; 1382 | } 1383 | } 1384 | 1385 | function isCopy (item, container) { 1386 | return typeof o.copy === 'boolean' ? o.copy : o.copy(item, container); 1387 | } 1388 | } 1389 | 1390 | function touchy (el, op, type, fn) { 1391 | var touch = { 1392 | mouseup: 'touchend', 1393 | mousedown: 'touchstart', 1394 | mousemove: 'touchmove' 1395 | }; 1396 | var pointers = { 1397 | mouseup: 'pointerup', 1398 | mousedown: 'pointerdown', 1399 | mousemove: 'pointermove' 1400 | }; 1401 | var microsoft = { 1402 | mouseup: 'MSPointerUp', 1403 | mousedown: 'MSPointerDown', 1404 | mousemove: 'MSPointerMove' 1405 | }; 1406 | if (global.navigator.pointerEnabled) { 1407 | crossvent[op](el, pointers[type], fn); 1408 | } else if (global.navigator.msPointerEnabled) { 1409 | crossvent[op](el, microsoft[type], fn); 1410 | } else { 1411 | crossvent[op](el, touch[type], fn); 1412 | crossvent[op](el, type, fn); 1413 | } 1414 | } 1415 | 1416 | function whichMouseButton (e) { 1417 | if (e.touches !== void 0) { return e.touches.length; } 1418 | if (e.which !== void 0 && e.which !== 0) { return e.which; } // see https://github.com/bevacqua/dragula/issues/261 1419 | if (e.buttons !== void 0) { return e.buttons; } 1420 | var button = e.button; 1421 | if (button !== void 0) { // see https://github.com/jquery/jquery/blob/99e8ff1baa7ae341e94bb89c3e84570c7c3ad9ea/src/event.js#L573-L575 1422 | return button & 1 ? 1 : button & 2 ? 3 : (button & 4 ? 2 : 0); 1423 | } 1424 | } 1425 | 1426 | function getOffset (el) { 1427 | var rect = el.getBoundingClientRect(); 1428 | return { 1429 | left: rect.left + getScroll('scrollLeft', 'pageXOffset'), 1430 | top: rect.top + getScroll('scrollTop', 'pageYOffset') 1431 | }; 1432 | } 1433 | 1434 | function getScroll (scrollProp, offsetProp) { 1435 | if (typeof global[offsetProp] !== 'undefined') { 1436 | return global[offsetProp]; 1437 | } 1438 | if (documentElement.clientHeight) { 1439 | return documentElement[scrollProp]; 1440 | } 1441 | return doc.body[scrollProp]; 1442 | } 1443 | 1444 | function getElementBehindPoint (point, x, y) { 1445 | point = point || {}; 1446 | var state = point.className || ''; 1447 | var el; 1448 | point.className += ' gu-hide'; 1449 | el = doc.elementFromPoint(x, y); 1450 | point.className = state; 1451 | return el; 1452 | } 1453 | 1454 | function never () { return false; } 1455 | function always () { return true; } 1456 | function getRectWidth (rect) { return rect.width || (rect.right - rect.left); } 1457 | function getRectHeight (rect) { return rect.height || (rect.bottom - rect.top); } 1458 | function getParent (el) { return el.parentNode === doc ? null : el.parentNode; } 1459 | function isInput (el) { return el.tagName === 'INPUT' || el.tagName === 'TEXTAREA' || el.tagName === 'SELECT' || isEditable(el); } 1460 | function isEditable (el) { 1461 | if (!el) { return false; } // no parents were editable 1462 | if (el.contentEditable === 'false') { return false; } // stop the lookup 1463 | if (el.contentEditable === 'true') { return true; } // found a contentEditable element in the chain 1464 | return isEditable(getParent(el)); // contentEditable is set to 'inherit' 1465 | } 1466 | 1467 | function nextEl (el) { 1468 | return el.nextElementSibling || manually(); 1469 | function manually () { 1470 | var sibling = el; 1471 | do { 1472 | sibling = sibling.nextSibling; 1473 | } while (sibling && sibling.nodeType !== 1); 1474 | return sibling; 1475 | } 1476 | } 1477 | 1478 | function getEventHost (e) { 1479 | // on touchend event, we have to use `e.changedTouches` 1480 | // see http://stackoverflow.com/questions/7192563/touchend-event-properties 1481 | // see https://github.com/bevacqua/dragula/issues/34 1482 | if (e.targetTouches && e.targetTouches.length) { 1483 | return e.targetTouches[0]; 1484 | } 1485 | if (e.changedTouches && e.changedTouches.length) { 1486 | return e.changedTouches[0]; 1487 | } 1488 | return e; 1489 | } 1490 | 1491 | function getCoord (coord, e) { 1492 | var host = getEventHost(e); 1493 | var missMap = { 1494 | pageX: 'clientX', // IE8 1495 | pageY: 'clientY' // IE8 1496 | }; 1497 | if (coord in missMap && !(coord in host) && missMap[coord] in host) { 1498 | coord = missMap[coord]; 1499 | } 1500 | return host[coord]; 1501 | } 1502 | 1503 | module.exports = dragula; 1504 | 1505 | }).call(this)}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) 1506 | },{"./classes":8,"contra/emitter":4,"crossvent":5}],10:[function(require,module,exports){ 1507 | // shim for using process in browser 1508 | var process = module.exports = {}; 1509 | 1510 | // cached from whatever global is present so that test runners that stub it 1511 | // don't break things. But we need to wrap it in a try catch in case it is 1512 | // wrapped in strict mode code which doesn't define any globals. It's inside a 1513 | // function because try/catches deoptimize in certain engines. 1514 | 1515 | var cachedSetTimeout; 1516 | var cachedClearTimeout; 1517 | 1518 | function defaultSetTimout() { 1519 | throw new Error('setTimeout has not been defined'); 1520 | } 1521 | function defaultClearTimeout () { 1522 | throw new Error('clearTimeout has not been defined'); 1523 | } 1524 | (function () { 1525 | try { 1526 | if (typeof setTimeout === 'function') { 1527 | cachedSetTimeout = setTimeout; 1528 | } else { 1529 | cachedSetTimeout = defaultSetTimout; 1530 | } 1531 | } catch (e) { 1532 | cachedSetTimeout = defaultSetTimout; 1533 | } 1534 | try { 1535 | if (typeof clearTimeout === 'function') { 1536 | cachedClearTimeout = clearTimeout; 1537 | } else { 1538 | cachedClearTimeout = defaultClearTimeout; 1539 | } 1540 | } catch (e) { 1541 | cachedClearTimeout = defaultClearTimeout; 1542 | } 1543 | } ()) 1544 | function runTimeout(fun) { 1545 | if (cachedSetTimeout === setTimeout) { 1546 | //normal enviroments in sane situations 1547 | return setTimeout(fun, 0); 1548 | } 1549 | // if setTimeout wasn't available but was latter defined 1550 | if ((cachedSetTimeout === defaultSetTimout || !cachedSetTimeout) && setTimeout) { 1551 | cachedSetTimeout = setTimeout; 1552 | return setTimeout(fun, 0); 1553 | } 1554 | try { 1555 | // when when somebody has screwed with setTimeout but no I.E. maddness 1556 | return cachedSetTimeout(fun, 0); 1557 | } catch(e){ 1558 | try { 1559 | // When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally 1560 | return cachedSetTimeout.call(null, fun, 0); 1561 | } catch(e){ 1562 | // same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error 1563 | return cachedSetTimeout.call(this, fun, 0); 1564 | } 1565 | } 1566 | 1567 | 1568 | } 1569 | function runClearTimeout(marker) { 1570 | if (cachedClearTimeout === clearTimeout) { 1571 | //normal enviroments in sane situations 1572 | return clearTimeout(marker); 1573 | } 1574 | // if clearTimeout wasn't available but was latter defined 1575 | if ((cachedClearTimeout === defaultClearTimeout || !cachedClearTimeout) && clearTimeout) { 1576 | cachedClearTimeout = clearTimeout; 1577 | return clearTimeout(marker); 1578 | } 1579 | try { 1580 | // when when somebody has screwed with setTimeout but no I.E. maddness 1581 | return cachedClearTimeout(marker); 1582 | } catch (e){ 1583 | try { 1584 | // When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally 1585 | return cachedClearTimeout.call(null, marker); 1586 | } catch (e){ 1587 | // same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error. 1588 | // Some versions of I.E. have different rules for clearTimeout vs setTimeout 1589 | return cachedClearTimeout.call(this, marker); 1590 | } 1591 | } 1592 | 1593 | 1594 | 1595 | } 1596 | var queue = []; 1597 | var draining = false; 1598 | var currentQueue; 1599 | var queueIndex = -1; 1600 | 1601 | function cleanUpNextTick() { 1602 | if (!draining || !currentQueue) { 1603 | return; 1604 | } 1605 | draining = false; 1606 | if (currentQueue.length) { 1607 | queue = currentQueue.concat(queue); 1608 | } else { 1609 | queueIndex = -1; 1610 | } 1611 | if (queue.length) { 1612 | drainQueue(); 1613 | } 1614 | } 1615 | 1616 | function drainQueue() { 1617 | if (draining) { 1618 | return; 1619 | } 1620 | var timeout = runTimeout(cleanUpNextTick); 1621 | draining = true; 1622 | 1623 | var len = queue.length; 1624 | while(len) { 1625 | currentQueue = queue; 1626 | queue = []; 1627 | while (++queueIndex < len) { 1628 | if (currentQueue) { 1629 | currentQueue[queueIndex].run(); 1630 | } 1631 | } 1632 | queueIndex = -1; 1633 | len = queue.length; 1634 | } 1635 | currentQueue = null; 1636 | draining = false; 1637 | runClearTimeout(timeout); 1638 | } 1639 | 1640 | process.nextTick = function (fun) { 1641 | var args = new Array(arguments.length - 1); 1642 | if (arguments.length > 1) { 1643 | for (var i = 1; i < arguments.length; i++) { 1644 | args[i - 1] = arguments[i]; 1645 | } 1646 | } 1647 | queue.push(new Item(fun, args)); 1648 | if (queue.length === 1 && !draining) { 1649 | runTimeout(drainQueue); 1650 | } 1651 | }; 1652 | 1653 | // v8 likes predictible objects 1654 | function Item(fun, array) { 1655 | this.fun = fun; 1656 | this.array = array; 1657 | } 1658 | Item.prototype.run = function () { 1659 | this.fun.apply(null, this.array); 1660 | }; 1661 | process.title = 'browser'; 1662 | process.browser = true; 1663 | process.env = {}; 1664 | process.argv = []; 1665 | process.version = ''; // empty string to avoid regexp issues 1666 | process.versions = {}; 1667 | 1668 | function noop() {} 1669 | 1670 | process.on = noop; 1671 | process.addListener = noop; 1672 | process.once = noop; 1673 | process.off = noop; 1674 | process.removeListener = noop; 1675 | process.removeAllListeners = noop; 1676 | process.emit = noop; 1677 | process.prependListener = noop; 1678 | process.prependOnceListener = noop; 1679 | 1680 | process.listeners = function (name) { return [] } 1681 | 1682 | process.binding = function (name) { 1683 | throw new Error('process.binding is not supported'); 1684 | }; 1685 | 1686 | process.cwd = function () { return '/' }; 1687 | process.chdir = function (dir) { 1688 | throw new Error('process.chdir is not supported'); 1689 | }; 1690 | process.umask = function() { return 0; }; 1691 | 1692 | },{}],11:[function(require,module,exports){ 1693 | (function (setImmediate){(function (){ 1694 | var si = typeof setImmediate === 'function', tick; 1695 | if (si) { 1696 | tick = function (fn) { setImmediate(fn); }; 1697 | } else { 1698 | tick = function (fn) { setTimeout(fn, 0); }; 1699 | } 1700 | 1701 | module.exports = tick; 1702 | }).call(this)}).call(this,require("timers").setImmediate) 1703 | },{"timers":12}],12:[function(require,module,exports){ 1704 | (function (setImmediate,clearImmediate){(function (){ 1705 | var nextTick = require('process/browser.js').nextTick; 1706 | var apply = Function.prototype.apply; 1707 | var slice = Array.prototype.slice; 1708 | var immediateIds = {}; 1709 | var nextImmediateId = 0; 1710 | 1711 | // DOM APIs, for completeness 1712 | 1713 | exports.setTimeout = function() { 1714 | return new Timeout(apply.call(setTimeout, window, arguments), clearTimeout); 1715 | }; 1716 | exports.setInterval = function() { 1717 | return new Timeout(apply.call(setInterval, window, arguments), clearInterval); 1718 | }; 1719 | exports.clearTimeout = 1720 | exports.clearInterval = function(timeout) { timeout.close(); }; 1721 | 1722 | function Timeout(id, clearFn) { 1723 | this._id = id; 1724 | this._clearFn = clearFn; 1725 | } 1726 | Timeout.prototype.unref = Timeout.prototype.ref = function() {}; 1727 | Timeout.prototype.close = function() { 1728 | this._clearFn.call(window, this._id); 1729 | }; 1730 | 1731 | // Does not start the time, just sets up the members needed. 1732 | exports.enroll = function(item, msecs) { 1733 | clearTimeout(item._idleTimeoutId); 1734 | item._idleTimeout = msecs; 1735 | }; 1736 | 1737 | exports.unenroll = function(item) { 1738 | clearTimeout(item._idleTimeoutId); 1739 | item._idleTimeout = -1; 1740 | }; 1741 | 1742 | exports._unrefActive = exports.active = function(item) { 1743 | clearTimeout(item._idleTimeoutId); 1744 | 1745 | var msecs = item._idleTimeout; 1746 | if (msecs >= 0) { 1747 | item._idleTimeoutId = setTimeout(function onTimeout() { 1748 | if (item._onTimeout) 1749 | item._onTimeout(); 1750 | }, msecs); 1751 | } 1752 | }; 1753 | 1754 | // That's not how node.js implements it but the exposed api is the same. 1755 | exports.setImmediate = typeof setImmediate === "function" ? setImmediate : function(fn) { 1756 | var id = nextImmediateId++; 1757 | var args = arguments.length < 2 ? false : slice.call(arguments, 1); 1758 | 1759 | immediateIds[id] = true; 1760 | 1761 | nextTick(function onNextTick() { 1762 | if (immediateIds[id]) { 1763 | // fn.call() is faster so we optimize for the common use-case 1764 | // @see http://jsperf.com/call-apply-segu 1765 | if (args) { 1766 | fn.apply(null, args); 1767 | } else { 1768 | fn.call(null); 1769 | } 1770 | // Prevent ids from leaking 1771 | exports.clearImmediate(id); 1772 | } 1773 | }); 1774 | 1775 | return id; 1776 | }; 1777 | 1778 | exports.clearImmediate = typeof clearImmediate === "function" ? clearImmediate : function(id) { 1779 | delete immediateIds[id]; 1780 | }; 1781 | }).call(this)}).call(this,require("timers").setImmediate,require("timers").clearImmediate) 1782 | },{"process/browser.js":10,"timers":12}]},{},[1]); 1783 | -------------------------------------------------------------------------------- /dist/jkanban.min.css: -------------------------------------------------------------------------------- 1 | .kanban-container{position:relative;box-sizing:border-box;width:auto}.kanban-container *{box-sizing:border-box}.kanban-container:after{clear:both;display:block;content:""}.kanban-board{position:relative;float:left;background:#e2e4e6;transition:all .3s cubic-bezier(.23,1,.32,1)}.kanban-board.disabled-board{opacity:.3}.kanban-board.is-moving.gu-mirror{transform:rotate(3deg)}.kanban-board.is-moving.gu-mirror .kanban-drag{overflow:hidden;padding-right:50px}.kanban-board header{font-size:16px;padding:15px}.kanban-board header .kanban-title-board{font-weight:700;margin:0;padding:0;display:inline}.kanban-board header .kanban-title-button{float:right}.kanban-board .kanban-drag{min-height:200px;padding:20px}.kanban-board:after{clear:both;display:block;content:""}.kanban-item{background:#fff;padding:15px;margin-bottom:20px;transition:all .3s cubic-bezier(.23,1,.32,1);animation:append-animate .3s cubic-bezier(.23,1,.32,1)}@keyframes append-animate{from{transform:translateY(-20px)}to{transform:translateY(0)}}.kanban-item:hover{cursor:move}.kanban-item:last-child{margin:0}.kanban-item.is-moving.gu-mirror{transform:rotate(3deg);height:auto!important}.gu-mirror{position:fixed!important;margin:0!important;z-index:9999!important}.gu-hide{display:none!important}.gu-unselectable{-webkit-user-select:none!important;-moz-user-select:none!important;-ms-user-select:none!important;user-select:none!important}.gu-transit{opacity:.2!important;transform:rotate(0)!important}.drag_handler{background:#fff;border-radius:50%;width:24px;height:24px;position:relative;float:left;top:-3px;margin-right:4px}.drag_handler:hover{cursor:move}.drag_handler_icon{position:relative;display:block;background:#000;width:24px;height:2px;top:12px;transition:.5s ease-in-out}.drag_handler_icon:after,.drag_handler_icon:before{background:#000;content:'';display:block;width:100%;height:100%;position:absolute;transition:.5s ease-in-out}.drag_handler_icon:before{top:6px}.drag_handler_icon:after{bottom:6px} -------------------------------------------------------------------------------- /dist/jkanban.min.js: -------------------------------------------------------------------------------- 1 | !function(){return function e(t,n,o){function i(a,c){if(!n[a]){if(!t[a]){var d="function"==typeof require&&require;if(!c&&d)return d(a,!0);if(r)return r(a,!0);var s=new Error("Cannot find module '"+a+"'");throw s.code="MODULE_NOT_FOUND",s}var l=n[a]={exports:{}};t[a][0].call(l.exports,function(e){return i(t[a][1][e]||e)},l,l.exports,e,t,n,o)}return n[a].exports}for(var r="function"==typeof require&&require,a=0;a-1||t.setAttribute("data-"+o,n[o])}function l(t){var n="title"in t?t.title:"";if(e.options.itemHandleOptions.enabled){if(void 0!==(e.options.itemHandleOptions.customHandler||void 0))return n="
"+e.options.itemHandleOptions.customHandler.replace(/%([^%]+)%/g,function(e,n){return void 0!==t[n]?t[n]:""})+"
";var o=e.options.itemHandleOptions.customCssHandler,i=e.options.itemHandleOptions.customCssIconHandler,r=e.options.itemHandleOptions.customItemLayout;void 0===(o||void 0)&&(o="drag_handler"),void 0===(i||void 0)&&(i=o+"_icon"),void 0===(r||void 0)&&(r=""),n="
"+n+"
"}return n}arguments[0]&&"object"==typeof arguments[0]&&(this.options=function(e,t){var n;for(n in t)t.hasOwnProperty(n)&&(e[n]=t[n]);return e}(i,arguments[0])),this.__getCanMove=function(t){return e.options.itemHandleOptions.enabled?e.options.itemHandleOptions.handleClass?t.classList.contains(e.options.itemHandleOptions.handleClass):t.classList.contains("item_handle"):!!e.options.dragItems},this.init=function(){!function(){e.element=document.querySelector(e.options.element);var t=document.createElement("div");t.classList.add("kanban-container"),e.container=t,document.querySelector(e.options.element).dataset.hasOwnProperty("board")?(url=document.querySelector(e.options.element).dataset.board,window.fetch(url,{method:"GET",headers:{"Content-Type":"application/json"}}).then(function(t){t.json().then(function(t){e.options.boards=t,e.addBoards(e.options.boards,!0)})}).catch(function(e){console.log("Error: ",e)})):e.addBoards(e.options.boards,!0);e.element.appendChild(e.container)}(),window.innerWidth>e.options.responsive&&(e.drakeBoard=e.dragula([e.container],{moves:function(t,n,o,i){return!!e.options.dragBoards&&(o.classList.contains("kanban-board-header")||o.classList.contains("kanban-title-board"))},accepts:function(e,t,n,o){return t.classList.contains("kanban-container")},revertOnSpill:!0,direction:"horizontal"}).on("drag",function(t,n){t.classList.add("is-moving"),e.options.dragBoard(t,n),"function"==typeof t.dragfn&&t.dragfn(t,n)}).on("dragend",function(t){!function(){for(var t=1,n=0;n-1)e.drake.cancel(!0);else{t.classList.add("is-moving"),e.options.dragEl(t,n);var i=d(n.parentNode.dataset.id);void 0!==i.dragTo&&e.options.boards.map(function(t){-1===i.dragTo.indexOf(t.id)&&t.id!==n.parentNode.dataset.id&&e.findBoard(t.id).classList.add("disabled-board")}),null!==t&&"function"==typeof t.dragfn&&t.dragfn(t,n)}}).on("dragend",function(t){e.options.dragendEl(t),null!==t&&"function"==typeof t.dragendfn&&t.dragendfn(t)}).on("drop",function(t,n,o,i){e.enableAllBoards();var r=d(o.parentNode.dataset.id);(void 0!==r.dragTo&&-1===r.dragTo.indexOf(n.parentNode.dataset.id)&&n.parentNode.dataset.id!==o.parentNode.dataset.id&&e.drake.cancel(!0),null!==t)&&(!1===e.options.dropEl(t,n,o,i)&&e.drake.cancel(!0),t.classList.remove("is-moving"),"function"==typeof t.dropfn&&t.dropfn(t,n,o,i))}))},this.enableAllBoards=function(){var e=document.querySelectorAll(".kanban-board");if(e.length>0&&void 0!==e)for(var t=0;te.options.responsive)var o=(100-2*t.length)/t.length;else o=100-2*t.length;else o=e.options.widthBoard;var i=e.options.itemAddOptions.enabled,d=e.options.itemAddOptions.content,u=e.options.itemAddOptions.class,f=e.options.itemAddOptions.footer;for(var p in t){var v=t[p];n||e.options.boards.push(v),e.options.responsivePercentage||(""===e.container.style.width?e.container.style.width=parseInt(o)+2*parseInt(e.options.gutter)+"px":e.container.style.width=parseInt(e.container.style.width)+parseInt(o)+2*parseInt(e.options.gutter)+"px");var m=document.createElement("div");m.dataset.id=v.id,m.dataset.order=e.container.childNodes.length+1,m.classList.add("kanban-board"),e.options.responsivePercentage?m.style.width=o+"%":m.style.width=o,m.style.marginLeft=e.options.gutter,m.style.marginRight=e.options.gutter;var h=document.createElement("header");if(""!==v.class&&void 0!==v.class)var g=v.class.split(",");else g=[];h.classList.add("kanban-board-header"),g.map(function(e){e=e.replace(/^[ ]+/g,""),h.classList.add(e)}),h.innerHTML='
'+v.title+"
";var y=document.createElement("main");if(y.classList.add("kanban-drag"),""!==v.bodyClass&&void 0!==v.bodyClass)var b=v.bodyClass.split(",");else b=[];for(var w in b.map(function(e){y.classList.add(e)}),e.boardContainer.push(y),v.item){var E=v.item[w],T=document.createElement("div");T.classList.add("kanban-item"),E.id&&(T.dataset.eid=E.id),E.class&&Array.isArray(E.class)&&E.class.forEach(function(e){T.classList.add(e)}),T.innerHTML=l(E),T.clickfn=E.click,T.contextfn=E.context,T.dragfn=E.drag,T.dragendfn=E.dragend,T.dropfn=E.drop,s(T,E),r(T),a(T),e.options.itemHandleOptions.enabled&&(T.style.cursor="default"),y.appendChild(T)}var x=document.createElement("footer");if(i){var C=document.createElement("BUTTON"),O=document.createTextNode(d||"+");C.setAttribute("class",u||"kanban-title-button btn btn-default btn-xs"),C.appendChild(O),f?x.appendChild(C):h.appendChild(C),c(C,v.id)}m.appendChild(h),m.appendChild(y),m.appendChild(x),e.container.appendChild(m)}return e},this.findBoard=function(t){return e.element.querySelector('[data-id="'+t+'"]')},this.getParentBoardID=function(t){return"string"==typeof t&&(t=e.element.querySelector('[data-eid="'+t+'"]')),null===t?null:t.parentNode.parentNode.dataset.id},this.moveElement=function(e,t,n){if(e!==this.getParentBoardID(t))return this.removeElement(t),this.addElement(e,n)},this.replaceElement=function(t,n){var o=t;return"string"==typeof o&&(o=e.element.querySelector('[data-eid="'+t+'"]')),o.innerHTML=l(n),o.clickfn=n.click,o.contextfn=n.context,o.dragfn=n.drag,o.dragendfn=n.dragend,o.dropfn=n.drop,s(o,n),r(o),a(o),e},this.findElement=function(t){return e.element.querySelector('[data-eid="'+t+'"]')},this.getBoardElements=function(t){return e.element.querySelector('[data-id="'+t+'"] .kanban-drag').childNodes},this.removeElement=function(t){return"string"==typeof t&&(t=e.element.querySelector('[data-eid="'+t+'"]')),null!==t&&("function"==typeof t.remove?t.remove():t.parentNode.removeChild(t)),e},this.removeBoard=function(t){var n=null;"string"==typeof t&&(n=e.element.querySelector('[data-id="'+t+'"]')),null!==n&&("function"==typeof n.remove?n.remove():n.parentNode.removeChild(n));for(var o=0;o0?e:A.revertOnSpill,n=L||E,o=h(n),i=$(o);!1===i&&t&&(L?o&&o.removeChild(L):w.insertBefore(n,k)),i||t?_.emit("cancel",n,w,w):_.emit("drop",n,o,w,S),G()}}function G(){var e=L||E;U(),n&&(r.rm(A.mirrorContainer,"gu-unselectable"),d(c,"remove","mousemove",Q),h(n).removeChild(n),n=null),e&&r.rm(e,"gu-transit"),B&&clearTimeout(B),_.dragging=!1,I&&_.emit("out",e,I,w),_.emit("dragend",e),w=E=L=k=S=B=I=null}function $(e,t){var o;return o=void 0!==t?t:n?S:y(L||E),e===w&&o===k}function J(e,t,n){for(var o=e;o&&!i();)o=h(o);return o;function i(){if(!1===H(o))return!1;var i=Z(o,e),r=ee(o,i,t,n);return!!$(o,r)||A.accepts(E,o,w,r)}}function Q(e){if(n){e.preventDefault();var t=b("clientX",e)||0,o=b("clientY",e)||0,i=t-T,r=o-x;n.style.left=i+"px",n.style.top=r+"px";var a=L||E,c=u(n,t,o),d=J(c,t,o),s=null!==d&&d!==I;(s||null===d)&&(I&&v("out"),I=d,s&&v("over"));var l=h(a);if(d!==w||!L||A.copySortSource){var f,p=Z(d,c);if(null!==p)f=ee(d,p,t,o);else{if(!0!==A.revertOnSpill||L)return void(L&&l&&l.removeChild(a));f=k,d=w}(null===f&&s||f!==a&&f!==y(a))&&(S=f,d.insertBefore(a,f),_.emit("shadow",a,d,w))}else l&&l.removeChild(a)}function v(e){_.emit(e,a,I,w)}}function Z(e,t){for(var n=t;n!==e&&h(n)!==e;)n=h(n);return n===c?null:n}function ee(e,t,n,o){var i,r="horizontal"===A.direction;return t!==e?(i=t.getBoundingClientRect(),a(r?n>i.left+v(i)/2:o>i.top+m(i)/2)):function(){var t,i,a,c=e.children.length;for(t=0;tn)return i;if(!r&&a.top+a.height/2>o)return i}return null}();function a(e){return e?y(t):t}}}}).call(this)}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{"./classes":8,"contra/emitter":4,crossvent:5}],10:[function(e,t,n){var o,i,r=t.exports={};function a(){throw new Error("setTimeout has not been defined")}function c(){throw new Error("clearTimeout has not been defined")}function d(e){if(o===setTimeout)return setTimeout(e,0);if((o===a||!o)&&setTimeout)return o=setTimeout,setTimeout(e,0);try{return o(e,0)}catch(t){try{return o.call(null,e,0)}catch(t){return o.call(this,e,0)}}}!function(){try{o="function"==typeof setTimeout?setTimeout:a}catch(e){o=a}try{i="function"==typeof clearTimeout?clearTimeout:c}catch(e){i=c}}();var s,l=[],u=!1,f=-1;function p(){u&&s&&(u=!1,s.length?l=s.concat(l):f=-1,l.length&&v())}function v(){if(!u){var e=d(p);u=!0;for(var t=l.length;t;){for(s=l,l=[];++f1)for(var n=1;n=0&&(e._idleTimeoutId=setTimeout(function(){e._onTimeout&&e._onTimeout()},t))},n.setImmediate="function"==typeof t?t:function(e){var t=d++,o=!(arguments.length<2)&&a.call(arguments,1);return c[t]=!0,i(function(){c[t]&&(o?e.apply(null,o):e.call(null),n.clearImmediate(t))}),t},n.clearImmediate="function"==typeof o?o:function(e){delete c[e]}}).call(this)}).call(this,e("timers").setImmediate,e("timers").clearImmediate)},{"process/browser.js":10,timers:12}]},{},[1]); -------------------------------------------------------------------------------- /example/drag_auto_scroll.js: -------------------------------------------------------------------------------- 1 | function drag_auto_scroll(el_drg) { 2 | //Thanks to StackOverFlow 3 | //Thanks to Peter-Paul Koch for Some of his concepts borrowed from http://www.quirksmode.org/js/dragdrop.html 4 | this.addEventSimple = function (obj, evt, fn) { 5 | if (obj.addEventListener) 6 | obj.addEventListener(evt, fn, false); 7 | else if (obj.attachEvent) 8 | obj.attachEvent('on' + evt, fn); 9 | }; 10 | this.removeEventSimple = function (obj, evt, fn) { 11 | if (obj.removeEventListener) 12 | obj.removeEventListener(evt, fn, false); 13 | else if (obj.detachEvent) 14 | obj.detachEvent('on' + evt, fn); 15 | }; 16 | this.fade = function (el_f, o) { 17 | var oo = o / 100; 18 | el_f.style.opacity = oo; 19 | el_f.style['-ms-filter'] = "progid:DXImageTransform.Microsoft.Alpha(Opacity=" + o + ")"; 20 | el_f.style['-khtml-opacity'] = oo; 21 | el_f.style['-moz-opacity'] = oo; 22 | el_f.style.filter = "Alpha(Opacity=" + o + "); -moz-opacity:" + oo + "; opacity:" + oo + ";-khtml-opacity:" + oo + ";"; 23 | if (o >= 100) { 24 | el_f.style.filter = ''; 25 | } 26 | }; 27 | this.intersectRect = function (r1, r2) { 28 | return !(r2.left > r1.right || 29 | r2.right < r1.left || 30 | r2.top > r1.bottom || 31 | r2.bottom < r1.top); 32 | }; 33 | this.getposition=function(el){ 34 | var rect = el.getBoundingClientRect(), 35 | scrollLeft = window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft, 36 | scrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop; 37 | var l, t, w, h, sw, sh, sbw, sbh; 38 | if (el == document.body) { 39 | l = 0; 40 | t = 0; 41 | w = document.body.clientWidth || document.documentElement.clientWidth || window.innerWidth ;//don't change this order 42 | h = document.body.clientHeight || document.documentElement.clientHeight || window.innerHeight; //don't change this order 43 | sw = Math.max(document.body.scrollWidth, document.documentElement.scrollWidth); 44 | sh = Math.max(document.body.scrollHeight, document.documentElement.scrollHeight); 45 | sbw = (document.documentElement.clientWidth - document.body.offsetWidth); //16 46 | sbh = (document.documentElement.clientHeight - document.body.offsetHeight); //16 47 | } else { 48 | l=rect.left + scrollLeft; 49 | t=rect.top + scrollTop ; 50 | w=rect.right - rect.left; 51 | h = rect.bottom - rect.top; 52 | sw = el.scrollWidth; 53 | sh = el.scrollHeight; 54 | sbw = el.offsetWidth - el.clientWidth; //17 55 | sbh = el.offsetHeight - el.clientHeight; //17 56 | } 57 | return { left: l, top: t 58 | , width: w, height: h 59 | , right: l + w 60 | , bottom: t + h 61 | , scrollLeft: scrollLeft 62 | , scrollTop: scrollTop 63 | , scrollWidth: sw 64 | , scrollHeight: sh 65 | }; 66 | }; 67 | this.getoffset=function(){ 68 | var el=arguments[0]; 69 | var rect = el.getBoundingClientRect(); 70 | var includescroll=(arguments.length==1 || arguments[1]!==false); 71 | var l,t,w, h,scrollLeft=0,scrollTop=0,isbody=false,sw,sh,sbw,sbh; 72 | if (el == document.body) { 73 | scrollLeft = window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft; 74 | scrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop; 75 | isbody = true; 76 | l = (includescroll==true?scrollLeft:0);//scrollLeft// 77 | t= (includescroll==true?scrollTop:0) ;//scrollTop// 78 | w = document.body.clientWidth || document.documentElement.clientWidth || window.innerWidth ;//don't change this order 79 | h = document.body.clientHeight || document.documentElement.clientHeight || window.innerHeight; //don't change this order 80 | sw = Math.max(document.body.scrollWidth, document.documentElement.scrollWidth); 81 | sh = Math.max(document.body.scrollHeight, document.documentElement.scrollHeight); 82 | sbw = (document.documentElement.clientWidth - document.body.offsetWidth); //16 83 | sbh = (document.documentElement.clientHeight - document.body.offsetHeight); //16 84 | } else { 85 | scrollLeft = el.parentNode.scrollLeft; 86 | scrollTop = el.parentNode.scrollTop; 87 | l=rect.left + (includescroll==true?scrollLeft:0); 88 | t=rect.top + (includescroll==true?scrollTop:0) ; 89 | w=rect.right - rect.left; 90 | h = rect.bottom - rect.top; 91 | sw = el.parentNode.scrollWidth; 92 | sh = el.parentNode.scrollHeight; 93 | sbw = el.parentNode.offsetWidth - el.parentNode.clientWidth; //17 94 | sbh = el.parentNode.offsetHeight - el.parentNode.clientHeight; //17 95 | } 96 | return { left: l, top: t 97 | , width: w, height: h 98 | , offsetLeft: (isbody == true ?0:rect.left), offsetTop:(isbody == true ?0: rect.top) 99 | , right: l + w 100 | , bottom: t + h 101 | , scrollLeft: scrollLeft 102 | , scrollTop: scrollTop 103 | , isBody: isbody 104 | , scrollWidth: sw 105 | , scrollHeight: sh 106 | , scrollBarWidth: sbw 107 | , scrollBarHeight: sbh 108 | }; 109 | }; 110 | this.rectoverlap = function (target, drgpos, xpos, ypos) { 111 | var targetpos = {}; 112 | if (target.getBoundingClientRect) { 113 | targetpos = target.getBoundingClientRect(); 114 | } 115 | var x_overlap = Math.max(0, Math.min(drgpos.right, targetpos.right) - Math.max(drgpos.left, targetpos.left)) 116 | var y_overlap = Math.max(0, Math.min(drgpos.bottom, targetpos.bottom) - Math.max(drgpos.top, targetpos.top)); 117 | var lp = Math.floor(x_overlap / Math.min((drgpos.right - drgpos.left), (targetpos.right - targetpos.left)) * 100); 118 | var tp = Math.floor(y_overlap / Math.min((drgpos.bottom - drgpos.top), (targetpos.bottom - targetpos.top)) * 100); 119 | if (lp > 0 && tp > 0) { 120 | // if (this.intersectRect(drgpos, targetpos) == true) { 121 | return { target: target, lp: lp, tp: tp }; 122 | } 123 | return null; 124 | }; /*rectoverlap*/ 125 | this.hittest = function (target, x, y ) { 126 | var offset=this.getposition(target); 127 | x+=offset.scrollLeft; 128 | y+=offset.scrollTop; 129 | //info.innerHTML=scrollLeft+'x'+scrollTop; 130 | //info.innerHTML+='
'+JSON.stringify(offset); 131 | //info.innerHTML+='
'+x+'x'+y; 132 | if ((x > offset.left && y > offset.top) && (x < offset.right && y < offset.bottom)) { 133 | var lp = Math.floor(Math.min(x-offset.left,offset.right-x )*2/ offset.width * 100); 134 | var tp = Math.floor(Math.min(y-offset.top,offset.bottom-y )*2 / offset.height * 100); 135 | //info.innerHTML+='
'+lp+'x'+tp; 136 | return { target: target, lp: lp, tp: tp }; 137 | } 138 | return null; 139 | }; /*hittest*/ 140 | this.getevent = function (e) { 141 | e = e || window.event; 142 | // if (e.originalEvent && e.originalEvent.touches) { 143 | // var oe = e.originalEvent; 144 | // e = oe.touches.length ? oe.touches[0] : oe.changedTouches[0]; 145 | // } 146 | if (e.touches) { 147 | e = e.touches[0]; 148 | } 149 | }; 150 | this.scrollLeft = function (el_s, left, top) { 151 | if (el_s == document.body || el_s == null) { 152 | if (left == undefined) { 153 | //return (window.pageXOffset !== undefined) ? window.pageXOffset : (document.body || document.documentElement || document.body.parentNode).scrollLeft; 154 | return window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft; 155 | } else { 156 | document.body.scrollLeft = left; //Chrome 157 | document.documentElement.scrollLeft = left; //IE8 158 | // window.scrollTo(left, top); //All 159 | } 160 | } else { 161 | if (left == undefined) { 162 | return el_s.scrollLeft; 163 | } else { 164 | el_s.scrollLeft = left; 165 | } 166 | } 167 | }; 168 | this.scrollTop = function (el_s, top, left) { 169 | if (el_s == document.body || el_s == null) { 170 | if (top == undefined) { 171 | //return (window.pageYOffset !== undefined) ? window.pageYOffset : (document.body || document.documentElement || document.body.parentNode).scrollTop; 172 | return window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop; 173 | } else { 174 | document.body.scrollTop = top; //Chrome 175 | document.documentElement.scrollTop = top; //IE8 176 | // window.scrollTo(left, top); //All 177 | } 178 | } else { 179 | if (top == undefined) { 180 | return el_s.scrollTop; 181 | } else { 182 | el_s.scrollTop = top; 183 | } 184 | } 185 | }; 186 | this.extend = function extend(obj, src) { 187 | for (var key in src) { 188 | try { 189 | // if (src.hasOwnProperty(key)) obj[key] = src[key]; 190 | obj[key] = src[key]; 191 | } catch (e) { } 192 | } 193 | return obj; 194 | }; 195 | this.autoscroll = function (offset, poffset, parentNode) { 196 | //info.innerHTML=JSON.stringify(offset); 197 | //info.innerHTML+='
'+JSON.stringify(poffset); 198 | // var xb = this.xcell; 199 | // var yb = this.ycell; 200 | var xb = 0; 201 | var yb = 0; 202 | // var xb = -this.xcell; 203 | // var yb = -this.ycell; 204 | if (poffset.isBody == true) { 205 | var scrollLeft = poffset.scrollLeft; 206 | var scrollTop = poffset.scrollTop; 207 | //var scrollbarwidth = window.pageXOffset ? (document.documentElement.scrollWidth - document.body.clientWidth) : (document.documentElement.clientWidth - document.body.offsetWidth); //Chrome & IE 16 208 | // info.innerHTML = '
' + (document.documentElement.scrollWidth - document.body.clientWidth); 209 | // info.innerHTML += '
' + (document.documentElement.clientWidth - document.body.offsetWidth);//IE 210 | var scrollbarwidth = (document.documentElement.clientWidth - document.body.offsetWidth); //All 211 | var scrollspeed = (offset.right + xb) - (poffset.right + scrollbarwidth); 212 | //info.innerHTML+='
'+scrollspeed; 213 | if (scrollspeed > 0) { 214 | this.scrollLeft(parentNode, scrollLeft + scrollspeed); 215 | } 216 | scrollspeed = offset.left - (xb); 217 | if (scrollspeed < 0) { 218 | this.scrollLeft(parentNode, scrollLeft + scrollspeed); 219 | } 220 | scrollspeed = (offset.bottom + yb) - (poffset.bottom); 221 | //info.innerHTML+='
'+scrollspeed; 222 | if (scrollspeed > 0) { 223 | this.scrollTop(parentNode, scrollTop + scrollspeed); 224 | } 225 | scrollspeed = offset.top - (yb); 226 | if (scrollspeed < 0) { 227 | this.scrollTop(parentNode, scrollTop + scrollspeed); 228 | } 229 | } else { 230 | var scrollLeft = offset.scrollLeft; 231 | var scrollTop = offset.scrollTop; 232 | var scrollbarwidth = parentNode.offsetWidth - parentNode.clientWidth; //17 233 | var scrollbarheight = parentNode.offsetHeight - parentNode.clientHeight; //17 234 | var scrollspeed = (offset.right + xb) - (poffset.right - scrollbarwidth); 235 | //info.innerHTML+='
'+scrollspeed; 236 | if (scrollspeed > 0) { 237 | this.scrollLeft(parentNode, scrollLeft + scrollspeed); 238 | } 239 | scrollspeed = offset.left - (xb + poffset.left); 240 | if (scrollspeed < 0) { 241 | this.scrollLeft(parentNode, scrollLeft + scrollspeed); 242 | } 243 | scrollspeed = (offset.bottom + scrollbarheight + yb) - (poffset.bottom); 244 | //info.innerHTML+='
'+scrollspeed; 245 | if (scrollspeed > 0) { 246 | this.scrollTop(parentNode, scrollTop + scrollspeed); 247 | } 248 | scrollspeed = offset.top - (yb + poffset.top); 249 | if (scrollspeed < 0) { 250 | this.scrollTop(parentNode, scrollTop + scrollspeed); 251 | } 252 | } 253 | }; 254 | 255 | this.el = el_drg; 256 | this.dragstarted = false; 257 | this.dragmoved = false; 258 | this.droppables = []; 259 | this.dropinfo = null; 260 | this.helper = null; 261 | this.xcell = 1;//snap to grid x must be >=1 262 | this.ycell = 1; //snap to grid y must be >=1 263 | this.parentNode = null; 264 | this.forceobjectmove = false; 265 | this.dragforce = 1; 266 | this.restricttoscrollview = true; 267 | this.restrictlimit = null; 268 | this.draghandle = null; 269 | this.axis = 'both'; //'both,x,y 270 | this.keyboardcontrol = false; 271 | this.keySpeed = 10; 272 | this.keys_on = false; 273 | this.start = function (e) { 274 | e = e || window.event; 275 | var relTarg = e.relatedTarget || e.fromElement; 276 | var relTarg = e.relatedTarget || e.toElement; 277 | this.mx = e.clientX; 278 | this.my = e.clientY; 279 | // e.preventDefault(); 280 | (e.preventDefault) ? e.preventDefault() : e.returnValue = false; 281 | if (this.dragstarted == true) { 282 | return this; 283 | } 284 | this.dragstarted = true; 285 | this.dragmoved = false; 286 | document.onselectstart = function (e) { 287 | return false; 288 | }; 289 | if (this.mousedown) { 290 | this.mousedown(e); 291 | } 292 | this.kx = 0; 293 | this.ky = 0; 294 | if (this.keyboardcontrol == true) { 295 | // this.starte = e; 296 | this.cur_e = this.extend({}, e); 297 | this.starte = null; 298 | // this.cur_e = null; 299 | this.canmove = true; 300 | this.keys_on = false; 301 | this.l_l = true; 302 | this.w_l = true; 303 | this.t_l = true; 304 | this.h_l = true; 305 | this.el.focus(); 306 | } 307 | return false; 308 | }; 309 | this.move = function (e) { 310 | e = e || window.event; 311 | //if (this.dragstarted == true) { 312 | // this.el.innerHTML = 'move ' + e.clientX; 313 | //} 314 | if (this.keys_on == true) { 315 | if (!e.keys_on) { 316 | return; 317 | } 318 | } 319 | if (this.dragstarted == true && (this.dragmoved == false && !e.keys_on ? (Math.abs(this.mx - e.clientX) > this.dragforce || Math.abs(this.my - e.clientY) > this.dragforce) : true)) { 320 | // this.kx = e.clientX; 321 | // this.ky = e.clientY; 322 | // e.preventDefault(); 323 | if (!e.keys_on) { 324 | (e.preventDefault) ? e.preventDefault() : e.returnValue = false; 325 | } 326 | if (this.dragmoved == false) { 327 | var offset = {}; 328 | var parentNode; 329 | var eld; 330 | if (this.helper) { 331 | eld = this.helper; 332 | if (this.helper.style.left == '') { 333 | offset = this.getposition(this.el); 334 | if (this.helper.parentNode != document.body) { 335 | var poffset = this.helper.parentNode.getBoundingClientRect(); 336 | offset.left -= poffset.left + offset.scrollLeft; 337 | offset.top -= poffset.top + offset.scrollTop; 338 | } 339 | var scrollLeft = this.scrollLeft(this.el.parentNode); 340 | var scrollTop = this.scrollTop(this.el.parentNode); 341 | offset.left += scrollLeft; 342 | offset.top += scrollTop; 343 | this.helper.style.left = (offset.left) + 'px'; //+this.scrollLeft(this.el.parentNode) 344 | this.helper.style.top = (offset.top) + 'px'; //+this.scrollTop(this.el.parentNode) 345 | } 346 | this.helper.style.display = ''; 347 | } else { 348 | eld = this.el; 349 | } 350 | if (this.draghandle) { 351 | eld = this.draghandle; 352 | } 353 | parentNode = eld.parentNode; 354 | offset = eld.getBoundingClientRect(); 355 | if (this.parentNode) { 356 | parentNode = this.parentNode; 357 | } 358 | var ol = 0, ot = 0; 359 | this.parents = []; 360 | if (parentNode != document.body) { 361 | var poffset = parentNode.getBoundingClientRect(); 362 | ol = poffset.left; 363 | ot = poffset.top; 364 | var obj = parentNode; 365 | while (obj.offsetParent) { 366 | this.parents.push(obj); 367 | if (obj == document.body) { 368 | break; 369 | } 370 | obj = obj.offsetParent; 371 | } 372 | // if (obj == document.body) { 373 | // this.parents.push(obj); 374 | // } 375 | } 376 | var scrollLeft = this.scrollLeft(parentNode); 377 | var scrollTop = this.scrollTop(parentNode); 378 | this.initialMouseX = scrollLeft + e.clientX; 379 | this.initialMouseY = scrollTop + e.clientY; 380 | this.startX = offset.left + scrollLeft; 381 | this.startY = offset.top + scrollTop; 382 | this.ol = ol; 383 | this.ot = ot; 384 | var dx = this.startX - this.ol + scrollLeft + e.clientX - this.initialMouseX; 385 | var dy = this.startY - this.ot + scrollTop + e.clientY - this.initialMouseY; 386 | dx = Math.floor(dx / this.xcell) * this.xcell; 387 | dy = Math.floor(dy / this.ycell) * this.ycell; 388 | if (parentNode == document.body) { 389 | this.sw = Math.max(document.body.scrollWidth, document.documentElement.scrollWidth); 390 | this.sh = Math.max(document.body.scrollHeight, document.documentElement.scrollHeight); 391 | } else { 392 | this.sw = parentNode.scrollWidth; 393 | this.sh = parentNode.scrollHeight; 394 | } 395 | this.dx = dx; 396 | this.dy = dy; 397 | if (this.beforedrag) { 398 | if (this.beforedrag(dx, dy, scrollLeft + e.clientX, scrollTop + e.clientY, e) === false) { 399 | this.dragmoved = true; 400 | this.end(e); 401 | return false; 402 | } 403 | } 404 | if (this.keyboardcontrol == true) { 405 | this.starte = e; 406 | this.cur_e = this.extend({}, this.starte); 407 | } 408 | } //this.dragmoved==false 409 | this.dragmoved = true; 410 | var parentNode; 411 | var eld; 412 | if (this.helper) { 413 | eld = this.helper; 414 | } else { 415 | eld = this.el; 416 | } 417 | if (this.draghandle) { 418 | eld = this.draghandle; 419 | } 420 | parentNode = eld.parentNode; 421 | if (this.parentNode) { 422 | parentNode = this.parentNode; 423 | } 424 | var offset = this.getoffset(eld, false); 425 | var poffset = this.getoffset(parentNode, false); 426 | for (var i = 0; i < this.parents.length; i++) { 427 | var o1 = this.getoffset(this.parents[i], false); 428 | var o2 = this.getoffset(this.parents[i].parentNode, false); 429 | o1.left = offset.left; 430 | o1.right = offset.right; 431 | o1.top = offset.top; 432 | o1.bottom = offset.bottom; 433 | var scrollspeed = (o1.right + this.xcell) - (o2.right - 16); 434 | this.autoscroll(o1, o2, this.parents[i].parentNode); 435 | } 436 | if (this.parents.length > 0) { 437 | //offset = this.getoffset(eld,false); 438 | //poffset = this.getoffset(parentNode,false); 439 | if (parentNode != document.body) { 440 | var poffset = parentNode.getBoundingClientRect(); 441 | this.ol = poffset.left; 442 | this.ot = poffset.top; 443 | } 444 | } 445 | var scrollLeft = this.scrollLeft(parentNode); 446 | var scrollTop = this.scrollTop(parentNode); 447 | var dx = this.startX - this.ol + scrollLeft + e.clientX - this.initialMouseX; 448 | var dy = this.startY - this.ot + scrollTop + e.clientY - this.initialMouseY; 449 | dx = Math.floor(dx / this.xcell) * this.xcell; 450 | dy = Math.floor(dy / this.ycell) * this.ycell; 451 | var b = true; 452 | this.l_l = false; 453 | this.w_l = false; 454 | this.t_l = false; 455 | this.h_l = false; 456 | if (this.restricttoscrollview == true) { 457 | //info.innerHTML = (dx + offset.width - this.xcell) + 'x' + (this.sw); 458 | if ((dx + offset.width - this.xcell) > (this.sw)) { 459 | // b = false; 460 | dx = this.sw - offset.width; 461 | // this.kx -= (this.keySpeed*1); 462 | this.w_l = true; 463 | } 464 | if (dx < 0) { 465 | // b = false; 466 | dx = 0; 467 | // this.kx += (this.keySpeed * 1); 468 | this.l_l = true; 469 | } 470 | if ((dy + offset.height - this.ycell) > (this.sh)) { 471 | // b = false; 472 | dy = this.sh - offset.height; 473 | // this.ky -= (this.keySpeed * 1); 474 | this.h_l = true; 475 | } 476 | if (dy < 0) { 477 | // b = false; 478 | dy = 0; 479 | // this.ky += (this.keySpeed * 1); 480 | this.t_l = true; 481 | } 482 | } 483 | if (this.restrictlimit) { 484 | if ((dx + offset.width - this.xcell) > (this.restrictlimit.width)) { 485 | dx = this.restrictlimit.width - offset.width; 486 | // this.kx -= (this.keySpeed * 2); 487 | this.w_l = true; 488 | } 489 | if (dx < this.restrictlimit.left) { 490 | dx = this.restrictlimit.left; 491 | // this.kx += (this.keySpeed * 2); 492 | this.l_l = true; 493 | } 494 | if ((dy + offset.height - this.ycell) > (this.restrictlimit.height)) { 495 | dy = this.restrictlimit.height - offset.height; 496 | // this.ky -= (this.keySpeed * 2); 497 | this.h_l = true; 498 | } 499 | if (dy < this.restrictlimit.top) { 500 | dy = this.restrictlimit.top; 501 | // this.ky += (this.keySpeed * 2); 502 | this.t_l = true; 503 | } 504 | } 505 | if (this.keyboardcontrol == true) { 506 | // this.kx = this.startX - this.ol + scrollLeft + e.clientX - this.initialMouseX; 507 | // this.ky = this.startY - this.ot + scrollTop + e.clientY - this.initialMouseY; 508 | // info.innerHTML = e.clientX + 'x' + (dx - (this.startX - this.ol + scrollLeft - this.initialMouseX)); 509 | // this.kx = dx - (this.startX + this.ol + scrollLeft - this.initialMouseX); 510 | // this.ky = dy - (this.startY + this.ot + scrollTop - this.initialMouseY); 511 | // this.kx = dx - (this.startX - this.initialMouseX+ this.ol + scrollLeft); 512 | // this.ky = dy - (this.startY - this.initialMouseY+ this.ot + scrollTop); 513 | // if (this.kx < (poffset.left - (this.starte.clientX))) { 514 | // this.kx = (poffset.left - (this.starte.clientX)); 515 | // } 516 | this.cur_e.clientX=(dx - (this.startX - this.ol + scrollLeft - this.initialMouseX)); 517 | } 518 | if (b == true) { 519 | if (this.axis == 'both' || this.axis == 'x') { 520 | eld.style.left = dx + 'px'; 521 | } 522 | if (this.axis == 'both' || this.axis == 'y') { 523 | eld.style.top = dy + 'px'; 524 | } 525 | this.autoscroll(offset, poffset, parentNode); 526 | this.dx = dx; 527 | this.dy = dy; 528 | this.dropinfo = null; 529 | for (var n = this.droppables.length - 1; n >= 0; n--) { 530 | // this.droppables[n].style.border = '1px solid black'; 531 | if (this.droppables[n] != this.el) { 532 | //var x=dx+this.initialMouseX-this.startX-this.ol; 533 | //var y=dy+this.initialMouseY-this.startY-this.ot; 534 | var x = e.clientX; 535 | var y = e.clientY; 536 | this.dropinfo = this.hittest(this.droppables[n], x, y); 537 | if (this.dropinfo !== null) { 538 | // dropped.target.style.border = '1px solid red'; 539 | break; 540 | } 541 | } 542 | } 543 | if (this.dragover) { 544 | this.dragover(this.dropinfo, this.el, dx, dy, scrollLeft + e.clientX, scrollTop + e.clientY, e); 545 | } 546 | } 547 | } 548 | }; 549 | this.end = function (e) { 550 | e = e || window.event; 551 | //this.el.innerHTML = 'end ' + e.clientX; 552 | if (this.dragstarted == true) { 553 | this.dragstarted = false; 554 | document.onselectstart = null; 555 | if (this.dragmoved == true) { 556 | (e.preventDefault) ? e.preventDefault() : e.returnValue = false; 557 | e.cancelBubble = true; 558 | if (e.stopPropagation) e.stopPropagation(); 559 | if (this.helper) { 560 | if (this.forceobjectmove == true && this.dragmoved == true) { 561 | this.el.style.left = this.dx + 'px'; 562 | this.el.style.top = this.dy + 'px'; 563 | } 564 | this.helper.style.display = 'none'; 565 | } 566 | if (this.dragdrop) { 567 | this.dragdrop(this.dropinfo, this.el, this.dx, this.dy, e); 568 | } 569 | this.dragmoved = false; 570 | this.canmove = false; 571 | return false; 572 | } else { 573 | if (this.click) { 574 | this.click(e); 575 | } 576 | } 577 | } 578 | if (this.mouseup) { 579 | this.mouseup(e); 580 | } 581 | }; 582 | this.keycontrol = function (e) { 583 | e = e || window.event; 584 | if (this.canmove !== true) { 585 | return true; 586 | } 587 | this.dragstarted = true; 588 | this.keys_on = true; 589 | if (this.starte) { 590 | var key = e.keyCode; 591 | switch (key) { 592 | case 37: case 63234: // left 593 | if (this.l_l == false) { 594 | // this.kx -= ; 595 | this.cur_e.clientX -= this.keySpeed ; 596 | } 597 | break; 598 | case 39: case 63235: // right 599 | if (this.w_l == false) { 600 | // this.kx += this.keySpeed; 601 | this.cur_e.clientX += this.keySpeed ; 602 | } 603 | break; 604 | case 38: case 63232: // up 605 | if (this.t_l == false) { 606 | // this.ky -= this.keySpeed; 607 | this.cur_e.clientY -= this.keySpeed ; 608 | } 609 | break; 610 | case 40: case 63233: // down 611 | if (this.h_l == false) { 612 | // this.ky += this.keySpeed; 613 | this.cur_e.clientY += this.keySpeed ; 614 | } 615 | break; 616 | case 13: case 27: 617 | this.end(e); 618 | return false; 619 | default: 620 | return true; 621 | } 622 | // var ne = this.extend({}, this.starte); 623 | // ne.keys_on = true; 624 | // ne.clientX += this.kx; 625 | // ne.clientY += this.ky; 626 | // this.cur_e.clientX = this.starte.clientX + this.kx; 627 | // this.cur_e.clientY = this.starte.clientY + this.ky; 628 | } 629 | this.cur_e.keys_on = true; 630 | this.move(this.cur_e); 631 | (e.preventDefault) ? e.preventDefault() : e.returnValue = false; 632 | }; 633 | this.bindevents = function (b) { 634 | var fn; 635 | if (b == true) { 636 | fn = this.addEventSimple; 637 | } else { 638 | fn = this.removeEventSimple; 639 | } 640 | var _this = this; 641 | fn(this.el, 'mousedown', function (e) { 642 | return _this.start(e); 643 | }); 644 | fn(this.el, 'mousemove', function (e) { 645 | return _this.move(e); 646 | }); 647 | fn(this.el, 'mouseup', function (e) { 648 | return _this.end(e); 649 | }); 650 | fn(document, 'mousemove', function (e) { 651 | return _this.move(e); 652 | }); 653 | fn(document, 'mouseup', function (e) { 654 | return _this.end(e); 655 | }); 656 | fn(document, 'click', function (e) { 657 | return _this.end(e); 658 | }); 659 | this.addEventSimple(this.el, 'selectstart', function (e) { 660 | return false; 661 | }); 662 | if (this.el.addEventListener) { 663 | this.el.addEventListener('touchstart', function (e) { 664 | //_this.el.innerHTML = 'start ' + e.touches[0].clientX; 665 | e.preventDefault(); 666 | _this.start(e.touches[0]); 667 | }); 668 | this.el.addEventListener('touchmove', function (e) { 669 | //_this.el.innerHTML = 'move ' + e.touches[0].clientX; 670 | e.preventDefault(); 671 | _this.move(e.touches[0]); 672 | }); 673 | this.el.addEventListener('touchend', function (e) { 674 | //_this.el.innerHTML = 'end ' + e.touches[0].clientX; 675 | e.stopPropagation(); 676 | e.preventDefault(); 677 | _this.end(e.touches[0]); 678 | }); 679 | document.addEventListener('touchmove', function (e) { 680 | e.preventDefault(); 681 | _this.move(e.touches[0]); 682 | }); 683 | document.addEventListener('touchend', function (e) { 684 | e.stopPropagation(); 685 | e.preventDefault(); 686 | _this.end(e.touches[0]); 687 | }); 688 | } 689 | if (this.keyboardcontrol == true) { 690 | var tabIndex = this.el.getAttribute('tabIndex'); 691 | if (!tabIndex) { 692 | tabIndex = 0; 693 | } 694 | if (b == true) { 695 | this.el.setAttribute('tabIndex', 0); 696 | } else { 697 | if (tabIndex == 0) { 698 | this.el.removeAttribute('tabIndex'); 699 | } 700 | } 701 | fn(this.el, 'keydown', function (e) { 702 | return _this.keycontrol(e); 703 | }); 704 | } 705 | }; 706 | this.attach=function(){ 707 | this.bindevents(true); 708 | }; 709 | this.detach=function(){ 710 | this.bindevents(false); 711 | }; 712 | this.reattach=function(){ 713 | this.bindevents(false); 714 | this.bindevents(true); 715 | }; 716 | this.attach(); 717 | return this; 718 | } /*drag_auto_scroll*/ -------------------------------------------------------------------------------- /example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Title 8 | 9 | 13 | 14 | 54 | 55 | 56 |
57 | 58 |
59 | 60 |
61 | 62 |
63 | 64 |
65 | 66 | 67 | 68 | 226 | 227 | 228 | -------------------------------------------------------------------------------- /jest-unit-config.js: -------------------------------------------------------------------------------- 1 | const config = require('./jest.config') 2 | config.testMatch = ['**/*.spec.js'] 3 | module.exports = config -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | coverageDirectory: 'coverage', 3 | testEnvironment: 'node', 4 | collectCoverageFrom: ['jkanban.js'] 5 | } -------------------------------------------------------------------------------- /jkanban.css: -------------------------------------------------------------------------------- 1 | .kanban-container { 2 | position: relative; 3 | box-sizing: border-box; 4 | width: auto; 5 | } 6 | 7 | .kanban-container * { 8 | box-sizing: border-box; 9 | } 10 | 11 | .kanban-container:after { 12 | clear: both; 13 | display: block; 14 | content: ""; 15 | } 16 | 17 | .kanban-board { 18 | position: relative; 19 | float: left; 20 | background: #e2e4e6; 21 | transition: all 0.3s cubic-bezier(0.23, 1, 0.32, 1); 22 | } 23 | 24 | .kanban-board.disabled-board { 25 | opacity: 0.3; 26 | } 27 | 28 | .kanban-board.is-moving.gu-mirror { 29 | transform: rotate(3deg); 30 | } 31 | 32 | .kanban-board.is-moving.gu-mirror .kanban-drag { 33 | overflow: hidden; 34 | padding-right: 50px; 35 | } 36 | 37 | .kanban-board header { 38 | font-size: 16px; 39 | padding: 15px; 40 | } 41 | 42 | .kanban-board header .kanban-title-board { 43 | font-weight: 700; 44 | margin: 0; 45 | padding: 0; 46 | display: inline; 47 | } 48 | 49 | .kanban-board header .kanban-title-button { 50 | float: right; 51 | } 52 | 53 | .kanban-board .kanban-drag { 54 | min-height: 200px; 55 | padding: 20px; 56 | } 57 | 58 | .kanban-board:after { 59 | clear: both; 60 | display: block; 61 | content: ""; 62 | } 63 | 64 | .kanban-item { 65 | background: #fff; 66 | padding: 15px; 67 | margin-bottom: 20px; 68 | transition: all 0.3s cubic-bezier(0.23, 1, 0.32, 1); 69 | animation: append-animate 0.3s cubic-bezier(0.23, 1, 0.32, 1); 70 | } 71 | 72 | @keyframes append-animate { 73 | from { 74 | transform: translateY(-20px); 75 | } 76 | to { 77 | transform: translateY(0px); 78 | } 79 | } 80 | 81 | .kanban-item:hover { 82 | cursor: move; 83 | } 84 | 85 | .kanban-item:last-child { 86 | margin: 0; 87 | } 88 | 89 | .kanban-item.is-moving.gu-mirror { 90 | transform: rotate(3deg); 91 | height: auto !important; 92 | } 93 | 94 | /* Dragula CSS */ 95 | .gu-mirror { 96 | position: fixed !important; 97 | margin: 0 !important; 98 | z-index: 9999 !important; 99 | } 100 | 101 | .gu-hide { 102 | display: none !important; 103 | } 104 | 105 | .gu-unselectable { 106 | -webkit-user-select: none !important; 107 | -moz-user-select: none !important; 108 | -ms-user-select: none !important; 109 | user-select: none !important; 110 | } 111 | 112 | .gu-transit { 113 | opacity: 0.2 !important; 114 | transform: rotate(0deg) !important; 115 | } 116 | 117 | .drag_handler { 118 | background: #fff; 119 | border-radius: 50%; 120 | width: 24px; 121 | height: 24px; 122 | position: relative; 123 | float: left; 124 | top: -3px; 125 | margin-right: 4px; 126 | } 127 | 128 | .drag_handler:hover { 129 | cursor: move; 130 | } 131 | 132 | .drag_handler_icon { 133 | position: relative; 134 | display: block; 135 | background: #000; 136 | width: 24px; 137 | height: 2px; 138 | top: 12px; 139 | transition: .5s ease-in-out; 140 | } 141 | 142 | .drag_handler_icon:before, 143 | .drag_handler_icon:after { 144 | background: #000; 145 | content: ''; 146 | display: block; 147 | width: 100%; 148 | height: 100%; 149 | position: absolute; 150 | transition: .5s ease-in-out; 151 | } 152 | 153 | .drag_handler_icon:before { 154 | top: 6px; 155 | } 156 | 157 | .drag_handler_icon:after { 158 | bottom: 6px; 159 | } -------------------------------------------------------------------------------- /jkanban.js: -------------------------------------------------------------------------------- 1 | /** 2 | * jKanban 3 | * Vanilla Javascript plugin for manage kanban boards 4 | * 5 | * @site: http://www.riccardotartaglia.it/jkanban/ 6 | * @author: Riccardo Tartaglia 7 | */ 8 | 9 | //Require dragula 10 | var dragula = require('dragula'); 11 | 12 | (function () { 13 | this.jKanban = function () { 14 | var self = this 15 | var __DEFAULT_ITEM_HANDLE_OPTIONS = { 16 | enabled: false 17 | } 18 | var __DEFAULT_ITEM_ADD_OPTIONS = { 19 | enabled: false 20 | } 21 | this._disallowedItemProperties = [ 22 | 'id', 23 | 'title', 24 | 'click', 25 | 'context', 26 | 'drag', 27 | 'dragend', 28 | 'drop', 29 | 'order' 30 | ] 31 | this.element = '' 32 | this.container = '' 33 | this.boardContainer = [] 34 | this.handlers = [] 35 | this.dragula = dragula 36 | this.drake = '' 37 | this.drakeBoard = '' 38 | this.itemAddOptions = __DEFAULT_ITEM_ADD_OPTIONS 39 | this.itemHandleOptions = __DEFAULT_ITEM_HANDLE_OPTIONS 40 | var defaults = { 41 | element: '', 42 | gutter: '15px', 43 | widthBoard: '250px', 44 | responsive: '700', 45 | responsivePercentage: false, 46 | boards: [], 47 | dragBoards: true, 48 | dragItems: true, //whether can drag cards or not, useful when set permissions on it. 49 | itemAddOptions: __DEFAULT_ITEM_ADD_OPTIONS, 50 | itemHandleOptions: __DEFAULT_ITEM_HANDLE_OPTIONS, 51 | dragEl: function (el, source) {}, 52 | dragendEl: function (el) {}, 53 | dropEl: function (el, target, source, sibling) {}, 54 | dragBoard: function (el, source) {}, 55 | dragendBoard: function (el) {}, 56 | dropBoard: function (el, target, source, sibling) {}, 57 | click: function (el) {}, 58 | context: function (el, e) {}, 59 | buttonClick: function (el, boardId) {}, 60 | propagationHandlers: [], 61 | } 62 | 63 | if (arguments[0] && typeof arguments[0] === 'object') { 64 | this.options = __extendDefaults(defaults, arguments[0]) 65 | } 66 | 67 | this.__getCanMove = function (handle) { 68 | if (!self.options.itemHandleOptions.enabled) { 69 | return !!self.options.dragItems 70 | } 71 | 72 | if (self.options.itemHandleOptions.handleClass) { 73 | return handle.classList.contains(self.options.itemHandleOptions.handleClass) 74 | } 75 | 76 | return handle.classList.contains('item_handle') 77 | } 78 | 79 | this.init = function () { 80 | //set initial boards 81 | __setBoard() 82 | //set drag with dragula 83 | if (window.innerWidth > self.options.responsive) { 84 | //Init Drag Board 85 | self.drakeBoard = self 86 | .dragula([self.container], { 87 | moves: function (el, source, handle, sibling) { 88 | if (!self.options.dragBoards) return false 89 | return ( 90 | handle.classList.contains('kanban-board-header') || 91 | handle.classList.contains('kanban-title-board') 92 | ) 93 | }, 94 | accepts: function (el, target, source, sibling) { 95 | return target.classList.contains('kanban-container') 96 | }, 97 | revertOnSpill: true, 98 | direction: 'horizontal' 99 | }) 100 | .on('drag', function (el, source) { 101 | el.classList.add('is-moving') 102 | self.options.dragBoard(el, source) 103 | if (typeof el.dragfn === 'function') el.dragfn(el, source) 104 | }) 105 | .on('dragend', function (el) { 106 | __updateBoardsOrder() 107 | el.classList.remove('is-moving') 108 | self.options.dragendBoard(el) 109 | if (typeof el.dragendfn === 'function') el.dragendfn(el) 110 | }) 111 | .on('drop', function (el, target, source, sibling) { 112 | el.classList.remove('is-moving') 113 | self.options.dropBoard(el, target, source, sibling) 114 | if (typeof el.dropfn === 'function') 115 | el.dropfn(el, target, source, sibling) 116 | }) 117 | 118 | //Init Drag Item 119 | self.drake = self 120 | .dragula(self.boardContainer, { 121 | moves: function (el, source, handle, sibling) { 122 | return self.__getCanMove(handle) 123 | }, 124 | revertOnSpill: true 125 | }) 126 | .on('cancel', function (el, container, source) { 127 | self.enableAllBoards() 128 | }) 129 | .on('drag', function (el, source) { 130 | var elClass = el.getAttribute('class') 131 | if (elClass !== '' && elClass.indexOf('not-draggable') > -1) { 132 | self.drake.cancel(true) 133 | return 134 | } 135 | 136 | el.classList.add('is-moving') 137 | 138 | self.options.dragEl(el, source) 139 | 140 | var boardJSON = __findBoardJSON(source.parentNode.dataset.id) 141 | if (boardJSON.dragTo !== undefined) { 142 | self.options.boards.map(function (board) { 143 | if ( 144 | boardJSON.dragTo.indexOf(board.id) === -1 && 145 | board.id !== source.parentNode.dataset.id 146 | ) { 147 | self.findBoard(board.id).classList.add('disabled-board') 148 | } 149 | }) 150 | } 151 | 152 | if (el !== null && typeof el.dragfn === 'function') 153 | el.dragfn(el, source) 154 | }) 155 | .on('dragend', function (el) { 156 | self.options.dragendEl(el) 157 | if (el !== null && typeof el.dragendfn === 'function') 158 | el.dragendfn(el) 159 | }) 160 | .on('drop', function (el, target, source, sibling) { 161 | self.enableAllBoards() 162 | 163 | var boardJSON = __findBoardJSON(source.parentNode.dataset.id) 164 | if (boardJSON.dragTo !== undefined) { 165 | if ( 166 | boardJSON.dragTo.indexOf(target.parentNode.dataset.id) === -1 && 167 | target.parentNode.dataset.id !== source.parentNode.dataset.id 168 | ) { 169 | self.drake.cancel(true) 170 | } 171 | } 172 | if (el !== null) { 173 | var result = self.options.dropEl(el, target, source, sibling) 174 | if (result === false) { 175 | self.drake.cancel(true) 176 | } 177 | el.classList.remove('is-moving') 178 | if (typeof el.dropfn === 'function') 179 | el.dropfn(el, target, source, sibling) 180 | } 181 | }) 182 | } 183 | } 184 | 185 | this.enableAllBoards = function () { 186 | var allB = document.querySelectorAll('.kanban-board') 187 | if (allB.length > 0 && allB !== undefined) { 188 | for (var i = 0; i < allB.length; i++) { 189 | allB[i].classList.remove('disabled-board') 190 | } 191 | } 192 | } 193 | 194 | this.addElement = function (boardID, element, position) { 195 | if (typeof position === 'undefined') { 196 | position = -1 197 | } 198 | var board = self.element.querySelector( 199 | '[data-id="' + boardID + '"] .kanban-drag' 200 | ) 201 | var refElement = board.childNodes[position] 202 | var nodeItem = document.createElement('div') 203 | nodeItem.classList.add('kanban-item') 204 | if (typeof element.id !== 'undefined' && element.id !== '') { 205 | nodeItem.setAttribute('data-eid', element.id) 206 | } 207 | if (element.class && Array.isArray(element.class)) { 208 | element.class.forEach(function (cl) { 209 | nodeItem.classList.add(cl) 210 | }) 211 | } 212 | nodeItem.innerHTML = __buildItemCard(element) 213 | //add function 214 | nodeItem.clickfn = element.click 215 | nodeItem.contextfn = element.context; 216 | nodeItem.dragfn = element.drag 217 | nodeItem.dragendfn = element.dragend 218 | nodeItem.dropfn = element.drop 219 | __appendCustomProperties(nodeItem, element) 220 | __onclickHandler(nodeItem) 221 | __onContextHandler(nodeItem) 222 | if (self.options.itemHandleOptions.enabled) { 223 | nodeItem.style.cursor = 'default' 224 | } 225 | board.insertBefore(nodeItem, refElement) 226 | return self 227 | } 228 | 229 | this.addForm = function (boardID, formItem) { 230 | var board = self.element.querySelector( 231 | '[data-id="' + boardID + '"] .kanban-drag' 232 | ) 233 | var _attribute = formItem.getAttribute('class') 234 | formItem.setAttribute('class', _attribute + ' not-draggable') 235 | board.appendChild(formItem) 236 | return self 237 | } 238 | 239 | this.addBoards = function (boards, isInit) { 240 | if (self.options.responsivePercentage) { 241 | self.container.style.width = '100%' 242 | self.options.gutter = '1%' 243 | if (window.innerWidth > self.options.responsive) { 244 | var boardWidth = (100 - boards.length * 2) / boards.length 245 | } else { 246 | var boardWidth = 100 - boards.length * 2 247 | } 248 | } else { 249 | var boardWidth = self.options.widthBoard 250 | } 251 | var addButton = self.options.itemAddOptions.enabled 252 | var buttonContent = self.options.itemAddOptions.content 253 | var buttonClass = self.options.itemAddOptions.class 254 | var buttonFooter = self.options.itemAddOptions.footer 255 | 256 | //for on all the boards 257 | for (var boardkey in boards) { 258 | // single board 259 | var board = boards[boardkey] 260 | if (!isInit) { 261 | self.options.boards.push(board) 262 | } 263 | 264 | if (!self.options.responsivePercentage) { 265 | //add width to container 266 | if (self.container.style.width === '') { 267 | self.container.style.width = 268 | parseInt(boardWidth) + parseInt(self.options.gutter) * 2 + 'px' 269 | } else { 270 | self.container.style.width = 271 | parseInt(self.container.style.width) + 272 | parseInt(boardWidth) + 273 | parseInt(self.options.gutter) * 2 + 274 | 'px' 275 | } 276 | } 277 | //create node 278 | var boardNode = document.createElement('div') 279 | boardNode.dataset.id = board.id 280 | boardNode.dataset.order = self.container.childNodes.length + 1 281 | boardNode.classList.add('kanban-board') 282 | //set style 283 | if (self.options.responsivePercentage) { 284 | boardNode.style.width = boardWidth + '%' 285 | } else { 286 | boardNode.style.width = boardWidth 287 | } 288 | boardNode.style.marginLeft = self.options.gutter 289 | boardNode.style.marginRight = self.options.gutter 290 | // header board 291 | var headerBoard = document.createElement('header') 292 | if (board.class !== '' && board.class !== undefined) 293 | var allClasses = board.class.split(',') 294 | else allClasses = [] 295 | headerBoard.classList.add('kanban-board-header') 296 | allClasses.map(function (value) { 297 | // Remove empty spaces 298 | value = value.replace(/^[ ]+/g, '') 299 | headerBoard.classList.add(value) 300 | }) 301 | headerBoard.innerHTML = 302 | '
' + board.title + '
' 303 | //content board 304 | var contentBoard = document.createElement('main') 305 | contentBoard.classList.add('kanban-drag') 306 | if (board.bodyClass !== '' && board.bodyClass !== undefined) 307 | var bodyClasses = board.bodyClass.split(',') 308 | else bodyClasses = [] 309 | bodyClasses.map(function (value) { 310 | contentBoard.classList.add(value) 311 | }) 312 | //add drag to array for dragula 313 | self.boardContainer.push(contentBoard) 314 | for (var itemkey in board.item) { 315 | //create item 316 | var itemKanban = board.item[itemkey] 317 | var nodeItem = document.createElement('div') 318 | nodeItem.classList.add('kanban-item') 319 | if (itemKanban.id) { 320 | nodeItem.dataset.eid = itemKanban.id 321 | } 322 | if (itemKanban.class && Array.isArray(itemKanban.class)) { 323 | itemKanban.class.forEach(function (cl) { 324 | nodeItem.classList.add(cl) 325 | }) 326 | } 327 | nodeItem.innerHTML = __buildItemCard(itemKanban) 328 | //add function 329 | nodeItem.clickfn = itemKanban.click 330 | nodeItem.contextfn = itemKanban.context 331 | nodeItem.dragfn = itemKanban.drag 332 | nodeItem.dragendfn = itemKanban.dragend 333 | nodeItem.dropfn = itemKanban.drop 334 | __appendCustomProperties(nodeItem, itemKanban) 335 | //add click handler of item 336 | __onclickHandler(nodeItem) 337 | __onContextHandler(nodeItem) 338 | if (self.options.itemHandleOptions.enabled) { 339 | nodeItem.style.cursor = 'default' 340 | } 341 | contentBoard.appendChild(nodeItem) 342 | } 343 | //footer board 344 | var footerBoard = document.createElement('footer') 345 | // if add button is true, add button to the board 346 | if (addButton) { 347 | var btn = document.createElement('BUTTON') 348 | var t = document.createTextNode(buttonContent ? buttonContent : '+') 349 | btn.setAttribute( 350 | 'class', 351 | buttonClass ? buttonClass : 'kanban-title-button btn btn-default btn-xs' 352 | ) 353 | btn.appendChild(t) 354 | //var buttonHtml = '' 355 | if (buttonFooter) { 356 | footerBoard.appendChild(btn) 357 | } else { 358 | headerBoard.appendChild(btn) 359 | } 360 | __onButtonClickHandler(btn, board.id) 361 | } 362 | //board assembly 363 | boardNode.appendChild(headerBoard) 364 | boardNode.appendChild(contentBoard) 365 | boardNode.appendChild(footerBoard) 366 | //board add 367 | self.container.appendChild(boardNode) 368 | } 369 | return self 370 | } 371 | 372 | this.findBoard = function (id) { 373 | var el = self.element.querySelector('[data-id="' + id + '"]') 374 | return el 375 | } 376 | 377 | this.getParentBoardID = function (el) { 378 | if (typeof el === 'string') { 379 | el = self.element.querySelector('[data-eid="' + el + '"]') 380 | } 381 | if (el === null) { 382 | return null 383 | } 384 | return el.parentNode.parentNode.dataset.id 385 | } 386 | 387 | this.moveElement = function (targetBoardID, elementID, element) { 388 | if (targetBoardID === this.getParentBoardID(elementID)) { 389 | return 390 | } 391 | 392 | this.removeElement(elementID) 393 | return this.addElement(targetBoardID, element) 394 | } 395 | 396 | this.replaceElement = function (el, element) { 397 | var nodeItem = el 398 | if (typeof nodeItem === 'string') { 399 | nodeItem = self.element.querySelector('[data-eid="' + el + '"]') 400 | } 401 | nodeItem.innerHTML = __buildItemCard(element) 402 | // add function 403 | nodeItem.clickfn = element.click 404 | nodeItem.contextfn = element.context 405 | nodeItem.dragfn = element.drag 406 | nodeItem.dragendfn = element.dragend 407 | nodeItem.dropfn = element.drop 408 | __appendCustomProperties(nodeItem, element) 409 | __onclickHandler(nodeItem) 410 | __onContextHandler(nodeItem) 411 | return self 412 | } 413 | 414 | this.findElement = function (id) { 415 | var el = self.element.querySelector('[data-eid="' + id + '"]') 416 | return el 417 | } 418 | 419 | this.getBoardElements = function (id) { 420 | var board = self.element.querySelector( 421 | '[data-id="' + id + '"] .kanban-drag' 422 | ) 423 | return board.childNodes 424 | } 425 | 426 | this.removeElement = function (el) { 427 | if (typeof el === 'string') 428 | el = self.element.querySelector('[data-eid="' + el + '"]') 429 | if (el !== null) { 430 | //fallback for IE 431 | if (typeof el.remove == 'function') { 432 | el.remove() 433 | } else { 434 | el.parentNode.removeChild(el) 435 | } 436 | } 437 | return self 438 | } 439 | 440 | this.removeBoard = function (board) { 441 | var boardElement = null 442 | if (typeof board === 'string') 443 | boardElement = self.element.querySelector('[data-id="' + board + '"]') 444 | if (boardElement !== null) { 445 | //fallback for IE 446 | if (typeof boardElement.remove == 'function') { 447 | boardElement.remove() 448 | } else { 449 | boardElement.parentNode.removeChild(boardElement) 450 | } 451 | } 452 | 453 | // remove thboard in options.boards 454 | for (var i = 0; i < self.options.boards.length; i++) { 455 | if (self.options.boards[i].id === board) { 456 | self.options.boards.splice(i, 1) 457 | break 458 | } 459 | } 460 | 461 | return self 462 | } 463 | 464 | // board button on click function 465 | this.onButtonClick = function (el) {} 466 | 467 | //PRIVATE FUNCTION 468 | function __extendDefaults (source, properties) { 469 | var property 470 | for (property in properties) { 471 | if (properties.hasOwnProperty(property)) { 472 | source[property] = properties[property] 473 | } 474 | } 475 | return source 476 | } 477 | 478 | function __setBoard () { 479 | self.element = document.querySelector(self.options.element) 480 | //create container 481 | var boardContainer = document.createElement('div') 482 | boardContainer.classList.add('kanban-container') 483 | self.container = boardContainer 484 | //add boards 485 | 486 | if (document.querySelector(self.options.element).dataset.hasOwnProperty('board')) { 487 | url = document.querySelector(self.options.element).dataset.board 488 | window.fetch(url, { 489 | method: 'GET', 490 | headers: { 'Content-Type': 'application/json' } 491 | }) 492 | .then(function (response) { 493 | // log response text 494 | response.json().then(function (data) { 495 | self.options.boards = data 496 | self.addBoards(self.options.boards, true) 497 | }) 498 | 499 | }) 500 | .catch(function (error) { 501 | console.log('Error: ', error) 502 | }) 503 | } else { 504 | self.addBoards(self.options.boards, true) 505 | } 506 | 507 | //appends to container 508 | self.element.appendChild(self.container) 509 | } 510 | 511 | function __onclickHandler (nodeItem, clickfn) { 512 | nodeItem.addEventListener('click', function (e) { 513 | if (!self.options.propagationHandlers.includes('click')) e.preventDefault() 514 | self.options.click(this) 515 | if (typeof this.clickfn === 'function') this.clickfn(this) 516 | }) 517 | } 518 | 519 | function __onContextHandler(nodeItem, contextfn) { 520 | if (nodeItem.addEventListener) { 521 | nodeItem.addEventListener('contextmenu', function (e) { 522 | if (!self.options.propagationHandlers.includes('context')) e.preventDefault() 523 | self.options.context(this, e) 524 | if (typeof this.contextfn === 'function') this.contextfn(this, e) 525 | }, false) 526 | } else { 527 | nodeItem.attachEvent('oncontextmenu', function () { 528 | self.options.context(this) 529 | if (typeof this.contextfn === 'function') this.contextfn(this) 530 | if (!self.options.propagationHandlers.includes('context')) window.event.returnValue = false 531 | }) 532 | } 533 | } 534 | 535 | function __onButtonClickHandler (nodeItem, boardId) { 536 | nodeItem.addEventListener('click', function (e) { 537 | e.preventDefault() 538 | self.options.buttonClick(this, boardId) 539 | // if(typeof(this.clickfn) === 'function') 540 | // this.clickfn(this); 541 | }) 542 | } 543 | 544 | function __findBoardJSON (id) { 545 | var el = [] 546 | self.options.boards.map(function (board) { 547 | if (board.id === id) { 548 | return el.push(board) 549 | } 550 | }) 551 | return el[0] 552 | } 553 | 554 | function __appendCustomProperties (element, parentObject) { 555 | for (var propertyName in parentObject) { 556 | if (self._disallowedItemProperties.indexOf(propertyName) > -1) { 557 | continue 558 | } 559 | 560 | element.setAttribute( 561 | 'data-' + propertyName, 562 | parentObject[propertyName] 563 | ) 564 | } 565 | } 566 | 567 | function __updateBoardsOrder () { 568 | var index = 1 569 | for (var i = 0; i < self.container.childNodes.length; i++) { 570 | self.container.childNodes[i].dataset.order = index++ 571 | } 572 | } 573 | 574 | function __buildItemCard(item) { 575 | var result = 'title' in item ? item.title : ''; 576 | 577 | if (self.options.itemHandleOptions.enabled) { 578 | if ((self.options.itemHandleOptions.customHandler || undefined) === undefined) { 579 | var customCssHandler = self.options.itemHandleOptions.customCssHandler 580 | var customCssIconHandler = self.options.itemHandleOptions.customCssIconHandler 581 | var customItemLayout = self.options.itemHandleOptions.customItemLayout 582 | if ((customCssHandler || undefined) === undefined) { 583 | customCssHandler = 'drag_handler'; 584 | } 585 | 586 | if ((customCssIconHandler || undefined) === undefined) { 587 | customCssIconHandler = customCssHandler + '_icon'; 588 | } 589 | 590 | if ((customItemLayout || undefined) === undefined) { 591 | customItemLayout = ''; 592 | } 593 | 594 | result = '
' + result + '
' 595 | } else { 596 | result = '
' + self.options.itemHandleOptions.customHandler.replace(/%([^%]+)%/g, function (match, key) 597 | { return item[key] !== undefined ? item[key] : '' }) + '
' 598 | return result 599 | } 600 | } 601 | 602 | return result 603 | } 604 | 605 | //init plugin 606 | this.init() 607 | } 608 | })() 609 | -------------------------------------------------------------------------------- /jkanban.spec.js: -------------------------------------------------------------------------------- 1 | require('./jkanban'); 2 | 3 | const initializeDom = () => { 4 | document.body.innerHTML = '
'; 5 | } 6 | 7 | beforeEach(() => { 8 | initializeDom(); 9 | }) 10 | 11 | const makeSut = (additionalParams) => { 12 | let options = { 13 | element: "#test" 14 | } 15 | if (additionalParams !== undefined) { 16 | for (var prop in additionalParams) { 17 | options[prop] = additionalParams[prop]; 18 | } 19 | } 20 | 21 | let jkanban = new jKanban(options); 22 | return jkanban; 23 | } 24 | 25 | describe('jKanban TestCase', () => { 26 | test('Should init jKanban', async () => { 27 | const sut = makeSut() 28 | 29 | const expected = document.createElement("div") 30 | expected.setAttribute("id", "test") 31 | expected.innerHTML = "
" 32 | 33 | expect(sut.element).toStrictEqual(expected); 34 | }) 35 | 36 | test('Should add a board with no items', async () => { 37 | const boardName = "test-board" 38 | const sut = makeSut({ 39 | boards: [ 40 | { 41 | "id": boardName 42 | } 43 | ] 44 | }) 45 | 46 | expect(sut.findBoard(boardName)).not.toBeUndefined() 47 | expect(sut.getBoardElements(boardName).length).toEqual(0) 48 | }) 49 | }); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jkanban", 3 | "version": "1.3.1", 4 | "description": "Javascript plugin for Kanban boards", 5 | "main": "jkanban.js", 6 | "watch": { 7 | "build": [ 8 | "jkanban.js", 9 | "jkanban.css" 10 | ] 11 | }, 12 | "scripts": { 13 | "watch_build": "npm-watch build", 14 | "build": "npm run scripts && npm run styles", 15 | "scripts": "browserify ./jkanban.js -o dist/jkanban.js && uglifyjs -m -c -o dist/jkanban.min.js dist/jkanban.js", 16 | "styles": "npm-css ./jkanban.css > dist/jkanban.css && cleancss dist/jkanban.css -o dist/jkanban.min.css", 17 | "test": "jest --env=jsdom" 18 | }, 19 | "repository": { 20 | "type": "git", 21 | "url": "https://github.com/riktar/jkanban.git" 22 | }, 23 | "homepage": "http://www.riccardotartaglia.it/jkanban", 24 | "keywords": [ 25 | "kanban", 26 | "js", 27 | "drag", 28 | "todo list", 29 | "javascript", 30 | "plugin" 31 | ], 32 | "author": "Riccardo Tartaglia", 33 | "license": "Apache 2.0", 34 | "dependencies": { 35 | "dragula": "^3.7.3", 36 | "npm-watch": "^0.7.0" 37 | }, 38 | "devDependencies": { 39 | "browserify": "^17.0.0", 40 | "clean-css-cli": "^4.3.0", 41 | "husky": "^5.0.6", 42 | "jest": "^26.6.3", 43 | "npm-css": "0.2.3", 44 | "uglify-es": "^3.3.9" 45 | } 46 | } 47 | --------------------------------------------------------------------------------