mirror of
				https://github.com/zulip/zulip.git
				synced 2025-11-04 05:53:43 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			168 lines
		
	
	
		
			5.5 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			168 lines
		
	
	
		
			5.5 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;
 | 
						|
            const navigation_keys = ["ArrowUp", "ArrowDown", "Escape"];
 | 
						|
            if (!navigation_keys.includes(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,
 | 
						|
    };
 | 
						|
};
 |