com.servoy.j2db.server.headlessclient.dataui.WebEventExecutor.java Source code

Java tutorial

Introduction

Here is the source code for com.servoy.j2db.server.headlessclient.dataui.WebEventExecutor.java

Source

/*
    
 This file belongs to the Servoy development and deployment environment, Copyright (C) 1997-2010 Servoy BV
    
 This program is free software; you can redistribute it and/or modify it under
 the terms of the GNU Affero General Public License as published by the Free
 Software Foundation; either version 3 of the License, or (at your option) any
 later version.
    
 This program is distributed in the hope that it will be useful, but WITHOUT
 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
    
 You should have received a copy of the GNU Affero General Public License along
 with this program; if not, see http://www.gnu.org/licenses or write to the Free
 Software Foundation,Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
 */
package com.servoy.j2db.server.headlessclient.dataui;

import java.awt.Event;
import java.awt.Point;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.wicket.Component;
import org.apache.wicket.Component.IVisitor;
import org.apache.wicket.MarkupContainer;
import org.apache.wicket.Page;
import org.apache.wicket.RequestCycle;
import org.apache.wicket.Response;
import org.apache.wicket.Session;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.ajax.IAjaxCallDecorator;
import org.apache.wicket.ajax.calldecorator.AjaxCallDecorator;
import org.apache.wicket.ajax.calldecorator.AjaxPostprocessingCallDecorator;
import org.apache.wicket.behavior.AbstractBehavior;
import org.apache.wicket.behavior.IBehavior;
import org.apache.wicket.markup.ComponentTag;
import org.apache.wicket.markup.html.IHeaderResponse;
import org.apache.wicket.markup.html.form.FormComponent;
import org.apache.wicket.markup.html.form.TextArea;
import org.apache.wicket.markup.html.form.TextField;
import org.apache.wicket.markup.html.list.ListItem;
import org.apache.wicket.markup.html.list.ListView;
import org.apache.wicket.model.IModel;
import org.apache.wicket.protocol.http.ClientProperties;
import org.apache.wicket.protocol.http.request.WebClientInfo;

import com.servoy.base.scripting.api.IJSEvent.EventType;
import com.servoy.j2db.FormController;
import com.servoy.j2db.IForm;
import com.servoy.j2db.dataprocessing.FoundSet;
import com.servoy.j2db.dataprocessing.IDisplay;
import com.servoy.j2db.dataprocessing.IDisplayData;
import com.servoy.j2db.dataprocessing.IFoundSet;
import com.servoy.j2db.dataprocessing.IFoundSetInternal;
import com.servoy.j2db.dataprocessing.IRecordInternal;
import com.servoy.j2db.dnd.IFormDataDragNDrop;
import com.servoy.j2db.scripting.IScriptable;
import com.servoy.j2db.scripting.IScriptableProvider;
import com.servoy.j2db.scripting.JSEvent;
import com.servoy.j2db.server.headlessclient.CloseableAjaxRequestTarget;
import com.servoy.j2db.server.headlessclient.MainPage;
import com.servoy.j2db.server.headlessclient.ServoyForm;
import com.servoy.j2db.server.headlessclient.WebClientSession;
import com.servoy.j2db.server.headlessclient.WebClientsApplication.ModifiedAccessStackPageMap;
import com.servoy.j2db.server.headlessclient.WebForm;
import com.servoy.j2db.server.headlessclient.WebOnRenderHelper;
import com.servoy.j2db.server.headlessclient.WrapperContainer;
import com.servoy.j2db.server.headlessclient.dataui.WebCellBasedView.WebCellBasedViewListViewItem;
import com.servoy.j2db.server.headlessclient.dataui.WebDataCompositeTextField.AugmentedTextField;
import com.servoy.j2db.server.headlessclient.dataui.WebDataImgMediaField.ImageDisplay;
import com.servoy.j2db.server.headlessclient.dnd.DraggableBehavior;
import com.servoy.j2db.server.headlessclient.eventthread.IEventDispatcher;
import com.servoy.j2db.server.headlessclient.eventthread.WicketEvent;
import com.servoy.j2db.ui.BaseEventExecutor;
import com.servoy.j2db.ui.IComponent;
import com.servoy.j2db.ui.IEventExecutor;
import com.servoy.j2db.ui.IFieldComponent;
import com.servoy.j2db.ui.ILabel;
import com.servoy.j2db.ui.IProviderStylePropertyChanges;
import com.servoy.j2db.ui.ISupportOnRender;
import com.servoy.j2db.ui.ISupportOnRenderCallback;
import com.servoy.j2db.ui.RenderEventExecutor;
import com.servoy.j2db.ui.runtime.IRuntimeComponent;
import com.servoy.j2db.util.Debug;
import com.servoy.j2db.util.ISupplyFocusChildren;
import com.servoy.j2db.util.Utils;

/**
 * The event executor that handles the events for a webclient.
 *
 * @author jcompagner
 */
public class WebEventExecutor extends BaseEventExecutor {
    private final Component component;
    private final boolean useAJAX;
    private IBehavior updatingBehavior;

