Skip to content

Commit

Permalink
dropdown_list_widget: Add support for Multiselect dropdown list widge…
Browse files Browse the repository at this point in the history
…t (MDLW).

This commit adds the support to select multiple dropdown items by inheriting
dropdown list widget and overriding some of it's properties.

The parameters that can be passed along with it are-

- widget_name: The desired name of the widget.
- data: The data that needs to be populated as dropdown items.
- default_text: The default text to be rendered when none of the items is selected.
- on_update: Function to trigger once the filter button is pressed.
- on_close: Function to trigger once the dropdown is successfully closed after filtering.
- value: The default value that is initially selected by user.
- limit: The maximum number of dropdown items to display on button text.

This widget can later be implemented in recent topic view to replace the
several ellipses filter button and also within the organisation user's page
to quickly sort the users list according to their org role.
  • Loading branch information
aryanshridhar authored and timabbott committed Jul 29, 2021
1 parent 0d20a20 commit 7c588d4
Show file tree
Hide file tree
Showing 6 changed files with 563 additions and 4 deletions.
128 changes: 124 additions & 4 deletions frontend_tests/node_tests/dropdown_list_widget.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,11 @@ const noop = () => {};
mock_esm("../../static/js/list_widget", {
create: () => ({init: noop}),
});
const {DropdownListWidget} = zrequire("dropdown_list_widget");

