Skip to content

Instantly share code, notes, and snippets.

@aryanshridhar
Created July 3, 2021 17:41
Show Gist options
  • Save aryanshridhar/520f2ee135eea56936a253cf601d04ec to your computer and use it in GitHub Desktop.
Save aryanshridhar/520f2ee135eea56936a253cf601d04ec to your computer and use it in GitHub Desktop.
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