    public WebEventExecutor(Component c, boolean useAJAX) {
        this.component = c;
        this.useAJAX = useAJAX;

        if (useAJAX && !(component instanceof WebDataHtmlView) && !(component instanceof WebImageBeanHolder)
                && !(component instanceof ILabel)) {
            if (component instanceof WebDataRadioChoice || component instanceof WebDataCheckBoxChoice) {
                component.add(new ServoyChoiceComponentUpdatingBehavior(component, this));
            } else if (component instanceof WebBaseSelectBox.ISelector) {
                updatingBehavior = new ServoySelectBoxUpdatingBehavior("onclick", //$NON-NLS-1$
                        ((WebBaseSelectBox.ISelector) component).getSelectBox(), this, "FormUpdate"); //$NON-NLS-1$
                component.add(updatingBehavior);
            } else if (component instanceof WebDataLookupField || component instanceof WebDataComboBox
                    || component instanceof AugmentedTextField || component instanceof WebDataListBox) // these fields can change contents without having focus or should generate dataProvider update without loosing focus; for example calendar&spinner might modify field content without field having focus
            {
                component.add(new ServoyFormComponentUpdatingBehavior("onchange", component, this, "FormUpdate")); //$NON-NLS-1$ //$NON-NLS-2$
            } else if (!(component instanceof FormComponent<?>)) // updating FormComponent is handled in focusLost event handler in PageContributor
            {
                Debug.trace("Component didn't get a updating behaviour: " + component); //$NON-NLS-1$
            }
        } else if (component instanceof ILabel && useAJAX) {
            component.add(new AbstractBehavior() {
                private static final long serialVersionUID = 1L;

                @Override
                public void onComponentTag(Component comp, ComponentTag tag) {
                    CharSequence type = tag.getString("type"); //$NON-NLS-1$
                    if (type != null && type.equals("submit")) //$NON-NLS-1$
                    {
                        // in ajax we can remove the submit. see case 177070
                        tag.put("type", "button"); //$NON-NLS-1$ //$NON-NLS-2$
                    }
                }
            });
        }
    }

    @Override
    public void setValidationEnabled(boolean b) {
        super.setValidationEnabled(b);
        if (component instanceof IProviderStylePropertyChanges) {
            ((IProviderStylePropertyChanges) component).getStylePropertyChanges().setChanged();
        }
    }

    /**
     * @see com.servoy.j2db.ui.BaseEventExecutor#setActionCmd(java.lang.String, Object[])
     */
    @Override
    public void setActionCmd(String id, Object[] args) {
        if (id != null && useAJAX) {
            if (!((component instanceof TextField<?> || component instanceof TextArea<?>)
                    && component instanceof IDisplay && ((IDisplay) component).isReadOnly())
                    && !(component instanceof ILabel) && !(component instanceof WebBaseSelectBox.ISelector)
                    && component instanceof FormComponent<?>) {
                component.add(new ServoyActionEventBehavior("onKeyDown", component, this, "ActionCmd")); // please keep the case in the event name //$NON-NLS-1$ //$NON-NLS-2$
            } else if (component instanceof WebBaseSelectBox.ISelector) {
                ((ServoySelectBoxUpdatingBehavior) updatingBehavior).setFireActionCommand(true);
            } else {
                // for ImageDisplay (that is an input with type='image') 'onclick' cannot be used, as it considered a submit button and any
                // enter inside the input's form will fire the 'onclick' - as workaround, we use 'onmouseup'
                component.add(new ServoyAjaxEventBehavior(
                        component instanceof ImageDisplay ? "onmouseup" : "onclick", "ActionCmd") //$NON-NLS-1$ //$NON-NLS-2$
                {
                    private static final long serialVersionUID = 1L;

                    @Override
                    protected void onEvent(AjaxRequestTarget target) {
                        WebEventExecutor.this.onEvent(JSEvent.EventType.action, target, component,
                                Utils.getAsInteger(RequestCycle.get().getRequest()
                                        .getParameter(IEventExecutor.MODIFIERS_PARAMETER)),
                                new Point(Utils.getAsInteger(RequestCycle.get().getRequest().getParameter("mx")), //$NON-NLS-1$
                                        Utils.getAsInteger(RequestCycle.get().getRequest().getParameter("my")))); //$NON-NLS-1$

                        target.appendJavascript("clearDoubleClickId('" + component.getMarkupId() + "')"); //$NON-NLS-1$ //$NON-NLS-2$
                    }

                    @Override
                    protected CharSequence generateCallbackScript(final CharSequence partialCall) {
                        return super.generateCallbackScript(partialCall + "+actionParam"); //$NON-NLS-1$
                    }

                    @Override
                    public CharSequence getCallbackUrl(boolean onlyTargetActivePage) {
                        return super.getCallbackUrl(true);
                    }

                    @SuppressWarnings("nls")
                    @Override
                    public boolean isEnabled(Component comp) {
                        if (super.isEnabled(comp)) {
                            if (comp instanceof IScriptableProvider && ((IScriptableProvider) comp)
                                    .getScriptObject() instanceof IRuntimeComponent) {
                                Object oe = ((IRuntimeComponent) ((IScriptableProvider) comp).getScriptObject())
                                        .getClientProperty("ajax.enabled");
                                if (oe != null)
                                    return Utils.getAsBoolean(oe);
                            }
                            return true;
                        }
                        return false;
                    }

                    @Override
                    protected IAjaxCallDecorator getAjaxCallDecorator() {
                        return new AjaxPostprocessingCallDecorator(null) {
                            private static final long serialVersionUID = 1L;

                            @SuppressWarnings("nls")
                            @Override
                            public CharSequence postDecorateScript(CharSequence script) {
                                String functionScript = "if (testDoubleClickId('" + component.getMarkupId()
                                        + "')) { " + script + "};";
                                return "var actionParam = Servoy.Utils.getActionParams(event,false); "
                                        + (hasDoubleClickCmd() ? "Servoy.Utils.startClickTimer(function() { "
                                                + functionScript
                                                + " Servoy.Utils.clickTimerRunning = false; return false; });"
                                                : functionScript);
                            }
                        };
                    }

                    @Override
                    protected String getJSEventName() {
                        String jsEventName = super.getJSEventName();
                        return hasDoubleClickCmd() ? jsEventName + "WithDblClick" : jsEventName; //$NON-NLS-1$
                    }
                });
            }
        }
        super.setActionCmd(id, args);
    }

