mirror of
				https://github.com/zulip/zulip.git
				synced 2025-11-04 05:53:43 +00:00 
			
		
		
		
	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.
		
			
				
	
	
		
			167 lines
		
	
	
		
			5.4 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			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,
 | 
						|
    };
 | 
						|
};
 |