What is the best way to go about adding functionality to a splitpanel?

I have run into bug #11911 (SplitPanel dragging halts if mouse goes over iframe in IE) and noted the solution of enabling the dragcurtain if IE is in use.

To implement this change, I want to know which is the best approach, or if these are even feasible:
(1) Make a custom widget that extends VAbstractSplitPanel, then extend Horizontal and Vertical splitpanels, creating connector(s) to make the whole thing work.
(2) Write an extension that does this for me.
(3) Do some JAR surgery and replace VAbstractSplitPanel.

I am joking when I suggest option 3.

I have read Nicolas Frankels’ 3-part article (
here
) on using GWT widgets where he extends/wraps the Bootstrap button, and his explanation of the Component-Connector-Widget gives me just enough confidence to try the approach of (1). I am unclear if doing this means I have to add a new to my .gwt.xml file. My experience with custom components is basically zero at this point, and I do not want to end up thrashing about like I have in the past.

Extensions look simpler, but I am not even sure this is a workable approach for something like this.

Ideas or past experiences, anyone?

Hi!

I don’t think it would help much to extend VAbstractSplitPanel, since showDraggingCurtain() and hideDraggingCurtain() seem to be private. Or, of course, you could try to override onBrowserEvent(Event event), but there are so many private variables…

You could do your own rip-off clone until the issue is fixed. Here, I will just demonstrate the VerticalSplitPanel

  1. Create your serverside component com.your.app.ExtendedVerticalSplitPanel extends VerticalSplitPanel (no more code needed)
  2. create client package (unless you don’t want vaadin plugin for eclipse to do it) com.your.app.widgetset.client.extendedverticalsplitpanel
    2.1) create VExtendedSplitPanelVertical (copy over all code from VSplitPanelVertical) and do needed modifications
    2.2) create ExtendedAbstractSplitPanelConnector (copy over all code from AbstractSplitPanelConnector) and do needed modifications
    2.3) create ExtendedVerticalSplitPanelConnector. It should look something like this (note that we connect to our server side component ExtendedVerticalSplitPanel):
@Connect(value = ExtendedVerticalSplitPanel.class, loadStyle = LoadStyle.EAGER)
public class ExtendedVerticalSplitPanelConnector extends
        ExtendedAbstractSplitPanelConnector {

    @Override
    public VExtendedSplitPanelVertical getWidget() {
        return super.getWidget();
    }

    @Override
    public VerticalSplitPanelState getState() {
        return (VerticalSplitPanelState) super.getState();
    }
}
  1. unless you didn’t create an addon of this, you don’t need to add anything to the .gwt.xml

  2. Check that your client side classes refer to their own events and listeners by checking the imports, for example that the correct SplitterMoveHandler is used and so on… this ist just some common sense stuff…

  3. The rpc and state can be left untouched, no need to modify

Hope this helps a little

Thanks Johan. Now I know the overall approach that is needed.

One further question. You stated:
3) unless you didn’t create an addon of this, you don’t need to add anything to the .gwt.xml

I haven’t created an addon or anything, but the “didn’t” and “don’t” above have me guessing as to whether I need to modify my .gwt.xml. Since I haven’t created an addon, do I need to add an inherits section? If so, I assume it would be for the com.your.app.widgetset.client.extendedverticalsplitpanel I would create. In the case where I do have to add an inherits, could I just inherit com.your.app.widgetset.client once and put the horizontal version in the same package?

Sorry for all the detailed questions, I just want to be fully up-to-date before I try this, and just need a little clarity on how this procedure affects my widget add-ons, if at all.

Hi,

What I meant by 3) is that if you are going to create your own add-on for this purpose and then import it to your main project, you are going to need to add a <inhertits …> to your gwt.xml in the main project. Having an addon, you can use it in any project.

If it is ok to just use the widget in your current project, then you should not need to do any modifications to your gwt.xml