    @Override
    public void setDoubleClickCmd(String id, Object[] args) {
        if (id != null && useAJAX) {
            if (component instanceof ILabel) {
                component.add(new ServoyAjaxEventBehavior("ondblclick", "Cmd") //$NON-NLS-1$ //$NON-NLS-2$
                {
                    @Override
                    protected void onEvent(AjaxRequestTarget target) {
                        WebEventExecutor.this.onEvent(JSEvent.EventType.doubleClick, target, component,
                                Utils.getAsInteger(RequestCycle.get().getRequest()
                                        .getParameter(IEventExecutor.MODIFIERS_PARAMETER)),
                                new Point(Utils.getAsInteger(RequestCycle.get().getRequest().getParameter("mx")), //$NON-NLS-1$
                                        Utils.getAsInteger(RequestCycle.get().getRequest().getParameter("my")))); //$NON-NLS-1$
                    }

                    @SuppressWarnings("nls")
                    @Override
                    protected CharSequence generateCallbackScript(final CharSequence partialCall) {
                        return super.generateCallbackScript(
                                partialCall + "+Servoy.Utils.getActionParams(event,false)"); //$NON-NLS-1$
                    }

                    @SuppressWarnings("nls")
                    @Override
                    public boolean isEnabled(Component comp) {
                        if (super.isEnabled(comp)) {
                            if (comp instanceof IScriptableProvider && ((IScriptableProvider) comp)
                                    .getScriptObject() instanceof IRuntimeComponent) {
                                Object oe = ((IRuntimeComponent) ((IScriptableProvider) comp).getScriptObject())
                                        .getClientProperty("ajax.enabled");
                                if (oe != null)
                                    return Utils.getAsBoolean(oe);
                            }
                            return true;
                        }
                        return false;
                    }

                    @Override
                    protected IAjaxCallDecorator getAjaxCallDecorator() {
                        return new AjaxPostprocessingCallDecorator(null) {
                            private static final long serialVersionUID = 1L;

                            @SuppressWarnings("nls")
                            @Override
                            public CharSequence postDecorateScript(CharSequence script) {
                                return "Servoy.Utils.stopClickTimer();" + script + "return !"
                                        + IAjaxCallDecorator.WICKET_CALL_RESULT_VAR + ";";
                            }
                        };
                    }
                });
            }
        }
        super.setDoubleClickCmd(id, args);
    }

    @Override
    public void setRightClickCmd(String id, Object[] args) {
        if (id != null && useAJAX) {
            if (component instanceof ILabel || component instanceof IFieldComponent
                    || component instanceof SortableCellViewHeader) {
                String sharedName = "Cmd";
                if (component instanceof SortableCellViewHeader) {
                    sharedName = null;
                }
                component.add(new ServoyAjaxEventBehavior("oncontextmenu", sharedName, true) //$NON-NLS-1$
                {
                    @Override
                    protected void onEvent(AjaxRequestTarget target) {
                        WebEventExecutor.this.onEvent(JSEvent.EventType.rightClick, target, component,
                                Utils.getAsInteger(RequestCycle.get().getRequest()
                                        .getParameter(IEventExecutor.MODIFIERS_PARAMETER)),
                                new Point(Utils.getAsInteger(RequestCycle.get().getRequest().getParameter("mx")), //$NON-NLS-1$
                                        Utils.getAsInteger(RequestCycle.get().getRequest().getParameter("my"))), //$NON-NLS-1$
                                new Point(Utils.getAsInteger(RequestCycle.get().getRequest().getParameter("glx")), Utils.getAsInteger(RequestCycle.get().getRequest().getParameter("gly")))); //$NON-NLS-2$
                    }

                    @Override
                    protected CharSequence generateCallbackScript(final CharSequence partialCall) {
                        return super.generateCallbackScript(partialCall + "+Servoy.Utils.getActionParams(event," //$NON-NLS-1$
                                + ((component instanceof SortableCellViewHeader) ? "true" : "false") + ")");
                    }

                    @Override
                    public boolean isEnabled(Component comp) {
                        if (super.isEnabled(comp)) {
                            if (comp instanceof IScriptableProvider && ((IScriptableProvider) comp)
                                    .getScriptObject() instanceof IRuntimeComponent) {
                                Object oe = ((IRuntimeComponent) ((IScriptableProvider) comp).getScriptObject())
                                        .getClientProperty("ajax.enabled"); //$NON-NLS-1$
                                if (oe != null)
                                    return Utils.getAsBoolean(oe);
                            }
                            return true;
                        }
                        return false;
                    }

                    // We need to return false, otherwise the context menu of the browser is displayed.
                    @Override
                    protected IAjaxCallDecorator getAjaxCallDecorator() {
                        return new AjaxCallDecorator() {
                            @Override
                            public CharSequence decorateScript(CharSequence script) {
                                return script + " return false;"; //$NON-NLS-1$
                            }
                        };
                    }
                });
            }
        }
        super.setRightClickCmd(id, args);
    }

    public void onEvent(EventType type, AjaxRequestTarget target, Component comp, int webModifiers) {
        onEvent(type, target, comp, webModifiers, null);
    }

    public void onEvent(EventType type, AjaxRequestTarget target, Component comp, int webModifiers,
            Point mouseLocation) {
        onEvent(type, target, comp, webModifiers, mouseLocation, null);
    }

    public void onEvent(final EventType type, final AjaxRequestTarget target, final Component comp,
            final int webModifiers, final Point mouseLocation, final Point absoluteMouseLocation) {
        ServoyForm form = comp.findParent(ServoyForm.class);
        if (form == null) {
            return;
        }

        final Page page = form.getPage(); // JS might change the page this form belongs to... so remember it now

        IEventDispatcher<WicketEvent> eventDispatcher = WebClientSession.get().getWebClient().getEventDispatcher();
        if (eventDispatcher != null) {
            eventDispatcher.addEvent(new WicketEvent(WebClientSession.get().getWebClient(), new Runnable() {
                public void run() {
                    handleEvent(type, target, comp, webModifiers, mouseLocation, absoluteMouseLocation, page);
                }
            }));
        } else {
            handleEvent(type, target, comp, webModifiers, mouseLocation, absoluteMouseLocation, page);
        }
        if (target != null) {
            generateResponse(target, page);
        }
    }

