Created
July 3, 2021 17:41
-
-
Save aryanshridhar/520f2ee135eea56936a253cf601d04ec to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
export function MultiSelectDropdownListWidget({ | |
widget_name, | |
data, | |
default_text, | |
null_value = null, | |
on_update = () => {}, | |
value, | |
limit, | |
}) { | |
// A widget mostly similar to `DropdownListWidget` but | |
// used in cases of multiple dropdown selection. | |
// Initializing values specific to `MultiSelectDropdownListWidget`. | |
this.limit = limit; | |
// Important thing to note is that this needs to be maintained as | |
// a reference type and not to deep clone it/assign it to a | |
// different variable, so that it can be later referenced within | |
// `list_widget` as well. The way we manage dropdown elements are | |
// essentially by just modifying the values in `data_selected` variable. | |
this.data_selected = []; // Populate the dropdown values selected by user. | |
DropdownListWidget.call(this, { | |
widget_name, | |
data, | |
default_text, | |
null_value, | |
on_update, | |
value, | |
}); | |
if (limit === undefined) { | |
this.limit = 2; | |
blueslip.warn( | |
"Multiselect dropdown-list-widget: Called without limit value; using 2 as the limit", | |
); | |
} | |
this.initialize_dropdown_values(); | |
} | |
MultiSelectDropdownListWidget.prototype = Object.create(DropdownListWidget.prototype); | |
MultiSelectDropdownListWidget.prototype.initialize_dropdown_values = function () { | |
if (!this.initial_value) { | |
return; | |
} | |
const elem = $(`#${CSS.escape(this.container_id)} #${CSS.escape(this.widget_name)}_name`); | |
// Push values from initial valued array to `data_selected`. | |
this.data_selected.push(...this.initial_value); | |
this.render_button_text(elem, this.limit); | |
}; | |
// Set the button text as per the selected data. | |
MultiSelectDropdownListWidget.prototype.render_button_text = function (elem, limit) { | |
const items_selected = this.data_selected.length; | |
let text = ""; | |
if (items_selected === 0) { | |
this.render_default_text(elem); | |
return; | |
} else if (limit >= items_selected) { | |
const data_selected = this.data.filter((data) => | |
this.data_selected.includes(Number.parseInt(data.value, 10)), | |
); | |
text = data_selected.map((data) => data.name).toString(); | |
} else { | |
text = `${items_selected} selected`; | |
} | |
elem.text(text); | |
elem.removeClass("text-warning"); | |
elem.closest(".input-group").find(".dropdown_list_reset_button:enabled").show(); | |
}; | |
// Override the DrodownListWidget `render` function. | |
MultiSelectDropdownListWidget.prototype.render = function (value) { | |
const elem = $(`#${CSS.escape(this.container_id)} #${CSS.escape(this.widget_name)}_name`); | |
if (!value || value === this.null_value) { | |
this.render_default_text(elem); | |
return; | |
} | |
this.render_button_text(elem, this.limit); | |
}; | |
// Cases where a user presses any dropdown item but accidentally closes | |
// the dropdown list. | |
MultiSelectDropdownListWidget.prototype.reset_dropdown_items = function () { | |
const original_items = this.checked_items ? this.checked_items : this.initial_value; | |
const items_added = _.difference(this.data_selected, original_items); | |
// Removing the unnecessary items from dropdown. | |
for (const val of items_added) { | |
const index = this.data_selected.indexOf(val); | |
if (index > -1) { | |
this.data_selected.splice(index, 1); | |
} | |
} | |
// Items that are removed in dropdown but should have been a part of it | |
const items_removed = _.difference(original_items, this.data_selected); | |
this.data_selected.push(...items_removed); | |
}; | |
// Override the DrodownListWidget `setup_dropdown_widget` function. | |
MultiSelectDropdownListWidget.prototype.setup_dropdown_widget = function (data) { | |
const dropdown_list_body = $( | |
`#${CSS.escape(this.container_id)} .dropdown-list-body`, | |
).expectOne(); | |
const search_input = $(`#${CSS.escape(this.container_id)} .dropdown-search > input[type=text]`); | |
ListWidget.create(dropdown_list_body, data, { | |
name: `${CSS.escape(this.widget_name)}_list`, | |
modifier(item) { | |
return render_dropdown_list({item}); | |
}, | |
multiselect: { | |
selected_items: this.data_selected, | |
}, | |
filter: { | |
element: search_input, | |
predicate(item, value) { | |
return item.name.toLowerCase().includes(value); | |
}, | |
}, | |
simplebar_container: $(`#${CSS.escape(this.container_id)} .dropdown-list-wrapper`), | |
}); | |
}; | |
// Add the check mark to dropdown element passed. | |
MultiSelectDropdownListWidget.prototype.add_check_mark = function (element) { | |
const value = Number.parseInt(element.attr("data-value"), 10); | |
const link_elem = element.find("a").expectOne(); | |
link_elem.prepend($("<i>", {class: "fa fa-check"})); | |
element.addClass("checked"); | |
this.data_selected.push(value); | |
}; | |
// Remove the check mark from dropdown element. | |
MultiSelectDropdownListWidget.prototype.remove_check_mark = function (element) { | |
const icon = element.find("i").expectOne(); | |
const value = Number.parseInt(element.attr("data-value"), 10); | |
const index = this.data_selected.indexOf(value); | |
if (index > -1) { | |
icon.remove(); | |
element.removeClass("checked"); | |
this.data_selected.splice(index, 1); | |
} | |
}; | |
// Override the `register_event_handlers` function. | |
MultiSelectDropdownListWidget.prototype.register_event_handlers = function () { | |
const dropdown_toggle = $(`#${CSS.escape(this.container_id)} .dropdown-toggle`); | |
const search_input = $(`#${CSS.escape(this.container_id)} .dropdown-search > input[type=text]`); | |
const dropdown_list_body = $( | |
`#${CSS.escape(this.container_id)} .dropdown-list-body`, | |
).expectOne(); | |
dropdown_toggle.on("click", () => { | |
this.reset_dropdown_items(); | |
search_input.val("").trigger("input"); | |
}); | |
dropdown_list_body.on("click keypress", ".list_item", (e) => { | |
const element = $(e.target.closest("li")); | |
if (element.hasClass("checked")) { | |
this.remove_check_mark(element); | |
} else { | |
this.add_check_mark(element); | |
} | |
e.stopPropagation(); | |
}); | |
$(`#${CSS.escape(this.container_id)} .dropdown_list_reset_button`).on("click", (e) => { | |
// Default back the value. | |
this.checked_items = undefined; | |
this.update(this.null_value); | |
e.preventDefault(); | |
}); | |
$(`#${CSS.escape(this.container_id)} .multiselect_btn`).on("click", (e) => { | |
e.preventDefault(); | |
// We deep clone the values of `data_selected` to a new | |
// variable. This is so because, arrays are reference types | |
// and modifying the parent array can change the values | |
// within the child array. Here, `checked_items` copies over the | |
// value and not just the reference. | |
this.checked_items = _.cloneDeep(this.data_selected); | |
this.update(this.data_selected); | |
}); | |
}; | |
// Returns array of values selected by user. | |
MultiSelectDropdownListWidget.prototype.value = function () { | |
let val = this.checked_items; | |
// Cases where user never pressed the filter button, | |
// we return the initial values itself in that case. | |
if (val === undefined) { | |
val = this.initial_value; | |
} | |
return val; | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment