mirror of https://github.com/zulip/zulip.git
list_render: Fix filtering/sorting.
This code has always been kind of convoluted
and buggy, starting with the first
sorting-related commit, which put filtering
before sorting for some reason:
3706e2c6ba
This should fix bugs like the fact that
changing filter text would not respect
reversed sorts.
Now the scheme is simple:
- external UI actions set `meta` values like
filter_value, reverse_mode, and
sorting_function, as needed, through
simple setters
- use `hard_redraw` to do a redraw and
trigger external actions
- all filtering/sorting/reverse logic on
the *data* happens in a single, simple
function called `filter_and_sort`
This commit is contained in:
parent
ec854f4b12
commit
eb1344c41c
|
@ -236,7 +236,13 @@ function sort_button(opts) {
|
||||||
closest: lookup('.progressive-table-wrapper', {
|
closest: lookup('.progressive-table-wrapper', {
|
||||||
data: lookup('list-render', opts.list_name),
|
data: lookup('list-render', opts.list_name),
|
||||||
}),
|
}),
|
||||||
hasClass: lookup('active', opts.active),
|
hasClass: (sel) => {
|
||||||
|
if (sel === 'active') {
|
||||||
|
return opts.active;
|
||||||
|
}
|
||||||
|
assert.equal(sel, 'descend');
|
||||||
|
return false;
|
||||||
|
},
|
||||||
siblings: lookup('.active', {
|
siblings: lookup('.active', {
|
||||||
removeClass: (sel) => {
|
removeClass: (sel) => {
|
||||||
assert.equal(sel, 'active');
|
assert.equal(sel, 'active');
|
||||||
|
|
|
@ -10,6 +10,10 @@ exports.filter = (value, list, opts) => {
|
||||||
but we split it out to make it a bit easier
|
but we split it out to make it a bit easier
|
||||||
to test.
|
to test.
|
||||||
*/
|
*/
|
||||||
|
if (!opts.filter) {
|
||||||
|
return [...list];
|
||||||
|
}
|
||||||
|
|
||||||
if (opts.filter.filterer) {
|
if (opts.filter.filterer) {
|
||||||
return opts.filter.filterer(list, value);
|
return opts.filter.filterer(list, value);
|
||||||
}
|
}
|
||||||
|
@ -63,19 +67,15 @@ exports.create = function ($container, list, opts) {
|
||||||
|
|
||||||
const meta = {
|
const meta = {
|
||||||
sorting_function: null,
|
sorting_function: null,
|
||||||
prop: null,
|
|
||||||
sorting_functions: new Map(),
|
sorting_functions: new Map(),
|
||||||
generic_sorting_functions: new Map(),
|
generic_sorting_functions: new Map(),
|
||||||
offset: 0,
|
offset: 0,
|
||||||
list: list,
|
list: list,
|
||||||
filtered_list: list,
|
filtered_list: list,
|
||||||
|
reverse_mode: false,
|
||||||
|
filter_value: '',
|
||||||
};
|
};
|
||||||
|
|
||||||
function filter_list(value) {
|
|
||||||
meta.filtered_list = exports.filter(value, meta.list, opts);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!opts) {
|
if (!opts) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -83,6 +83,24 @@ exports.create = function ($container, list, opts) {
|
||||||
|
|
||||||
const widget = {};
|
const widget = {};
|
||||||
|
|
||||||
|
widget.filter_and_sort = function () {
|
||||||
|
meta.filtered_list = exports.filter(
|
||||||
|
meta.filter_value,
|
||||||
|
meta.list,
|
||||||
|
opts
|
||||||
|
);
|
||||||
|
|
||||||
|
if (meta.sorting_function) {
|
||||||
|
meta.filtered_list.sort(
|
||||||
|
meta.sorting_function
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (meta.reverse_mode) {
|
||||||
|
meta.filtered_list.reverse();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Reads the provided list (in the scope directly above)
|
// Reads the provided list (in the scope directly above)
|
||||||
// and renders the next block of messages automatically
|
// and renders the next block of messages automatically
|
||||||
// into the specified container.
|
// into the specified container.
|
||||||
|
@ -142,10 +160,6 @@ exports.create = function ($container, list, opts) {
|
||||||
return this;
|
return this;
|
||||||
};
|
};
|
||||||
|
|
||||||
widget.filter = function (map_function) {
|
|
||||||
meta.filtered_list = meta.list(map_function);
|
|
||||||
};
|
|
||||||
|
|
||||||
// reset the data associated with a list. This is so that instead of
|
// reset the data associated with a list. This is so that instead of
|
||||||
// initializing a new progressive list render instance, you can just
|
// initializing a new progressive list render instance, you can just
|
||||||
// update the data of an existing one.
|
// update the data of an existing one.
|
||||||
|
@ -162,11 +176,7 @@ exports.create = function ($container, list, opts) {
|
||||||
meta.list = data;
|
meta.list = data;
|
||||||
meta.filtered_list = data;
|
meta.filtered_list = data;
|
||||||
|
|
||||||
if (opts.filter && opts.filter.element) {
|
widget.filter_and_sort();
|
||||||
const value = $(opts.filter.element).val().toLocaleLowerCase();
|
|
||||||
filter_list(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
widget.clear();
|
widget.clear();
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
|
@ -199,24 +209,18 @@ exports.create = function ($container, list, opts) {
|
||||||
return this;
|
return this;
|
||||||
};
|
};
|
||||||
|
|
||||||
widget.reverse = function () {
|
widget.set_filter_value = function (filter_value) {
|
||||||
meta.filtered_list.reverse();
|
meta.filter_value = filter_value;
|
||||||
widget.init();
|
};
|
||||||
return this;
|
|
||||||
|
widget.set_reverse_mode = function (reverse_mode) {
|
||||||
|
meta.reverse_mode = reverse_mode;
|
||||||
};
|
};
|
||||||
|
|
||||||
// the sorting function is either the function or string that calls the
|
// the sorting function is either the function or string that calls the
|
||||||
// function to sort the list by. The prop is used for generic functions
|
// function to sort the list by. The prop is used for generic functions
|
||||||
// that can be called to sort with a particular prop.
|
// that can be called to sort with a particular prop.
|
||||||
|
widget.set_sorting_function = function (sorting_function, prop) {
|
||||||
// the `map` will normalize the values with a function you provide to make
|
|
||||||
// it easier to sort with.
|
|
||||||
|
|
||||||
// `do_not_display` will signal to not update the DOM, likely because in
|
|
||||||
// the next function it will be updated in the DOM.
|
|
||||||
widget.sort = function (sorting_function, prop, do_not_display) {
|
|
||||||
meta.prop = prop;
|
|
||||||
|
|
||||||
if (typeof sorting_function === "function") {
|
if (typeof sorting_function === "function") {
|
||||||
meta.sorting_function = sorting_function;
|
meta.sorting_function = sorting_function;
|
||||||
} else if (typeof sorting_function === "string") {
|
} else if (typeof sorting_function === "string") {
|
||||||
|
@ -227,22 +231,6 @@ exports.create = function ($container, list, opts) {
|
||||||
meta.sorting_function = meta.sorting_functions.get(sorting_function);
|
meta.sorting_function = meta.sorting_functions.get(sorting_function);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// we do not want to sort if we are just looking to reverse
|
|
||||||
// by calling with no sorting_function
|
|
||||||
if (meta.sorting_function) {
|
|
||||||
meta.filtered_list = meta.filtered_list.sort(meta.sorting_function);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!do_not_display) {
|
|
||||||
// clear and re-initialize the list with the newly filtered subset
|
|
||||||
// of items.
|
|
||||||
widget.init();
|
|
||||||
|
|
||||||
if (opts.filter && opts.filter.onupdate) {
|
|
||||||
opts.filter.onupdate();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
widget.add_sort_function = function (name, sorting_function) {
|
widget.add_sort_function = function (name, sorting_function) {
|
||||||
|
@ -255,10 +243,6 @@ exports.create = function ($container, list, opts) {
|
||||||
meta.generic_sorting_functions.set(name, sorting_function);
|
meta.generic_sorting_functions.set(name, sorting_function);
|
||||||
};
|
};
|
||||||
|
|
||||||
widget.remove_sort = function () {
|
|
||||||
meta.sorting_function = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
widget.set_up_event_handlers = function () {
|
widget.set_up_event_handlers = function () {
|
||||||
meta.scroll_container = scroll_util.get_list_scrolling_container($container);
|
meta.scroll_container = scroll_util.get_list_scrolling_container($container);
|
||||||
|
|
||||||
|
@ -277,29 +261,29 @@ exports.create = function ($container, list, opts) {
|
||||||
if (opts.filter && opts.filter.element) {
|
if (opts.filter && opts.filter.element) {
|
||||||
opts.filter.element.on("input", function () {
|
opts.filter.element.on("input", function () {
|
||||||
const value = this.value.toLocaleLowerCase();
|
const value = this.value.toLocaleLowerCase();
|
||||||
|
widget.set_filter_value(value);
|
||||||
// run the sort algorithm that was used last, which is done
|
widget.hard_redraw();
|
||||||
// by passing `undefined` -- which will make it use the params
|
|
||||||
// from the last sort.
|
|
||||||
// it will then also not run an update in the DOM (because we
|
|
||||||
// pass `true`), because it will update regardless below at
|
|
||||||
// `widget.init()`.
|
|
||||||
widget.sort(undefined, meta.prop, true);
|
|
||||||
filter_list(value);
|
|
||||||
|
|
||||||
// clear and re-initialize the list with the newly filtered subset
|
|
||||||
// of items.
|
|
||||||
widget.init();
|
|
||||||
|
|
||||||
if (opts.filter.onupdate) {
|
|
||||||
opts.filter.onupdate();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
widget.sort = function (sorting_function, prop) {
|
||||||
|
widget.set_sorting_function(sorting_function, prop);
|
||||||
|
widget.hard_redraw();
|
||||||
|
};
|
||||||
|
|
||||||
|
widget.hard_redraw = function () {
|
||||||
|
widget.filter_and_sort();
|
||||||
|
widget.clear();
|
||||||
|
widget.render(DEFAULTS.INITIAL_RENDER_COUNT);
|
||||||
|
|
||||||
|
if (opts.filter && opts.filter.onupdate) {
|
||||||
|
opts.filter.onupdate();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// add built-in generic sort functions.
|
// add built-in generic sort functions.
|
||||||
widget.add_generic_sort_function("alphabetic", function (prop) {
|
widget.add_generic_sort_function("alphabetic", function (prop) {
|
||||||
return function (a, b) {
|
return function (a, b) {
|
||||||
|
@ -379,18 +363,16 @@ exports.handle_sort = function () {
|
||||||
} else {
|
} else {
|
||||||
$this.removeClass("descend");
|
$this.removeClass("descend");
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
list.reverse();
|
$this.siblings(".active").removeClass("active");
|
||||||
// Table has already been sorted by this property; do not re-sort.
|
$this.addClass("active");
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
list.set_reverse_mode($this.hasClass("descend"));
|
||||||
|
|
||||||
// if `prop_name` is defined, it will trigger the generic codepath,
|
// if `prop_name` is defined, it will trigger the generic codepath,
|
||||||
// and not if it is undefined.
|
// and not if it is undefined.
|
||||||
list.sort(sort_type, prop_name);
|
list.sort(sort_type, prop_name);
|
||||||
|
|
||||||
$this.siblings(".active").removeClass("active");
|
|
||||||
$this.addClass("active");
|
|
||||||
};
|
};
|
||||||
|
|
||||||
window.list_render = exports;
|
window.list_render = exports;
|
||||||
|
|
Loading…
Reference in New Issue