    /**
     * @param type
     * @param target
     * @param comp
     * @param webModifiers
     * @param mouseLocation
     * @param page
     */
    private void handleEvent(EventType type, AjaxRequestTarget target, Component comp, int webModifiers,
            Point mouseLocation, Point absoluteMouseLocation, Page page) {
        WebClientSession.get().getWebClient().executeEvents(); // process model changes from web components

        Component renderScriptProvider = comp;
        ISupplyFocusChildren<?> componentWithChildren = renderScriptProvider.findParent(ISupplyFocusChildren.class);
        if (componentWithChildren != null)
            renderScriptProvider = (Component) componentWithChildren;

        RenderEventExecutor renderEventExecutor = null;
        if (renderScriptProvider instanceof IScriptableProvider) {
            IScriptable s = ((IScriptableProvider) renderScriptProvider).getScriptObject();
            if (s instanceof ISupportOnRenderCallback) {
                renderEventExecutor = ((ISupportOnRenderCallback) s).getRenderEventExecutor();
                if (!renderEventExecutor.hasRenderCallback())
                    renderEventExecutor = null;
            }
        }

        if (type == EventType.focusGained || type == EventType.action || type == EventType.focusLost) {
            if (type == EventType.focusGained || type == EventType.action) {
                ((MainPage) page).setFocusedComponent(comp);
            } else {
                ((MainPage) page).setFocusedComponent(null);
            }
            if (renderEventExecutor != null) {
                renderEventExecutor.setRenderStateChanged();
                // if component's onRender did not change any properties, don't add it to the target
                if (comp instanceof ISupportOnRender && WebOnRenderHelper.doRender((ISupportOnRender) comp)) {
                    target.addComponent(comp);
                }
            }
        }

        if (type == EventType.focusLost || setSelectedIndex(comp, target, convertModifiers(webModifiers),
                type == EventType.focusGained || type == EventType.action)) {
            if (skipFireFocusGainedCommand && type.equals(JSEvent.EventType.focusGained)) {
                skipFireFocusGainedCommand = false;
            } else {
                switch (type) {
                case action:
                    fireActionCommand(false, comp, convertModifiers(webModifiers), mouseLocation);
                    break;
                case focusGained:
                    fireEnterCommands(false, comp, convertModifiers(webModifiers));
                    break;
                case focusLost:
                    fireLeaveCommands(comp, false, convertModifiers(webModifiers));
                    break;
                case doubleClick:
                    fireDoubleclickCommand(false, comp, convertModifiers(webModifiers), mouseLocation);
                    break;
                case rightClick:
                    // if right click, mark the meta flag as it is on the smart client
                    fireRightclickCommand(false, comp, convertModifiers(webModifiers | 8), null, mouseLocation,
                            absoluteMouseLocation);
                    break;
                case none:
                case dataChange:
                case form:
                case onDrag:
                case onDragOver:
                case onDrop:
                }
            }
        }
    }

    /**
     * Convert JS modifiers to AWT/Swing modifiers (used by Servoy event)
     *
     * @param webModifiers
     * @return
     */
    public static int convertModifiers(int webModifiers) {
        if (webModifiers == IEventExecutor.MODIFIERS_UNSPECIFIED)
            return IEventExecutor.MODIFIERS_UNSPECIFIED;

        // see function Servoy.Utils.getModifiers() in servoy.js
        int awtModifiers = 0;
        if ((webModifiers & 1) != 0)
            awtModifiers |= Event.CTRL_MASK;
        if ((webModifiers & 2) != 0)
            awtModifiers |= Event.SHIFT_MASK;
        if ((webModifiers & 4) != 0)
            awtModifiers |= Event.ALT_MASK;
        if ((webModifiers & 8) != 0)
            awtModifiers |= Event.META_MASK;

        return awtModifiers;
    }

    public void onError(AjaxRequestTarget target, Component comp) {
        if (target == null) {
            return;
        }
        ServoyForm form = comp.findParent(ServoyForm.class);
        if (form == null) {
            return;
        }
        generateResponse(target, form.getPage());
    }

    @SuppressWarnings("nls")
    public static boolean setSelectedIndex(Component component, AjaxRequestTarget target, int modifiers) {
        return setSelectedIndex(component, target, modifiers, false);
    }