OK I don’t understand why my implementation is not working. Johan, your explanation makes it look easy, but I think I have missed something. I am starting with the horizontal split panel enhancement since that is what I need to fix first.

  1. Create your serverside component com.your.app.ExtendedVerticalSplitPanel extends VerticalSplitPanel
    Done:
package com.synopsys.lynx.gts.filebrowser.addon;

import com.vaadin.ui.HorizontalSplitPanel;
// No modifications needed on this class.
public class EnhancedHorizontalSplitPanel extends HorizontalSplitPanel {

}
  1. create client package (unless you don’t want vaadin plugin for eclipse to do it) com.your.app.widgetset.client.extendedverticalsplitpanel

I created a package named com.synopsys.lynx.gts.filebrowser.addon.widgetset.client.EnhancedHorizontalSplitPanel. That’s all I did, really, was create this package, and all the other classes mentioned in 2) were placed in this package. I am wondering if this package was supposed to follow some naming convention? I made it a subset of com.synopsys.lynx.gts.filebrowser.addon by naming it com.synopsys.lynx.gts.filebrowser.addon.widgetset.client.EnhancedHorizontalSplitPanel, because that looked like what you were telling me to do based on the examples you were giving in your instructions.

2.1) create VExtendedSplitPanelVertical (copy over all code from VSplitPanelVertical) and do needed modifications

I called mine VEnhancedHorizontalSplitPanel and I extended a new class called VEnhancedAbstractSplitPanel that has the functional changes to get IE to work:
package com.synopsys.lynx.gts.filebrowser.addon.widgetset.client.EnhancedHorizontalSplitPanel;

//import com.vaadin.client.ui.VAbstractSplitPanel;
import com.vaadin.shared.ui.Orientation;
// similar to VSplitPanelHorizontal
public class VEnhancedHorizontalSplitPanel extends VEnhancedAbstractSplitPanel {
    public VEnhancedHorizontalSplitPanel() {
        super(Orientation.HORIZONTAL);
    }
}

The extended class with the changes that should make IE work. For brevity, I show just the beginning of the class, along with the changes specified for the dragging curtain.

[code]
package com.synopsys.lynx.gts.filebrowser.addon.widgetset.client.EnhancedHorizontalSplitPanel;

import java.util.Collections;
import java.util.List;