const setup_zjquery_data = (name) => {
const {DropdownListWidget, MultiSelectDropdownListWidget} = zrequire("dropdown_list_widget");

// For DropdownListWidget
const setup_dropdown_zjquery_data = (name) => {
const input_group = $(".input_group");
const reset_button = $(".dropdown_list_reset_button");
input_group.set_find_results(".dropdown_list_reset_button:enabled", reset_button);
Expand All @@ -36,7 +38,7 @@ run_test("basic_functions", () => {
render_text: (text) => `rendered: ${text}`,
};

const {reset_button, $widget} = setup_zjquery_data(opts.widget_name);
const {reset_button, $widget} = setup_dropdown_zjquery_data(opts.widget_name);

const widget = new DropdownListWidget(opts);

Expand Down Expand Up @@ -76,7 +78,125 @@ run_test("no_default_value", () => {
"warn",
"dropdown-list-widget: Called without a default value; using null value",
);
setup_zjquery_data(opts.widget_name);
setup_dropdown_zjquery_data(opts.widget_name);
const widget = new DropdownListWidget(opts);
assert.equal(widget.value(), "null-value");
});

// For MultiSelectDropdownListWidget
const setup_multiselect_dropdown_zjquery_data = function (name) {
$(`#${CSS.escape(name)}_widget`)[0] = {};
return setup_dropdown_zjquery_data(name);
};

run_test("basic MDLW functions", () => {
let updated_value;
const opts = {
widget_name: "my_setting",
data: ["one", "two", "three", "four"].map((x) => ({name: x, value: x})),
value: ["one"],
limit: 2,
on_update: (val) => {
updated_value = val;
},
default_text: $t({defaultMessage: "not set"}),
};

const {reset_button, $widget} = setup_multiselect_dropdown_zjquery_data(opts.widget_name);
const widget = new MultiSelectDropdownListWidget(opts);

function set_dropdown_variables(widget, value) {
widget.data_selected = value;
widget.checked_items = value;
}

assert.deepEqual(widget.value(), ["one"]);
assert.equal(updated_value, undefined);
assert.equal($widget.text(), "one");
assert.ok(reset_button.visible());

set_dropdown_variables(widget, ["one", "two"]);
widget.update(widget.data_selected);

assert.equal($widget.text(), "one,two");
assert.deepEqual(widget.value(), ["one", "two"]);
assert.deepEqual(updated_value, ["one", "two"]);
assert.ok(reset_button.visible());

set_dropdown_variables(widget, ["one", "two", "three"]);
widget.update(widget.data_selected);

assert.equal($widget.text(), "translated: 3 selected");
assert.deepEqual(widget.value(), ["one", "two", "three"]);
assert.deepEqual(updated_value, ["one", "two", "three"]);
assert.ok(reset_button.visible());

set_dropdown_variables(widget, null);
widget.update(widget.data_selected);

assert.equal($widget.text(), "translated: not set");
assert.equal(widget.value(), null);
assert.equal(updated_value, null);
assert.ok(!reset_button.visible());

set_dropdown_variables(widget, ["one"]);
widget.update(widget.data_selected);

assert.equal($widget.text(), "one");
assert.deepEqual(widget.value(), ["one"]);
assert.deepEqual(updated_value, ["one"]);
assert.ok(reset_button.visible());
});

run_test("MDLW no_default_value", () => {
const opts = {
widget_name: "my_setting",
data: ["one", "two", "three", "four"].map((x) => ({name: x, value: x})),
limit: 2,
null_value: "null-value",
default_text: $t({defaultMessage: "not set"}),
};

blueslip.expect(
"warn",
"dropdown-list-widget: Called without a default value; using null value",
);

setup_multiselect_dropdown_zjquery_data(opts.widget_name);
const widget = new MultiSelectDropdownListWidget(opts);

assert.equal(widget.value(), "null-value");
});

run_test("MDLW no_limit_set", () => {
const opts = {
widget_name: "my_setting",
data: ["one", "two", "three", "four"].map((x) => ({name: x, value: x})),
value: ["one"],
default_text: $t({defaultMessage: "not set"}),
};

blueslip.expect(
"warn",
"Multiselect dropdown-list-widget: Called without limit value; using 2 as the limit",
);

function set_dropdown_variables(widget, value) {
widget.data_selected = value;
widget.checked_items = value;
}

const {$widget} = setup_multiselect_dropdown_zjquery_data(opts.widget_name);
const widget = new MultiSelectDropdownListWidget(opts);

set_dropdown_variables(widget, ["one", "two", "three"]);
widget.update(widget.data_selected);

// limit is set to 2 (Default value).
assert.equal($widget.text(), "translated: 3 selected");

set_dropdown_variables(widget, ["one"]);
widget.update(widget.data_selected);

assert.equal($widget.text(), "one");
});
92 changes: 92 additions & 0 deletions frontend_tests/node_tests/list_widget.js
Original file line number Diff line number Diff line change
Expand Up @@ -832,3 +832,95 @@ run_test("render item", () => {
widget_3.render_item(item);
blueslip.reset();
});

run_test("Multiselect dropdown retain_selected_items", () => {
const container = make_container();
const scroll_container = make_scroll_container();
const filter_element = make_filter_element();
let data_rendered = [];

const list = ["one", "two", "three", "four"].map((x) => ({name: x, value: x}));
const data = ["one"]; // Data initially selected.

container.html = () => {};
container.find = (elem) => DropdownItem(elem);

// We essentially create fake Jquery functions
// whose return value are stored in objects so that
// they can be later asserted with expected values.
function DropdownItem(element) {
const temp = {};

function length() {
if (element) {
return true;
}
return false;
}

function find(tag) {
return ListItem(tag, temp);
}

function addClass(cls) {
temp.appended_class = cls;
}

temp.element = element;
return {
length: length(),
find,
addClass,
};
}

function ListItem(element, temp) {
function expectOne() {
data_rendered.push(temp);
return ListItem(element, temp);
}

function prepend(data) {
temp.prepended_data = data.html();
}

return {
expectOne,
prepend,
};
}

const widget = ListWidget.create(container, list, {
name: "replace-list",
modifier: (item) => `<li data-value="${item.value}">${item.name}</li>\n`,
multiselect: {
selected_items: data,
},
filter: {
element: filter_element,
predicate: () => true,
},
simplebar_container: scroll_container,
});

const expected_value = [
{
element: 'li[data-value = "one"]',
appended_class: "checked",
prepended_data: "<i>",
},
];

assert.deepEqual(expected_value, data_rendered);

// Reset the variable and re execute the `widget.render` method.
data_rendered = [];

// Making sure!
assert.deepEqual(data_rendered, []);

widget.hard_redraw();

// Expect the `data_rendered` array to be same again.
assert.deepEqual(expected_value, data_rendered);
});

0 comments on commit 7c588d4

Please sign in to comment.