    /**
     * @param component
     */
    @SuppressWarnings("nls")
    public static boolean setSelectedIndex(Component component, AjaxRequestTarget target, int modifiers,
            boolean bHandleMultiselect) {
        WebForm parentForm = component.findParent(WebForm.class);
        WebCellBasedView tableView = null;
        if (parentForm != null) {
            int parentFormViewType = parentForm.getController().getForm().getView();
            if (parentFormViewType == FormController.TABLE_VIEW
                    || parentFormViewType == FormController.LOCKED_TABLE_VIEW
                    || parentFormViewType == IForm.LIST_VIEW
                    || parentFormViewType == FormController.LOCKED_LIST_VIEW) {
                tableView = component.findParent(WebCellBasedView.class);
                if (tableView == null) {
                    // the component is not part of the table view (it is on other form part), so ignore selection change
                    return true;
                } else
                    tableView.setSelectionMadeByCellAction();

                if (parentFormViewType == IForm.LIST_VIEW
                        || parentFormViewType == FormController.LOCKED_LIST_VIEW) {
                    if (component instanceof WebCellBasedViewListViewItem) {
                        ((WebCellBasedViewListViewItem) component).markSelected(target);
                    } else {
                        WebCellBasedViewListViewItem listViewItem = component
                                .findParent(WebCellBasedView.WebCellBasedViewListViewItem.class);
                        if (listViewItem != null) {
                            listViewItem.markSelected(target);
                        }
                    }
                }
            }
        }

        //search for recordItem model
        Component recordItemModelComponent = component;
        IModel<?> someModel = recordItemModelComponent.getDefaultModel();
        while (!(someModel instanceof RecordItemModel)) {
            recordItemModelComponent = recordItemModelComponent.getParent();
            if (recordItemModelComponent == null)
                break;
            someModel = recordItemModelComponent.getDefaultModel();
        }

        if (someModel instanceof RecordItemModel) {
            if (!(component instanceof WebCellBasedViewListViewItem)) {
                // update the last rendered value for the events component (if updated)
                ((RecordItemModel) someModel).updateRenderedValue(component);
            }

            IRecordInternal rec = (IRecordInternal) someModel.getObject();
            if (rec != null) {
                int index;
                IFoundSetInternal fs = rec.getParentFoundSet();
                if (someModel instanceof FoundsetRecordItemModel) {
                    index = ((FoundsetRecordItemModel) someModel).getRowIndex();
                } else {
                    index = fs.getRecordIndex(rec); // this is used only on "else", because a "plugins.rawSQL.flushAllClientsCache" could result in index = -1 although the record has not changed (but record & underlying row instances changed)
                }

                if (fs instanceof FoundSet && ((FoundSet) fs).isMultiSelect()) {
                    //set the selected record
                    ClientProperties clp = ((WebClientInfo) Session.get().getClientInfo()).getProperties();
                    String navPlatform = clp.getNavigatorPlatform();
                    int controlMask = (navPlatform != null && navPlatform.toLowerCase().indexOf("mac") != -1)
                            ? Event.META_MASK
                            : Event.CTRL_MASK;

                    boolean toggle = (modifiers != MODIFIERS_UNSPECIFIED) && ((modifiers & controlMask) != 0);
                    boolean extend = (modifiers != MODIFIERS_UNSPECIFIED) && ((modifiers & Event.SHIFT_MASK) != 0);
                    boolean isRightClick = (modifiers != MODIFIERS_UNSPECIFIED)
                            && ((modifiers & Event.ALT_MASK) != 0);

                    if (!isRightClick) {
                        if (!toggle && !extend && tableView != null && tableView.getDragNDropController() != null
                                && Arrays.binarySearch(((FoundSet) fs).getSelectedIndexes(), index) > -1) {
                            return true;
                        }

                        if (toggle || extend) {
                            if (bHandleMultiselect) {
                                if (toggle) {
                                    int[] selectedIndexes = ((FoundSet) fs).getSelectedIndexes();
                                    ArrayList<Integer> selectedIndexesA = new ArrayList<Integer>();
                                    Integer selectedIndex = new Integer(index);

                                    for (int selected : selectedIndexes)
                                        selectedIndexesA.add(new Integer(selected));
                                    if (selectedIndexesA.indexOf(selectedIndex) != -1) {
                                        if (selectedIndexesA.size() > 1)
                                            selectedIndexesA.remove(selectedIndex);
                                    } else
                                        selectedIndexesA.add(selectedIndex);
                                    selectedIndexes = new int[selectedIndexesA.size()];
                                    for (int i = 0; i < selectedIndexesA.size(); i++)
                                        selectedIndexes[i] = selectedIndexesA.get(i).intValue();
                                    ((FoundSet) fs).setSelectedIndexes(selectedIndexes);
                                } else if (extend) {
                                    int anchor = ((FoundSet) fs).getSelectedIndex();
                                    int min = Math.min(anchor, index);
                                    int max = Math.max(anchor, index);

                                    int[] newSelectedIndexes = new int[max - min + 1];
                                    for (int i = min; i <= max; i++)
                                        newSelectedIndexes[i - min] = i;
                                    ((FoundSet) fs).setSelectedIndexes(newSelectedIndexes);
                                }
                            }
                        } else if (index != -1 || fs.getSize() == 0) {
                            fs.setSelectedIndex(index);
                        }
                    }
                } else if (!isIndexSelected(fs, index))
                    fs.setSelectedIndex(index);
                if (!isIndexSelected(fs, index) && !(fs instanceof FoundSet && ((FoundSet) fs).isMultiSelect())) {
                    // setSelectedIndex failed, probably due to validation failed, do a blur()
                    if (target != null)
                        target.appendJavascript("var toBlur = document.getElementById(\"" + component.getMarkupId()
                                + "\");if (toBlur) toBlur.blur();");
                    return false;
                }
            }
        }
        return true;
    }

    private static boolean isIndexSelected(IFoundSet fs, int index) {
        if (fs instanceof FoundSet) {
            FoundSet fsObj = (FoundSet) fs;
            for (int selectedIdx : fsObj.getSelectedIndexes()) {
                if (selectedIdx == index)
                    return true;
            }
        }
        return fs.getSelectedIndex() == index;
    }

