Built files from Bizgaze WebServer
Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

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. })();