Sorting undoes filtering of rows

Hello everyone.

I have a table with four columns:

Filename, Date Added, Owner, File Type.

I have added options for the user to filter Owner and File Type (categorical variables), and Sort by date added.

I do all the filtering/sorting in the client to avoid calling to the server.

To filter, I do something like this:

  def button_filter_data_owner_click(self, **event_args):
    """This method is called when the button is clicked"""
    if self.options.get('data_owner', None) is None:
      self.options['data_owner'] = {}
      self.options['data_owner']['items'] = set([ft['data_owner']['name'] for ft in self.repeating_panel_uploads.items])
      self.options['data_owner']['selected'] = set(self.options['data_owner']['items'])
    show_types = alert(FilterColumnComponent(items = self.options['data_owner']['items'],
                                          selected = self.options['data_owner']['selected'],
                                         title="Filter by data owner"), buttons=[])
    rows = self.repeating_panel_uploads.get_components()
    for row in rows:
      row.visible = row.item['data_owner']['name'] in show_types
    self.options['data_owner']['selected'] = set(show_types)

Where FilterColumnComponent displays a checkbox for each possible option, and when it is closed, it returns the options that were selected.

To sort, I have being doing something like this:

  def button_sort_date_click(self, **event_args):
    """This method is called when the button is clicked"""
    self.repeating_panel_uploads.items = sorted(self.repeating_panel_uploads.items,
                                                key=lambda item: item['added_on'],
                                                reverse=self.sort_date_ascending)
    self.sort_date_ascending = not self.sort_date_ascending

The problem I have encountered is that, if I filter by owner it works. But, if I then sort by date added the whole table becomes visible again. In other words, it resets the visible attribute to True on all rows.

Has anyone encountered this issue? Any recommended solutions?

Thank you.

Anders.

I fixed it with a bit of a convoluted approach. I am hoping people will have suggestions to improve it:

When the Form is first initialized I get the data rows, and then create a backup copy:

    self.repeating_panel_uploads.items = list(anvil.server.call('get_uploaded_files', self.user))
    self.all_items = self.repeating_panel_uploads.items

I then create a visibility list indicating if a particular element should be visible or not:

    self.options = {}
    self.options['row_visibility'] = [True] * len(self.repeating_panel_uploads.items)

I then created a function that filters through the visibility list and updates items:

  def update_row_visibility(self):
    rows = self.all_items
    if self.options.get('row_visibility', None) is not None:
      if len(self.options['row_visibility']) == 0:
        return
      else:
        visible_rows = [row for ix,row in enumerate(rows) if self.options['row_visibility'][ix]]
        self.repeating_panel_uploads.items = visible_rows

Then, I update the visibility list, and call the above function:

  def button_filter_data_type_click(self, **event_args):
    """This method is called when the button is clicked"""
    if self.options.get('data_type', None) is None:
      self.options['data_type'] = {}
      self.options['data_type']['items'] = set([ft['file_type']['name'] for ft in self.repeating_panel_uploads.items])
      self.options['data_type']['selected'] = set(self.options['data_type']['items'])
    show_types = alert(FilterColumnComponent(items = self.options['data_type']['items'],
                                          selected = self.options['data_type']['selected'],
                                         title="Filter by file type"), buttons=[])
    if show_types is None:
      return
    items = self.all_items
    for ix, item in enumerate(rows):
      self.options['row_visibility'][ix] = item['file_type']['name'] in show_types
    self.update_row_visibility()
    self.options['data_type']['selected'] = set(show_types)

Which means that when I apply sorting it only works on what is currently in items:

  def button_sort_date_click(self, **event_args):
    """This method is called when the button is clicked"""
    self.repeating_panel_uploads.items = sorted(self.repeating_panel_uploads.items,
                                                key=lambda item: item['added_on'],
                                                reverse=self.sort_date_ascending)
    self.sort_date_ascending = not self.sort_date_ascending

This seems to also fix an issue I was having with pagination by only grabbing the rows in display through self.repeating_panel_uploads.get_components().

Cheers. Anders.

When you do items = in the date picker change event…
You essentially reset the whole repeating panel and so the visibility that you dynamically added to each row template is lost because it’s not set the item property itself.

You seem to have got round this now by adding the visibility to the item property itself and then only doing items = for visible rows.

This seems sensible to me.

Thank you @stucork! I appreciate the reply.

It was not clear from the docs that this would happen when filtering/sorting.

Not sure how this will work when the table gets large though. In that case, I may get better performance with running things on the server.

Cheers. A.

Yeah. Data grids with lots of data and data bindings that update regularly can be slow.

If there aren’t too many databindings it’s usually ok.

There is also the tabulator component which does filtering and sorting quite well.

1 Like

Rather than having the two event functions doing two different things, one filtering and the other sorting, it’s usually cleaner to have the two event functions collecting the options and calling a third function to execute both filtering and sorting.

Something like this:

def button_sort_date_click(self, **event_args):
  self.sort_date_ascending = not self.sort_date_ascending
  self.update_list()

def button_filter_data_type_click(self, **event_args):
  self.update_list()

def update_list(self):
  # use self.sort_date_ascending and whatever other input is set to filter, sort
  # and assign to self.all_items
  self.all_items = ...
1 Like