import com.google.gwt.dom.client.Node;
import com.google.gwt.dom.client.Style;
import com.google.gwt.event.dom.client.TouchCancelEvent;
import com.google.gwt.event.dom.client.TouchCancelHandler;
import com.google.gwt.event.dom.client.TouchEndEvent;
import com.google.gwt.event.dom.client.TouchEndHandler;
import com.google.gwt.event.dom.client.TouchMoveEvent;
import com.google.gwt.event.dom.client.TouchMoveHandler;
import com.google.gwt.event.dom.client.TouchStartEvent;
import com.google.gwt.event.dom.client.TouchStartHandler;
import com.google.gwt.event.shared.EventHandler;
import com.google.gwt.event.shared.GwtEvent;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.ui.ComplexPanel;
import com.google.gwt.user.client.ui.Widget;
import com.vaadin.client.ApplicationConnection;
import com.vaadin.client.BrowserInfo;
import com.vaadin.client.ComponentConnector;
import com.vaadin.client.ConnectorMap;
import com.vaadin.client.LayoutManager;
import com.vaadin.client.StyleConstants;
import com.vaadin.client.Util;
import com.vaadin.client.VConsole;
import com.vaadin.client.ui.TouchScrollDelegate;
import com.vaadin.client.ui.TouchScrollDelegate.TouchScrollHandler;
import com.vaadin.client.ui.VAbstractSplitPanel.SplitterMoveHandler.SplitterMoveEvent;
import com.vaadin.client.ui.VOverlay;
import com.vaadin.shared.ui.Orientation;
// Replaces VAbstractSplitPanel so that split panels behave correctly in IE
public class VEnhancedAbstractSplitPanel extends ComplexPanel {

private void showDraggingCurtain() {
System.out.println(“--------->is dragging curtain required?”);
if (!isDraggingCurtainRequired()) {
return;
}
System.out.println(“--------->dragging curtain required!”);
if (draggingCurtain == null) {
draggingCurtain = DOM.createDiv();
DOM.setStyleAttribute(draggingCurtain, “position”, “absolute”);
DOM.setStyleAttribute(draggingCurtain, “top”, “0px”);
DOM.setStyleAttribute(draggingCurtain, “left”, “0px”);
DOM.setStyleAttribute(draggingCurtain, “width”, “100%”);
DOM.setStyleAttribute(draggingCurtain, “height”, “100%”);
DOM.setStyleAttribute(draggingCurtain, “zIndex”, “”
+ VOverlay.Z_INDEX);
// For some reason we need to set a background colour to make the scrollbar move smoothly in IE.
// From reading forums this forces the layout of the div to be set, however this should be achievable by
// setting another css property. However I could not get this to work by setting the properties
// suggested on some forums.
if (BrowserInfo.get().isIE()) {
DOM.setStyleAttribute(draggingCurtain, “background-color”, “white”);
if (BrowserInfo.get().isIE8()) {
DOM.setStyleAttribute(draggingCurtain, “filter”, “alpha(opacity=0)”);
} else {
DOM.setStyleAttribute(draggingCurtain, “opacity”, “0.0”);
}
}
DOM.appendChild(wrapper, draggingCurtain);
}
}

/**
* A dragging curtain is required in Gecko and Webkit.
*
* @return true if the browser requires a dragging curtain
*/
private boolean isDraggingCurtainRequired() {
System.out.println(“---------> in enhanced version of isDraggingCurtainRequired”);
// return (BrowserInfo.get().isGecko() || BrowserInfo.get().isWebkit());
return (BrowserInfo.get().isGecko() || BrowserInfo.get().isWebkit() || BrowserInfo.get().isIE8()
|| BrowserInfo.get().isIE9() || BrowserInfo.get().isIE10() || BrowserInfo.get().isIE());
}
[/code]The code in BrowserInfo does not accomodate IE 11, so I tacked on a check for just isIE() to make it react to IE 11.
I also have some System.out.println lines in there to make sure this was getting called (it isn’t).

2.2) create ExtendedAbstractSplitPanelConnector (copy over all code from AbstractSplitPanelConnector) and do needed modifications

Mine is called EnhancedAbstractSplitPanelConnector, with what I think are the right changes to getWidget():

package com.synopsys.lynx.gts.filebrowser.addon.widgetset.client.EnhancedHorizontalSplitPanel;

import java.util.LinkedList;
import java.util.List;

