Files
zulip/static/js/dropdown_list_widget.js
Priyank Patel 94459f1425 dropdown_list_widget: Use e.key instead of deprecated e.keyCode.
We use e.key for our side of code and still pass e.keyCode and
e.which to the custom jQuery event we trigger. The event is handled
by our patched copy of outdated bootstrap and it still requires the
e.keyCode and e.which properties.

One of the places this widget is used is in the settings panel, at
Manage Organization > Organization Settings > Notifications > New
stream notifications. One of the handler is attached to the
dropdown menu items that shows up when you click the "#announce"
button. The other one is attached to the search button. The second
one triggers the custom jQuery event and passes the event data to
bootstrap unless the Up & Down arrow or Esacape is used. The first
event handler handles the Enter click on one of the items and saves
it. Verified that the widget works as expected.
2021-06-02 14:04:53 -07:00

167 lines
5.4 KiB
JavaScript

import $ from "jquery";
import render_dropdown_list from "../templates/settings/dropdown_list.hbs";
import * as blueslip from "./blueslip";
import * as ListWidget from "./list_widget";
export const DropdownListWidget = function ({
widget_name,
data,
default_text,
render_text = (item_name) => item_name,
null_value = null,
include_current_item = true,
value,
on_update = () => {},
}) {
const container_id = `${widget_name}_widget`;
const value_id = `id_${widget_name}`;
if (value === undefined) {
value = null_value;
blueslip.warn("dropdown-list-widget: Called without a default value; using null value");
}
const render_default_text = (elem) => {
elem.text(default_text);
elem.addClass("text-warning");
elem.closest(".input-group").find(".dropdown_list_reset_button:enabled").hide();
};
const render = (value) => {
$(`#${CSS.escape(container_id)} #${CSS.escape(value_id)}`).data("value", value);
const elem = $(`#${CSS.escape(container_id)} #${CSS.escape(widget_name)}_name`);
if (!value || value === null_value) {
render_default_text(elem);
return;
}
// Happy path
const item = data.find((x) => x.value === value.toString());
if (item === undefined) {
render_default_text(elem);
return;
}
const text = render_text(item.name);
elem.text(text);
elem.removeClass("text-warning");
elem.closest(".input-group").find(".dropdown_list_reset_button:enabled").show();
};
const update = (value) => {
render(value);
on_update(value);
};
const register_event_handlers = () => {
$(`#${CSS.escape(container_id)} .dropdown-list-body`).on(
"click keypress",
".list_item",
function (e) {
const setting_elem = $(this).closest(`.${CSS.escape(widget_name)}_setting`);
if (e.type === "keypress") {
if (e.key === "Enter") {
setting_elem.find(".dropdown-menu").dropdown("toggle");
} else {
return;
}
}
const value = $(this).attr("data-value");
update(value);
},
);
$(`#${CSS.escape(container_id)} .dropdown_list_reset_button`).on("click", (e) => {
update(null_value);
e.preventDefault();
});
};
const setup = () => {
// populate the dropdown
const dropdown_list_body = $(
`#${CSS.escape(container_id)} .dropdown-list-body`,
).expectOne();
const search_input = $(`#${CSS.escape(container_id)} .dropdown-search > input[type=text]`);
const dropdown_toggle = $(`#${CSS.escape(container_id)} .dropdown-toggle`);
const get_data = (data) => {
if (include_current_item) {
return data;
}
return data.filter((x) => x.value !== value.toString());
};
ListWidget.create(dropdown_list_body, get_data(data), {
name: `${CSS.escape(widget_name)}_list`,
modifier(item) {
return render_dropdown_list({item});
},
filter: {
element: search_input,
predicate(item, value) {
return item.name.toLowerCase().includes(value);
},
},
simplebar_container: $(`#${CSS.escape(container_id)} .dropdown-list-wrapper`),
});
$(`#${CSS.escape(container_id)} .dropdown-search`).on("click", (e) => {
e.stopPropagation();
});
dropdown_toggle.on("click", () => {
search_input.val("").trigger("input");
});
dropdown_toggle.on("focus", (e) => {
// On opening a Bootstrap Dropdown, the parent element receives focus.
// Here, we want our search input to have focus instead.
e.preventDefault();
// This function gets called twice when focusing the
// dropdown, and only in the second call is the input
// field visible in the DOM; so the following visibility
// check ensures we wait for the second call to focus.
if (dropdown_list_body.is(":visible")) {
search_input.trigger("focus");
}
});
search_input.on("keydown", (e) => {
const {key, keyCode, which} = e;
if (!/(ArrowUp|ArrowDown|Escape)/.test(key)) {
return;
}
e.preventDefault();
e.stopPropagation();
// We pass keyCode instead of key here because the outdated
// bootstrap library we have at static/third/ still uses the
// deprecated keyCode & which properties.
const custom_event = new $.Event("keydown.dropdown.data-api", {keyCode, which});
dropdown_toggle.trigger(custom_event);
});
render(value);
register_event_handlers();
};
const get_value = () => {
let val = $(`#${CSS.escape(container_id)} #${CSS.escape(value_id)}`).data("value");
if (val === null) {
val = "";
}
return val;
};
// Run setup() automatically on initialization.
setup();
return {
render,
value: get_value,
update,
};
};