    @SuppressWarnings("nls")
    public static void generateResponse(final AjaxRequestTarget target, Page page) {
        WebClientSession webClientSession = WebClientSession.get();
        if (target != null && page instanceof MainPage && webClientSession != null
                && webClientSession.getWebClient() != null
                && webClientSession.getWebClient().getSolution() != null) {
            if (target instanceof CloseableAjaxRequestTarget && ((CloseableAjaxRequestTarget) target).isClosed()) {
                return;
            }
            // do executed the events for before generating the response.
            webClientSession.getWebClient().executeEvents();

            if (webClientSession.getWebClient() == null || webClientSession.getWebClient().getSolution() == null) {
                // how can web client be null here ?
                return;
            }
            final MainPage mainPage = ((MainPage) page);

            if (mainPage.getPageMap() instanceof ModifiedAccessStackPageMap) {
                // at every request mark the pagemap as dirty so lru eviction really works
                ((ModifiedAccessStackPageMap) mainPage.getPageMap()).flagDirty();
            }

            // If the main form is switched then do a normal redirect.
            if (mainPage.isMainFormSwitched()) {
                mainPage.versionPush();
                RequestCycle.get().setResponsePage(page);
            }

            else {
                page.visitChildren(WebTabPanel.class, new Component.IVisitor<WebTabPanel>() {
                    public Object component(WebTabPanel component) {
                        component.initalizeFirstTab();
                        return IVisitor.CONTINUE_TRAVERSAL;
                    }
                });

                mainPage.addWebAnchoringInfoIfNeeded();

                final Set<WebCellBasedView> tableViewsToRender = new HashSet<WebCellBasedView>();
                final List<String> valueChangedIds = new ArrayList<String>();
                final List<String> invalidValueIds = new ArrayList<String>();
                final Map<WebCellBasedView, List<Integer>> tableViewsWithChangedRowIds = new HashMap<WebCellBasedView, List<Integer>>();

                // first, get all invalidValue & valueChanged components
                page.visitChildren(IProviderStylePropertyChanges.class, new Component.IVisitor<Component>() {
                    public Object component(Component component) {
                        if (component instanceof IDisplayData && !((IDisplayData) component).isValueValid()) {
                            invalidValueIds.add(component.getMarkupId());
                        }
                        if (((IProviderStylePropertyChanges) component).getStylePropertyChanges()
                                .isValueChanged()) {
                            if (component.getParent().isVisibleInHierarchy()) {
                                // the component will get added to the target & rendered only if it's parent is visible in hierarchy because changed flag is also set (see the visitor below)
                                // so we will only list these components if they are visible otherwise ajax timer could end up sending hundreds of id's that don't actually render every 5 seconds
                                // because the valueChanged flag is cleared only by onRender
                                valueChangedIds.add(component.getMarkupId());
                                if (component instanceof MarkupContainer) {
                                    ((MarkupContainer) component).visitChildren(IDisplayData.class,
                                            new IVisitor<Component>() {
                                                public Object component(Component comp) {
                                                    // labels/buttons that don't display data are not changed
                                                    if (!(comp instanceof ILabel)) {
                                                        valueChangedIds.add(comp.getMarkupId());
                                                    }
                                                    return CONTINUE_TRAVERSAL;
                                                }
                                            });
                                }
                            }
                        }
                        return CONTINUE_TRAVERSAL;
                    }
                });

                // add changed components to target; if a component is changed, the change check won't go deeper in hierarchy
                page.visitChildren(IProviderStylePropertyChanges.class, new Component.IVisitor<Component>() {
                    public Object component(Component component) {
                        if (((IProviderStylePropertyChanges) component).getStylePropertyChanges().isChanged()) {
                            if (component.getParent().isVisibleInHierarchy()) {
                                target.addComponent(component);
                                generateDragAttach(component, target.getHeaderResponse());

                                WebForm parentForm = component.findParent(WebForm.class);
                                boolean isDesignMode = parentForm != null && parentForm.isDesignMode();

                                if (!component.isVisible() || (component instanceof WrapperContainer
                                        && !((WrapperContainer) component).getDelegate().isVisible())) {
                                    ((IProviderStylePropertyChanges) component).getStylePropertyChanges()
                                            .setRendered();
                                    if (isDesignMode) {
                                        target.appendJavascript("Servoy.ClientDesign.hideSelected('"
                                                + component.getMarkupId() + "')");
                                    }
                                } else {
                                    if (isDesignMode) {
                                        target.appendJavascript("Servoy.ClientDesign.refreshSelected('"
                                                + component.getMarkupId() + "')");
                                    }
                                    // some components need to perform js layout tasks when their markup is replaced when using anchored layout
                                    mainPage.getPageContributor().markComponentForAnchorLayoutIfNeeded(component);
                                }

                                ListItem<IRecordInternal> row = component.findParent(ListItem.class);
                                if (row != null) {
                                    WebCellBasedView wcbv = row.findParent(WebCellBasedView.class);
                                    if (wcbv != null) {
                                        if (tableViewsWithChangedRowIds.get(wcbv) == null) {
                                            tableViewsWithChangedRowIds.put(wcbv, new ArrayList<Integer>());
                                        }
                                        List<Integer> ids = tableViewsWithChangedRowIds.get(wcbv);
                                        int changedRowIdx = wcbv.indexOf(row);
                                        if (changedRowIdx >= 0 && !ids.contains(changedRowIdx)) {
                                            ids.add(changedRowIdx);
                                        }
                                    }
                                }
                            }
                            return IVisitor.CONTINUE_TRAVERSAL_BUT_DONT_GO_DEEPER;
                        } else if (component instanceof WebCellBasedView)
                            tableViewsToRender.add((WebCellBasedView) component);
                        return component.isVisible() ? IVisitor.CONTINUE_TRAVERSAL
                                : IVisitor.CONTINUE_TRAVERSAL_BUT_DONT_GO_DEEPER;
                    }
                });

                page.visitChildren(IComponentToRequestAttacher.class, new Component.IVisitor<Component>() {
                    public Object component(Component component) {
                        ((IComponentToRequestAttacher) component).attachComponents(target);
                        return IVisitor.CONTINUE_TRAVERSAL;
                    }
                });

                final List<String> visibleEditors = new ArrayList<String>();
                page.visitChildren(WebDataHtmlArea.class, new Component.IVisitor<Component>() {
                    public Object component(Component component) {
                        visibleEditors.add(((WebDataHtmlArea) component).getEditorID());
                        return IVisitor.CONTINUE_TRAVERSAL;
                    }
                });
                StringBuffer argument = new StringBuffer();
                for (String id : visibleEditors) {
                    argument.append("\"");
                    argument.append(id);
                    argument.append("\"");
                    if (visibleEditors.indexOf(id) != visibleEditors.size() - 1) {
                        argument.append(",");
                    }
                }
                target.prependJavascript("Servoy.HTMLEdit.removeInvalidEditors(" + argument + ");");

                String rowSelectionScript, columnResizeScript;
                for (final WebCellBasedView wcbv : tableViewsToRender) {
                    if (wcbv.isScrollMode())
                        wcbv.scrollViewPort(target, true);
                    wcbv.updateRowSelection(target);
                    List<Integer> changedIds = tableViewsWithChangedRowIds.get(wcbv);
                    List<Integer> selectedIndexesChanged = wcbv.getIndexToUpdate(false);
                    List<Integer> mergedIds = selectedIndexesChanged != null ? selectedIndexesChanged
                            : new ArrayList<Integer>();
                    if (changedIds != null) {
                        for (Integer id : changedIds) {
                            if (!mergedIds.contains(id)) {
                                mergedIds.add(id);
                            }
                        }
                    }
                    rowSelectionScript = wcbv.getRowSelectionScript(mergedIds);
                    wcbv.clearSelectionByCellActionFlag();
                    if (rowSelectionScript != null)
                        target.appendJavascript(rowSelectionScript);
                    columnResizeScript = wcbv.getColumnResizeScript();
                    if (columnResizeScript != null)
                        target.appendJavascript(columnResizeScript);
                }

                // double check if the page contributor is changed, because the above IStylePropertyChanges ischanged could have altered it.
                if (mainPage.getPageContributor().getStylePropertyChanges().isChanged()) {
                    target.addComponent((Component) mainPage.getPageContributor());
                }
                if (invalidValueIds.size() == 0) {
                    target.appendJavascript("setValidationFailed(null);"); //$NON-NLS-1$
                } else {
                    target.appendJavascript("setValidationFailed('" + invalidValueIds.get(0) + "');"); //$NON-NLS-1$
                }
                Component comp = mainPage.getAndResetToFocusComponent();
                if (comp != null) {
                    if (comp instanceof WebDataHtmlArea) {
                        target.appendJavascript("tinyMCE.activeEditor.focus()");
                    } else {
                        target.focusComponent(comp);
                    }
                } else if (mainPage.getAndResetMustFocusNull()) {
                    // This is needed for example when showing a non-modal dialog in IE7 (or otherwise
                    // the new window would be displayed in the background).
                    target.focusComponent(null);
                }
                if (valueChangedIds.size() > 0) {
                    argument = new StringBuffer();
                    for (String id : valueChangedIds) {
                        argument.append("\"");
                        argument.append(id);
                        argument.append("\"");
                        if (valueChangedIds.indexOf(id) != valueChangedIds.size() - 1) {
                            argument.append(",");
                        }
                    }
                    target.prependJavascript("storeValueAndCursorBeforeUpdate(" + argument + ");");
                    target.appendJavascript("restoreValueAndCursorAfterUpdate();");
                }

                //if we have admin info, show it
                String adminInfo = mainPage.getAdminInfo();
                if (adminInfo != null) {
                    adminInfo = Utils.stringReplace(adminInfo, "\r", "");
                    adminInfo = Utils.stringReplace(adminInfo, "\n", "\\n");
                    target.appendJavascript("alert('Servoy admin info : " + adminInfo + "');");
                }

                // If we have a status text, set it.
                String statusText = mainPage.getStatusText();
                if (statusText != null) {
                    target.appendJavascript("setStatusText('" + statusText + "');");
                }

                String show = mainPage.getShowUrlScript();
                if (show != null) {
                    target.appendJavascript(show);
                }

                mainPage.renderJavascriptChanges(target);

                if (((WebClientInfo) webClientSession.getClientInfo()).getProperties().isBrowserInternetExplorer()
                        && ((WebClientInfo) webClientSession.getClientInfo()).getProperties()
                                .getBrowserVersionMajor() < 9) {
                    target.appendJavascript("Servoy.Utils.checkWebFormHeights();");
                }
                try {
                    if (isStyleSheetLimitForIE(page.getSession())) {
                        target.appendJavascript("testStyleSheets();");
                    }
                } catch (Exception e) {
                    Debug.error(e);//cannot retrieve session/clientinfo/properties?
                    target.appendJavascript("testStyleSheets();");
                }
            }
        }
    }