import com.google.gwt.dom.client.NativeEvent;
import com.google.gwt.event.dom.client.DomEvent;
import com.google.gwt.event.dom.client.DomEvent.Type;
import com.google.gwt.event.shared.EventHandler;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.ui.Widget;
import com.vaadin.client.ComponentConnector;
import com.vaadin.client.ConnectorHierarchyChangeEvent;
import com.vaadin.client.LayoutManager;
import com.vaadin.client.communication.StateChangeEvent;
import com.vaadin.client.ui.AbstractComponentContainerConnector;
import com.vaadin.client.ui.ClickEventHandler;
import com.vaadin.client.ui.SimpleManagedLayout;
//import com.vaadin.client.ui.VAbstractSplitPanel;
import com.vaadin.client.ui.VAbstractSplitPanel.SplitterMoveHandler;
import com.vaadin.client.ui.VAbstractSplitPanel.SplitterMoveHandler.SplitterMoveEvent;
import com.vaadin.shared.MouseEventDetails;
import com.vaadin.shared.ui.ComponentStateUtil;
import com.vaadin.shared.ui.splitpanel.AbstractSplitPanelRpc;
import com.vaadin.shared.ui.splitpanel.AbstractSplitPanelState;
import com.vaadin.shared.ui.splitpanel.AbstractSplitPanelState.SplitterState;
// This is similar to AbstractSplitPanelConnector
public abstract class EnhancedAbstractSplitPanelConnector extends
        AbstractComponentContainerConnector implements SimpleManagedLayout {
    private static final long serialVersionUID = 1L;

    @Override
        protected void init() {
            super.init();
            // TODO Remove
            getWidget().client = getConnection();

            getWidget().addHandler(new SplitterMoveHandler() {

                @Override
                public void splitterMoved(SplitterMoveEvent event) {
                    String position = getWidget().getSplitterPosition();
                    float pos = 0;
                    if (position.indexOf("%") > 0) {
                        // Send % values as a fraction to avoid that the splitter
                        // "jumps" when server responds with the integer pct value
                        // (e.g. dragged 16.6% -> should not jump to 17%)
                        pos = Float.valueOf(position.substring(0,
                                position.length() - 1));
                    } else {
                        pos = Integer.parseInt(position.substring(0,
                                position.length() - 2));
                    }

                    getRpcProxy(AbstractSplitPanelRpc.class).setSplitterPosition(
                            pos);
                }

            }, SplitterMoveEvent.TYPE);
        }
     
     @Override
        public void updateCaption(ComponentConnector component) {
            // TODO Implement caption handling
        }
     
     ClickEventHandler clickEventHandler = new ClickEventHandler(this) {

            @Override
            protected <H extends EventHandler> HandlerRegistration registerHandler(
                    H handler, Type<H> type) {
                if ((Event.getEventsSunk(getWidget().splitter) & Event
                        .getTypeInt(type.getName())) != 0) {
                    // If we are already sinking the event for the splitter we do
                    // not want to additionally sink it for the root element
                    return getWidget().addHandler(handler, type);
                } else {
                    return getWidget().addDomHandler(handler, type);
                }
            }

            @Override
            protected boolean shouldFireEvent(DomEvent<?> event) {
                Element target = event.getNativeEvent().getEventTarget().cast();
                if (!getWidget().splitter.isOrHasChild(target)) {
                    return false;
                }

                return super.shouldFireEvent(event);
            };

            @Override
            protected Element getRelativeToElement() {
                return getWidget().splitter;
            };

            @Override
            protected void fireClick(NativeEvent event,
                    MouseEventDetails mouseDetails) {
                getRpcProxy(AbstractSplitPanelRpc.class)
                        .splitterClick(mouseDetails);
            }

        };

        @Override
        public void onStateChanged(StateChangeEvent stateChangeEvent) {
            super.onStateChanged(stateChangeEvent);

            getWidget().immediate = getState().immediate;

            getWidget().setEnabled(isEnabled());

            clickEventHandler.handleEventHandlerRegistration();

            if (ComponentStateUtil.hasStyles(getState())) {
                getWidget().componentStyleNames = getState().styles;
            } else {
                getWidget().componentStyleNames = new LinkedList<String>();
            }

            // Splitter updates
            SplitterState splitterState = getState().splitterState;

            getWidget().setStylenames();

            getWidget().minimumPosition = splitterState.minPosition
                    + splitterState.minPositionUnit;

            getWidget().maximumPosition = splitterState.maxPosition
                    + splitterState.maxPositionUnit;

            getWidget().position = splitterState.position
                    + splitterState.positionUnit;

            getWidget().setPositionReversed(splitterState.positionReversed);

            getWidget().setLocked(splitterState.locked);

            // This is needed at least for cases like #3458 to take
            // appearing/disappearing scrollbars into account.
            getConnection().runDescendentsLayout(getWidget());

            getLayoutManager().setNeedsLayout(this);

            getWidget().makeScrollable();
        }

        @Override
        public void layout() {
            VEnhancedAbstractSplitPanel splitPanel = getWidget();
            splitPanel.setSplitPosition(splitPanel.position);
            splitPanel.updateSizes();
            // Report relative sizes in other direction for quicker propagation
            List<ComponentConnector> children = getChildComponents();
            for (ComponentConnector child : children) {
                reportOtherDimension(child);
            }
        }

        private void reportOtherDimension(ComponentConnector child) {
            LayoutManager layoutManager = getLayoutManager();
            if (this instanceof EnhancedHorizontalSplitPanelConnector) { // created in step 2.3
                if (child.isRelativeHeight()) {
                    int height = layoutManager.getInnerHeight(getWidget()
                            .getElement());
                    layoutManager.reportHeightAssignedToRelative(child, height);
                }
            } else {
                if (child.isRelativeWidth()) {
                    int width = layoutManager.getInnerWidth(getWidget()
                            .getElement());
                    layoutManager.reportWidthAssignedToRelative(child, width);
                }
            }
        }

        @Override
        public VEnhancedAbstractSplitPanel getWidget() {
            return (VEnhancedAbstractSplitPanel) super.getWidget();
        }

        @Override
        public AbstractSplitPanelState getState() {
            return (AbstractSplitPanelState) super.getState();
        }

        private ComponentConnector getFirstChild() {
            return (ComponentConnector) getState().firstChild;
        }

        private ComponentConnector getSecondChild() {
            return (ComponentConnector) getState().secondChild;
        }

        @Override
        public void onConnectorHierarchyChange(ConnectorHierarchyChangeEvent event) {
            /*
             * When the connector gets detached, the state isn't updated but there's
             * still a hierarchy change -> verify that the child from the state is
             * still our child before attaching the widget. See #10150.
             */

            Widget newFirstChildWidget = null;
            ComponentConnector firstChild = getFirstChild();
            if (firstChild != null && firstChild.getParent() == this) {
                newFirstChildWidget = firstChild.getWidget();
            }
            getWidget().setFirstWidget(newFirstChildWidget);

            Widget newSecondChildWidget = null;
            ComponentConnector secondChild = getSecondChild();
            if (secondChild != null && secondChild.getParent() == this) {
                newSecondChildWidget = secondChild.getWidget();
            }
            getWidget().setSecondWidget(newSecondChildWidget);
        }

}

