cc.alcina.framework.gwt.client.widget.complex.FastROBoundTable.java Source code

Java tutorial

Introduction

Here is the source code for cc.alcina.framework.gwt.client.widget.complex.FastROBoundTable.java

Source

/* 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package cc.alcina.framework.gwt.client.widget.complex;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.Node;
import com.google.gwt.dom.client.NodeList;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.DomEvent;
import com.google.gwt.event.dom.client.HasMouseMoveHandlers;
import com.google.gwt.event.dom.client.HasMouseOutHandlers;
import com.google.gwt.event.dom.client.HasMouseOverHandlers;
import com.google.gwt.event.dom.client.MouseMoveEvent;
import com.google.gwt.event.dom.client.MouseMoveHandler;
import com.google.gwt.event.dom.client.MouseOutEvent;
import com.google.gwt.event.dom.client.MouseOutHandler;
import com.google.gwt.event.dom.client.MouseOverEvent;
import com.google.gwt.event.dom.client.MouseOverHandler;
import com.google.gwt.event.logical.shared.CloseEvent;
import com.google.gwt.event.logical.shared.CloseHandler;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.ui.CheckBox;
import com.google.gwt.user.client.ui.FlexTable;
import com.google.gwt.user.client.ui.Widget;
import com.totsp.gwittir.client.beans.Binding;
import com.totsp.gwittir.client.beans.Property;
import com.totsp.gwittir.client.beans.SourcesPropertyChangeEvents;
import com.totsp.gwittir.client.ui.BoundWidget;
import com.totsp.gwittir.client.ui.table.DataProvider;
import com.totsp.gwittir.client.ui.table.Field;
import com.totsp.gwittir.client.ui.util.BoundWidgetProvider;
import com.totsp.gwittir.client.ui.util.BoundWidgetTypeFactory;

import cc.alcina.framework.common.client.collections.CollectionFilter;
import cc.alcina.framework.common.client.collections.CollectionFilters;
import cc.alcina.framework.common.client.util.CommonUtils;
import cc.alcina.framework.gwt.client.gwittir.BasicBindingAction;
import cc.alcina.framework.gwt.client.gwittir.GwittirBridge;
import cc.alcina.framework.gwt.client.gwittir.RequiresContextBindable;
import cc.alcina.framework.gwt.client.gwittir.widget.BoundTableExt;
import cc.alcina.framework.gwt.client.gwittir.widget.EndRowButtonClickedEvent;
import cc.alcina.framework.gwt.client.util.RelativePopupPositioning;
import cc.alcina.framework.gwt.client.util.RelativePopupPositioning.RelativePopupAxis;
import cc.alcina.framework.gwt.client.widget.dialog.RelativePopupPanel;

@SuppressWarnings("unchecked")
/**
 *
 * @author Nick Reddel
 */
public class FastROBoundTable extends BoundTableExt {
    Map<String, BoundWidgetProvider> wpMap = new HashMap<String, BoundWidgetProvider>();

    Map<String, Property> pMap = new HashMap<String, Property>();

    private List selectedObjects = new ArrayList();

    public boolean reallyClear;

    public CollectionFilter<CheckEditableTuple> checkEditableFilter;

    private EditOverlayHandler editOverlayHandler;

    private int lastSelectedIndex = -1;

    private Object selectedObject = null;

    public FastROBoundTable(int mask, BoundWidgetTypeFactory factory, Field[] fields, DataProvider provider) {
        super(mask, factory, fields, provider);
    }

    public boolean checkEditable(SourcesPropertyChangeEvents target, Field editableField) {
        return checkEditableFilter == null
                || checkEditableFilter.allow(new CheckEditableTuple(target, editableField));
    }

    public void edit(Object target, final String fieldName) {
        int row = CommonUtils.indexOf(((Collection) getValue()).iterator(), target);
        if (row == -1) {
            return;
        }
        row += ((this.masks & BoundTableExt.HEADER_MASK) > 0) ? 1 : 0;
        int col = CollectionFilters.indexOf(Arrays.asList(columns), new CollectionFilter<Field>() {
            @Override
            public boolean allow(Field field) {
                return field.getPropertyName().equals(fieldName);
            }
        });
        col += ((this.masks & BoundTableExt.ROW_HANDLE_MASK) > 0) ? 1 : 0;
        editOverlayHandler.edit(new RowCol(row, col));
    }

    /**
     * Note - you may well want to make the parent of this table relative
     */
    public void editOverlay(Class tableObjectClass) {
        ROFlexTable t = (ROFlexTable) table;
        editOverlayHandler = new EditOverlayHandler(tableObjectClass);
        t.addClickHandler(editOverlayHandler);
        t.addMouseOutHandler(editOverlayHandler);
        t.addMouseMoveHandler(editOverlayHandler);
        t.addMouseOverHandler(editOverlayHandler);
        table.addStyleName("editable");
    }

