Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ajax functionality #38

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ Features
- All / None optgroup selection buttons...
- **Translatable**.
- **Zero dependencies**. And written in vanilla JavaScript...
- **Ajax request**
- **Free to use**. Because it's MIT licensed <3

Install & Embed
Expand Down Expand Up @@ -197,3 +198,39 @@ tail.select("select", {
cbLoopGroup: undefined // [0.4.0] Function
});
```

### Ajax functionality
```
tail.select('#tail-select', {ajaxUrl: url});
```
Widget makes a request to the backend and waits for object like this
```
{
"results": [
{
"id": 1,
"text": "Boston"
},
{
"id": 2,
"text": "Seatle"
},
{
"id": 3,
"text": "New York"
},
{
"id": 4,
"text": "Chicago"
},
{
"id": 5,
"text": "Washington"
},
{
"id": 6,
"text": "San Diego"
}
]
}
```
208 changes: 130 additions & 78 deletions js/tail.select.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
/**
* Added functionality for AJAX requests, ensuring that checked items are not removed after an AJAX request
* and are left at the top of the result list. Additionally, the list scrolls to the last checked item,
* making it easier to review newly received items.
*
* @modified_by Igor Kremin <[email protected]>
* @modified_date 2024-01-06
* @forked_repo https://github.com/igor-kremin/tail.select.js/
*
* tail.select - The vanilla JavaScript solution to make your <select> fields awesome!
*
* @author Ciprian Popescu <[email protected]>
Expand All @@ -14,6 +22,8 @@ const tail = {
const defaultOptions = {
multiTags: false,
multiCounter: true,
toolbar: true,
ajaxUrl: null,
theme: 'light', // light|dark
classNames: 'tail-default',
strings: {
Expand All @@ -28,7 +38,7 @@ const tail = {
const opts = { ...defaultOptions, ...options };

// Extract options
const { multiTags, multiCounter, theme, classNames, strings } = opts;
const { multiTags, multiCounter, theme, classNames, ajaxUrl, strings } = opts;

//
const originalSelects = document.querySelectorAll(selector);
Expand All @@ -55,10 +65,10 @@ const tail = {
searchInput.type = "text";
searchInput.classList.add('tail--search');
searchInput.placeholder = strings.placeholder || "Select an option...";

// Add focus event to change the placeholder
searchInput.addEventListener("focus", () => {
searchInput.placeholder = strings.search || "Type in to search...";
searchInput.select();
});

// Add blur event to revert the placeholder when not focused
Expand All @@ -72,30 +82,32 @@ const tail = {
);

// Create floating toolbar
const tailFloatingToolbar = document.createElement("div");
tailFloatingToolbar.classList.add("tail--toolbar");

// Create toggle-all checkbox
const toggleAllCheckbox = document.createElement("input");
toggleAllCheckbox.type = "checkbox";
toggleAllCheckbox.value = strings.all || "All";
toggleAllCheckbox.addEventListener("change", () =>
toggleAll(originalSelect, toggleAllCheckbox)
);
if (opts.toolbar) {
const tailFloatingToolbar = document.createElement("div");
tailFloatingToolbar.classList.add("tail--toolbar");

// Create toggle-all checkbox
const toggleAllCheckbox = document.createElement("input");
toggleAllCheckbox.type = "checkbox";
toggleAllCheckbox.value = strings.all || "All";
toggleAllCheckbox.addEventListener("change", () =>
toggleAll(originalSelect, toggleAllCheckbox)
);

const toggleAllLabel = document.createElement("label");
toggleAllLabel.textContent = strings.all || "All";
toggleAllLabel.classList.add("all");
toggleAllLabel.appendChild(toggleAllCheckbox);

// Create uncheck-all button
const uncheckAllButton = document.createElement("button");
uncheckAllButton.type = 'button';
uncheckAllButton.textContent = strings.none || "None";
uncheckAllButton.classList.add("uncheck");
uncheckAllButton.addEventListener("click", () =>
uncheckAll(originalSelect)
);
const toggleAllLabel = document.createElement("label");
toggleAllLabel.textContent = strings.all || "All";
toggleAllLabel.classList.add("all");
toggleAllLabel.appendChild(toggleAllCheckbox);

// Create uncheck-all button
const uncheckAllButton = document.createElement("button");
uncheckAllButton.type = 'button';
uncheckAllButton.textContent = strings.none || "None";
uncheckAllButton.classList.add("uncheck");
uncheckAllButton.addEventListener("click", () =>
uncheckAll(originalSelect)
);
}

if (opts.multiCounter) {
// Create counter
Expand All @@ -112,11 +124,15 @@ const tail = {
nestedList.style.display = "none"; // Initially hide the list

customDropdown.appendChild(searchInput);
customDropdown.appendChild(tailFloatingToolbar);

if (opts.toolbar) {
document.querySelectorAll(`${originalSelect.id}`).classList.add('new-class');
customDropdown.appendChild(tailFloatingToolbar);
tailFloatingToolbar.appendChild(toggleAllLabel);
tailFloatingToolbar.appendChild(uncheckAllButton);
}
customDropdown.appendChild(nestedList);

tailFloatingToolbar.appendChild(toggleAllLabel);
tailFloatingToolbar.appendChild(uncheckAllButton);

// Insert custom dropdown after the original select
originalSelect.insertAdjacentElement("afterend", customDropdown);
Expand Down Expand Up @@ -214,13 +230,9 @@ const tail = {
updateCustomTextInput(originalSelect);
}

//

optionLabel.appendChild(optionCheckbox);
optionLabel.appendChild(optionLabelText);

optionItem.appendChild(optionLabel);

nestedOptionsList.appendChild(optionItem);
}

Expand Down Expand Up @@ -260,7 +272,6 @@ const tail = {
updateCounter(originalSelect);
updateCustomTextInput(originalSelect);
}
//

optionLabel.appendChild(optionCheckbox);
optionLabel.appendChild(optionLabelText);
Expand Down Expand Up @@ -388,11 +399,7 @@ const tail = {
);

if (option) {
if (checkbox.checked) {
option.selected = true;
} else {
option.selected = false;
}
option.selected = checkbox.checked

// Trigger change event for the original select
const event = new Event("change", { bubbles: true });
Expand Down Expand Up @@ -441,16 +448,7 @@ const tail = {

if (opts.multiCounter) {
// Update the counter element
let customId = originalSelect.id;
if (customId) {
let counterElement = document
.querySelector(`.${customId}`)
.querySelector(".tail--counter");

if (counterElement) {
counterElement.textContent = count;
}
}
updateCounter(originalSelect);
}
}

Expand All @@ -459,18 +457,16 @@ const tail = {
const optionItems = nestedList.querySelectorAll("div");

optionItems.forEach((optionItem) => {
const optionCheckbox = optionItem.querySelector(
'input[type="checkbox"]'
);
const optionLabel = optionCheckbox.nextElementSibling.textContent.toLowerCase();
const optgroupItem = optionItem.closest("div");
const optionCheckbox = optionItem.querySelector('input[type="checkbox"]');
const optionText = optionCheckbox.nextElementSibling.textContent.toLowerCase();
const isMatch = optionText.includes(searchTerm);
const isChecked = optionCheckbox.checked;

// Hide or show options based on the search term
optionCheckbox.style.display = optionLabel.includes(
searchTerm
)
? "inline-block"
: "none";
if (isChecked || isMatch) {
optionItem.style.display = "";
} else {
optionItem.style.display = "none";
}
});

optionItems.forEach((optionItem) => {
Expand Down Expand Up @@ -543,26 +539,11 @@ const tail = {
}
}


function updateCounter(originalSelect) {
// Get the custom ID for the current original select
let customId = originalSelect.id;

if (customId) {
// Get the counter element
let counterElement = document
.querySelector(`.${customId}`)
.querySelector(".tail--counter");

if (counterElement) {
// Get the count of selected options
const count = Array.from(originalSelect.options).filter(
(opt) => opt.selected
).length;

// Update the counter element
counterElement.textContent = count;
}
const counterElement = document.querySelector(`.${originalSelect.id} .tail--counter`);
if (counterElement) {
const count = Array.from(originalSelect.options).filter(option => option.selected).length;
counterElement.textContent = count;
}
}

Expand Down Expand Up @@ -598,6 +579,77 @@ const tail = {
document.addEventListener("keydown", handleKeyDown);

buildNestedList();

if (ajaxUrl) {
searchInput.addEventListener("input", function() {
loadData(this.value);
});
}
function loadData(searchQuery = "", selectedIds = "") {
let url = opts.ajaxUrl;
if (!url) return;
if (searchQuery) {
url += `?term=${encodeURIComponent(searchQuery)}`;
fetch(url)
.then(response => response.json())
.then(data => updateOptions(data.results))
.catch(error => console.error('Error loading data:', error));
}
}
function updateOptions(data) {
// checkedIds ids from checked options
const checkedIds = [];
nestedList.querySelectorAll('input[type="checkbox"]:checked').forEach(checkbox => {
checkedIds.push(parseInt(checkbox.value, 10));
});

// delete not checked options
nestedList.querySelectorAll('.tail--nested-dropdown-item').forEach(item => {
const checkbox = item.querySelector('input[type="checkbox"]');
if (!checkbox.checked) {
const optionToBeRemoved = Array.from(originalSelect.options).find(option => option.value === checkbox.value);
if (optionToBeRemoved) originalSelect.remove(optionToBeRemoved.index);
item.remove();
}
});

data.forEach(item => {
const id = parseInt(item.id, 10);
if (!checkedIds.includes(id)) {
// create options from data
const optionItem = document.createElement("div");
optionItem.classList.add("tail--nested-dropdown-item");

const checkbox = document.createElement("input");
checkbox.type = "checkbox";
checkbox.value = id;
checkbox.addEventListener('change', () => {
toggleOption(checkbox);
});
const optionLabel = document.createElement("label");
const optionLabelText = document.createElement("span");
optionLabelText.textContent = item.text;

optionLabel.appendChild(checkbox);
optionLabel.appendChild(optionLabelText);
optionItem.appendChild(optionLabel);
nestedList.appendChild(optionItem);

const newOption = new Option(item.text, id, false, false);
// also add the option to the original select
originalSelect.add(newOption);

// scroll to the last checked checkbox
if (checkedIds.length > 0) {
const lastCheckedCheckbox = nestedList.querySelectorAll('input[type="checkbox"]:checked')[
nestedList.querySelectorAll('input[type="checkbox"]:checked').length - 1
].closest(".tail--nested-dropdown-item");
nestedList.scrollTop = (lastCheckedCheckbox.clientHeight * (checkedIds.length -1 ));
}
}
});
}

});
}
};
Loading