Creating a custom reusable filtering interface with macro component: An Example"

From Documentation
m
Line 1: Line 1:
{{Template:UnderConstruction}}
 
 
 
{{Template:Smalltalk_Author|
 
{{Template:Smalltalk_Author|
 
|author=Neil Lee, Engineer, Potix Corporation
 
|author=Neil Lee, Engineer, Potix Corporation
Line 6: Line 4:
 
|version=ZK 7.0.4
 
|version=ZK 7.0.4
 
}}
 
}}
 +
 +
{{Template:UnderConstruction}}
 +
  
 
= Introduction =
 
= Introduction =

Revision as of 07:21, 17 March 2015

DocumentationSmall Talks2015AprilCreating a custom reusable filtering interface with macro component: An Example
Creating a custom reusable filtering interface with macro component: An Example

Author
Neil Lee, Engineer, Potix Corporation
Date
March xx, 2015
Version
ZK 7.0.4

WarningTriangle-32x32.png This page is under construction, so we cannot guarantee the accuracy of the content!


Introduction

Filtering commonly augments data displaying functionality. Users can create multiple views of a single data set based on different filter criteria. As data sets become larger and larger, filtering also helps increase performance and improve usability by reducing the information shown.

Application authors would need to design an user interface to allow users to select their filtering preferences. Take Excel for example. After filtering is enabled, each column header will include a button, such that when clicked, displays the filtering configuration dialog box for the respective column. Users can then edit the condition that the data in that column must satisfy in order to be included in the final display.

In this smalltalk, I created an Excel-like user interface for collecting filtering criteria. The gathered information is then used in the application to perform the actual filtering operation. For simplicity, I assume the original dataset is small enough to fit into memory entirely, and provide a sample implementation for in-memory filtering.

Live Demo

As an example, law enforcement agencies can use filtering to find trends in criminal activities. For concreteness, criminal records were taken from Sacramento in 2006 [1], which consists of 7,584 offenses. It is also interesting to mark illegal occurrences on google maps.

Please watch the video below to see the application in action.

How it works

In the demo, I use a listbox to display the criminal records in a table. Each column heading contains a button with a filter icon. That button, together with the popup dialog box, is encapsulated into a macro component called FilterConfig (see the image below). This dialog box allows the user to create a filter rule (e.g. contains, begins with, less than, ...) with its associated parameter, or to choose values from an optional distinct values set. The user could also remove the filter rule for the corresponding column. Moreover, the dialog box comes with two custom events for an application to handle filter application and removal.

Filter rules are dependent on the data type. For numerical data, the available rules could be "less than", "equal to", "greater than", etc. For textual data, the available rules could include "begins with", "ends with", and "contains...". Since the service layer knows about the data the application tries to filter, the application should obtain the available filter model from the service.

FilterConfigPopup.png

User Interface

FilterConfig macro component can be inserted in the list header. It requires a FilterModel object to keep track of the current filter configuration in the dialog. The component will also notify the application when filter configuration changes or clears.

<listheader>
   <filterConfig 
        model="..."
        onFilterChanged="..."
        onFilterCleared="...".../>
</listheader>

The macro component provides a default template for filter config user interface. Developers can also define their own template, and specify it via popupTemplate argument.

    <template name="custom">
        ...
    </template>
    ...
    <filterConfig 
        popupTemplate="custom" .../>

Application

  • retrieves available filter models from a service
@Init
public void init() {
    // prepare model for unfiltered data
    availableFilterModels = service.getAvailableFilterModels();
    filterData();
}
  • renders filter component based on the filter models retrieved
  • listens to onFilterChanged and onFilterCleared events
@Command("applyFilter")
@NotifyChange({"crimeRecords", "captionLabel"})
public void applyFilter(@BindingParam("model") FilterModelImpl<?> model) {
    activeFilterModels.add(model);
    filterData();
}
  • calls service to filter data based on user choices
  • replace listmodel items to update the UI
private void filterData() {             
    crimeRecords.clear();       
    List<CrimeRecord> findRecords = service.findRecords(activeFilterModels, LIMIT);
    for (CrimeRecord crimeRecord : findRecords) {
        crimeRecords.add(new UiCrimeRecord(crimeRecord));           
    }           
    updateCaption();
}

Service

(keep brief, provide memory implementation, it should be easy to create database queries based on filter configuration)

  • provides available filter options
public Map<String, FilterModel<?>> getAvailableFilterModels() {
    Map<String, FilterModel<?>> availableFilterModels = new LinkedHashMap<String, FilterModel<?>>();

    // could be cached
    List<?> completeList = dao.getRecords();
    Set<Integer> distinctDistricts = 
        MemoryFilterUtils.<Integer> getDistinctValues(completeList, "district");
    Set<String> distinctDescriptions = MemoryFilterUtils
        .<String> getDistinctValues(completeList, "description");

    // consider dynamic creation based on reflection
    availableFilterModels
        .put("address", new FilterModelImpl<String>("address", "string", null));
    availableFilterModels
        .put("district", new FilterModelImpl<Integer>("district", "number", distinctDistricts));
    availableFilterModels
        .put("description", new FilterModelImpl<String>("description", "string", distinctDescriptions));
    availableFilterModels
        .put("latitude", new FilterModelImpl<Number>("latitude", "number", null));
    availableFilterModels
        .put("longitude", new FilterModelImpl<Number>("longitude", "number", null));

    return availableFilterModels;
}
  • filter data based on filter configuration
public List<CrimeRecord> findRecords(Set<FilterModel<?>> filterModels, int limit) {
    // instead of memory filtering, e.g. create a dynamic database query, or
    // call your dao
    List<CrimeRecord> completeList = dao.getRecords();
    List<CrimeRecord> filteredList = new ArrayList<CrimeRecord>(completeList);
    Predicate combinedPredicate = MemoryFilterUtils.createCombinedFilterPredicate(filterModels);
    CollectionUtils.filter(filteredList, combinedPredicate);
    return filteredList.subList(0, Math.min(filteredList.size(), limit));
}

Summary

Download

References

  1. Data source: spatialkey sample


Comments



Copyright © Potix Corporation. This article is licensed under GNU Free Documentation License.