PageSourceSearch

https://odt.co.th/assets/controllers/dropdown_controller-cbd6f2b8.js

js odt.co.th collected 2026-10-10 00:35:55 UTC 3,104 bytes, 107 lines download raw bytes

1import { Controller } from "@hotwired/stimulus";
2
3export default class DropdownController extends Controller {
4  static targets = ["selectedItems", "checkbox", "hiddenInput", "menu"];
5
6  connect() {
7    this.showPlaceholder();
8    this.updateSelectedItems();
9    document.addEventListener("click", this.outsideClick.bind(this));
10  }
11
12  toggleDropdown() {
13    // Check if menu is currently hidden (either by CSS class or inline style)
14    const isHidden = this.menuTarget.classList.contains('hidden') || 
15                     this.menuTarget.style.display === 'none' ||
16                     !this.menuTarget.style.display;
17    
18    if (isHidden) {
19      this.menuTarget.classList.remove('hidden');
20      this.menuTarget.style.display = 'block';
21    } else {
22      this.menuTarget.classList.add('hidden');
23      this.menuTarget.style.display = 'none';
24    }
25  }
26
27  outsideClick(event) {
28    if (!this.element.contains(event.target)) {
29      this.menuTarget.classList.add('hidden');
30      this.menuTarget.style.display = 'none';
31    }
32  }
33
34  updateSelectedItems() {
35    const selected = this.checkboxTargets
36      .filter((cb) => cb.checked)
37      .map((cb) => cb.value);
38
39    this.hiddenInputTarget.value = selected.join(",");
40
41    if (selected.length === 0) {
42      this.selectedItemsTarget.innerHTML = "";
43      this.showPlaceholder();
44      return;
45    }
46
47    if (this.selectedItemsTarget.dataset.placeholder === "true") {
48      this.selectedItemsTarget.innerHTML = "";
49      delete this.selectedItemsTarget.dataset.placeholder;
50    }
51
52    this.selectedItemsTarget.querySelectorAll("[data-value]").forEach((badge) => {
53      if (!selected.includes(badge.dataset.value)) {
54        badge.remove();
55      }
56    });
57
58    selected.forEach((value) => {
59      if (this.selectedItemsTarget.querySelector(`[data-value="${value}"]`)) {
60        return;
61      }
62
63      const span = document.createElement("span");
64      span.classList.add(
65        "bg-primary",
66        "text-white",
67        "px-2",
68        "py-1",
69        "flex",
70        "items-center",
71        "gap-1",
72        "rounded-md",
73        "text-md"
74      );
75      span.dataset.value = value;
76      span.dataset.testid = "related-course-names";
77      span.textContent = value;
78
79      const removeBtn = document.createElement("button");
80      removeBtn.type = "button";
81      removeBtn.textContent = "⊗";
82      removeBtn.classList.add(
83        "text-white",
84        "bg-transparent",
85        "border-0",
86        "text-lg",
87        "cursor-pointer",
88        "ml-1"
89      );
90      removeBtn.addEventListener("click", () => {
91        const checkbox = this.checkboxTargets.find((c) => c.value === value);
92        if (checkbox) checkbox.checked = false;
93        span.remove();
94        this.updateSelectedItems();
95      });
96
97      span.appendChild(removeBtn);
98      this.selectedItemsTarget.appendChild(span);
99    });
100  }
101
102  showPlaceholder() {
103    this.selectedItemsTarget.textContent = "Select related courses...";
104    this.selectedItemsTarget.classList.add("rounded-lg", "text-gray-500");
105    this.selectedItemsTarget.dataset.placeholder = "true";
106  }
107}

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.