Built files from Bizgaze WebServer
Du kannst nicht mehr als 25 Themen auswählen Themen müssen mit entweder einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.

jkanban.js 21KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527
  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. //Require dragula
  9. var dragula = require("dragula");
  10. (function () {
  11. this.jKanban = function () {
  12. var self = this;
  13. var __DEFAULT_ITEM_HANDLE_OPTIONS = {
  14. enabled: false
  15. }
  16. this._disallowedItemProperties = [
  17. "id",
  18. "title",
  19. "click",
  20. "drag",
  21. "dragend",
  22. "drop",
  23. "order"
  24. ];
  25. this.element = "";
  26. this.container = "";
  27. this.boardContainer = [];
  28. this.handlers = [];
  29. this.dragula = dragula;
  30. this.drake = "";
  31. this.drakeBoard = "";
  32. this.addItemButton = false;
  33. this.buttonContent = "+";
  34. this.itemHandleOptions = __DEFAULT_ITEM_HANDLE_OPTIONS;
  35. var defaults = {
  36. element: "",
  37. gutter: "15px",
  38. widthBoard: "250px",
  39. responsive: "700",
  40. responsivePercentage: false,
  41. boards: [],
  42. dragBoards: true,
  43. dragItems: true, //whether can drag cards or not, useful when set permissions on it.
  44. addItemButton: false,
  45. buttonContent: "+",
  46. itemHandleOptions: __DEFAULT_ITEM_HANDLE_OPTIONS,
  47. dragEl: function (el, source) { },
  48. dragendEl: function (el) { },
  49. dropEl: function (el, target, source, sibling) { },
  50. dragBoard: function (el, source) { },
  51. dragendBoard: function (el) { },
  52. dropBoard: function (el, target, source, sibling) { },
  53. click: function (el) { },
  54. buttonClick: function (el, boardId) { }
  55. };
  56. if (arguments[0] && typeof arguments[0] === "object") {
  57. this.options = __extendDefaults(defaults, arguments[0]);
  58. }
  59. this.__getCanMove = function (handle) {
  60. if (!self.options.itemHandleOptions.enabled) {
  61. return !!self.options.dragItems;
  62. }
  63. if (self.options.itemHandleOptions.handleClass) {
  64. return handle.classList.contains(self.options.itemHandleOptions.handleClass);
  65. }
  66. return handle.classList.contains("item_handle")
  67. }
  68. this.init = function () {
  69. //set initial boards
  70. __setBoard();
  71. //set drag with dragula
  72. if (window.innerWidth > self.options.responsive) {
  73. //Init Drag Board
  74. self.drakeBoard = self
  75. .dragula([self.container], {
  76. moves: function (el, source, handle, sibling) {
  77. if (!self.options.dragBoards) return false;
  78. return (
  79. handle.classList.contains("kanban-board-header") ||
  80. handle.classList.contains("kanban-title-board")
  81. );
  82. },
  83. accepts: function (el, target, source, sibling) {
  84. return target.classList.contains("kanban-container");
  85. },
  86. revertOnSpill: true,
  87. direction: "horizontal"
  88. })
  89. .on("drag", function (el, source) {
  90. el.classList.add("is-moving");
  91. self.options.dragBoard(el, source);
  92. if (typeof el.dragfn === "function") el.dragfn(el, source);
  93. })
  94. .on("dragend", function (el) {
  95. __updateBoardsOrder();
  96. el.classList.remove("is-moving");
  97. self.options.dragendBoard(el);
  98. if (typeof el.dragendfn === "function") el.dragendfn(el);
  99. })
  100. .on("drop", function (el, target, source, sibling) {
  101. el.classList.remove("is-moving");
  102. self.options.dropBoard(el, target, source, sibling);
  103. if (typeof el.dropfn === "function")
  104. el.dropfn(el, target, source, sibling);
  105. });
  106. //Init Drag Item
  107. self.drake = self
  108. .dragula(self.boardContainer, {
  109. moves: function (el, source, handle, sibling) {
  110. return self.__getCanMove(handle);
  111. },
  112. revertOnSpill: true
  113. })
  114. .on("cancel", function (el, container, source) {
  115. self.enableAllBoards();
  116. })
  117. .on("drag", function (el, source) {
  118. var elClass = el.getAttribute("class");
  119. if (elClass !== "" && elClass.indexOf("not-draggable") > -1) {
  120. self.drake.cancel(true);
  121. return;
  122. }
  123. el.classList.add("is-moving");
  124. var boardJSON = __findBoardJSON(source.parentNode.dataset.id);
  125. if (boardJSON.dragTo !== undefined) {
  126. self.options.boards.map(function (board) {
  127. if (
  128. boardJSON.dragTo.indexOf(board.id) === -1 &&
  129. board.id !== source.parentNode.dataset.id
  130. ) {
  131. self.findBoard(board.id).classList.add("disabled-board");
  132. }
  133. });
  134. }
  135. self.options.dragEl(el, source);
  136. if (el !== null && typeof el.dragfn === "function")
  137. el.dragfn(el, source);
  138. })
  139. .on("dragend", function (el) {
  140. self.options.dragendEl(el);
  141. if (el !== null && typeof el.dragendfn === "function")
  142. el.dragendfn(el);
  143. })
  144. .on("drop", function (el, target, source, sibling) {
  145. self.enableAllBoards();
  146. var boardJSON = __findBoardJSON(source.parentNode.dataset.id);
  147. if (boardJSON.dragTo !== undefined) {
  148. if (
  149. boardJSON.dragTo.indexOf(target.parentNode.dataset.id) === -1 &&
  150. target.parentNode.dataset.id !== source.parentNode.dataset.id
  151. ) {
  152. self.drake.cancel(true);
  153. }
  154. }
  155. if (el !== null) {
  156. var result = self.options.dropEl(el, target, source, sibling);
  157. if (result === false) {
  158. self.drake.cancel(true);
  159. }
  160. el.classList.remove("is-moving");
  161. if (typeof el.dropfn === "function")
  162. el.dropfn(el, target, source, sibling);
  163. }
  164. });
  165. }
  166. };
  167. this.enableAllBoards = function () {
  168. var allB = document.querySelectorAll(".kanban-board");
  169. if (allB.length > 0 && allB !== undefined) {
  170. for (var i = 0; i < allB.length; i++) {
  171. allB[i].classList.remove("disabled-board");
  172. }
  173. }
  174. };
  175. this.addElement = function (boardID, element) {
  176. var board = self.element.querySelector(
  177. '[data-id="' + boardID + '"] .kanban-drag'
  178. );
  179. var nodeItem = document.createElement("div");
  180. nodeItem.classList.add("kanban-item");
  181. if (typeof element.id !== "undefined" && element.id !== "") {
  182. nodeItem.setAttribute("data-eid", element.id);
  183. }
  184. if (element.class && Array.isArray(element.class)) {
  185. element.class.forEach(function (cl) {
  186. nodeItem.classList.add(cl);
  187. })
  188. }
  189. nodeItem.innerHTML = __buildItemTitle(element.title);
  190. //add function
  191. nodeItem.clickfn = element.click;
  192. nodeItem.dragfn = element.drag;
  193. nodeItem.dragendfn = element.dragend;
  194. nodeItem.dropfn = element.drop;
  195. __appendCustomProperties(nodeItem, element);
  196. __onclickHandler(nodeItem);
  197. if (self.options.itemHandleOptions.enabled) {
  198. nodeItem.style.cursor = "default";
  199. }
  200. board.appendChild(nodeItem);
  201. return self;
  202. };
  203. this.addForm = function (boardID, formItem) {
  204. var board = self.element.querySelector(
  205. '[data-id="' + boardID + '"] .kanban-drag'
  206. );
  207. var _attribute = formItem.getAttribute("class");
  208. formItem.setAttribute("class", _attribute + " not-draggable");
  209. board.appendChild(formItem);
  210. return self;
  211. };
  212. this.addBoards = function (boards, isInit) {
  213. if (self.options.responsivePercentage) {
  214. self.container.style.width = "100%";
  215. self.options.gutter = "1%";
  216. if (window.innerWidth > self.options.responsive) {
  217. var boardWidth = (100 - boards.length * 2) / boards.length;
  218. } else {
  219. var boardWidth = 100 - boards.length * 2;
  220. }
  221. } else {
  222. var boardWidth = self.options.widthBoard;
  223. }
  224. var addButton = self.options.addItemButton;
  225. var buttonContent = self.options.buttonContent;
  226. //for on all the boards
  227. for (var boardkey in boards) {
  228. // single board
  229. var board = boards[boardkey];
  230. if (!isInit) {
  231. self.options.boards.push(board);
  232. }
  233. if (!self.options.responsivePercentage) {
  234. //add width to container
  235. if (self.container.style.width === "") {
  236. self.container.style.width =
  237. parseInt(boardWidth) + parseInt(self.options.gutter) * 2 + "px";
  238. } else {
  239. self.container.style.width =
  240. parseInt(self.container.style.width) +
  241. parseInt(boardWidth) +
  242. parseInt(self.options.gutter) * 2 +
  243. "px";
  244. }
  245. }
  246. //create node
  247. var boardNode = document.createElement("div");
  248. boardNode.dataset.id = board.id;
  249. boardNode.dataset.order = self.container.childNodes.length + 1;
  250. boardNode.classList.add("kanban-board");
  251. //set style
  252. if (self.options.responsivePercentage) {
  253. boardNode.style.width = boardWidth + "%";
  254. } else {
  255. boardNode.style.width = boardWidth;
  256. }
  257. boardNode.style.marginLeft = self.options.gutter;
  258. boardNode.style.marginRight = self.options.gutter;
  259. // header board
  260. var headerBoard = document.createElement("header");
  261. if (board.class !== "" && board.class !== undefined)
  262. var allClasses = board.class.split(",");
  263. else allClasses = [];
  264. headerBoard.classList.add("kanban-board-header");
  265. allClasses.map(function (value) {
  266. headerBoard.classList.add(value);
  267. });
  268. headerBoard.innerHTML =
  269. '<div class="kanban-title-board">' + board.title + "</div>";
  270. // if add button is true, add button to the board
  271. if (addButton) {
  272. var btn = document.createElement("BUTTON");
  273. var t = document.createTextNode(buttonContent);
  274. btn.setAttribute(
  275. "class",
  276. "kanban-title-button btn btn-default btn-xs"
  277. );
  278. btn.appendChild(t);
  279. //var buttonHtml = '<button class="kanban-title-button btn btn-default btn-xs">'+buttonContent+'</button>'
  280. headerBoard.appendChild(btn);
  281. __onButtonClickHandler(btn, board.id);
  282. }
  283. //content board
  284. var contentBoard = document.createElement("main");
  285. contentBoard.classList.add("kanban-drag");
  286. if (board.bodyClass !== "" && board.bodyClass !== undefined)
  287. var bodyClasses = board.bodyClass.split(",");
  288. else bodyClasses = [];
  289. bodyClasses.map(function (value) {
  290. contentBoard.classList.add(value);
  291. });
  292. //add drag to array for dragula
  293. self.boardContainer.push(contentBoard);
  294. for (var itemkey in board.item) {
  295. //create item
  296. var itemKanban = board.item[itemkey];
  297. var nodeItem = document.createElement("div");
  298. nodeItem.classList.add("kanban-item");
  299. if (itemKanban.id) {
  300. nodeItem.dataset.eid = itemKanban.id;
  301. }
  302. if (itemKanban.class && Array.isArray(itemKanban.class)) {
  303. itemKanban.class.forEach(function (cl) {
  304. nodeItem.classList.add(cl);
  305. })
  306. }
  307. nodeItem.innerHTML = __buildItemTitle(itemKanban.title);
  308. //add function
  309. nodeItem.clickfn = itemKanban.click;
  310. nodeItem.dragfn = itemKanban.drag;
  311. nodeItem.dragendfn = itemKanban.dragend;
  312. nodeItem.dropfn = itemKanban.drop;
  313. __appendCustomProperties(nodeItem, itemKanban);
  314. //add click handler of item
  315. __onclickHandler(nodeItem);
  316. if (self.options.itemHandleOptions.enabled) {
  317. nodeItem.style.cursor = "default";
  318. }
  319. contentBoard.appendChild(nodeItem);
  320. }
  321. //footer board
  322. var footerBoard = document.createElement("footer");
  323. //board assembly
  324. boardNode.appendChild(headerBoard);
  325. boardNode.appendChild(contentBoard);
  326. boardNode.appendChild(footerBoard);
  327. //board add
  328. self.container.appendChild(boardNode);
  329. }
  330. return self;
  331. };
  332. this.findBoard = function (id) {
  333. var el = self.element.querySelector('[data-id="' + id + '"]');
  334. return el;
  335. };
  336. this.getParentBoardID = function (el) {
  337. if (typeof el === "string") {
  338. el = self.element.querySelector('[data-eid="' + el + '"]');
  339. }
  340. if (el === null) {
  341. return null;
  342. }
  343. return el.parentNode.parentNode.dataset.id;
  344. };
  345. this.moveElement = function (targetBoardID, elementID, element) {
  346. if (targetBoardID === this.getParentBoardID(elementID)) {
  347. return;
  348. }
  349. this.removeElement(elementID);
  350. return this.addElement(targetBoardID, element);
  351. };
  352. this.replaceElement = function (el, element) {
  353. var nodeItem = el;
  354. if (typeof nodeItem === "string") {
  355. nodeItem = self.element.querySelector('[data-eid="' + el + '"]');
  356. }
  357. nodeItem.innerHTML = element.title;
  358. // add function
  359. nodeItem.clickfn = element.click;
  360. nodeItem.dragfn = element.drag;
  361. nodeItem.dragendfn = element.dragend;
  362. nodeItem.dropfn = element.drop;
  363. __appendCustomProperties(nodeItem, element);
  364. return self;
  365. };
  366. this.findElement = function (id) {
  367. var el = self.element.querySelector('[data-eid="' + id + '"]');
  368. return el;
  369. };
  370. this.getBoardElements = function (id) {
  371. var board = self.element.querySelector(
  372. '[data-id="' + id + '"] .kanban-drag'
  373. );
  374. return board.childNodes;
  375. };
  376. this.removeElement = function (el) {
  377. if (typeof el === "string")
  378. el = self.element.querySelector('[data-eid="' + el + '"]');
  379. if (el !== null) {
  380. el.remove();
  381. }
  382. return self;
  383. };
  384. this.removeBoard = function (board) {
  385. var boardElement = null;
  386. if (typeof board === "string")
  387. boardElement = self.element.querySelector('[data-id="' + board + '"]');
  388. if (boardElement !== null) {
  389. boardElement.remove();
  390. }
  391. // remove thboard in options.boards
  392. for (var i = 0; i < self.options.boards.length; i++) {
  393. if (self.options.boards[i].id === board) {
  394. self.options.boards.splice(i, 1);
  395. break;
  396. }
  397. }
  398. return self;
  399. };
  400. // board button on click function
  401. this.onButtonClick = function (el) { };
  402. //PRIVATE FUNCTION
  403. function __extendDefaults(source, properties) {
  404. var property;
  405. for (property in properties) {
  406. if (properties.hasOwnProperty(property)) {
  407. source[property] = properties[property];
  408. }
  409. }
  410. return source;
  411. }
  412. function __setBoard() {
  413. self.element = document.querySelector(self.options.element);
  414. //create container
  415. var boardContainer = document.createElement("div");
  416. boardContainer.classList.add("kanban-container");
  417. self.container = boardContainer;
  418. //add boards
  419. self.addBoards(self.options.boards, true);
  420. //appends to container
  421. self.element.appendChild(self.container);
  422. }
  423. function __onclickHandler(nodeItem, clickfn) {
  424. nodeItem.addEventListener("click", function (e) {
  425. e.preventDefault();
  426. self.options.click(this);
  427. if (typeof this.clickfn === "function") this.clickfn(this);
  428. });
  429. }
  430. function __onButtonClickHandler(nodeItem, boardId) {
  431. nodeItem.addEventListener("click", function (e) {
  432. e.preventDefault();
  433. self.options.buttonClick(this, boardId);
  434. // if(typeof(this.clickfn) === 'function')
  435. // this.clickfn(this);
  436. });
  437. }
  438. function __findBoardJSON(id) {
  439. var el = [];
  440. self.options.boards.map(function (board) {
  441. if (board.id === id) {
  442. return el.push(board);
  443. }
  444. });
  445. return el[0];
  446. }
  447. function __appendCustomProperties(element, parentObject) {
  448. for (var propertyName in parentObject) {
  449. if (self._disallowedItemProperties.indexOf(propertyName) > -1) {
  450. continue;
  451. }
  452. element.setAttribute(
  453. "data-" + propertyName,
  454. parentObject[propertyName]
  455. );
  456. }
  457. }
  458. function __updateBoardsOrder() {
  459. var index = 1;
  460. for (var i = 0; i < self.container.childNodes.length; i++) {
  461. self.container.childNodes[i].dataset.order = index++;
  462. }
  463. }
  464. function __buildItemTitle(title) {
  465. var result = title;
  466. if (self.options.itemHandleOptions.enabled) {
  467. if ((self.options.itemHandleOptions.customHandler || undefined) === undefined) {
  468. var customCssHandler = self.options.itemHandleOptions.customCssHandler;
  469. var customCssIconHandler = self.options.itemHandleOptions.customCssIconHandler;
  470. if ((customCssHandler || undefined) === undefined) {
  471. customCssHandler = "drag_handler";
  472. }
  473. if ((customCssIconHandler || undefined) === undefined) {
  474. customCssIconHandler = customCssHandler + "_icon";
  475. }
  476. result = "<div class='item_handle " + customCssHandler + "'><i class='item_handle " + customCssIconHandler + "'></i></div><div>" + result + "</div>";
  477. } else {
  478. result = self.options.itemHandleOptions.customHandler.replace("%s", result);
  479. }
  480. }
  481. return result;
  482. }
  483. //init plugin
  484. this.init();
  485. };
  486. })();