    public CollectionFilter<CheckEditableTuple> getCheckEditableFilter() {
        return this.checkEditableFilter;
    }

    public List getSelectedObjects() {
        return this.selectedObjects;
    }

    public void redrawRowForObject(Object o) {
        List list = (List) getValue();
        Iterator itr = list.iterator();
        int i = 0;
        int startColumn = (this.masks & BoundTableExt.ROW_HANDLE_MASK) > 0 ? 1 : 0;
        for (; itr.hasNext(); i++) {
            if (itr.next() == o) {
                break;
            }
        }
        if (i == list.size()) {
            return;
        }
        int row = calculateObjectToRowOffset(i);
        reallyClear = true;
        for (int col = 0; col < this.columns.length; col++) {
            Widget widget = (Widget) createCellWidget(col, (SourcesPropertyChangeEvents) o);
            table.setWidget(row, col + startColumn, widget);
        }
        reallyClear = false;
    }

    public void removeRow(Object o) {
        List list = (List) getValue();
        Iterator itr = list.iterator();
        int i = 0;
        int startColumn = (this.masks & BoundTableExt.ROW_HANDLE_MASK) > 0 ? 1 : 0;
        for (; itr.hasNext(); i++) {
            if (itr.next() == o) {
                break;
            }
        }
        if (i == list.size()) {
            return;
        }
        int row = calculateObjectToRowOffset(i);
        table.removeRow(i + ((this.masks & BoundTableExt.HEADER_MASK) > 0 ? 1 : 0));
        ((Collection) getValue()).remove(o);
    }

    public void setCheckEditableFilter(CollectionFilter<CheckEditableTuple> checkEditableFilter) {
        this.checkEditableFilter = checkEditableFilter;
    }

    public void setSelectedObject(Object object) {
        selectedObject = object;
        Iterator itr = ((Collection) getValue()).iterator();
        int rowIndex = 0;
        while (itr.hasNext()) {
            Object value = itr.next();
            if (value == object) {
                break;
            }
            rowIndex++;
        }
        int headerOffset = ((this.masks & BoundTableExt.HEADER_MASK) > 0) ? 1 : 0;
        rowIndex += headerOffset;
        if (lastSelectedIndex != -1) {
            table.getRowFormatter().removeStyleName(lastSelectedIndex, "selected");
        }
        if (table.getRowCount() > rowIndex) {
            table.getRowFormatter().addStyleName(rowIndex, "selected");
            lastSelectedIndex = rowIndex;
        }
    }

    public void setSelectedObjects(List selectedObjects) {
        this.selectedObjects = selectedObjects;
    }

    @Override
    protected void addRow(final SourcesPropertyChangeEvents o) {
        int row = table.getRowCount();
        final CheckBox handle;
        int startColumn = 0;
        final List<CheckBox> handles = this.rowHandles;
        if ((this.masks & BoundTableExt.ROW_HANDLE_MASK) > 0) {
            handle = new CheckBox();
            handle.addClickHandler(new ClickHandler() {
                public void onClick(ClickEvent event) {
                    setActive(true);
                    List newSelected = null;
                    if ((masks & BoundTableExt.MULTIROWSELECT_MASK) > 0) {
                        newSelected = new ArrayList(getSelectedObjects());
                    } else {
                        for (CheckBox cb : handles) {
                            if (cb != handle) {
                                cb.setValue(false);
                            }
                        }
                        newSelected = new ArrayList();
                    }
                    if (!handle.getValue()) {
                        newSelected.remove(o);
                    } else {
                        newSelected.add(o);
                    }
                    setSelected(newSelected);
                    setSelectedObjects(newSelected);
                }
            });
            startColumn++;
            this.rowHandles.add(handle);
            this.table.setWidget(row, 0, handle);
        } else {
            handle = null;
        }
        for (int col = 0; col < this.columns.length; col++) {
            Widget widget = (Widget) createCellWidget(col, o);
            if (this.columns[col].getWidgetStyleName() != null) {
                widget.addStyleName(this.columns[col].getWidgetStyleName());
            }
            table.setWidget(row, col + startColumn, widget);
            if (this.columns[col].getStyleName() != null) {
                table.getCellFormatter().setStyleName(row, col + startColumn, this.columns[col].getStyleName());
            }
        }
        if ((this.masks & BoundTableExt.END_ROW_BUTTON) > 0) {
            EndRowButton endRowButton = new EndRowButton();
            table.setWidget(row, this.columns.length + startColumn, endRowButton);
            int f_row = row;
            endRowButton.addClickHandler(e -> {
                EndRowButtonClickedEvent.fire(FastROBoundTable.this, f_row, o);
            });
        }
        boolean odd = (this.calculateRowToObjectOffset(new Integer(row)).intValue() % 2) != 0;
        this.table.getRowFormatter().setStyleName(row, odd ? "odd" : "even");
    }

