list_widget: Make `get_item` option required and pass a default where needed.

This is a prep commit for moving towards typescript migration of this module.
We should make `get_item` required on `list_widget` because its intended to be
used with a set of keys and then hydrate the keys lazily. Also this will help
us to avoid writing messy if-conditions while filtering and sorting and make
our life much easier when we migrate to TypeScript.

I added a `default_get_item` function which is just n identity function and
also refactored some code to make use of default get item.
This commit is contained in:
Lalit 2023-05-01 17:14:40 +05:30 committed by Tim Abbott
parent 7176590eea
commit 3720fd5c76
19 changed files with 51 additions and 24 deletions

View File

@ -23,6 +23,7 @@ export function rerender_alert_words_ui() {
ListWidget.create($word_list, words, { ListWidget.create($word_list, words, {
name: "alert-words-list", name: "alert-words-list",
get_item: ListWidget.default_get_item,
modifier(alert_word) { modifier(alert_word) {
return render_alert_word_settings_item({alert_word}); return render_alert_word_settings_item({alert_word});
}, },

View File

@ -94,6 +94,7 @@ function render_attachments_ui() {
ListWidget.create($uploaded_files_table, attachments, { ListWidget.create($uploaded_files_table, attachments, {
name: "uploaded-files-list", name: "uploaded-files-list",
get_item: ListWidget.default_get_item,
modifier(attachment) { modifier(attachment) {
return render_uploaded_files_list({attachment}); return render_uploaded_files_list({attachment});
}, },

View File

@ -116,6 +116,7 @@ export class DropdownListWidget {
this.list_widget = ListWidget.create($dropdown_list_body, this.get_data(data), { this.list_widget = ListWidget.create($dropdown_list_body, this.get_data(data), {
name: `${CSS.escape(this.widget_name)}_list`, name: `${CSS.escape(this.widget_name)}_list`,
get_item: ListWidget.default_get_item,
modifier(item) { modifier(item) {
return render_dropdown_list({item}); return render_dropdown_list({item});
}, },
@ -414,6 +415,7 @@ export class MultiSelectDropdownListWidget extends DropdownListWidget {
this.list_widget = ListWidget.create($dropdown_list_body, data, { this.list_widget = ListWidget.create($dropdown_list_body, data, {
name: `${CSS.escape(this.widget_name)}_list`, name: `${CSS.escape(this.widget_name)}_list`,
get_item: ListWidget.default_get_item,
modifier(item) { modifier(item) {
return render_dropdown_list({item}); return render_dropdown_list({item});
}, },

View File

@ -47,6 +47,7 @@ export function setup(tippy_props, get_options, item_click_callback, dropdown_pr
const list_widget = ListWidget.create($dropdown_list_body, get_options(), { const list_widget = ListWidget.create($dropdown_list_body, get_options(), {
name: `${CSS.escape(tippy_props.target)}-list-widget`, name: `${CSS.escape(tippy_props.target)}-list-widget`,
get_item: ListWidget.default_get_item,
modifier(item) { modifier(item) {
return render_dropdown_list({item}); return render_dropdown_list({item});
}, },

View File

@ -24,6 +24,10 @@ export function validate_opts(opts) {
blueslip.error("$simplebar_container is missing."); blueslip.error("$simplebar_container is missing.");
return false; return false;
} }
if (!opts.get_item) {
blueslip.error("get_item is missing.");
return false;
}
return true; return true;
} }
@ -36,25 +40,18 @@ export function get_filtered_items(value, list, opts) {
const get_item = opts.get_item; const get_item = opts.get_item;
if (!opts.filter) { if (!opts.filter) {
if (get_item) {
return list.map((key) => get_item(key)); return list.map((key) => get_item(key));
} }
return [...list];
}
if (opts.filter.filterer) { if (opts.filter.filterer) {
if (get_item) {
return opts.filter.filterer( return opts.filter.filterer(
list.map((key) => get_item(key)), list.map((key) => get_item(key)),
value, value,
); );
} }
return opts.filter.filterer(list, value);
}
const predicate = (item) => opts.filter.predicate(item, value); const predicate = (item) => opts.filter.predicate(item, value);
if (get_item) {
const result = []; const result = [];
for (const key of list) { for (const key of list) {
@ -67,9 +64,6 @@ export function get_filtered_items(value, list, opts) {
return result; return result;
} }
return list.filter((item) => predicate(item));
}
export function alphabetic_sort(prop) { export function alphabetic_sort(prop) {
return function (a, b) { return function (a, b) {
// The conversion to uppercase helps make the sorting case insensitive. // The conversion to uppercase helps make the sorting case insensitive.
@ -262,9 +256,7 @@ export function create($container, list, opts) {
return; return;
} }
if (opts.get_item) {
item = opts.get_item(item); item = opts.get_item(item);
}
const html = opts.modifier(item); const html = opts.modifier(item);
if (typeof html !== "string") { if (typeof html !== "string") {
blueslip.error("List item is not a string", {item: html}); blueslip.error("List item is not a string", {item: html});
@ -495,3 +487,5 @@ export function handle_sort($th, list) {
// and not if it is undefined. // and not if it is undefined.
list.sort(sort_type, prop_name); list.sort(sort_type, prop_name);
} }
export const default_get_item = (item) => item;

View File

@ -843,6 +843,7 @@ export function complete_rerender() {
$container.empty(); $container.empty();
topics_widget = ListWidget.create($container, mapped_topic_values, { topics_widget = ListWidget.create($container, mapped_topic_values, {
name: "recent_topics_table", name: "recent_topics_table",
get_item: ListWidget.default_get_item,
$parent_container: $("#recent_topics_table"), $parent_container: $("#recent_topics_table"),
modifier(item) { modifier(item) {
return render_recent_topic_row(format_conversation(item)); return render_recent_topic_row(format_conversation(item));

View File

@ -111,6 +111,7 @@ export function populate_emoji() {
const $emoji_table = $("#admin_emoji_table").expectOne(); const $emoji_table = $("#admin_emoji_table").expectOne();
ListWidget.create($emoji_table, Object.values(emoji_data), { ListWidget.create($emoji_table, Object.values(emoji_data), {
name: "emoji_list", name: "emoji_list",
get_item: ListWidget.default_get_item,
modifier(item) { modifier(item) {
if (item.deactivated !== true) { if (item.deactivated !== true) {
return render_admin_emoji_list({ return render_admin_emoji_list({

View File

@ -41,6 +41,7 @@ export function populate_exports_table(exports) {
const $exports_table = $("#admin_exports_table").expectOne(); const $exports_table = $("#admin_exports_table").expectOne();
ListWidget.create($exports_table, Object.values(exports), { ListWidget.create($exports_table, Object.values(exports), {
name: "admin_exports_list", name: "admin_exports_list",
get_item: ListWidget.default_get_item,
modifier(data) { modifier(data) {
let failed_timestamp = data.failed_timestamp; let failed_timestamp = data.failed_timestamp;
let deleted_timestamp = data.deleted_timestamp; let deleted_timestamp = data.deleted_timestamp;

View File

@ -61,6 +61,7 @@ function populate_invites(invites_data) {
const $invites_table = $("#admin_invites_table").expectOne(); const $invites_table = $("#admin_invites_table").expectOne();
ListWidget.create($invites_table, invites_data.invites, { ListWidget.create($invites_table, invites_data.invites, {
name: "admin_invites_list", name: "admin_invites_list",
get_item: ListWidget.default_get_item,
modifier(item) { modifier(item) {
item.invited_absolute_time = timerender.absolute_time(item.invited * 1000); item.invited_absolute_time = timerender.absolute_time(item.invited * 1000);
if (item.expiry_date !== null) { if (item.expiry_date !== null) {

View File

@ -136,6 +136,7 @@ export function populate_linkifiers(linkifiers_data) {
const $linkifiers_table = $("#admin_linkifiers_table").expectOne(); const $linkifiers_table = $("#admin_linkifiers_table").expectOne();
ListWidget.create($linkifiers_table, linkifiers_data, { ListWidget.create($linkifiers_table, linkifiers_data, {
name: "linkifiers_list", name: "linkifiers_list",
get_item: ListWidget.default_get_item,
modifier(linkifier) { modifier(linkifier) {
return render_admin_linkifier_list({ return render_admin_linkifier_list({
linkifier: { linkifier: {

View File

@ -21,6 +21,7 @@ export function populate_list() {
ListWidget.create($muted_users_table, all_muted_users, { ListWidget.create($muted_users_table, all_muted_users, {
name: "muted-users-list", name: "muted-users-list",
get_item: ListWidget.default_get_item,
modifier(muted_user) { modifier(muted_user) {
return render_muted_user_ui_row({muted_user}); return render_muted_user_ui_row({muted_user});
}, },

View File

@ -35,6 +35,7 @@ export function populate_playgrounds(playgrounds_data) {
const $playgrounds_table = $("#admin_playgrounds_table").expectOne(); const $playgrounds_table = $("#admin_playgrounds_table").expectOne();
ListWidget.create($playgrounds_table, playgrounds_data, { ListWidget.create($playgrounds_table, playgrounds_data, {
name: "playgrounds_list", name: "playgrounds_list",
get_item: ListWidget.default_get_item,
modifier(playground) { modifier(playground) {
return render_admin_playground_list({ return render_admin_playground_list({
playground: { playground: {

View File

@ -108,6 +108,7 @@ export function build_default_stream_table() {
ListWidget.create($table, subs, { ListWidget.create($table, subs, {
name: "default_streams_list", name: "default_streams_list",
get_item: ListWidget.default_get_item,
modifier(item) { modifier(item) {
return render_admin_default_streams_list({ return render_admin_default_streams_list({
stream: item, stream: item,

View File

@ -25,6 +25,7 @@ export function populate_list() {
ListWidget.create($user_topics_table, all_user_topics, { ListWidget.create($user_topics_table, all_user_topics, {
name: "user-topics-list", name: "user-topics-list",
get_item: ListWidget.default_get_item,
modifier(user_topic) { modifier(user_topic) {
const context = { const context = {
user_topic, user_topic,

View File

@ -112,6 +112,7 @@ function make_list_widget({$parent_container, name, user_ids, user_can_remove_su
return ListWidget.create($list_container, users, { return ListWidget.create($list_container, users, {
name, name,
get_item: ListWidget.default_get_item,
modifier(item) { modifier(item) {
return format_member_list_elem(item, user_can_remove_subscribers); return format_member_list_elem(item, user_can_remove_subscribers);
}, },

View File

@ -66,6 +66,7 @@ function make_list_widget({$parent_container, name, user_ids}) {
return ListWidget.create($list_container, users, { return ListWidget.create($list_container, users, {
name, name,
get_item: ListWidget.default_get_item,
$parent_container, $parent_container,
sort_fields: { sort_fields: {
email: settings_users.sort_email, email: settings_users.sort_email,

View File

@ -199,6 +199,7 @@ export function setup_page(callback) {
group_list_widget = ListWidget.create($container, user_groups_list, { group_list_widget = ListWidget.create($container, user_groups_list, {
name: "user-groups-overlay", name: "user-groups-overlay",
get_item: ListWidget.default_get_item,
modifier(item) { modifier(item) {
item.is_member = user_groups.is_direct_member_of( item.is_member = user_groups.is_direct_member_of(
people.my_current_user_id(), people.my_current_user_id(),

View File

@ -83,6 +83,7 @@ function render_user_stream_list(streams, user) {
$container.empty(); $container.empty();
ListWidget.create($container, streams, { ListWidget.create($container, streams, {
name: `user-${user.user_id}-stream-list`, name: `user-${user.user_id}-stream-list`,
get_item: ListWidget.default_get_item,
modifier(item) { modifier(item) {
return format_user_stream_list_item(item, user); return format_user_stream_list_item(item, user);
}, },
@ -102,6 +103,7 @@ function render_user_group_list(groups, user) {
$container.empty(); $container.empty();
ListWidget.create($container, groups, { ListWidget.create($container, groups, {
name: `user-${user.user_id}-group-list`, name: `user-${user.user_id}-group-list`,
get_item: ListWidget.default_get_item,
modifier(item) { modifier(item) {
return format_user_group_list_item(item); return format_user_group_list_item(item);
}, },

View File

@ -157,6 +157,7 @@ run_test("scrolling", () => {
const opts = { const opts = {
modifier: (item) => item, modifier: (item) => item,
get_item: (item) => item,
$simplebar_container: $scroll_container, $simplebar_container: $scroll_container,
}; };
@ -205,6 +206,7 @@ run_test("not_scrolling", () => {
const opts = { const opts = {
modifier: (item) => item, modifier: (item) => item,
get_item: (item) => item,
$simplebar_container: $scroll_container, $simplebar_container: $scroll_container,
is_scroll_position_for_render: () => false, is_scroll_position_for_render: () => false,
post_scroll__pre_render_callback, post_scroll__pre_render_callback,
@ -243,6 +245,7 @@ run_test("filtering", () => {
predicate: (item, value) => item.includes(value), predicate: (item, value) => item.includes(value),
}, },
modifier: (item) => div(item), modifier: (item) => div(item),
get_item: (item) => item,
$simplebar_container: $scroll_container, $simplebar_container: $scroll_container,
}; };
@ -288,6 +291,7 @@ run_test("no filtering", () => {
callback_after_render() { callback_after_render() {
callback_called = true; callback_called = true;
}, },
get_item: (item) => item,
}; };
const widget = ListWidget.create($container, ["apple", "banana"], opts); const widget = ListWidget.create($container, ["apple", "banana"], opts);
widget.render(); widget.render();
@ -362,6 +366,7 @@ run_test("wire up filter element", () => {
$element: $filter_element, $element: $filter_element,
}, },
modifier: (s) => "(" + s + ")", modifier: (s) => "(" + s + ")",
get_item: (item) => item,
$simplebar_container: $scroll_container, $simplebar_container: $scroll_container,
}; };
@ -392,6 +397,7 @@ run_test("sorting", () => {
name: "sorting-list", name: "sorting-list",
$parent_container: $sort_container, $parent_container: $sort_container,
modifier: (item) => div(item.name) + div(item.salary), modifier: (item) => div(item.name) + div(item.salary),
get_item: (item) => item,
filter: { filter: {
predicate: () => true, predicate: () => true,
}, },
@ -492,6 +498,7 @@ run_test("custom sort", () => {
ListWidget.create($container, list, { ListWidget.create($container, list, {
name: "custom-sort-list", name: "custom-sort-list",
modifier: (n) => "(" + n.x + ", " + n.y + ")", modifier: (n) => "(" + n.x + ", " + n.y + ")",
get_item: (item) => item,
sort_fields: { sort_fields: {
product: sort_by_product, product: sort_by_product,
x_value: sort_by_x, x_value: sort_by_x,
@ -529,6 +536,7 @@ run_test("clear_event_handlers", () => {
name: "list-we-create-twice", name: "list-we-create-twice",
$parent_container: $sort_container, $parent_container: $sort_container,
modifier() {}, modifier() {},
get_item() {},
filter: { filter: {
$element: $filter_element, $element: $filter_element,
predicate: /* istanbul ignore next */ () => true, predicate: /* istanbul ignore next */ () => true,
@ -578,6 +586,7 @@ run_test("errors", () => {
predicate: "wrong type", predicate: "wrong type",
}, },
$simplebar_container: $scroll_container, $simplebar_container: $scroll_container,
get_item() {},
}); });
blueslip.reset(); blueslip.reset();
@ -588,6 +597,7 @@ run_test("errors", () => {
predicate: /* istanbul ignore next */ () => true, predicate: /* istanbul ignore next */ () => true,
}, },
$simplebar_container: $scroll_container, $simplebar_container: $scroll_container,
get_item() {},
}); });
blueslip.reset(); blueslip.reset();
@ -595,6 +605,7 @@ run_test("errors", () => {
ListWidget.create($container, list, { ListWidget.create($container, list, {
filter: {}, filter: {},
$simplebar_container: $scroll_container, $simplebar_container: $scroll_container,
get_item() {},
}); });
blueslip.reset(); blueslip.reset();
@ -602,6 +613,7 @@ run_test("errors", () => {
ListWidget.create($container, list, { ListWidget.create($container, list, {
modifier: () => 999, modifier: () => 999,
$simplebar_container: $scroll_container, $simplebar_container: $scroll_container,
get_item: (item) => item,
}); });
blueslip.reset(); blueslip.reset();
}); });
@ -638,6 +650,7 @@ run_test("replace_list_data w/filter update", () => {
ListWidget.create($container, list, { ListWidget.create($container, list, {
name: "replace-list", name: "replace-list",
modifier: (n) => "(" + n.toString() + ")", modifier: (n) => "(" + n.toString() + ")",
get_item: (item) => item,
filter: { filter: {
predicate: (n) => n % 2 === 0, predicate: (n) => n % 2 === 0,
onupdate() { onupdate() {
@ -873,6 +886,7 @@ run_test("Multiselect dropdown retain_selected_items", () => {
const widget = ListWidget.create($container, list, { const widget = ListWidget.create($container, list, {
name: "replace-list", name: "replace-list",
modifier: (item) => `<li data-value="${item.value}">${item.name}</li>\n`, modifier: (item) => `<li data-value="${item.value}">${item.name}</li>\n`,
get_item: (item) => item,
multiselect: { multiselect: {
selected_items: data, selected_items: data,
}, },