    public static boolean isStyleSheetLimitForIE(Session session) {
        if (session != null) {
            return ((WebClientInfo) session.getClientInfo()).getProperties().isBrowserInternetExplorer()
                    && ((WebClientInfo) session.getClientInfo()).getProperties().getBrowserVersionMajor() < 10;
        }
        return false;
    }

    @Override
    protected String getFormName() {
        return getFormName(component);
    }

    @Override
    protected String getFormName(Object display) {
        WebForm form = ((Component) display).findParent(WebForm.class);
        if (form == null) {
            return null;
        }
        return form.getController().getName();
    }

    @SuppressWarnings("nls")
    private static void updateDragAttachOutput(Object component, StringBuilder sbAttachDrag,
            StringBuilder sbAttachDrop, boolean hasDragEvent, boolean hasDropEvent) {
        StringBuilder sb = null;
        if (hasDragEvent && (component instanceof WebBaseLabel || component instanceof WebBaseButton
                || component instanceof WebBaseSubmitLink
                || ((component instanceof IDisplay) && ((IDisplay) component).isReadOnly())))
            sb = sbAttachDrag;
        else if (hasDropEvent)
            sb = sbAttachDrop;

        if (sb != null) {
            sb.append('\'');
            sb.append(((Component) component).getMarkupId());
            sb.append("',");
        }
    }

