mirror of https://github.com/zulip/zulip.git
dropdown_list_widget: Convert DropdownListWidget to ES6 class.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
This commit is contained in:
parent
d131327760
commit
567859d15d
|
@ -8,244 +8,248 @@ import * as blueslip from "./blueslip";
|
|||
import {$t} from "./i18n";
|
||||
import * as ListWidget from "./list_widget";
|
||||
|
||||
export function DropdownListWidget({
|
||||
widget_name,
|
||||
data,
|
||||
default_text,
|
||||
render_text = (item_name) => item_name,
|
||||
null_value = null,
|
||||
include_current_item = true,
|
||||
value,
|
||||
on_update = () => {},
|
||||
}) {
|
||||
// Initializing values
|
||||
this.widget_name = widget_name;
|
||||
this.data = data;
|
||||
this.default_text = default_text;
|
||||
this.render_text = render_text;
|
||||
this.null_value = null_value;
|
||||
this.include_current_item = include_current_item;
|
||||
this.initial_value = value;
|
||||
this.on_update = on_update;
|
||||
export class DropdownListWidget {
|
||||
constructor({
|
||||
widget_name,
|
||||
data,
|
||||
default_text,
|
||||
render_text = (item_name) => item_name,
|
||||
null_value = null,
|
||||
include_current_item = true,
|
||||
value,
|
||||
on_update = () => {},
|
||||
}) {
|
||||
// Initializing values
|
||||
this.widget_name = widget_name;
|
||||
this.data = data;
|
||||
this.default_text = default_text;
|
||||
this.render_text = render_text;
|
||||
this.null_value = null_value;
|
||||
this.include_current_item = include_current_item;
|
||||
this.initial_value = value;
|
||||
this.on_update = on_update;
|
||||
|
||||
this.container_id = `${widget_name}_widget`;
|
||||
this.value_id = `id_${widget_name}`;
|
||||
this.container_id = `${widget_name}_widget`;
|
||||
this.value_id = `id_${widget_name}`;
|
||||
|
||||
if (value === undefined) {
|
||||
this.initial_value = null_value;
|
||||
blueslip.warn("dropdown-list-widget: Called without a default value; using null value");
|
||||
}
|
||||
}
|
||||
|
||||
DropdownListWidget.prototype.render_default_text = function ($elem) {
|
||||
$elem.text(this.default_text);
|
||||
$elem.addClass("text-warning");
|
||||
$elem.closest(".input-group").find(".dropdown_list_reset_button").hide();
|
||||
};
|
||||
|
||||
DropdownListWidget.prototype.render = function (value) {
|
||||
$(`#${CSS.escape(this.container_id)} #${CSS.escape(this.value_id)}`).data("value", 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;
|
||||
}
|
||||
|
||||
// Happy path
|
||||
const item = this.data.find((x) => x.value === value.toString());
|
||||
|
||||
if (item === undefined) {
|
||||
this.render_default_text($elem);
|
||||
return;
|
||||
}
|
||||
|
||||
const text = this.render_text(item.name);
|
||||
$elem.text(text);
|
||||
$elem.removeClass("text-warning");
|
||||
$elem.closest(".input-group").find(".dropdown_list_reset_button").show();
|
||||
};
|
||||
|
||||
DropdownListWidget.prototype.update = function (value) {
|
||||
this.render(value);
|
||||
this.on_update(value);
|
||||
};
|
||||
|
||||
DropdownListWidget.prototype.register_event_handlers = function () {
|
||||
$(`#${CSS.escape(this.container_id)} .dropdown-list-body`).on(
|
||||
"click keypress",
|
||||
".list_item",
|
||||
(e) => {
|
||||
const $setting_elem = $(e.currentTarget).closest(
|
||||
`.${CSS.escape(this.widget_name)}_setting`,
|
||||
);
|
||||
if (e.type === "keypress") {
|
||||
if (e.key === "Enter") {
|
||||
$setting_elem.find(".dropdown-menu").dropdown("toggle");
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
const value = $(e.currentTarget).attr("data-value");
|
||||
this.update(value);
|
||||
},
|
||||
);
|
||||
$(`#${CSS.escape(this.container_id)} .dropdown_list_reset_button`).on("click", (e) => {
|
||||
this.update(this.null_value);
|
||||
e.preventDefault();
|
||||
});
|
||||
};
|
||||
|
||||
DropdownListWidget.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]`,
|
||||
);
|
||||
const get_data = () => {
|
||||
if (this.include_current_item) {
|
||||
return data;
|
||||
if (value === undefined) {
|
||||
this.initial_value = null_value;
|
||||
blueslip.warn("dropdown-list-widget: Called without a default value; using null value");
|
||||
}
|
||||
return data.filter((x) => x.value !== this.value.toString());
|
||||
};
|
||||
}
|
||||
|
||||
ListWidget.create($dropdown_list_body, get_data(data), {
|
||||
name: `${CSS.escape(this.widget_name)}_list`,
|
||||
modifier(item) {
|
||||
return render_dropdown_list({item});
|
||||
},
|
||||
filter: {
|
||||
$element: $search_input,
|
||||
predicate(item, value) {
|
||||
return item.name.toLowerCase().includes(value);
|
||||
render_default_text($elem) {
|
||||
$elem.text(this.default_text);
|
||||
$elem.addClass("text-warning");
|
||||
$elem.closest(".input-group").find(".dropdown_list_reset_button").hide();
|
||||
}
|
||||
|
||||
render(value) {
|
||||
$(`#${CSS.escape(this.container_id)} #${CSS.escape(this.value_id)}`).data("value", 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;
|
||||
}
|
||||
|
||||
// Happy path
|
||||
const item = this.data.find((x) => x.value === value.toString());
|
||||
|
||||
if (item === undefined) {
|
||||
this.render_default_text($elem);
|
||||
return;
|
||||
}
|
||||
|
||||
const text = this.render_text(item.name);
|
||||
$elem.text(text);
|
||||
$elem.removeClass("text-warning");
|
||||
$elem.closest(".input-group").find(".dropdown_list_reset_button").show();
|
||||
}
|
||||
|
||||
update(value) {
|
||||
this.render(value);
|
||||
this.on_update(value);
|
||||
}
|
||||
|
||||
register_event_handlers() {
|
||||
$(`#${CSS.escape(this.container_id)} .dropdown-list-body`).on(
|
||||
"click keypress",
|
||||
".list_item",
|
||||
(e) => {
|
||||
const $setting_elem = $(e.currentTarget).closest(
|
||||
`.${CSS.escape(this.widget_name)}_setting`,
|
||||
);
|
||||
if (e.type === "keypress") {
|
||||
if (e.key === "Enter") {
|
||||
$setting_elem.find(".dropdown-menu").dropdown("toggle");
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
const value = $(e.currentTarget).attr("data-value");
|
||||
this.update(value);
|
||||
},
|
||||
},
|
||||
$simplebar_container: $(`#${CSS.escape(this.container_id)} .dropdown-list-wrapper`),
|
||||
});
|
||||
};
|
||||
);
|
||||
$(`#${CSS.escape(this.container_id)} .dropdown_list_reset_button`).on("click", (e) => {
|
||||
this.update(this.null_value);
|
||||
e.preventDefault();
|
||||
});
|
||||
}
|
||||
|
||||
// Sets the focus to the ListWidget input once the dropdown button is clicked.
|
||||
DropdownListWidget.prototype.dropdown_toggle_click_handler = function () {
|
||||
const $dropdown_toggle = $(`#${CSS.escape(this.container_id)} .dropdown-toggle`);
|
||||
const $search_input = $(
|
||||
`#${CSS.escape(this.container_id)} .dropdown-search > input[type=text]`,
|
||||
);
|
||||
|
||||
$dropdown_toggle.on("click", () => {
|
||||
$search_input.val("").trigger("input");
|
||||
});
|
||||
};
|
||||
|
||||
DropdownListWidget.prototype.dropdown_focus_events = function () {
|
||||
const $search_input = $(
|
||||
`#${CSS.escape(this.container_id)} .dropdown-search > input[type=text]`,
|
||||
);
|
||||
const $dropdown_menu = $(`.${CSS.escape(this.widget_name)}_setting .dropdown-menu`);
|
||||
|
||||
const dropdown_elements = () => {
|
||||
setup_dropdown_widget(data) {
|
||||
const $dropdown_list_body = $(
|
||||
`#${CSS.escape(this.container_id)} .dropdown-list-body`,
|
||||
).expectOne();
|
||||
|
||||
return $dropdown_list_body.children().find("a");
|
||||
};
|
||||
|
||||
// Rest of the key handlers are supported by our
|
||||
// bootstrap library.
|
||||
$dropdown_menu.on("keydown", (e) => {
|
||||
function trigger_element_focus($element) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
$element.trigger("focus");
|
||||
}
|
||||
|
||||
switch (e.key) {
|
||||
case "ArrowDown": {
|
||||
switch (e.target) {
|
||||
case dropdown_elements().last()[0]:
|
||||
trigger_element_focus($search_input);
|
||||
break;
|
||||
case $search_input[0]:
|
||||
trigger_element_focus(dropdown_elements().first());
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
const $search_input = $(
|
||||
`#${CSS.escape(this.container_id)} .dropdown-search > input[type=text]`,
|
||||
);
|
||||
const get_data = () => {
|
||||
if (this.include_current_item) {
|
||||
return data;
|
||||
}
|
||||
case "ArrowUp": {
|
||||
switch (e.target) {
|
||||
case dropdown_elements().first()[0]:
|
||||
trigger_element_focus($search_input);
|
||||
break;
|
||||
case $search_input[0]:
|
||||
trigger_element_focus(dropdown_elements().last());
|
||||
}
|
||||
return data.filter((x) => x.value !== this.value.toString());
|
||||
};
|
||||
|
||||
break;
|
||||
}
|
||||
case "Tab": {
|
||||
switch (e.target) {
|
||||
case $search_input[0]:
|
||||
trigger_element_focus(dropdown_elements().first());
|
||||
break;
|
||||
case dropdown_elements().last()[0]:
|
||||
trigger_element_focus($search_input);
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
DropdownListWidget.prototype.setup = function () {
|
||||
// populate the dropdown
|
||||
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]`,
|
||||
);
|
||||
const $dropdown_toggle = $(`#${CSS.escape(this.container_id)} .dropdown-toggle`);
|
||||
|
||||
this.setup_dropdown_widget(this.data);
|
||||
|
||||
$(`#${CSS.escape(this.container_id)} .dropdown-search`).on("click", (e) => {
|
||||
e.stopPropagation();
|
||||
});
|
||||
|
||||
this.dropdown_toggle_click_handler();
|
||||
|
||||
$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");
|
||||
}
|
||||
});
|
||||
|
||||
this.dropdown_focus_events();
|
||||
|
||||
this.render(this.initial_value);
|
||||
this.register_event_handlers();
|
||||
};
|
||||
|
||||
// Returns the updated value
|
||||
DropdownListWidget.prototype.value = function () {
|
||||
let val = $(`#${CSS.escape(this.container_id)} #${CSS.escape(this.value_id)}`).data("value");
|
||||
if (val === null) {
|
||||
val = "";
|
||||
ListWidget.create($dropdown_list_body, get_data(data), {
|
||||
name: `${CSS.escape(this.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(this.container_id)} .dropdown-list-wrapper`),
|
||||
});
|
||||
}
|
||||
return val;
|
||||
};
|
||||
|
||||
// Sets the focus to the ListWidget input once the dropdown button is clicked.
|
||||
dropdown_toggle_click_handler() {
|
||||
const $dropdown_toggle = $(`#${CSS.escape(this.container_id)} .dropdown-toggle`);
|
||||
const $search_input = $(
|
||||
`#${CSS.escape(this.container_id)} .dropdown-search > input[type=text]`,
|
||||
);
|
||||
|
||||
$dropdown_toggle.on("click", () => {
|
||||
$search_input.val("").trigger("input");
|
||||
});
|
||||
}
|
||||
|
||||
dropdown_focus_events() {
|
||||
const $search_input = $(
|
||||
`#${CSS.escape(this.container_id)} .dropdown-search > input[type=text]`,
|
||||
);
|
||||
const $dropdown_menu = $(`.${CSS.escape(this.widget_name)}_setting .dropdown-menu`);
|
||||
|
||||
const dropdown_elements = () => {
|
||||
const $dropdown_list_body = $(
|
||||
`#${CSS.escape(this.container_id)} .dropdown-list-body`,
|
||||
).expectOne();
|
||||
|
||||
return $dropdown_list_body.children().find("a");
|
||||
};
|
||||
|
||||
// Rest of the key handlers are supported by our
|
||||
// bootstrap library.
|
||||
$dropdown_menu.on("keydown", (e) => {
|
||||
function trigger_element_focus($element) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
$element.trigger("focus");
|
||||
}
|
||||
|
||||
switch (e.key) {
|
||||
case "ArrowDown": {
|
||||
switch (e.target) {
|
||||
case dropdown_elements().last()[0]:
|
||||
trigger_element_focus($search_input);
|
||||
break;
|
||||
case $search_input[0]:
|
||||
trigger_element_focus(dropdown_elements().first());
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case "ArrowUp": {
|
||||
switch (e.target) {
|
||||
case dropdown_elements().first()[0]:
|
||||
trigger_element_focus($search_input);
|
||||
break;
|
||||
case $search_input[0]:
|
||||
trigger_element_focus(dropdown_elements().last());
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case "Tab": {
|
||||
switch (e.target) {
|
||||
case $search_input[0]:
|
||||
trigger_element_focus(dropdown_elements().first());
|
||||
break;
|
||||
case dropdown_elements().last()[0]:
|
||||
trigger_element_focus($search_input);
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
setup() {
|
||||
// populate the dropdown
|
||||
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]`,
|
||||
);
|
||||
const $dropdown_toggle = $(`#${CSS.escape(this.container_id)} .dropdown-toggle`);
|
||||
|
||||
this.setup_dropdown_widget(this.data);
|
||||
|
||||
$(`#${CSS.escape(this.container_id)} .dropdown-search`).on("click", (e) => {
|
||||
e.stopPropagation();
|
||||
});
|
||||
|
||||
this.dropdown_toggle_click_handler();
|
||||
|
||||
$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");
|
||||
}
|
||||
});
|
||||
|
||||
this.dropdown_focus_events();
|
||||
|
||||
this.render(this.initial_value);
|
||||
this.register_event_handlers();
|
||||
}
|
||||
|
||||
// Returns the updated value
|
||||
value() {
|
||||
let val = $(`#${CSS.escape(this.container_id)} #${CSS.escape(this.value_id)}`).data(
|
||||
"value",
|
||||
);
|
||||
if (val === null) {
|
||||
val = "";
|
||||
}
|
||||
return val;
|
||||
}
|
||||
}
|
||||
|
||||
// A widget mostly similar to `DropdownListWidget` but
|
||||
// used in cases of multiple dropdown selection.
|
||||
|
|
Loading…
Reference in New Issue