step 1 : autocomplete multi select (di hold)
This commit is contained in:
237
assets/mcu/htmx4.html
Normal file
237
assets/mcu/htmx4.html
Normal file
@@ -0,0 +1,237 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Typeahead Example with HTMX</title>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://cdn.jsdelivr.net/npm/bootstrap@4.1.3/dist/css/bootstrap.min.css"
|
||||
integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO"
|
||||
crossorigin="anonymous"
|
||||
/>
|
||||
<script src="https://unpkg.com/htmx.org@1.9.12/dist/htmx.js"></script>
|
||||
<style>
|
||||
.dropdown-menu {
|
||||
max-height: 200px;
|
||||
overflow-y: auto;
|
||||
width: 100%; /* Ensure the dropdown menu width matches the input width */
|
||||
}
|
||||
.dropdown-menu.show {
|
||||
display: block; /* Ensure the dropdown menu is displayed */
|
||||
}
|
||||
.spinner-border {
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
}
|
||||
.badge-container {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
margin-top: 10px;
|
||||
}
|
||||
.badge-container .badge {
|
||||
margin: 2px;
|
||||
}
|
||||
.badge-container input {
|
||||
border: none;
|
||||
outline: none;
|
||||
flex-grow: 1;
|
||||
}
|
||||
.dropdown-item.active {
|
||||
background-color: #007bff;
|
||||
color: white;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container mt-5">
|
||||
<h2>Typeahead Example with HTMX</h2>
|
||||
<input type="text" name="selectedvalue" />
|
||||
<div class="dropdown">
|
||||
<input
|
||||
type="text"
|
||||
id="search-inputx"
|
||||
name="query"
|
||||
placeholder="Search for a state..."
|
||||
hx-get="http://localhost:5000/mcu/proses_obj_search.php"
|
||||
hx-trigger="input changed delay:300ms, focus from:search-inputx"
|
||||
hx-target="#dropdown-menu"
|
||||
hx-indicator="#loading-indicator"
|
||||
aria-haspopup="true"
|
||||
aria-expanded="false"
|
||||
autocomplete="off"
|
||||
hx-include="[name='selectedvalue']"
|
||||
class="form-control"
|
||||
/>
|
||||
<div id="loading-indicator" style="display: none">
|
||||
<div class="spinner-border text-primary" role="status">
|
||||
<span class="sr-only">Loading...</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="dropdown-menu" id="dropdown-menu"></div>
|
||||
</div>
|
||||
|
||||
<div class="badge-container form-control">
|
||||
<!-- Badge will be appended here -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let selectedItems = [];
|
||||
|
||||
function selectItem(element) {
|
||||
var container = document.querySelector(".badge-container");
|
||||
|
||||
// Get the selected item text and id
|
||||
var itemText = element.textContent;
|
||||
var itemId = element.getAttribute("data-id");
|
||||
|
||||
// Check if the item is already selected
|
||||
if (selectedItems.some((item) => item.id === itemId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Add the item to the selected items list
|
||||
selectedItems.push({ id: itemId, name: itemText });
|
||||
|
||||
// Create a badge element
|
||||
var badge = document.createElement("span");
|
||||
badge.className = "badge badge-primary";
|
||||
badge.textContent = itemText;
|
||||
|
||||
// Create a close button
|
||||
var closeBtn = document.createElement("span");
|
||||
closeBtn.className = "badge badge-light ml-1";
|
||||
closeBtn.style.cursor = "pointer";
|
||||
closeBtn.textContent = "×";
|
||||
closeBtn.onclick = function () {
|
||||
container.removeChild(badge);
|
||||
selectedItems = selectedItems.filter((item) => item.id !== itemId);
|
||||
updateQueryParameter();
|
||||
};
|
||||
|
||||
// Append close button to badge
|
||||
badge.appendChild(closeBtn);
|
||||
|
||||
// Append the badge to the container
|
||||
container.appendChild(badge);
|
||||
|
||||
// Clear the input
|
||||
document.getElementById("search-inputx").value = "";
|
||||
|
||||
// Hide dropdown
|
||||
document.getElementById("dropdown-menu").classList.remove("show");
|
||||
|
||||
// Highlight the selected item in the dropdown
|
||||
element.classList.add("selected");
|
||||
|
||||
// Update query parameter
|
||||
updateQueryParameter();
|
||||
}
|
||||
|
||||
function updateQueryParameter() {
|
||||
var selectedValues = selectedItems.map((item) => item.id).join(",");
|
||||
document.querySelector('input[name="selectedvalue"]').value =
|
||||
selectedValues;
|
||||
}
|
||||
|
||||
// Display loading indicator when request is in progress
|
||||
document.body.addEventListener("htmx:configRequest", function (event) {
|
||||
document.getElementById("loading-indicator").style.display = "block";
|
||||
});
|
||||
|
||||
document.body.addEventListener("htmx:afterOnLoad", function (event) {
|
||||
document.getElementById("loading-indicator").style.display = "none";
|
||||
var dropdown = document.getElementById("dropdown-menu");
|
||||
if (dropdown.children.length > 0) {
|
||||
dropdown.classList.add("show");
|
||||
dropdown.focus();
|
||||
var activeItem = dropdown.querySelector(".active");
|
||||
if (activeItem) {
|
||||
dropdown.scrollTop = activeItem.offsetTop - dropdown.offsetTop;
|
||||
}
|
||||
} else {
|
||||
dropdown.classList.remove("show");
|
||||
}
|
||||
});
|
||||
|
||||
// Hide the dropdown when clicking outside
|
||||
document.addEventListener("click", function (event) {
|
||||
var isClickInside =
|
||||
event.target.classList.contains("search-input") ||
|
||||
event.target.classList.contains("badge-container") ||
|
||||
event.target.closest(".badge-container");
|
||||
if (!isClickInside) {
|
||||
document.getElementById("dropdown-menu").classList.remove("show");
|
||||
}
|
||||
});
|
||||
|
||||
// Handle keyboard navigation
|
||||
document
|
||||
.getElementById("search-inputx")
|
||||
.addEventListener("keydown", function (event) {
|
||||
var dropdownMenu = document.getElementById("dropdown-menu");
|
||||
var items = dropdownMenu.querySelectorAll(".dropdown-item");
|
||||
var activeItem = dropdownMenu.querySelector(".active");
|
||||
|
||||
if (event.key === "ArrowDown") {
|
||||
event.preventDefault(); // Prevent page scroll
|
||||
if (!activeItem) {
|
||||
items[0].classList.add("active");
|
||||
} else {
|
||||
activeItem.classList.remove("active");
|
||||
var next = activeItem.nextElementSibling;
|
||||
if (next) {
|
||||
next.classList.add("active");
|
||||
} else {
|
||||
items[0].classList.add("active");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (event.key === "ArrowUp") {
|
||||
event.preventDefault(); // Prevent page scroll
|
||||
if (!activeItem) {
|
||||
items[items.length - 1].classList.add("active");
|
||||
} else {
|
||||
activeItem.classList.remove("active");
|
||||
var prev = activeItem.previousElementSibling;
|
||||
if (prev) {
|
||||
prev.classList.add("active");
|
||||
} else {
|
||||
items[items.length - 1].classList.add("active");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (event.key === "Enter" && activeItem) {
|
||||
selectItem(activeItem);
|
||||
}
|
||||
|
||||
if (activeItem) {
|
||||
dropdownMenu.scrollTop =
|
||||
activeItem.offsetTop - dropdownMenu.offsetTop;
|
||||
}
|
||||
});
|
||||
|
||||
// Set the input value and hide the dropdown when an item is clicked
|
||||
document.body.addEventListener("click", function (event) {
|
||||
if (event.target.classList.contains("dropdown-item")) {
|
||||
selectItem(event.target);
|
||||
}
|
||||
});
|
||||
|
||||
// Show dropdown when input gets focus and has value
|
||||
document
|
||||
.getElementById("search-inputx")
|
||||
.addEventListener("focus", function (event) {
|
||||
var input = event.target;
|
||||
var dropdown = document.getElementById("dropdown-menu");
|
||||
if (input.value.length > 0) {
|
||||
dropdown.classList.add("show");
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
33
assets/mcu/proses_obj_search.php
Normal file
33
assets/mcu/proses_obj_search.php
Normal file
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
header('Content-Type: text/html'); // Set header to indicate HTML response
|
||||
|
||||
// Data dummy dengan id dan nama sebagai contoh
|
||||
$data = [
|
||||
["id" => 1, "name" => "Alabama"],
|
||||
["id" => 2, "name" => "Alaska"],
|
||||
// other data...
|
||||
["id" => 49, "name" => "Wisconsin"],
|
||||
["id" => 50, "name" => "Wyoming"],
|
||||
];
|
||||
|
||||
$query = isset($_GET['query']) ? $_GET['query'] : '';
|
||||
$selected = isset($_GET['selectedvalue']) ? explode(',', $_GET['selectedvalue']) : [];
|
||||
|
||||
if ($query) {
|
||||
$results = array_filter($data, function($item) use ($query, $selected) {
|
||||
// Filter out items that do not match the query and are already selected
|
||||
return stripos($item["name"], $query) !== false && !in_array($item["id"], $selected);
|
||||
});
|
||||
} else {
|
||||
$results = [];
|
||||
}
|
||||
|
||||
// Prepare HTML response
|
||||
$html = '';
|
||||
foreach ($results as $result) {
|
||||
$html .= '<a class="dropdown-item" href="#" data-id="' . htmlspecialchars($result["id"]) . '" onclick="selectItem(this)">' . htmlspecialchars($result["name"]) . '</a>';
|
||||
}
|
||||
|
||||
// Return HTML response
|
||||
echo $html;
|
||||
?>
|
||||
Reference in New Issue
Block a user