public class IndexedContainer implements Container, Container.Indexed,
Container.ItemSetChangeNotifier, Container.PropertySetChangeNotifier,
- Property.ValueChangeNotifier, Container.Sortable, Comparator, Cloneable {
+ Property.ValueChangeNotifier, Container.Sortable, Comparator, Cloneable, Container.Filterable {
/* Internal structure *************************************************** */
* Linked list of ordered Item IDs.
*/
private ArrayList itemIds = new ArrayList();
+
+ /** List of item ids that passes the filtering */
+ private LinkedHashSet filteredItemIds = null;
/**
* Linked list of ordered Property IDs.
* Temporary store for sorting direction.
*/
private boolean[] sortDirection;
+
+ /** Filters that are applied to the container to limit the items visible in it */
+ private HashSet filters;
/* Container constructors *********************************************** */
/**
* Gets the Item with the given Item ID from the list. If the list does not
- * contain the requested Item, <code>null</code> is returned.
+ * contain the requested Item (or it is filtered to be invisible),
+ * <code>null</code> is returned.
*
* @param itemId
* the ID of the Item to retrieve.
* not found in the list
*/
public Item getItem(Object itemId) {
- if (items.containsKey(itemId))
+ if (items.containsKey(itemId) && (filteredItemIds == null || filteredItemIds.contains(itemId)))
return new IndexedContainerItem(itemId);
return null;
}
* @return unmodifiable collection of Item IDs
*/
public Collection getItemIds() {
+ if (filteredItemIds != null) return Collections.unmodifiableCollection(filteredItemIds);
return Collections.unmodifiableCollection(itemIds);
}
* Object)
*/
public Property getContainerProperty(Object itemId, Object propertyId) {
- if (!items.containsKey(itemId))
- return null;
+ if (filteredItemIds == null) {
+ if (!items.containsKey(itemId)) return null;
+ } else if (!filteredItemIds.contains(itemId)) return null;
+
return new IndexedContainerProperty(itemId, propertyId);
}
* @return number of Items in the list
*/
public int size() {
- return itemIds.size();
+ if (filteredItemIds == null) return itemIds.size();
+ return filteredItemIds.size();
}
/**
* <code>false</code> if not
*/
public boolean containsId(Object itemId) {
+ if (filteredItemIds != null) return filteredItemIds.contains(itemId);
return items.containsKey(itemId);
}
// Removes all Items
itemIds.clear();
items.clear();
+ if (filteredItemIds != null) filteredItemIds.clear();
// Sends a change event
fireContentsChange();
// Adds the Item to container
itemIds.add(itemId);
items.put(itemId, new Hashtable());
+ Item item = getItem(itemId);
+ if (filteredItemIds != null)
+ if (passesFilters(item))
+ filteredItemIds.add(itemId);
// Sends the event
fireContentsChange();
- return getItem(itemId);
+ return item;
}
/**
if (items.remove(itemId) == null)
return false;
itemIds.remove(itemId);
+ if (filteredItemIds != null) filteredItemIds.remove(itemId);
fireContentsChange();
*/
public Object firstItemId() {
try {
+ if (filteredItemIds != null) return filteredItemIds.iterator().next();
return itemIds.get(0);
} catch (IndexOutOfBoundsException e) {
+ } catch (NoSuchElementException e) {
}
return null;
}
*/
public Object lastItemId() {
try {
+ if (filteredItemIds != null) {
+ Iterator i=filteredItemIds.iterator();
+ Object last = null;
+ while (i.hasNext()) last = i.next();
+ return last;
+ }
return itemIds.get(itemIds.size() - 1);
} catch (IndexOutOfBoundsException e) {
}
* @return ID of the next Item or <code>null</code>
*/
public Object nextItemId(Object itemId) {
+ if (filteredItemIds != null) {
+ if (!filteredItemIds.contains(itemId)) return null;
+ Iterator i=filteredItemIds.iterator();
+ if (itemId == null) return null;
+ while (i.hasNext() && !itemId.equals(i.next()));
+ if (i.hasNext()) return i.next();
+ return null;
+ }
try {
return itemIds.get(itemIds.indexOf(itemId) + 1);
} catch (IndexOutOfBoundsException e) {
* @return ID of the previous Item or <code>null</code>
*/
public Object prevItemId(Object itemId) {
+ if (filteredItemIds != null) {
+ if (!filteredItemIds.contains(itemId)) return null;
+ Iterator i=filteredItemIds.iterator();
+ if (itemId == null) return null;
+ Object prev = null;
+ Object current;
+ while (i.hasNext() && !itemId.equals(current = i.next())) prev = current;
+ return prev;
+ }
try {
return itemIds.get(itemIds.indexOf(itemId) - 1);
} catch (IndexOutOfBoundsException e) {
* <code>false</code> if not
*/
public boolean isFirstId(Object itemId) {
+ if (filteredItemIds != null) {
+ try {
+ Object first = filteredItemIds.iterator().next();
+ return (itemId != null && itemId.equals(first));
+ } catch (NoSuchElementException e) {
+ return false;
+ }
+ }
return (size() >= 1 && itemIds.get(0).equals(itemId));
}
* <code>false</code> if not
*/
public boolean isLastId(Object itemId) {
+ if (filteredItemIds != null) {
+ try {
+ Object last = null;
+ for (Iterator i=filteredItemIds.iterator(); i.hasNext();) last = i.next();
+ return (itemId != null && itemId.equals(last));
+ } catch (NoSuchElementException e) {
+ return false;
+ }
+ }
int s = size();
return (s >= 1 && itemIds.get(s - 1).equals(itemId));
}
* Index of the requested ID in the container.
*/
public Object getIdByIndex(int index) {
+
+ if (filteredItemIds != null) {
+ if (index<0) throw new IndexOutOfBoundsException();
+ try {
+ Iterator i = filteredItemIds.iterator();
+ while (index-- > 0) i.next();
+ return i.next();
+ } catch (NoSuchElementException e) {
+ throw new IndexOutOfBoundsException();
+ }
+ }
+
return itemIds.get(index);
}
* ID of an Item in the collection
*/
public int indexOfId(Object itemId) {
+ if (filteredItemIds != null) {
+ int index=0;
+ if (itemId == null) return -1;
+ try {
+ for (Iterator i=filteredItemIds.iterator(); itemId.equals(i.next());) index++;
+ return index;
+ } catch (NoSuchElementException e) {
+ return -1;
+ }
+ }
return itemIds.indexOf(itemId);
}
itemIds.add(index, newItemId);
items.put(newItemId, new Hashtable());
- // Sends the event
- fireContentsChange();
+ if (filteredItemIds != null)
+ updateContainerFiltering();
+ else
+ fireContentsChange();
return getItem(newItemId);
}
// Sort
Collections.sort(this.itemIds, this);
- fireContentsChange();
+ if (filteredItemIds != null) updateContainerFiltering();
+ else fireContentsChange();
// Remove temporary references
sortPropertyId = null;
.clone()
: null;
nc.types = this.types != null ? (Hashtable) this.types.clone() : null;
+
+ nc.filters = this.filters == null ? null : (HashSet) this.filters.clone();
+
+ nc.filteredItemIds = this.filteredItemIds == null ? null : (LinkedHashSet) this.filteredItemIds.clone();
// Clone property-values
if (this.items == null)
if (itemIds != o.itemIds && o.itemIds != null
&& !o.itemIds.equals(this.itemIds))
return false;
+ if (filters != o.filters && o.filters != null
+ && !o.filters.equals(this.filters))
+ return false;
if (items != o.items && o.items != null && !o.items.equals(this.items))
return false;
if (itemSetChangeListeners != o.itemSetChangeListeners
// The hash-code is calculated as combination hash of the members
return (this.itemIds != null ? this.itemIds.hashCode() : 0)
^ (this.items != null ? this.items.hashCode() : 0)
+ ^ (this.filters != null ? this.filters.hashCode() : 0)
^ (this.itemSetChangeListeners != null ? this.itemSetChangeListeners
.hashCode()
: 0)
: 0);
}
+ private class Filter {
+ Object propertyId;
+ String filterString;
+ boolean ignoreCase;
+ boolean onlyMatchPrefix;
+ Filter(Object propertyId, String filterString, boolean ignoreCase, boolean onlyMatchPrefix) {
+ this.propertyId = propertyId;;
+ this.filterString = filterString;
+ this.ignoreCase = ignoreCase;
+ this.onlyMatchPrefix = onlyMatchPrefix;
+ }
+ public boolean equals(Object obj) {
+
+ // Only ones of the objects of the same class can be equal
+ if (!(obj instanceof Filter))
+ return false;
+ Filter o = (Filter) obj;
+
+ // Checks the properties one by one
+ if (propertyId != o.propertyId && o.propertyId != null
+ && !o.propertyId.equals(this.propertyId))
+ return false;
+ if (filterString != o.filterString && o.filterString != null
+ && !o.filterString.equals(this.filterString))
+ return false;
+ if (ignoreCase!=o.ignoreCase)
+ return false;
+ if (onlyMatchPrefix!=o.onlyMatchPrefix)
+ return false;
+
+ return true;
+ }
+ public int hashCode() {
+ return (this.propertyId != null ? this.propertyId.hashCode() : 0)
+ ^ (this.filterString != null ? this.filterString.hashCode() : 0);
+ }
+
+ }
+
+ public void addContainerFilter(Object propertyId, String filterString, boolean ignoreCase, boolean onlyMatchPrefix) {
+ if (filters == null) filters = new HashSet();
+ filters.add(new Filter(propertyId, filterString, ignoreCase, onlyMatchPrefix));
+ updateContainerFiltering();
+ }
+
+ public void removeAllContainerFilters() {
+ if (filters == null) return;
+ filters.clear();
+ updateContainerFiltering();
+ }
+
+ public void removeContainerFilters(Object propertyId) {
+ if (filters == null || propertyId == null) return;
+ for (Iterator i=filters.iterator(); i.hasNext();) {
+ Filter f = (Filter) i.next();
+ if (propertyId.equals(f.propertyId)) i.remove();
+ }
+ updateContainerFiltering();
+ }
+
+ private void updateContainerFiltering() {
+
+ // Clearing filters?
+ if (filters == null || filters.isEmpty()) {
+ filters = null;
+ filteredItemIds = null;
+ return;
+ }
+
+ // Reset filteres list
+ if (filteredItemIds == null) filteredItemIds = new LinkedHashSet();
+ else filteredItemIds.clear();
+
+ // Filter
+ for (Iterator i=itemIds.iterator(); i.hasNext();) {
+ Object id = i.next();
+ if (passesFilters(new IndexedContainerItem(id))) filteredItemIds.add(id);
+ }
+
+ fireContentsChange();
+ }
+
+ private boolean passesFilters(Item item) {
+ if (filters == null) return true;
+ if (item == null) return false;
+ for (Iterator i=filters.iterator(); i.hasNext();) {
+ Filter f = (Filter) i.next();
+ String s1 = f.ignoreCase ? f.filterString.toLowerCase() : f.filterString;
+ Property p = item.getItemProperty(f.propertyId);
+ if (p==null) return false;
+ String s2 = f.ignoreCase ? p.toString().toLowerCase() : p.toString();
+ if (f.onlyMatchPrefix) {
+ if (s2.indexOf(s1) != 0) return false;
+ } else {
+ if (s2.indexOf(s1) < 0) return false;
+ }
+ }
+ return true;
+ }
+
}
\ No newline at end of file
--- /dev/null
+package com.itmill.toolkit.tests;
+
+import com.itmill.toolkit.data.util.IndexedContainer;
+import com.itmill.toolkit.ui.Button;
+import com.itmill.toolkit.ui.CustomComponent;
+import com.itmill.toolkit.ui.Label;
+import com.itmill.toolkit.ui.OrderedLayout;
+import com.itmill.toolkit.ui.Panel;
+import com.itmill.toolkit.ui.Table;
+import com.itmill.toolkit.ui.TextField;
+import com.itmill.toolkit.ui.Button.ClickEvent;
+
+public class TestForContainerFilterable extends CustomComponent {
+
+ OrderedLayout lo = new OrderedLayout();
+ IndexedContainer ic = new IndexedContainer();
+ Table t = new Table();
+ private static String parts[] = {"Neo","Sa","rem","the","adi","za","tre","day","Ca","re","cen","ter","mi","nal"};
+ TextField fooFilter = new TextField("foo-filter");
+ TextField barFilter = new TextField("bar-filter");
+ Button filterButton = new Button("Filter");
+ Label count = new Label();
+
+
+ public TestForContainerFilterable() {
+ setCompositionRoot(lo);
+
+ // Init datasource
+ ic.addContainerProperty("foo", String.class, "");
+ ic.addContainerProperty("bar", String.class, "");
+ for (int i=0;i<1000;i++) {
+ Object id = ic.addItem();
+ ic.getContainerProperty(id, "foo").setValue(randomWord());
+ ic.getContainerProperty(id, "bar").setValue(randomWord());
+ }
+
+ // Init filtering view
+ Panel filterPanel = new Panel("Filter", new OrderedLayout(OrderedLayout.ORIENTATION_HORIZONTAL));
+ filterPanel.setWidth(100);
+ filterPanel.setWidthUnits(Panel.UNITS_PERCENTAGE);
+ lo.addComponent(filterPanel);
+ filterPanel.addComponent(fooFilter);
+ filterPanel.addComponent(barFilter);
+ filterPanel.addComponent(filterButton);
+ fooFilter.setDescription("Filters foo column in case-sensitive contains manner.");
+ barFilter.setDescription("Filters bar column in case-insensitive prefix manner.");
+ filterPanel.addComponent(count);
+
+
+ // Table
+ lo.addComponent(t);
+ t.setPageLength(12);
+ t.setWidth(100);
+ t.setWidthUnits(Table.UNITS_PERCENTAGE);
+ t.setContainerDataSource(ic);
+
+ // Handler
+ filterButton.addListener(new Button.ClickListener() {
+ public void buttonClick(ClickEvent event) {
+ ic.removeAllContainerFilters();
+ if (fooFilter.toString().length()>0) ic.addContainerFilter("foo", fooFilter.toString(), false, false);
+ if (barFilter.toString().length()>0) ic.addContainerFilter("bar", barFilter.toString(), true, true);
+ count.setValue("Rows in table: " + ic.size());
+ }
+ });
+
+ // Resetbutton
+ lo.addComponent(new Button("Rebind table datasource", new Button.ClickListener() {
+ public void buttonClick(ClickEvent event) {
+ t.setContainerDataSource(ic);
+ }
+ }));
+ }
+
+ private String randomWord() {
+ int len = (int) (Math.random()*4);
+ StringBuffer buf = new StringBuffer();
+ while(len-->=0) buf.append(parts[(int) (Math.random()*parts.length)]);
+ return buf.toString();
+ }
+}