Lior Vernia has uploaded a new change for review.

Change subject: webadmin: Introducing AddRemoveRowWidget
......................................................................

webadmin: Introducing AddRemoveRowWidget

This is a reusable widget used to display a list of other widgets,
backed by a ListModel of values. Entries may be removed or added using
minus/plus buttons. A new entry is added by activating a semi-disabled
"ghost" entry.

Change-Id: I15958e4b7c1fcf0ec08ea35eb32c690964f2a366
Signed-off-by: Lior Vernia <lver...@redhat.com>
---
A 
frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/widget/AddRemoveRowWidget.java
1 file changed, 227 insertions(+), 0 deletions(-)


  git pull ssh://gerrit.ovirt.org:29418/ovirt-engine refs/changes/30/19530/1

diff --git 
a/frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/widget/AddRemoveRowWidget.java
 
b/frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/widget/AddRemoveRowWidget.java
new file mode 100644
index 0000000..e5165db
--- /dev/null
+++ 
b/frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/widget/AddRemoveRowWidget.java
@@ -0,0 +1,227 @@
+package org.ovirt.engine.ui.common.widget;
+
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.ovirt.engine.core.common.utils.Pair;
+import org.ovirt.engine.ui.common.CommonApplicationResources;
+import 
org.ovirt.engine.ui.common.widget.uicommon.popup.AbstractModelBoundPopupWidget;
+import org.ovirt.engine.ui.uicommonweb.models.ListModel;
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.event.logical.shared.HasValueChangeHandlers;
+import com.google.gwt.event.logical.shared.ValueChangeEvent;
+import com.google.gwt.event.logical.shared.ValueChangeHandler;
+import com.google.gwt.resources.client.CssResource;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.user.client.ui.FlowPanel;
+import com.google.gwt.user.client.ui.HorizontalPanel;
+import com.google.gwt.user.client.ui.Image;
+import com.google.gwt.user.client.ui.Panel;
+import com.google.gwt.user.client.ui.PushButton;
+import com.google.gwt.user.client.ui.Widget;
+
+/**
+ * This model-backed widget may be used to display a list of values of type T, 
where each value is display using a
+ * widget of type V. Existing values may be removed by pressing minus-signed 
buttons located next to them, while new
+ * values may be added by pressing a plus-signed button located next to a 
special "ghost" entry. This special entry is
+ * half-disabled, and only becomes enabled when its value is set to a valid 
one; it will be overlooked when flushing.
+ *
+ * @param <M>
+ *            the model backing this widget.
+ * @param <T>
+ *            the type of the values contained in the backing model.
+ * @param <V>
+ *            the type of widget used to display each value.
+ */
+public abstract class AddRemoveRowWidget<M extends ListModel, T, V extends 
Widget & HasValueChangeHandlers<T>> extends AbstractModelBoundPopupWidget<M> {
+
+    public interface WidgetStyle extends CssResource {
+        String entryStyle();
+        String buttonStyle();
+    }
+
+    @UiField
+    public FlowPanel contentPanel;
+
+    @UiField
+    public WidgetStyle style;
+
+    protected CommonApplicationResources resources = 
GWT.create(CommonApplicationResources.class);
+
+    private final List<Pair<T, V>> items;
+
+    public AddRemoveRowWidget() {
+        items = new LinkedList<Pair<T, V>>();
+    }
+
+    protected void init(ListModel model) {
+        items.clear();
+        contentPanel.clear();
+        Iterable<T> values = model.getItems();
+        if (values != null) {
+            for (T value : values) {
+                addEntry(value);
+            }
+        }
+        addGhostEntry();
+    }
+
+    protected void flush(ListModel model) {
+        ArrayList<T> values = new ArrayList<T>();
+        for (Pair<T, V> item : items) {
+            T value = item.getFirst();
+            if (!isGhost(value)) {
+                values.add(value);
+            }
+        }
+        model.setItems(values);
+    }
+
+    private void addGhostEntry() {
+        addEntry(createGhostValue());
+    }
+
+    private void addEntry(T value) {
+        final HorizontalPanel entry = new HorizontalPanel();
+        entry.addStyleName(style.entryStyle());
+        contentPanel.add(entry);
+
+        final V widget = createWidget(value);
+        Pair<T, V> item = new Pair<T, V>(value, widget);
+        items.add(item);
+        entry.add(widget);
+
+        PushButton button = createButton(item);
+        entry.add(button);
+
+        final boolean ghost = isGhost(value);
+        toggleGhost(value, widget, ghost);
+        widget.addValueChangeHandler(new ValueChangeHandler<T>() {
+
+            private boolean wasGhost = ghost;
+
+            @Override
+            public void onValueChange(ValueChangeEvent<T> event) {
+                T value = event.getValue();
+                boolean becomingGhost = isGhost(value);
+                if (becomingGhost != wasGhost) {
+                    ((PushButton) entry.getWidget(entry.getWidgetCount() - 
1)).setEnabled(!becomingGhost);
+                    toggleGhost(value, widget, becomingGhost);
+                    wasGhost = becomingGhost;
+                }
+            }
+        });
+
+        onAdd(value, widget);
+    }
+
+    private void removeEntry(Pair<T, V> item) {
+        items.remove(item);
+        contentPanel.remove(item.getSecond().getParent());
+        onRemove(item.getFirst(), item.getSecond());
+    }
+
+    private void swapAddButton(Pair<T, V> item, PushButton addButton) {
+        Panel entry = (Panel) item.getSecond().getParent();
+        entry.remove(addButton);
+        entry.add(createButton(item));
+    }
+
+    private PushButton createButton(final Pair<T, V> item) {
+        boolean ghostItem = isGhost(item.getFirst());
+        final PushButton button =
+                new PushButton(new Image(ghostItem ? resources.increaseIcon() 
: resources.decreaseIcon()));
+        button.addStyleName(style.buttonStyle());
+        button.setEnabled(!ghostItem);
+
+        button.addClickHandler(ghostItem ?
+                new ClickHandler() {
+
+                    @Override
+                    public void onClick(ClickEvent event) {
+                        swapAddButton(item, button);
+                        addGhostEntry();
+                    }
+                } :
+                new ClickHandler() {
+
+                    @Override
+                    public void onClick(ClickEvent event) {
+                        removeEntry(item);
+                    }
+                });
+
+        return button;
+    }
+
+    /**
+     * This method is called straight after an entry is removed. Override to 
specify implementation-specific behavior.
+     *
+     * @param value
+     *            the value removed.
+     * @param widget
+     *            the widget removed.
+     */
+    protected void onRemove(T value, V widget) {
+        // do nothing
+    }
+
+    /**
+     * This method is called straight after an entry is added. Override to 
specify implementation-specific behavior.
+     *
+     * @param value
+     *            the value added.
+     * @param widget
+     *            the widget added.
+     */
+    protected void onAdd(T value, V widget) {
+        // do nothing
+    }
+
+    /**
+     * This method should return a new widget of type V backed by a value of 
type T.
+     *
+     * @param value
+     *            the value backing the widget.
+     * @return a newly-constructed widget of type V.
+     */
+    protected abstract V createWidget(T value);
+
+    /**
+     * This method should manufacture a new object of type T, corresponding to 
a "ghost" entry as implemented by a
+     * specific subclass. This object should be distinct from regular values, 
so that the entry could not be mistaken
+     * for a regular entry.
+     *
+     * @return a value corresponding to a ghost entry.
+     */
+    protected abstract T createGhostValue();
+
+    /**
+     * This method receives a value of type T, and checks whether it 
corresponds to a "ghost" entry as implemented by a
+     * specific subclass. Please make sure to implement it in a consistent 
manner with respect to
+     * {@link #createGhostValue() }.
+     *
+     * @param value
+     *            the value to check.
+     * @return whether the value corresponds to a ghost entry.
+     */
+    protected abstract boolean isGhost(T value);
+
+    /**
+     * This method is called when the value backing the widget of type V has 
changed so that the widget transitioned into
+     * or out of "ghost" state. It should implement the details of the 
widget's appearance as it moves in or out of
+     * the ghost state.
+     *
+     * @param value
+     *            the value backing the widget.
+     * @param widget
+     *            the widget which is transitioning to/from ghost state.
+     * @param becomingGhost
+     *            true if the item is entering ghost state, false if exiting 
ghost state.
+     */
+    protected abstract void toggleGhost(T value, V widget, boolean 
becomingGhost);
+
+}


-- 
To view, visit http://gerrit.ovirt.org/19530
To unsubscribe, visit http://gerrit.ovirt.org/settings

Gerrit-MessageType: newchange
Gerrit-Change-Id: I15958e4b7c1fcf0ec08ea35eb32c690964f2a366
Gerrit-PatchSet: 1
Gerrit-Project: ovirt-engine
Gerrit-Branch: master
Gerrit-Owner: Lior Vernia <lver...@redhat.com>
_______________________________________________
Engine-patches mailing list
Engine-patches@ovirt.org
http://lists.ovirt.org/mailman/listinfo/engine-patches

Reply via email to