package com.turbomanage.gwt.client.ui.widget; import com.example.listmaker.common.client.ui.web.AppStyles; import com.google.gwt.dom.client.DivElement; import com.google.gwt.dom.client.Document; import com.google.gwt.dom.client.Element; import com.google.gwt.dom.client.NativeEvent; import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.ClickHandler; import com.google.gwt.event.dom.client.DoubleClickEvent; import com.google.gwt.event.dom.client.DoubleClickHandler; import com.google.gwt.event.shared.HandlerRegistration; import com.google.gwt.user.client.Timer; import com.google.gwt.user.client.ui.Composite; import com.google.gwt.user.client.ui.FlowPanel; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.Map.Entry; public class ItemsTable<T> extends Composite implements HasSelectableItems<T> { public static class FieldDescriptor<T> { public interface FieldAccessor<T> { Object getObject(T item); } private String id; private String style; private String headerText; private FieldAccessor<T> accessor; public FieldDescriptor(String id, String style, String headerText, FieldAccessor<T> acc) { this.id = id; this.style = style; this.headerText = headerText; this.accessor = acc; } public FieldDescriptor(String id, String style, String headerText) { this.id = id; this.style = style; this.headerText = headerText; } public FieldDescriptor(String id, String style) { this.id = id; this.style = style; } public FieldDescriptor(String id) { this.id = id; } public String getId() { return id; } public String getStyle() { return style; } public void setStyle(String style) { this.style = style; } public String getHeaderText() { return headerText; } public void setHeaderText(String headerText) { this.headerText = headerText; } public FieldAccessor<T> getAccessor() { return accessor; } public void setAccessor(FieldAccessor<T> accessor) { this.accessor = accessor; } } public static abstract class ItemDescriptor<T> { private ArrayList<FieldDescriptor<T>> fields = new ArrayList<FieldDescriptor<T>>(); private String itemStyle; public ArrayList<FieldDescriptor<T>> getFields() { return fields; } /** * Shortcut to providing FieldAccessor for each field, but be careful * that this returns the same number of values as there are * FieldDescriptors. * * @param item * @return String[] values */ public abstract String[] getValues(T item); public String getItemStyle() { return itemStyle; } public ItemDescriptor(String itemStyle) { this.itemStyle = itemStyle; } public void addField(FieldDescriptor<T> fieldDescriptor) { fields.add(fieldDescriptor); } public FieldDescriptor<T> getFieldDescriptor(String id) { for (FieldDescriptor<T> fieldDescriptor : fields) { if (fieldDescriptor.getId().equals(id)) return fieldDescriptor; } return null; } } private DivElement headerRow; private FlowPanel data = new FlowPanel(); private ArrayList<T> allItems; private ArrayList<T> showingItems = new ArrayList<T>(); private HashSet<T> selectedItems = new HashSet<T>(); protected int lastSelectedIndex; private DivElement[] itemDivs; private ItemDescriptor<T> itemDescriptor; private HashMap<String, Object> filters = new HashMap<String, Object>(); private String headerStyle; public ItemsTable(ItemDescriptor<T> desc) { this.itemDescriptor = desc; initWidget(data); // Lose browser default SHIFT-click highlighting disableTextSelectInternal(data.getElement(), true); addClickHandler(new ClickHandler() { @Override public void onClick(ClickEvent event) { // Convert click to selection event NativeEvent e = event.getNativeEvent(); Element clicked = Element.as(e.getEventTarget()); // Find item # from ID String[] split = clicked.getId().split(":"); int itemIndex = Integer.parseInt(split[1]); if (e.getCtrlKey()) { selectItem(itemIndex); } else if (e.getShiftKey()) { clearAllSelected(); // Select all items between current and previous selection int step = (itemIndex > lastSelectedIndex) ? 1 : -1; for (int i = lastSelectedIndex; i != itemIndex + step; i += step) { selectItem(i); } } else { clearAllSelected(); selectItem(itemIndex); } fireEvent(new ItemSelectionEvent<T>(selectedItems)); lastSelectedIndex = itemIndex; } }); addDoubleClickHandler(new DoubleClickHandler() { @Override public void onDoubleClick(DoubleClickEvent event) { // Clear all selections clearAllSelected(); // Convert click to activation event Element clicked = Element.as(event.getNativeEvent().getEventTarget()); // Find item # from ID String[] split = clicked.getId().split(":"); int itemIndex = Integer.parseInt(split[1]); T doubleClickedItem = showingItems.get(itemIndex); // fireEvent(new ItemActivationEvent<T>(doubleClickedItem)); showNote(); } }); initHeader(); } private void showNote() { } private void initHeader() { headerRow = Document.get().createDivElement(); headerRow.addClassName(AppStyles.HEADER_ROW); for (FieldDescriptor<T> fd : itemDescriptor.fields) { DivElement headerCell = Document.get().createDivElement(); headerCell.addClassName(fd.getStyle()); headerCell.setInnerText(fd.getHeaderText()); headerRow.appendChild(headerCell); } } protected void clearAllSelected() { selectedItems.clear(); for (DivElement div : itemDivs) { div.removeClassName(AppStyles.SELECTED); } } protected void selectItem(int itemIndex) { selectedItems.add(showingItems.get(itemIndex)); itemDivs[itemIndex].addClassName(AppStyles.SELECTED); } @Override public void addItemActivationHandler(ItemActivationHandler handler) { addHandler(handler, ItemActivationEvent.TYPE); } @Override public void addItemSelectionHandler(ItemSelectionHandler<T> handler) { addHandler(handler, ItemSelectionEvent.TYPE); } protected HandlerRegistration addClickHandler(ClickHandler clickHandler) { return addDomHandler(clickHandler, ClickEvent.getType()); } protected HandlerRegistration addDoubleClickHandler(DoubleClickHandler doubleClickhandler) { return addDomHandler(doubleClickhandler, DoubleClickEvent.getType()); } private void refresh() { // Clear any previous items selectedItems.clear(); lastSelectedIndex = 0; data.clear(); data.getElement().appendChild(headerRow); itemDivs = new DivElement[this.showingItems.size()]; for (int i = 0; i < this.showingItems.size(); i++) { T item = this.showingItems.get(i); DivElement itemDiv = Document.get().createDivElement(); itemDiv.addClassName(itemDescriptor.getItemStyle()); String[] values = itemDescriptor.getValues(item); for (int j = 0; j < values.length; j++) { FieldDescriptor<T> f = itemDescriptor.getFields().get(j); DivElement fieldDiv = Document.get().createDivElement(); fieldDiv.setId(f.getId() + ":" + i); fieldDiv.addClassName(f.getStyle()); fieldDiv.setInnerText(values[j]); itemDiv.appendChild(fieldDiv); } itemDivs[i] = itemDiv; } expando(data, itemDivs); } @Override public void populateAllItems(ArrayList<T> items) { this.allItems = new ArrayList<T>(items); } protected void expando(final FlowPanel div2, final DivElement[] notes) { Timer t = new Timer() { int i = 0; final int last = notes.length; @Override public void run() { if (i >= last) this.cancel(); else div2.getElement().appendChild(notes[i++]); } }; // Eye candy t.scheduleRepeating(10); } protected native static void disableTextSelectInternal(Element e, boolean disable) /*-{ if (disable) { e.ondrag = function () { return false; }; e.onselectstart = function () { return false; }; e.style.MozUserSelect="none" } else { e.ondrag = null; e.onselectstart = null; e.style.MozUserSelect="text" } }-*/; @Override public void selectAll() { for (int i = 0; i < itemDivs.length; i++) { selectItem(i); } fireEvent(new ItemSelectionEvent<T>(selectedItems)); } @Override public void selectNone() { clearAllSelected(); // This can't be moved into clearAllSelected() method or will // break multiple selection logic this.lastSelectedIndex = 0; fireEvent(new ItemSelectionEvent<T>(selectedItems)); } /** * Adds a filter and returns this for filter chaining. Must call refresh to * update display * * @param fieldId * @param filterValue * @return */ public void filter(String fieldId, Object filterValue) { if (filterValue == null) { filters.remove(fieldId); } else { filters.put(fieldId, filterValue); } filterAndSort(); } private void filterAndSort() { if (filters.isEmpty()) { showingItems = new ArrayList<T>(allItems); } else { showingItems.clear(); for (T item : this.allItems) { int match = 1; for (Entry<String, Object> e : filters.entrySet()) { String fieldId = e.getKey(); Object filterValue = e.getValue(); FieldDescriptor<T> fd = itemDescriptor.getFieldDescriptor(fieldId); Object fieldValue = fd.getAccessor().getObject(item); if (filterValue.equals(fieldValue)) match *= 1; else match *= 0; } if (match == 1) showingItems.add(item); } } refresh(); } /** * Reset filter and sort to defaults */ public void reset() { filters.clear(); showingItems = new ArrayList<T>(allItems); refresh(); } public String getHeaderStyle() { return headerStyle; } public void setHeaderStyle(String headerStyle) { this.headerStyle = headerStyle; } }