    protected void beautify() {
        if (allRowsHandle != null) {
            allRowsHandle.setVisible(false);
        }
    }

    protected BoundWidget createCellWidget(int colIndex, SourcesPropertyChangeEvents target) {
        final BoundWidget widget;
        Field col = this.columns[colIndex];
        if (!wpMap.containsKey(col.getPropertyName())) {
            Property p = GwittirBridge.get().getProperty(target, col.getPropertyName());
            BoundWidgetProvider wp = this.factory.getWidgetProvider(col.getPropertyName(), p.getType());
            pMap.put(col.getPropertyName(), p);
            wpMap.put(col.getPropertyName(), wp);
        }
        Property p = pMap.get(col.getPropertyName());
        BoundWidgetProvider wp = col.getCellProvider() != null ? col.getCellProvider()
                : wpMap.get(col.getPropertyName());
        if (wp instanceof RequiresContextBindable) {
            ((RequiresContextBindable) wp).setBindable(target);
        }
        widget = wp.get();
        try {
            widget.setModel(target);
            widget.setValue(p.getAccessorMethod().invoke(target, CommonUtils.EMPTY_OBJECT_ARRAY));
        } catch (Exception e) {
            GWT.log("Exception creating cell widget", e);
        }
        return widget;
    }

    @Override
    protected FlexTable createTableImpl() {
        ROFlexTable table = new ROFlexTable();
        return table;
    }

    @Override
    protected void renderAll() {
        if (wpMap == null) {
            return;
        }
        super.renderAll();
        setSelectedObject(selectedObject);
    }

    public static class CheckEditableTuple {
        public SourcesPropertyChangeEvents target;

        public Field field;

        public CheckEditableTuple(SourcesPropertyChangeEvents target, Field editableField) {
            this.target = target;
            this.field = editableField;
        }
    }

    // this actually duplicates a lot of stuff in HTMLTable -
    private class EditOverlayHandler implements ClickHandler, MouseOutHandler, MouseOverHandler, MouseMoveHandler {
        Map<Integer, Boolean> editableColumns = new HashMap<Integer, Boolean>();

        BasicBindingAction action;

        RowCol lastRowCol;

        public EditOverlayHandler(Class tableObjectClass) {
            editableColumns.put(-1, false);
            int i = 0;
            if ((masks & BoundTableExt.ROW_HANDLE_MASK) > 0) {
                editableColumns.put(i++, false);
            }
            for (Field f : columns) {
                editableColumns.put(i++,
                        GwittirBridge.get().isFieldEditable(tableObjectClass, f.getPropertyName()));
            }
        }

        public void onClick(ClickEvent event) {
            RowCol rowCol = getRowCol(event);
            edit(rowCol);
        }

        public void onMouseMove(MouseMoveEvent event) {
            RowCol rowCol = getRowCol(event);
            if (lastRowCol != null && !lastRowCol.equals(rowCol)) {
                showEditable(null);
            }
            if (editableColumns.get(rowCol.col)) {
                showEditable(rowCol);
            }
        }

        public void onMouseOut(MouseOutEvent event) {
            showEditable(null);
        }

        public void onMouseOver(MouseOverEvent event) {
        }

        private RowCol getRowCol(DomEvent event) {
            try {
                com.google.gwt.dom.client.Element elt = Element.as(event.getNativeEvent().getEventTarget());
                Element tableElt = table.getElement();
                com.google.gwt.dom.client.Element tr = null;
                com.google.gwt.dom.client.Element td = null;
                while (elt != null && elt != tableElt) {
                    if (elt.getTagName().equalsIgnoreCase("td")) {
                        td = elt;
                    }
                    if (elt.getTagName().equalsIgnoreCase("tr")) {
                        tr = elt;
                    }
                    elt = elt.getParentElement();
                }
                return new RowCol(indexInParent(tr), indexInParent(td));
            } catch (Exception e) {
                // messy but effective
                return new RowCol(-1, -1);
            }
        }

        private int indexInParent(com.google.gwt.dom.client.Element elt) {
            com.google.gwt.dom.client.Element parent = elt.getParentElement();
            NodeList<Node> childNodes = parent.getChildNodes();
            String eltName = elt.getTagName();
            int index = -1;
            for (int i = 0; i < childNodes.getLength(); i++) {
                Node item = childNodes.getItem(i);
                if (item.getNodeType() == Node.ELEMENT_NODE && item.getNodeName().equalsIgnoreCase(eltName)) {
                    index++;
                }
                if (item == elt) {
                    break;
                }
            }
            return index;
        }

