1import { Controller } from "@hotwired/stimulus" 2 3import Sortable from "sortablejs" 4 5/* 6 This Stimulus controller uses sortablejs for drag and drop, it then sends a JSON body with the 7 following items to the designated URL: 8 9 index : the new index 10 item-id : the item's id (if specified) 11 parent-id : the parent id (if specified) 12 13 Example usages: 14 15 <div data-controller="draggable"> 16 <div data-draggable-url="/url/to/item1">Item 1</div> 17 <div data-draggable-url="/url/to/item2">Item 2</div> 18 <div data-draggable-url="/url/to/item3">Item 3</div> 19 </div> 20 21 The above will send a PATCH request with the new index to the items' data-draggable-url 22 23 If you want another request type you can specify it like so: 24 25 <div data-controller="draggable" data-draggable-method="PUT"> 26 <div data-draggable-url="/url/to/item1">Item 1</div> 27 <div data-draggable-url="/url/to/item2">Item 2</div> 28 <div data-draggable-url="/url/to/item3">Item 3</div> 29 </div> 30 31 The above will send PUT requests 32 33 You can also use one URL for all your items: 34 35 <div data-controller="draggable" data-draggable-url="/url/to/items" data-draggable-item-id="true"> 36 <div data-draggable-item-id="1">Item 1</div> 37 <div data-draggable-item-id="2">Item 2</div> 38 <div data-draggable-item-id="3">Item 3</div> 39 </div> 40 41 The above will send a PATCH request with the new index and the item-id to the data-draggable-url. If 42 data-draggable-item-id is true it will not send a request without an item-id 43 44 If you want to use a nested structure you can specify a selector to specify which elements 45 under the controller's element will be activated, specifying the data-draggable-group allows 46 you to drag items between the different lists: 47 48 <div data-controller="draggable" data-draggable-url="/url/to/items" data-draggable-selector="ul" data-draggable-group="nested" data-draggable-item-id="true" data-draggable-parent-id="true"> 49 <ul> 50 <li data-draggable-item-id="1">Item 1</li> 51 <li data-draggable-item-id="2"> 52 Item 2 53 <ul data-draggable-parent-id="2"> 54 <li data-draggable-item-id="3">Item 3</li> 55 </ul> 56 </li> 57 </ul> 58 </div> 59 60 The above will also require a data-draggable-parent-id to drag an item to another list 61 */ 62export default class extends Controller { 63 connect() { 64 const self = this 65 66 let selector = self.data.get("selector") 67 68 if (selector) { 69 self.element.querySelectorAll(selector).forEach((el) => { 70 self.sortable(el) 71 }) 72 } else { 73 self.sortable(self.element) 74 } 75 } 76 77 sortable(element) { 78 const self = this 79 80 let options = { 81 animation: 150, 82 fallbackOnBody: true, 83 swapThreshold: 0.65, 84 onEnd: (evt) => { 85 let url = self.data.get("url") || evt.item.getAttribute("data-draggable-url") 86 let method = self.data.get("method") || evt.item.getAttribute("data-draggable-method") || "PATCH" 87 let csrfToken = document.querySelector("meta[name=csrf-token]").content 88 let itemIdSelector = self.data.get("item-id-selector") 89 let itemId = evt.item.getAttribute("data-draggable-item-id") 90 if (!itemId && itemIdSelector) { 91 itemId = evt.item.querySelector(itemIdSelector).getAttribute("data-draggable-item-id") 92 } 93 let parentId = evt.to.getAttribute("data-draggable-parent-id") 94 let newIndex = evt.newIndex 95 96 if (self.data.get("item-id") && !itemId) { 97 return 98 } 99 100 if (self.data.get("parent-id") && !parentId) { 101 return 102 } 103 104 let response = fetch(url, { 105 method: method, 106 headers: { 107 Accept: "application/json, text/javascript", 108 "Content-Type": "application/json", 109 "X-CSRF-Token": csrfToken, 110 }, 111 body: JSON.stringify({ 112 item_id: itemId, 113 parent_id: parentId, 114 index: newIndex, 115 }), 116 }).then((response) => { 117 var event 118 if (response.ok) { 119 event = new CustomEvent("draggable.success", { detail: { element: self.element } }) 120 self.element.dispatchEvent(event) 121 } else if (!response.ok) { 122 event = new CustomEvent("draggable.error", { detail: { element: self.element } }) 123 self.element.dispatchEvent(event) 124 window.alert("An unexpected error occured, your drop possibly failed.") 125 } 126 }) 127 }, 128 } 129 130 if (self.data.get("handle")) { 131 options["handle"] = self.data.get("handle") 132 } 133 134 if (self.data.get("group")) { 135 options["group"] = self.data.get("group") 136 } 137 138 if (self.data.get("item-selector")) { 139 options["draggable"] = self.data.get("item-selector") 140 } 141 142 Sortable.create(element, options) 143 } 144};
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.