    /**
     * @param component2
     * @param response
     */
    @SuppressWarnings("nls")
    public static void generateDragAttach(Component component, IHeaderResponse response) {
        DraggableBehavior draggableBehavior = null;
        Component behaviorComponent = component;

        if ((behaviorComponent instanceof IComponent) && !(behaviorComponent instanceof IFormDataDragNDrop)) {
            behaviorComponent = (Component) component.findParent(IFormDataDragNDrop.class);
        }
        if (behaviorComponent != null) {
            Iterator<IBehavior> behaviors = behaviorComponent.getBehaviors().iterator();
            Object behavior;
            while (behaviors.hasNext()) {
                behavior = behaviors.next();
                if (behavior instanceof DraggableBehavior) {
                    draggableBehavior = (DraggableBehavior) behavior;
                    break;
                }
            }
        }

        if (draggableBehavior == null)
            return;

        boolean bUseProxy = draggableBehavior.isUseProxy();
        boolean bResizeProxyFrame = draggableBehavior.isResizeProxyFrame();
        boolean bXConstraint = draggableBehavior.isXConstraint();
        boolean bYConstraint = draggableBehavior.isYConstraint();
        CharSequence dragUrl = draggableBehavior.getCallbackUrl();

        String jsCode = null;

        if (behaviorComponent instanceof IFormDataDragNDrop) {
            final StringBuilder sbAttachDrag = new StringBuilder(100);
            sbAttachDrag.append("Servoy.DD.attachDrag([");
            final StringBuilder sbAttachDrop = new StringBuilder(100);
            sbAttachDrop.append("Servoy.DD.attachDrop([");

            final boolean hasDragEvent = ((IFormDataDragNDrop) behaviorComponent).getDragNDropController().getForm()
                    .getOnDragMethodID() > 0
                    || ((IFormDataDragNDrop) behaviorComponent).getDragNDropController().getForm()
                            .getOnDragOverMethodID() > 0;
            final boolean hasDropEvent = ((IFormDataDragNDrop) behaviorComponent).getDragNDropController().getForm()
                    .getOnDropMethodID() > 0;

            if (component instanceof WebDataRenderer || component instanceof WebCellBasedView) {
                if (hasDragEvent)
                    sbAttachDrag.append('\'').append(component.getMarkupId()).append("',");
                if (hasDropEvent)
                    sbAttachDrop.append('\'').append(component.getMarkupId()).append("',");

                if (component instanceof WebDataRenderer) {
                    Iterator<? extends Component> dataRendererIte = ((WebDataRenderer) component).iterator();

                    Object dataRendererChild;
                    while (dataRendererIte.hasNext()) {
                        dataRendererChild = dataRendererIte.next();
                        if (dataRendererChild instanceof IWebFormContainer)
                            continue;
                        if (dataRendererChild instanceof WrapperContainer)
                            dataRendererChild = ((WrapperContainer) dataRendererChild).getDelegate();
                        if (dataRendererChild instanceof IComponent
                                && ((IComponent) dataRendererChild).isEnabled()) {
                            updateDragAttachOutput(dataRendererChild, sbAttachDrag, sbAttachDrop, hasDragEvent,
                                    hasDropEvent);
                        }
                    }
                } else if (component instanceof WebCellBasedView) {
                    ListView<IRecordInternal> table = ((WebCellBasedView) component).getTable();
                    table.visitChildren(new IVisitor<Component>() {
                        public Object component(Component comp) {
                            if (comp instanceof IComponent && comp.isEnabled()) {
                                updateDragAttachOutput(comp, sbAttachDrag, sbAttachDrop, hasDragEvent,
                                        hasDropEvent);
                            }
                            return null;
                        }
                    });
                }
            } else if (component != null && component.isEnabled()) {
                updateDragAttachOutput(component, sbAttachDrag, sbAttachDrop, hasDragEvent, hasDropEvent);
            }

            if (sbAttachDrag.length() > 25) {
                sbAttachDrag.setLength(sbAttachDrag.length() - 1);
                sbAttachDrag.append("],'");
                sbAttachDrag.append(dragUrl);
                sbAttachDrag.append("', ");
                sbAttachDrag.append(bUseProxy);
                sbAttachDrag.append(", ");
                sbAttachDrag.append(bResizeProxyFrame);
                sbAttachDrag.append(", ");
                sbAttachDrag.append(bXConstraint);
                sbAttachDrag.append(", ");
                sbAttachDrag.append(bYConstraint);
                sbAttachDrag.append(");");

                jsCode = sbAttachDrag.toString();
            }

            if (sbAttachDrop.length() > 25) {
                sbAttachDrop.setLength(sbAttachDrop.length() - 1);
                sbAttachDrop.append("],'");
                sbAttachDrop.append(dragUrl);
                sbAttachDrop.append("');");

                if (jsCode != null)
                    jsCode += '\n' + sbAttachDrop.toString();
                else
                    jsCode = sbAttachDrop.toString();
            }

            if (jsCode != null) {
                if (response == null) {
                    jsCode = (new StringBuilder().append("\n<script type=\"text/javascript\">\n").append(jsCode)
                            .append("</script>\n")).toString();
                    Response cyleResponse = RequestCycle.get().getResponse();
                    cyleResponse.write(jsCode);
                } else
                    response.renderOnDomReadyJavascript(jsCode);
            }
        } else
        //default handling
        {
            jsCode = "Servoy.DD.attachDrag(['" + component.getMarkupId() + "'],'" + dragUrl + "', " + bUseProxy
                    + ", " + bResizeProxyFrame + ", " + bXConstraint + ", " + bYConstraint + ")";
            if (response == null) {
                jsCode = (new StringBuilder().append("\n<script type=\"text/javascript\">\n").append(jsCode)
                        .append("</script>\n")).toString();
                Response cyleResponse = RequestCycle.get().getResponse();

                cyleResponse.write(jsCode);
            } else
                response.renderOnDomReadyJavascript(jsCode);
        }
    }

    @Override
    protected String getElementName(Object display) {
        String name = super.getElementName(display);
        if (name == null && display instanceof SortableCellViewHeader) {
            name = ((SortableCellViewHeader) display).getName();
        }
        return name;
    }

    @Override
    protected Object getSource(Object display) {
        return display instanceof SortableCellViewHeader ? null : super.getSource(display);
    }

}