        private void styleDelta(RowCol rowCol, String styleToAdd, String styleToRemove) {
            if (rowCol == null || rowCol.col == -1 || rowCol.row == -1) {
                return;
            }
            if (styleToAdd != null) {
                table.getCellFormatter().addStyleName(rowCol.row, rowCol.col, styleToAdd);
            }
            if (styleToRemove != null) {
                table.getCellFormatter().removeStyleName(rowCol.row, rowCol.col, styleToRemove);
            }
        }

        protected void edit(RowCol rowCol) {
            if (rowCol.row == 0 || !editableColumns.get(rowCol.col)) {
                return;
            }
            showEditable(rowCol);
            Iterator itr = ((Collection) getValue()).iterator();
            final SourcesPropertyChangeEvents target = (SourcesPropertyChangeEvents) CommonUtils.get(itr,
                    rowCol.row - 1);
            int startColumn = 0;
            if ((masks & BoundTableExt.ROW_HANDLE_MASK) > 0) {
                startColumn++;
            }
            Field col = columns[rowCol.col - startColumn];
            final Field field = GwittirBridge.get().getField(target.getClass(), col.getPropertyName(), true, false);
            if (!checkEditable(target, field)) {
                return;
            }
            BoundWidgetProvider wp = field.getCellProvider();
            if (wp instanceof RequiresContextBindable) {
                ((RequiresContextBindable) wp).setBindable(target);
            }
            final BoundWidget editableWidget = wp.get();
            Property p = GwittirBridge.get().getProperty(target, field.getPropertyName());
            try {
                editableWidget.setModel(target);
                action = new BasicBindingAction() {
                    @Override
                    protected void set0(BoundWidget widget) {
                        binding.getChildren().add(new Binding(widget, "value", field.getValidator(),
                                field.getFeedback(), target, field.getPropertyName(), null, null));
                        binding.setLeft();
                    }
                };
                editableWidget.setAction(action);
                final BoundWidget tableWidget = (BoundWidget) table.getWidget(rowCol.row, rowCol.col);
                RelativePopupPanel rpp = new RelativePopupPanel(true);
                rpp.setAnimationEnabled(true);
                rpp.addStyleName("edit-overlay");
                Widget relativeToWidget = (Widget) tableWidget;
                int tdh = table.getCellFormatter().getElement(rowCol.row, rowCol.col).getOffsetHeight();
                RelativePopupPanel relativePopupPanel = RelativePopupPositioning.showPopup(relativeToWidget,
                        (Widget) editableWidget, table,
                        new RelativePopupAxis[] { RelativePopupPositioning.BOTTOM_LTR }, null, rpp, -4, -tdh + 4);
                relativePopupPanel.addCloseHandler(new CloseHandler<RelativePopupPanel>() {
                    public void onClose(CloseEvent<RelativePopupPanel> event) {
                        tableWidget.setValue(editableWidget.getValue());
                    }
                });
            } catch (Exception e) {
                GWT.log("Exception creating cell widget", e);
            }
        }

        protected void showEditable(RowCol rowCol) {
            styleDelta(lastRowCol, null, "editableOver");
            lastRowCol = null;
            if (rowCol != null) {
                lastRowCol = rowCol;
                styleDelta(lastRowCol, "editableOver", null);
            }
        }
    }

    private class ROFlexTable extends FlexTable
            implements HasMouseOverHandlers, HasMouseOutHandlers, HasMouseMoveHandlers {
        public HandlerRegistration addMouseMoveHandler(MouseMoveHandler handler) {
            return addDomHandler(handler, MouseMoveEvent.getType());
        }

        public HandlerRegistration addMouseOutHandler(MouseOutHandler handler) {
            return addDomHandler(handler, MouseOutEvent.getType());
        }

        public HandlerRegistration addMouseOverHandler(MouseOverHandler handler) {
            return addDomHandler(handler, MouseOverEvent.getType());
        }

        @Override
        public Element getEventTargetCell(Event event) {
            return super.getEventTargetCell(event);
        }

        protected boolean internalClearCell(Element td, boolean clearInnerHTML) {
            if (!reallyClear) {
                return false;
            } else {
                return super.internalClearCell(td, clearInnerHTML);
            }
        }
    }

    private class RowCol {
        int row;

        int col;

        public RowCol(int row, int col) {
            super();
            this.row = row;
            this.col = col;
        }

        @Override
        public boolean equals(Object obj) {
            if (obj instanceof RowCol) {
                RowCol rc = (RowCol) obj;
                return row == rc.row && col == rc.col;
            }
            return false;
        }
    }
}