1import { Controller } from "@hotwired/stimulus"// app/javascript/controllers/category_sortable_controller.js 2 3 4 5/* 6 * HTML5Sortable package 7 * https://github.com/lukasoppermann/html5sortable 8 * 9 * Maintained by Lukas Oppermann <[email protected]> 10 * 11 * Released under the MIT license. 12 */ 13var sortable = (function () { 14 'use strict'; 15 console.log("load sortablejs") 16 /** 17 * Get or set data on element 18 * @param {HTMLElement} element 19 * @param {string} key 20 * @param {any} value 21 * @return {*} 22 */ 23 function addData(element, key, value) { 24 if (value === undefined) { 25 return element && element.h5s && element.h5s.data && element.h5s.data[key]; 26 } 27 else { 28 element.h5s = element.h5s || {}; 29 element.h5s.data = element.h5s.data || {}; 30 element.h5s.data[key] = value; 31 } 32 } 33 /** 34 * Remove data from element 35 * @param {HTMLElement} element 36 */ 37 function removeData(element) { 38 if (element.h5s) { 39 delete element.h5s.data; 40 } 41 } 42 43 /* eslint-env browser */ 44 /** 45 * Filter only wanted nodes 46 * @param {NodeList|HTMLCollection|Array} nodes 47 * @param {String} selector 48 * @returns {Array} 49 */ 50 var filter = (function (nodes, selector) { 51 if (!(nodes instanceof NodeList || nodes instanceof HTMLCollection || nodes instanceof Array)) { 52 throw new Error('You must provide a nodeList/HTMLCollection/Array of elements to be filtered.'); 53 } 54 if (typeof selector !== 'string') { 55 return Array.from(nodes); 56 } 57 return Array.from(nodes).filter(function (item) { return item.nodeType === 1 && item.matches(selector); }); 58 }); 59 60 /* eslint-env browser */ 61 /* eslint-disable no-use-before-define */ 62 var stores = new Map(); 63 /* eslint-enable no-use-before-define */ 64 /** 65 * Stores data & configurations per Sortable 66 * @param {Object} config 67 */ 68 var Store = /** @class */ (function () { 69 function Store() { 70 this._config = new Map(); // eslint-disable-line no-undef 71 this._placeholder = undefined; // eslint-disable-line no-undef 72 this._data = new Map(); // eslint-disable-line no-undef 73 } 74 Object.defineProperty(Store.prototype, "config", { 75 /** 76 * get the configuration map of a class instance 77 * @method config 78 * @return {object} 79 */ 80 get: function () { 81 // transform Map to object 82 var config = {}; 83 this._config.forEach(function (value, key) { 84 config[key] = value; 85 }); 86 // return object 87 return config; 88 }, 89 /** 90 * set the configuration of a class instance 91 * @method config 92 * @param {object} config object of configurations 93 */ 94 set: function (config) { 95 if (typeof config !== 'object') { 96 throw new Error('You must provide a valid configuration object to the config setter.'); 97 } 98 // combine config with default 99 var mergedConfig = Object.assign({}, config); 100 // add config to map 101 this._config = new Map(Object.entries(mergedConfig)); 102 }, 103 enumerable: false, 104 configurable: true 105 }); 106 /** 107 * set individual configuration of a class instance 108 * @method setConfig 109 * @param key valid configuration key 110 * @param value any value 111 * @return void 112 */ 113 Store.prototype.setConfig = function (key, value) { 114 if (!this._config.has(key)) { 115 throw new Error("Trying to set invalid configuration item: " + key); 116 } 117 // set config 118 this._config.set(key, value); 119 }; 120 /** 121 * get an individual configuration of a class instance 122 * @method getConfig 123 * @param key valid configuration key 124 * @return any configuration value 125 */ 126 Store.prototype.getConfig = function (key) { 127 if (!this._config.has(key)) { 128 throw new Error("Invalid configuration item requested: " + key); 129 } 130 return this._config.get(key); 131 }; 132 Object.defineProperty(Store.prototype, "placeholder", { 133 /** 134 * get the placeholder for a class instance 135 * @method placeholder 136 * @return {HTMLElement|null} 137 */ 138 get: function () { 139 return this._placeholder; 140 }, 141 /** 142 * set the placeholder for a class instance 143 * @method placeholder 144 * @param {HTMLElement} placeholder 145 * @return {void} 146 */ 147 set: function (placeholder) { 148 if (!(placeholder instanceof HTMLElement) && placeholder !== null) { 149 throw new Error('A placeholder must be an html element or null.'); 150 } 151 this._placeholder = placeholder; 152 }, 153 enumerable: false, 154 configurable: true 155 }); 156 /** 157 * set an data entry 158 * @method setData 159 * @param {string} key 160 * @param {any} value 161 * @return {void} 162 */ 163 Store.prototype.setData = function (key, value) { 164 if (typeof key !== 'string') { 165 throw new Error('The key must be a string.'); 166 } 167 this._data.set(key, value); 168 }; 169 /** 170 * get an data entry 171 * @method getData 172 * @param {string} key an existing key 173 * @return {any} 174 */ 175 Store.prototype.getData = function (key) { 176 if (typeof key !== 'string') { 177 throw new Error('The key must be a string.'); 178 } 179 return this._data.get(key); 180 }; 181 /** 182 * delete an data entry 183 * @method deleteData 184 * @param {string} key an existing key 185 * @return {boolean} 186 */ 187 Store.prototype.deleteData = function (key) { 188 if (typeof key !== 'string') { 189 throw new Error('The key must be a string.'); 190 } 191 return this._data.delete(key); 192 }; 193 return Store; 194 }()); 195 /** 196 * @param {HTMLElement} sortableElement 197 * @returns {Class: Store} 198 */ 199 var store = (function (sortableElement) {
200 // if sortableElement is wrong type 201 if (!(sortableElement instanceof HTMLElement)) { 202 throw new Error('Please provide a sortable to the store function.'); 203 } 204 // create new instance if not avilable 205 if (!stores.has(sortableElement)) { 206 stores.set(sortableElement, new Store()); 207 } 208 // return instance 209 return stores.get(sortableElement); 210 }); 211 212 /** 213 * @param {Array|HTMLElement} element 214 * @param {Function} callback 215 * @param {string} event 216 */ 217 function addEventListener(element, eventName, callback) { 218 if (element instanceof Array) { 219 for (var i = 0; i < element.length; ++i) { 220 addEventListener(element[i], eventName, callback); 221 } 222 return; 223 } 224 element.addEventListener(eventName, callback); 225 store(element).setData("event" + eventName, callback); 226 } 227 /** 228 * @param {Array<HTMLElement>|HTMLElement} element 229 * @param {string} eventName 230 */ 231 function removeEventListener(element, eventName) { 232 if (element instanceof Array) { 233 for (var i = 0; i < element.length; ++i) { 234 removeEventListener(element[i], eventName); 235 } 236 return; 237 } 238 element.removeEventListener(eventName, store(element).getData("event" + eventName)); 239 store(element).deleteData("event" + eventName); 240 } 241 242 /** 243 * @param {Array<HTMLElement>|HTMLElement} element 244 * @param {string} attribute 245 * @param {string} value 246 */ 247 function addAttribute(element, attribute, value) { 248 if (element instanceof Array) { 249 for (var i = 0; i < element.length; ++i) { 250 addAttribute(element[i], attribute, value); 251 } 252 return; 253 } 254 element.setAttribute(attribute, value); 255 } 256 /** 257 * @param {Array|HTMLElement} element 258 * @param {string} attribute 259 */ 260 function removeAttribute(element, attribute) { 261 if (element instanceof Array) { 262 for (var i = 0; i < element.length; ++i) { 263 removeAttribute(element[i], attribute); 264 } 265 return; 266 } 267 element.removeAttribute(attribute); 268 } 269 270 /** 271 * @param {HTMLElement} element 272 * @returns {Object} 273 */ 274 var offset = (function (element) { 275 if (!element.parentElement || element.getClientRects().length === 0) { 276 throw new Error('target element must be part of the dom'); 277 } 278 var rect = element.getClientRects()[0]; 279 return { 280 left: rect.left + window.pageXOffset, 281 right: rect.right + window.pageXOffset, 282 top: rect.top + window.pageYOffset, 283 bottom: rect.bottom + window.pageYOffset 284 }; 285 }); 286 287 /** 288 * Creates and returns a new debounced version of the passed function which will postpone its execution until after wait milliseconds have elapsed 289 * @param {Function} func to debounce 290 * @param {number} time to wait before calling function with latest arguments, 0 - no debounce 291 * @returns {function} - debounced function 292 */ 293 var debounce = (function (func, wait) { 294 if (wait === void 0) { wait = 0; } 295 var timeout; 296 return function () { 297 var args = []; 298 for (var _i = 0; _i < arguments.length; _i++) { 299 args[_i] = arguments[_i]; 300 } 301 clearTimeout(timeout); 302 timeout = setTimeout(function () { 303 func.apply(void 0, args); 304 }, wait); 305 }; 306 }); 307 308 /* eslint-env browser */ 309 /** 310 * Get position of the element relatively to its sibling elements 311 * @param {HTMLElement} element 312 * @returns {number} 313 */ 314 var getIndex = (function (element, elementList) { 315 if (!(element instanceof HTMLElement) || !(elementList instanceof NodeList || elementList instanceof HTMLCollection || elementList instanceof Array)) { 316 throw new Error('You must provide an element and a list of elements.'); 317 } 318 return Array.from(elementList).indexOf(element); 319 }); 320 321 /* eslint-env browser */ 322 /** 323 * Test whether element is in DOM 324 * @param {HTMLElement} element 325 * @returns {boolean} 326 */ 327 var isInDom = (function (element) { 328 if (!(element instanceof HTMLElement)) { 329 throw new Error('Element is not a node element.'); 330 } 331 return element.parentNode !== null; 332 }); 333 334 /* eslint-env browser */ 335 /** 336 * Insert node before or after target 337 * @param {HTMLElement} referenceNode - reference element 338 * @param {HTMLElement} newElement - element to be inserted 339 * @param {String} position - insert before or after reference element 340 */ 341 var insertNode = function (referenceNode, newElement, position) { 342 if (!(referenceNode instanceof HTMLElement) || !(referenceNode.parentElement instanceof HTMLElement)) { 343 throw new Error('target and element must be a node'); 344 } 345 referenceNode.parentElement.insertBefore(newElement, (position === 'before' ? referenceNode : referenceNode.nextElementSibling)); 346 }; 347 /** 348 * Insert before target 349 * @param {HTMLElement} target 350 * @param {HTMLElement} element 351 */ 352 var insertBefore = function (target, element) { return insertN
352ode(target, element, 'before'); }; 353 /** 354 * Insert after target 355 * @param {HTMLElement} target 356 * @param {HTMLElement} element 357 */ 358 var insertAfter = function (target, element) { return insertNode(target, element, 'after'); }; 359 360 /* eslint-env browser */ 361 /** 362 * Filter only wanted nodes 363 * @param {HTMLElement} sortableContainer 364 * @param {Function} customSerializer 365 * @returns {Array} 366 */ 367 var serialize = (function (sortableContainer, customItemSerializer, customContainerSerializer) { 368 if (customItemSerializer === void 0) { customItemSerializer = function (serializedItem, sortableContainer) { return serializedItem; }; } 369 if (customContainerSerializer === void 0) { customContainerSerializer = function (serializedContainer) { return serializedContainer; }; } 370 // check for valid sortableContainer 371 if (!(sortableContainer instanceof HTMLElement) || !sortableContainer.isSortable === true) { 372 throw new Error('You need to provide a sortableContainer to be serialized.'); 373 } 374 // check for valid serializers 375 if (typeof customItemSerializer !== 'function' || typeof customContainerSerializer !== 'function') { 376 throw new Error('You need to provide a valid serializer for items and the container.'); 377 } 378 // get options 379 var options = addData(sortableContainer, 'opts'); 380 var item = options.items; 381 // serialize container 382 var items = filter(sortableContainer.children, item); 383 var serializedItems = items.map(function (item) { 384 return { 385 parent: sortableContainer, 386 node: item, 387 html: item.outerHTML, 388 index: getIndex(item, items) 389 }; 390 }); 391 // serialize container 392 var container = { 393 node: sortableContainer, 394 itemCount: serializedItems.length 395 }; 396 return { 397 container: customContainerSerializer(container), 398 items: serializedItems.map(function (item) { return customItemSerializer(item, sortableContainer); }) 399 }; 400 }); 401 402 /* eslint-env browser */ 403 /** 404 * create a placeholder element 405 * @param {HTMLElement} sortableElement a single sortable 406 * @param {string|undefined} placeholder a string representing an html element 407 * @param {string} placeholderClasses a string representing the classes that should be added to the placeholder 408 */ 409 var makePlaceholder = (function (sortableElement, placeholder, placeholderClass) { 410 var _a; 411 if (placeholderClass === void 0) { placeholderClass = 'sortable-placeholder'; } 412 if (!(sortableElement instanceof HTMLElement)) { 413 throw new Error('You must provide a valid element as a sortable.'); 414 } 415 // if placeholder is not an element 416 if (!(placeholder instanceof HTMLElement) && placeholder !== undefined) { 417 throw new Error('You must provide a valid element as a placeholder or set ot to undefined.'); 418 } 419 // if no placeholder element is given 420 if (placeholder === undefined) { 421 if (['UL', 'OL'].includes(sortableElement.tagName)) { 422 placeholder = document.createElement('li'); 423 } 424 else if (['TABLE', 'TBODY'].includes(sortableElement.tagName)) { 425 placeholder = document.createElement('tr'); 426 // set colspan to always all rows, otherwise the item can only be dropped in first column 427 placeholder.innerHTML = '<td colspan="100"></td>'; 428 } 429 else { 430 placeholder = document.createElement('div'); 431 } 432 } 433 // add classes to placeholder 434 if (typeof placeholderClass === 'string') { 435 (_a = placeholder.classList).add.apply(_a, placeholderClass.split(' ')); 436 } 437 return placeholder; 438 }); 439 440 /* eslint-env browser */ 441 /** 442 * Get height of an element including padding 443 * @param {HTMLElement} element an dom element 444 */ 445 var getElementHeight = (function (element) { 446 if (!(element instanceof HTMLElement)) { 447 throw new Error('You must provide a valid dom element'); 448 } 449 // get calculated style of element 450 var style = window.getComputedStyle(element); 451 // get only height if element has box-sizing: border-box specified 452 if (style.getPropertyValue('box-sizing') === 'border-box') { 453 return parseInt(style.getPropertyValue('height'), 10); 454 } 455 // pick applicable properties, convert to int and reduce by adding 456 return ['height', 'padding-top', 'padding-bottom'] 457 .map(function (key) { 458 var int = parseInt(style.getPropertyValue(key), 10); 459 return isNaN(int) ? 0 : int; 460 }) 461 .reduce(function (sum, value) { return sum + value; }); 462 }); 463 464 /* eslint-env browser */ 465 /** 466 * Get width of an element including padding 467 * @param {HTMLElement} element an dom element 468 */ 469 var getElementWidth = (function (element) { 470 if (!(element instanceof HTMLElement)) { 471 throw new Error('You must provide a valid dom element'); 472 } 473 // get calculated style of element 474 var style = window.getComputedStyle(element); 475 // pick applicable properties, convert to int and reduce by adding 476 return ['width', 'padding-left', 'padding-right'] 477 .map(function (key) { 478 var int = parseInt(style.getPropertyValue(key), 10); 479 return isNaN(int) ? 0 : int; 480 }) 481 .reduce(function (sum, value) { return sum + value; }); 482 }); 483 484 /* eslint-env browser */ 485 /** 486 * get handle or return item 487 * @param {Array<HTMLElement>} items 488 * @param {string} selector 489 */ 490 var getHandles = (function (items, selector) { 491 if (!(items instanceof Array)) { 492 throw new Error('You must provide a Array of HTMLElements to be filtered.'); 493 } 494 if (typeof selector !== 'string') { 495 return items; 496 } 497 return items 498 // remove items without handle from array 499 .filter(function (item) { 500 return item.querySelector(selector) instanceof HTMLElement || 501 (item.shadowRoot && item.shadowRoot.querySelector(selector) instanceof HTMLElement); 502 }) 503 // replace item with handle in array 504 .map(function (item) { 505 return item.querySelector(selector) || (item.shadowRoot && item.shadowRoot.querySelector(selector)); 506 }); 507 }); 508 509 /** 510 * @param {Event} event 511 * @returns {HTMLElement} 512 */ 513 var getEventTarget = (function (event) { 514 return (event.composedPath && event.composedPath()[0]) || event.target; 515 }); 516 517 /* eslint-env browser */ 518 /** 519 * defaultDragImage returns the current item as dragged image 520 * @param {HTMLElement} draggedElement - the item that the user drags 521 * @param {object} elementOffset - an object with the offsets top, left, right & bottom 522 * @param {Event} event - the original drag event object 523 * @return {object} with element, posX and posY properties 524 */ 525 var defaultDragImage = function (draggedElement, elementOffset, event) { 526 return { 527 element: draggedElement, 528 posX: event.pageX - elementOffset.left, 529 posY: event.pageY - elementOffset.top 530 }; 531 }; 532 /** 533 * attaches an element as the drag image to an event 534 * @param {Event} event - the original drag event object 535 * @param {HTMLElement} draggedElement - the item that the user drags 536 * @param {Function} customDragImage - function to create a custom dragImage 537 * @return void 538 */ 539 var setDragImage = (function (event, draggedElement, customDragImage) { 540 // check if event is provided 541 if (!(event instanceof Event)) { 542 throw new Error('setDragImage requires a DragEvent as the first argument.'); 543 } 544 // check if draggedElement is provided 545 if (!(draggedElement instanceof HTMLElement)) { 546 throw new Error('setDragImage requires the dragged element as the second argument.'); 547 } 548 // set default function of none provided 549 if (!customDragImage) { 550 customDragImage = defaultDragImage; 551 } 552 // check if setDragImage method is available 553 if (event.dataTransfer && event.dataTransfer.setDragImage) { 554 // get the elements offset 555 var elementOffset = offset(draggedElement); 556 // get the dragImage 557 var dragImage = customDragImage(draggedElement, elementOffset, event); 558 // check if custom function returns correct values 559 if (!(dragImage.element instanceof HTMLElement) || typeof dragImage.posX !== 'number' || typeof dragImage.posY !== 'number') { 560 throw new Error('The customDragImage function you provided must return and object with the properties element[string], posX[integer], posY[integer].'); 561 } 562 // needs to be set for HTML5 drag & drop to work 563 event.dataTransfer.effectAllowed = 'copyMove'; 564 // Firefox requires it to use the event target's id for the data 565 event.dataTransfer.setData('text/plain', getEventTarget(event).id); 566 // set the drag image on the event 567 event.dataTransfer.setDragImage(dragImage.element, dragImage.posX, dragImage.posY); 568 } 569 }); 570 571 /** 572 * Check if curList accepts items from destList 573 * @param {sortable} destination the container an item is move to 574 * @param {sortable} origin the container an item comes from 575 */ 576 var listsConnected = (function (destination, origin) { 577 // check if valid sortable 578 if (destination.isSortable === true) { 579 var acceptFrom = store(destination).getConfig('acceptFrom'); 580 // check if acceptFrom is valid 581 if (acceptFrom !== null && acceptFrom !== false && typeof acceptFrom !== 'string') { 582 throw new Error('HTML5Sortable: Wrong argument, "acceptFrom" must be "null", "false", or a valid selector string.'); 583 } 584 if (acceptFrom !== null) { 585 return acceptFrom !== false && acceptFrom.split(',').filter(function (sel) { 586 return sel.length > 0 && origin.matches(sel); 587 }).length > 0; 588 } 589 // drop in same list 590 if (destination === origin) { 591 return true; 592 } 593 // check if lists are connected with connectWith 594 if (store(destination).getConfig('connectWith') !== undefined && store(destination).getConfig('connectWith') !== null) { 595 return store(destination).getConfig('connectWith') === store(origin).getConfig('connectWith'); 596 } 597 } 598 return false;
599 }); 600 601 /** 602 * default configurations 603 */ 604 var defaultConfiguration = { 605 items: null, 606 // deprecated 607 connectWith: null, 608 // deprecated 609 disableIEFix: null, 610 acceptFrom: null, 611 copy: false, 612 placeholder: null, 613 placeholderClass: 'sortable-placeholder', 614 draggingClass: 'sortable-dragging', 615 hoverClass: false, 616 dropTargetContainerClass: false, 617 debounce: 0, 618 throttleTime: 100, 619 maxItems: 0, 620 itemSerializer: undefined, 621 containerSerializer: undefined, 622 customDragImage: null, 623 orientation: 'vertical' 624 }; 625 626 /** 627 * make sure a function is only called once within the given amount of time 628 * @param {Function} fn the function to throttle 629 * @param {number} threshold time limit for throttling 630 */ 631 // must use function to keep this context 632 function throttle(fn, threshold) { 633 var _this = this; 634 if (threshold === void 0) { threshold = 250; } 635 // check function 636 if (typeof fn !== 'function') { 637 throw new Error('You must provide a function as the first argument for throttle.'); 638 } 639 // check threshold 640 if (typeof threshold !== 'number') { 641 throw new Error('You must provide a number as the second argument for throttle.'); 642 } 643 var lastEventTimestamp = null; 644 return function () { 645 var args = []; 646 for (var _i = 0; _i < arguments.length; _i++) { 647 args[_i] = arguments[_i]; 648 } 649 var now = Date.now(); 650 if (lastEventTimestamp === null || now - lastEventTimestamp >= threshold) { 651 lastEventTimestamp = now; 652 fn.apply(_this, args); 653 } 654 }; 655 } 656 657 /* eslint-env browser */ 658 /** 659 * enable or disable hoverClass on mouseenter/leave if container Items 660 * @param {sortable} sortableContainer a valid sortableContainer 661 * @param {boolean} enable enable or disable event 662 */ 663 var enableHoverClass = (function (sortableContainer, enable) { 664 if (typeof store(sortableContainer).getConfig('hoverClass') === 'string') { 665 var hoverClasses_1 = store(sortableContainer).getConfig('hoverClass').split(' '); 666 // add class on hover 667 if (enable === true) { 668 addEventListener(sortableContainer, 'mousemove', throttle(function (event) { 669 // check of no mouse button was pressed when mousemove started == no drag 670 if (event.buttons === 0) { 671 filter(sortableContainer.children, store(sortableContainer).getConfig('items')).forEach(function (item) { 672 var _a, _b; 673 if (item === event.target || item.contains(event.target)) { 674 (_a = item.classList).add.apply(_a, hoverClasses_1); 675 } 676 else { 677 (_b = item.classList).remove.apply(_b, hoverClasses_1); 678 } 679 }); 680 } 681 }, store(sortableContainer).getConfig('throttleTime'))); 682 // remove class on leave 683 addEventListener(sortableContainer, 'mouseleave', function () { 684 filter(sortableContainer.children, store(sortableContainer).getConfig('items')).forEach(function (item) { 685 var _a; 686 (_a = item.classList).remove.apply(_a, hoverClasses_1); 687 }); 688 }); 689 // remove events 690 } 691 else { 692 removeEventListener(sortableContainer, 'mousemove'); 693 removeEventListener(sortableContainer, 'mouseleave'); 694 } 695 } 696 }); 697 698 /* eslint-env browser */ 699 /* 700 * variables global to the plugin 701 */ 702 var dragging; 703 var draggingHeight; 704 var draggingWidth; 705 /* 706 * Keeps track of the initialy selected list, where 'dragstart' event was triggered 707 * It allows us to move the data in between individual Sortable List instances 708 */ 709 // Origin List - data from before any item was changed 710 var originContainer; 711 var originIndex; 712 var originElementIndex; 713 var originItemsBeforeUpdate; 714 // Previous Sortable Container - we dispatch as sortenter event when a 715 // dragged item enters a sortableContainer for the first time 716 var previousContainer; 717 // Destination List - data from before any item was changed 718 var destinationItemsBeforeUpdate; 719 /** 720 * remove event handlers from items 721 * @param {Array|NodeList} items 722 */ 723 var removeItemEvents = function (items) { 724 removeEventListener(items, 'dragstart'); 725 removeEventListener(items, 'dragend'); 726 removeEventListener(items, 'dragover'); 727 removeEventListener(items, 'dragenter'); 728 removeEventListener(items, 'drop'); 729 removeEventListener(items, 'mouseenter'); 730 removeEventListener(items, 'mouseleave'); 731 };
732 // Remove container events 733 var removeContainerEvents = function (originContainer, previousContainer) { 734 if (originContainer) { 735 removeEventListener(originContainer, 'dragleave'); 736 } 737 if (previousContainer && (previousContainer !== originContainer)) { 738 removeEventListener(previousContainer, 'dragleave'); 739 } 740 }; 741 /** 742 * getDragging returns the current element to drag or 743 * a copy of the element. 744 * Is Copy Active for sortable 745 * @param {HTMLElement} draggedItem - the item that the user drags 746 * @param {HTMLElement} sortable a single sortable 747 */ 748 var getDragging = function (draggedItem, sortable) { 749 var ditem = draggedItem; 750 if (store(sortable).getConfig('copy') === true) { 751 ditem = draggedItem.cloneNode(true); 752 addAttribute(ditem, 'aria-copied', 'true'); 753 draggedItem.parentElement.appendChild(ditem); 754 ditem.style.display = 'none'; 755 ditem.oldDisplay = draggedItem.style.display; 756 } 757 return ditem; 758 }; 759 /** 760 * Remove data from sortable 761 * @param {HTMLElement} sortable a single sortable 762 */ 763 var removeSortableData = function (sortable) { 764 removeData(sortable); 765 removeAttribute(sortable, 'aria-dropeffect'); 766 }; 767 /** 768 * Remove data from items 769 * @param {Array<HTMLElement>|HTMLElement} items 770 */ 771 var removeItemData = function (items) { 772 removeAttribute(items, 'aria-grabbed'); 773 removeAttribute(items, 'aria-copied'); 774 removeAttribute(items, 'draggable'); 775 removeAttribute(items, 'role'); 776 }; 777 /** 778 * find sortable from element. travels up parent element until found or null. 779 * @param {HTMLElement} element a single sortable 780 * @param {Event} event - the current event. We need to pass it to be able to 781 * find Sortable whith shadowRoot (document fragment has no parent) 782 */ 783 function findSortable(element, event) { 784 if (event.composedPath) { 785 return event.composedPath().find(function (el) { return el.isSortable; }); 786 } 787 while (element.isSortable !== true) { 788 element = element.parentElement; 789 } 790 return element; 791 } 792 /** 793 * Dragging event is on the sortable element. finds the top child that 794 * contains the element. 795 * @param {HTMLElement} sortableElement a single sortable 796 * @param {HTMLElement} element is that being dragged 797 */ 798 function findDragElement(sortableElement, element) { 799 var options = addData(sortableElement, 'opts'); 800 var items = filter(sortableElement.children, options.items); 801 var itemlist = items.filter(function (ele) { 802 return ele.contains(element) || (ele.shadowRoot && ele.shadowRoot.contains(element)); 803 }); 804 return itemlist.length > 0 ? itemlist[0] : element; 805 } 806 /** 807 * Destroy the sortable 808 * @param {HTMLElement} sortableElement a single sortable 809 */ 810 var destroySortable = function (sortableElement) { 811 var opts = addData(sortableElement, 'opts') || {}; 812 var items = filter(sortableElement.children, opts.items); 813 var handles = getHandles(items, opts.handle); 814 // disable adding hover class 815 enableHoverClass(sortableElement, false); 816 // remove event handlers & data from sortable 817 removeEventListener(sortableElement, 'dragover'); 818 removeEventListener(sortableElement, 'dragenter'); 819 removeEventListener(sortableElement, 'dragstart'); 820 removeEventListener(sortableElement, 'dragend'); 821 removeEventListener(sortableElement, 'drop'); 822 // remove event data from sortable 823 removeSortableData(sortableElement); 824 // remove event handlers & data from items 825 removeEventListener(handles, 'mousedown'); 826 removeItemEvents(items); 827 removeItemData(items); 828 removeContainerEvents(originContainer, previousContainer); 829 // clear sortable flag 830 sortableElement.isSortable = false; 831 }; 832 /** 833 * Enable the sortable 834 * @param {HTMLElement} sortableElement a single sortable 835 */ 836 var enableSortable = function (sortableElement) { 837 var opts = addData(sortableElement, 'opts'); 838 var items = filter(sortableElement.children, opts.items); 839 var handles = getHandles(items, opts.handle); 840 addAttribute(sortableElement, 'aria-dropeffect', 'move'); 841 addData(sortableElement, '_disabled', 'false'); 842 addAttribute(handles, 'draggable', 'true'); 843 // enable hover class 844 enableHoverClass(sortableElement, true); 845 // @todo: remove this fix 846 // IE FIX for ghost 847 // can be disabled as it has the side effect that other events 848 // (e.g. click) will be ignored 849 if (opts.disableIEFix === false) { 850 var spanEl = (document || window.document).createElement('span'); 851 if (typeof spanEl.dragDrop === 'function') { 852 addEventListener(handles, 'mousedown', function () { 853 if (items.indexOf(this) !== -1) { 854 this.dragDrop(); 855 } 856 else { 857 var parent = this.parentElement; 858 while (items.indexOf(parent) === -1) { 859 parent = parent.parentElement; 860 } 861 parent.dragDrop(); 862 } 863 }); 864 } 865 } 866 }; 867 /** 868 * Disable the sortable 869 * @param {HTMLElement} sortableElement a single sortable 870 */ 871 var disableSortable = function (sortableElement) { 872 var opts = addData(sortableElement, 'opts'); 873 var items = filter(sortableElement.children, opts.items); 874 var handles = getHandles(items, opts.handle); 875 addAttribute(sortableElement, 'aria-dropeffect', 'none'); 876 addData(sortableElement, '_disabled', 'true'); 877 addAttribute(handles, 'draggable', 'false'); 878 removeEventListener(handles, 'mousedown'); 879 enableHoverClass(sortableElement, false); 880 }; 881 /** 882 * Reload the sortable 883 * @param {HTMLElement} sortableElement a single sortable
884 * @description events need to be removed to not be double bound 885 */ 886 var reloadSortable = function (sortableElement) { 887 var opts = addData(sortableElement, 'opts'); 888 var items = filter(sortableElement.children, opts.items); 889 var handles = getHandles(items, opts.handle); 890 addData(sortableElement, '_disabled', 'false'); 891 // remove event handlers from items 892 removeItemEvents(items); 893 removeContainerEvents(originContainer, previousContainer); 894 removeEventListener(handles, 'mousedown'); 895 // remove event handlers from sortable 896 removeEventListener(sortableElement, 'dragover'); 897 removeEventListener(sortableElement, 'dragenter'); 898 removeEventListener(sortableElement, 'drop'); 899 }; 900 /** 901 * Public sortable object 902 * @param {Array|NodeList} sortableElements 903 * @param {object|string} options|method 904 */ 905 function sortable(sortableElements, options) { 906 // get method string to see if a method is called 907 var method = String(options); 908 options = options || {}; 909 // check if the user provided a selector instead of an element 910 if (typeof sortableElements === 'string') { 911 sortableElements = document.querySelectorAll(sortableElements); 912 } 913 // if the user provided an element, return it in an array to keep the return value consistant 914 if (sortableElements instanceof HTMLElement) { 915 sortableElements = [sortableElements]; 916 } 917 sortableElements = Array.prototype.slice.call(sortableElements); 918 if (/serialize/.test(method)) { 919 return sortableElements.map(function (sortableContainer) { 920 var opts = addData(sortableContainer, 'opts'); 921 return serialize(sortableContainer, opts.itemSerializer, opts.containerSerializer); 922 }); 923 } 924 sortableElements.forEach(function (sortableElement) { 925 if (/enable|disable|destroy/.test(method)) { 926 return sortable[method](sortableElement); 927 } 928 // log deprecation 929 ['connectWith', 'disableIEFix'].forEach(function (configKey) { 930 if (Object.prototype.hasOwnProperty.call(options, configKey) && options[configKey] !== null) { 931 console.warn("HTML5Sortable: You are using the deprecated configuration \"" + configKey + "\". This will be removed in an upcoming version, make sure to migrate to the new options when updating."); 932 } 933 }); 934 // merge options with default options 935 options = Object.assign({}, defaultConfiguration, store(sortableElement).config, options); 936 // init data store for sortable 937 store(sortableElement).config = options; 938 // set options on sortable 939 addData(sortableElement, 'opts', options); 940 // property to define as sortable 941 sortableElement.isSortable = true; 942 // reset sortable 943 reloadSortable(sortableElement); 944 // initialize 945 var listItems = filter(sortableElement.children, options.items); 946 // create element if user defined a placeholder element as a string 947 var customPlaceholder; 948 if (options.placeholder !== null && options.placeholder !== undefined) { 949 var tempContainer = document.createElement(sortableElement.tagName); 950 if (options.placeholder instanceof HTMLElement) { 951 tempContainer.appendChild(options.placeholder); 952 } 953 else { 954 tempContainer.innerHTML = options.placeholder; 955 } 956 customPlaceholder = tempContainer.children[0]; 957 } 958 // add placeholder 959 store(sortableElement).placeholder = makePlaceholder(sortableElement, customPlaceholder, options.placeholderClass); 960 addData(sortableElement, 'items', options.items); 961 if (options.acceptFrom) { 962 addData(sortableElement, 'acceptFrom', options.acceptFrom); 963 } 964 else if (options.connectWith) { 965 addData(sortableElement, 'connectWith', options.connectWith); 966 } 967 enableSortable(sortableElement); 968 addAttribute(listItems, 'role', 'option'); 969 addAttribute(listItems, 'aria-grabbed', 'false'); 970 /* 971 Handle drag events on draggable items 972 Handle is set at the sortableElement level as it will bubble up 973 from the item 974 */ 975 addEventListener(sortableElement, 'dragstart', function (e) { 976 // ignore dragstart events 977 var target = getEventTarget(e); 978 if (target.isSortable === true) { 979 return; 980 } 981 e.stopImmediatePropagation(); 982 if ((options.handle && !target.matches(options.handle)) || target.getAttribute('draggable') === 'false') { 983 return; 984 } 985 var sortableContainer = findSortable(target, e); 986 var dragItem = findDragElement(sortableContainer, target); 987 // grab values 988 originItemsBeforeUpdate = filter(sortableContainer.children, options.items); 989 originIndex = originItemsBeforeUpdate.indexOf(dragItem); 990 originElementIndex = getIndex(dragItem, sortableContainer.children); 991 originContainer = sortableContainer; 992 // add transparent clone or other ghost to cursor 993 setDragImage(e, dragItem, options.customDragImage); 994 // cache selsection & add attr for dragging 995 draggingHeight = getElementHeight(dragItem); 996 draggingWidth = getElementWidth(dragItem); 997 dragItem.classList.add(options.draggingClass); 998 dragging = getDragging(dragItem, sortableContainer); 999 addAttribute(dragging, 'aria-grabbed', 'true'); 1000 // dispatch sortstart event on each element in group
1001 sortableContainer.dispatchEvent(new CustomEvent('sortstart', { 1002 detail: { 1003 origin: { 1004 elementIndex: originElementIndex, 1005 index: originIndex, 1006 container: originContainer 1007 }, 1008 item: dragging, 1009 originalTarget: target 1010 } 1011 })); 1012 }); 1013 /* 1014 We are capturing targetSortable before modifications with 'dragenter' event 1015 */ 1016 addEventListener(sortableElement, 'dragenter', function (e) { 1017 var target = getEventTarget(e); 1018 var sortableContainer = findSortable(target, e); 1019 if (sortableContainer && sortableContainer !== previousContainer) { 1020 destinationItemsBeforeUpdate = filter(sortableContainer.children, addData(sortableContainer, 'items')) 1021 .filter(function (item) { return item !== store(sortableElement).placeholder; }); 1022 if (options.dropTargetContainerClass) { 1023 sortableContainer.classList.add(options.dropTargetContainerClass); 1024 } 1025 sortableContainer.dispatchEvent(new CustomEvent('sortenter', { 1026 detail: { 1027 origin: { 1028 elementIndex: originElementIndex, 1029 index: originIndex, 1030 container: originContainer 1031 }, 1032 destination: { 1033 container: sortableContainer, 1034 itemsBeforeUpdate: destinationItemsBeforeUpdate 1035 }, 1036 item: dragging, 1037 originalTarget: target 1038 } 1039 })); 1040 addEventListener(sortableContainer, 'dragleave', function (e) { 1041 // TODO: rename outTarget to be more self-explanatory 1042 // e.fromElement for very old browsers, similar to relatedTarget 1043 var outTarget = e.relatedTarget || e.fromElement; 1044 if (!e.currentTarget.contains(outTarget)) { 1045 if (options.dropTargetContainerClass) { 1046 sortableContainer.classList.remove(options.dropTargetContainerClass); 1047 } 1048 sortableContainer.dispatchEvent(new CustomEvent('sortleave', { 1049 detail: { 1050 origin: { 1051 elementIndex: originElementIndex, 1052 index: originIndex, 1053 container: sortableContainer 1054 }, 1055 item: dragging, 1056 originalTarget: target 1057 } 1058 })); 1059 } 1060 }); 1061 } 1062 previousContainer = sortableContainer; 1063 }); 1064 /* 1065 * Dragend Event - https://developer.mozilla.org/en-US/docs/Web/Events/dragend 1066 * Fires each time dragEvent end, or ESC pressed 1067 * We are using it to clean up any draggable elements and placeholders 1068 */ 1069 addEventListener(sortableElement, 'dragend', function (e) { 1070 if (!dragging) { 1071 return; 1072 } 1073 dragging.classList.remove(options.draggingClass); 1074 addAttribute(dragging, 'aria-grabbed', 'false'); 1075 if (dragging.getAttribute('aria-copied') === 'true' && addData(dragging, 'dropped') !== 'true') { 1076 dragging.remove(); 1077 } 1078 if (dragging.oldDisplay !== undefined) { 1079 dragging.style.display = dragging.oldDisplay; 1080 delete dragging.oldDisplay; 1081 } 1082 var visiblePlaceholder = Array.from(stores.values()).map(function (data) { return data.placeholder; }) 1083 .filter(function (placeholder) { return placeholder instanceof HTMLElement; }) 1084 .filter(isInDom)[0]; 1085 if (visiblePlaceholder) { 1086 visiblePlaceholder.remove(); 1087 } 1088 // dispatch sortstart event on each element in group 1089 sortableElement.dispatchEvent(new CustomEvent('sortstop', { 1090 detail: { 1091 origin: { 1092 elementIndex: originElementIndex, 1093 index: originIndex, 1094 container: originContainer 1095 }, 1096 item: dragging 1097 } 1098 })); 1099 previousContainer = null; 1100 dragging = null; 1101 draggingHeight = null; 1102 draggingWidth = null; 1103 }); 1104 /* 1105 * Drop Event - https://developer.mozilla.org/en-US/docs/Web/Events/drop 1106 * Fires when valid drop target area is hit 1107 */ 1108 addEventListener(sortableElement, 'drop', function (e) { 1109 if (!listsConnected(sortableElement, dragging.parentElement)) { 1110 return; 1111 } 1112 e.preventDefault(); 1113 e.stopPropagation(); 1114 addData(dragging, 'dropped', 'true'); 1115 // get the one placeholder that is currently visible 1116 var visiblePlaceholder = Array.from(stores.values()).map(function (data) { 1117 return data.placeholder; 1118 }) 1119 // filter only HTMLElements 1120 .filter(function (placeholder) { return placeholder instanceof HTMLElement; }) 1121 // only elements in DOM 1122 .filter(isInDom)[0]; 1123 if (visiblePlaceholder) { 1124 visiblePlaceholder.replaceWith(dragging); 1125 // to avoid flickering restoring element display immediately after replacing placeholder 1126 if (dragging.oldDisplay !== undefined) { 1127 dragging.style.display = dragging.oldDisplay; 1128 delete dragging.oldDisplay; 1129 } 1130 } 1131 else { 1132 // set the dropped value to 'false' to delete copied dragging at the time of 'dragend' 1133 addData(dragging, 'dropped', 'false'); 1134 return; 1135 } 1136 /* 1137 * Fires Custom Event - 'sortstop' 1138 */ 1139 sortableElement.dispatchEvent(new CustomEvent('sortstop', { 1140 detail: { 1141 origin: { 1142 elementIndex: originElementIndex, 1143 index: originIndex, 1144 container: originContainer 1145 }, 1146 item: dragging 1147 } 1148 })); 1149 var placeholder = store(sortableElement).placeholder; 1150 var originItems = filter(originContainer.children, options.items) 1151 .filter(function (item) { return item !== placeholder; }); 1152 var destinationContainer = this.isSortable === true ? this : this.parentElement; 1153 var destinationItems = filter(destinationContainer.children, addData(destinationContainer, 'items')) 1154 .filter(function (item) { return item !== placeholder; }); 1155 var destinationElementIndex = getIndex(dragging, Array.from(dragging.parentElement.children) 1156 .filter(function (item) { return item !== placeholder; })); 1157 var destinationIndex = getIndex(dragging, destinationItems); 1158 if (options.dropTargetContainerClass) { 1159 destinationContainer.classList.remove(options.dropTargetContainerClass); 1160 } 1161 /* 1162 * When a list item changed container lists or index within a list 1163 * Fires Custom Event - 'sortupdate' 1164 */ 1165 if (originElementIndex !== destinationElementIndex || originContainer !== destinationContainer) { 1166 sortableElement.dispatchEvent(new CustomEvent('sortupdate', { 1167 detail: { 1168 origin: { 1169 elementIndex: originElementIndex, 1170 index: originIndex, 1171 container: originContainer, 1172 itemsBeforeUpdate: originItemsBeforeUpdate, 1173 items: originItems 1174 }, 1175 destination: { 1176 index: destinationIndex, 1177 elementIndex: destinationElementIndex, 1178 container: destinationContainer, 1179 itemsBeforeUpdate: destinationItemsBeforeUpdate, 1180 items: destinationItems 1181 }, 1182 item: dragging 1183 } 1184 })); 1185 } 1186 }); 1187 var debouncedDragOverEnter = debounce(function (sortableElement, element, pageX, pageY) { 1188 if (!dragging) { 1189 return; 1190 } 1191 // set placeholder height if forcePlaceholderSize option is set 1192 if (options.forcePlaceholderSize) { 1193 store(sortableElement).placeholder.style.height = draggingHeight + 'px'; 1194 store(sortableElement).placeholder.style.width = draggingWidth + 'px'; 1195 } 1196 // if element the draggedItem is dragged onto is within the array of all elements in list 1197 // (not only items, but also disabled, etc.) 1198 if (Array.from(sortableElement.children).indexOf(element) > -1) { 1199 var thisHeight = getElementHeight(element); 1200 var thisWidth = getElementWidth(element); 1201 var placeholderIndex = getIndex(store(sortableElement).placeholder, element.parentElement.children); 1202 var thisIndex = getIndex(element, element.parentElement.children); 1203 // Check if `element` is bigger than the draggable. If it is, we have to define a dead zone to prevent flickering 1204 if (thisHeight > draggingHeight || thisWidth > draggingWidth) { 1205 // Dead zone? 1206 var deadZoneVertical = thisHeight - draggingHeight; 1207 var deadZoneHorizontal = thisWidth - draggingWidth; 1208 var offsetTop = offset(element).top; 1209 var offsetLeft = offset(element).left; 1210 if (placeholderIndex < thisIndex && 1211 ((options.orientation === 'vertical' && pageY < offsetTop) || 1212 (options.orientation === 'horizontal' && pageX < offsetLeft))) { 1213 return; 1214 } 1215 if (placeholderIndex > thisIndex && 1216 ((options.orientation === 'vertical' && pageY > offsetTop + thisHeight - deadZoneVertical) || 1217 (options.orientation === 'horizontal' && pageX > offsetLeft + thisWidth - deadZoneHorizontal))) { 1218 return; 1219 } 1220 } 1221 if (dragging.oldDisplay === undefined) { 1222 dragging.oldDisplay = dragging.style.display; 1223 } 1224 if (dragging.style.display !== 'none') { 1225 dragging.style.display = 'none'; 1226 } 1227 // To avoid flicker, determine where to position the placeholder 1228 // based on where the mouse pointer is relative to the elements 1229 // vertical center. 1230 var placeAfter = false;
1231 try { 1232 var elementMiddleVertical = offset(element).top + element.offsetHeight / 2; 1233 var elementMiddleHorizontal = offset(element).left + element.offsetWidth / 2; 1234 placeAfter = (options.orientation === 'vertical' && (pageY >= elementMiddleVertical)) || 1235 (options.orientation === 'horizontal' && (pageX >= elementMiddleHorizontal)); 1236 } 1237 catch (e) { 1238 placeAfter = placeholderIndex < thisIndex; 1239 } 1240 if (placeAfter) { 1241 insertAfter(element, store(sortableElement).placeholder); 1242 } 1243 else { 1244 insertBefore(element, store(sortableElement).placeholder); 1245 } 1246 // get placeholders from all stores & remove all but current one 1247 Array.from(stores.values()) 1248 // remove empty values 1249 .filter(function (data) { return data.placeholder !== undefined; }) 1250 // foreach placeholder in array if outside of current sorableContainer -> remove from DOM 1251 .forEach(function (data) { 1252 if (data.placeholder !== store(sortableElement).placeholder) { 1253 data.placeholder.remove(); 1254 } 1255 }); 1256 } 1257 else { 1258 // get all placeholders from store 1259 var placeholders = Array.from(stores.values()) 1260 .filter(function (data) { return data.placeholder !== undefined; }) 1261 .map(function (data) { 1262 return data.placeholder; 1263 }); 1264 // check if element is not in placeholders 1265 if (placeholders.indexOf(element) === -1 && sortableElement === element && !filter(element.children, options.items).length) { 1266 placeholders.forEach(function (element) { return element.remove(); }); 1267 element.appendChild(store(sortableElement).placeholder); 1268 } 1269 } 1270 }, options.debounce); 1271 // Handle dragover and dragenter events on draggable items 1272 var onDragOverEnter = function (e) { 1273 var element = e.target; 1274 var sortableElement = element.isSortable === true ? element : findSortable(element, e); 1275 element = findDragElement(sortableElement, element); 1276 if (!dragging || !listsConnected(sortableElement, dragging.parentElement) || addData(sortableElement, '_disabled') === 'true') { 1277 return; 1278 } 1279 var options = addData(sortableElement, 'opts'); 1280 if (parseInt(options.maxItems) && filter(sortableElement.children, addData(sortableElement, 'items')).length > parseInt(options.maxItems) && dragging.parentElement !== sortableElement) { 1281 return; 1282 } 1283 e.preventDefault(); 1284 e.stopPropagation(); 1285 e.dataTransfer.dropEffect = store(sortableElement).getConfig('copy') === true ? 'copy' : 'move'; 1286 debouncedDragOverEnter(sortableElement, element, e.pageX, e.pageY); 1287 }; 1288 addEventListener(listItems.concat(sortableElement), 'dragover', onDragOverEnter); 1289 addEventListener(listItems.concat(sortableElement), 'dragenter', onDragOverEnter); 1290 }); 1291 return sortableElements; 1292 } 1293 sortable.destroy = function (sortableElement) { 1294 destroySortable(sortableElement); 1295 }; 1296 sortable.enable = function (sortableElement) { 1297 enableSortable(sortableElement); 1298 }; 1299 sortable.disable = function (sortableElement) { 1300 disableSortable(sortableElement); 1301 }; 1302 /* START.TESTS_ONLY */ 1303 sortable.__testing = { 1304 // add internal methods here for testing purposes 1305 data: addData, 1306 removeItemEvents: removeItemEvents, 1307 removeItemData: removeItemData, 1308 removeSortableData: removeSortableData, 1309 removeContainerEvents: removeContainerEvents 1310 }; 1311 1312 return sortable; 1313 1314}()); 1315 1316export default class extends Controller { 1317 static targets = ["category"] 1318 1319 connect() { 1320 console.log("category sortable controller connected") 1321 1322 sortable('#sortable-category-list', { 1323 })[0].addEventListener('sortupdate', function (e) { 1324 1325 const { items } = sortable('#sortable-category-list', "serialize")[0] 1326 const category_ids = items.map(item => item.node.dataset.categoryId) 1327 const category_positions = items.map(item => item.index) 1328 console.log(category_positions) 1329 1330 // Get the project ID from the current URL 1331 const projectId = window.location.pathname.split('/')[2] // /projects/:id/categories 1332 const url = `/projects/${projectId}/categories/update_position` 1333 1334 fetch(url, { 1335 method: 'PATCH', 1336 headers: { 1337 'Content-Type': 'application/json', 1338 'X-CSRF-Token': document.querySelector('meta[name="csrf-token"]').getAttribute('content') 1339 }, 1340 body: JSON.stringify({ category_ids: category_ids, category_positions: category_positions }) 1341 }).then(response => { 1342 console.log(response) 1343 }).catch(error => { 1344 alert(error) 1345 }) 1346 }) 1347 } 1348 1349 disconnect() { 1350 // Remove event listeners from each category element 1351 1352 1353 } 1354 1355 1356};
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.