PageSourceSearch

https://ods.nl/assets/satis/utility_controllers/draggable_controll…458e9e31ecc5ad61cd8177743cc33c831b23ea10.js

js ods.nl collected 2026-10-07 14:43:31 UTC 4,803 bytes, 144 lines download raw bytes

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.