2.3) create ExtendedVerticalSplitPanelConnector.

Mine is called EnhancedHorizontalSplitPanelConnector:

[code]
package com.synopsys.lynx.gts.filebrowser.addon.widgetset.client.EnhancedHorizontalSplitPanel;

import com.synopsys.lynx.gts.filebrowser.addon.EnhancedHorizontalSplitPanel;
import com.vaadin.shared.ui.Connect;
import com.vaadin.shared.ui.Connect.LoadStyle;
import com.vaadin.shared.ui.splitpanel.HorizontalSplitPanelState;

// similar to HorizontalSplitPanelConnector
@Connect(value =
EnhancedHorizontalSplitPanel.class
, loadStyle = LoadStyle.EAGER)
public class EnhancedHorizontalSplitPanelConnector extends EnhancedAbstractSplitPanelConnector{
private static final long serialVersionUID = 1L;

@Override
public [b]

VEnhancedHorizontalSplitPanel
[/b]getWidget() {
return (
VEnhancedHorizontalSplitPanel
) super.getWidget();
}

@Override
public HorizontalSplitPanelState getState() {
    return (HorizontalSplitPanelState) super.getState();
}

}
[/code]The changes to this class needed to create the proper connections, as I understand it, are shown in
bold
.

Since these enhancements are, for the time being, only kept in this one project for simplicity, there was nothing for me to do in step 3).

  1. Check that your client side classes refer to their own events and listeners by checking the imports, for example that the correct SplitterMoveHandler is used and so on… this ist just some common sense stuff.

This may be where my implementation falls apart. I just know about the changes in VAbstractSplitPanel’s dragging curtain code as mentioned in the bug report. That code is in VEnhancedAbstractSplitPanel, shown above. All I really did in that class was take VAbstractSplitPanel’s code and add the needed changes.

Hopefully this is enough of my implementation so that someone can see what I am doing wrong here…

I still think I am close here, but an missing something in my steps outlined above. As a result I am dead in the water here.

Has anyone done this, and can someone tell me where I might be going off-track here?