/* 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", ((WebBaseSelectBox.ISelector)component).getSelectBox(), this, "FormUpdate"); //$NON-NLS-1$ //$NON-NLS-2$ 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"))), new Point(Utils.getAsInteger(RequestCycle.get().getRequest().getParameter("glx")), //$NON-NLS-1$ Utils.getAsInteger(RequestCycle.get().getRequest().getParameter("gly")))); //$NON-NLS-1$ } @Override protected CharSequence generateCallbackScript(final CharSequence partialCall) { return super.generateCallbackScript(partialCall + "+Servoy.Utils.getActionParams(event," + ((component instanceof SortableCellViewHeader) ? "true" : "false") + ")"); //$NON-NLS-1$ } @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); } }