com.servoy.j2db.server.headlessclient.PageContributor.java Source code

Java tutorial

Introduction

Here is the source code for com.servoy.j2db.server.headlessclient.PageContributor.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;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.SortedSet;

import org.apache.wicket.AttributeModifier;
import org.apache.wicket.Component;
import org.apache.wicket.MarkupContainer;
import org.apache.wicket.Page;
import org.apache.wicket.RequestCycle;
import org.apache.wicket.ResourceReference;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.behavior.AbstractAjaxBehavior;
import org.apache.wicket.behavior.IBehavior;
import org.apache.wicket.markup.MarkupStream;
import org.apache.wicket.markup.html.IHeaderResponse;
import org.apache.wicket.markup.html.WebMarkupContainer;
import org.apache.wicket.markup.html.internal.HtmlHeaderContainer;
import org.apache.wicket.markup.html.resources.JavascriptResourceReference;
import org.apache.wicket.model.Model;

import com.servoy.j2db.IApplication;
import com.servoy.j2db.IBasicFormManager;
import com.servoy.j2db.MediaURLStreamHandler;
import com.servoy.j2db.server.headlessclient.dataui.AbstractServoyDefaultAjaxBehavior;
import com.servoy.j2db.server.headlessclient.dataui.ChangesRecorder;
import com.servoy.j2db.server.headlessclient.dataui.ISupportWebTabSeq;
import com.servoy.j2db.server.headlessclient.dataui.IWebFormContainer;
import com.servoy.j2db.server.headlessclient.dataui.StripHTMLTagsConverter;
import com.servoy.j2db.server.headlessclient.dataui.WebDataHtmlArea;
import com.servoy.j2db.server.headlessclient.dataui.WebEventExecutor;
import com.servoy.j2db.server.headlessclient.dataui.WebSplitPane;
import com.servoy.j2db.server.headlessclient.eventthread.IEventDispatcher;
import com.servoy.j2db.server.headlessclient.eventthread.WicketEvent;
import com.servoy.j2db.ui.IComponent;
import com.servoy.j2db.ui.IProviderStylePropertyChanges;
import com.servoy.j2db.ui.IStylePropertyChanges;
import com.servoy.j2db.util.Debug;
import com.servoy.j2db.util.OrientationApplier;
import com.servoy.j2db.util.Pair;
import com.servoy.j2db.util.Utils;

/**
 * Implementation of {@link IPageContributorInternal} that is a wicket component that is added to the page for adding all kinds of behaviors and scripts to the main page.
 *
 * @author jcompagner
 */
public class PageContributor extends WebMarkupContainer implements IPageContributorInternal {
    private static final long serialVersionUID = 1L;
    public static final ResourceReference anchorlayout = new JavascriptResourceReference(PageContributor.class,
            "anchorlayout.js"); //$NON-NLS-1$

    private IRepeatingView repeatingView;

    private final Map<String, IBehavior> behaviors = new HashMap<String, IBehavior>();

    private final EventCallbackBehavior eventCallbackBehavior;

    private StringBuffer dynamicJS;
    protected ChangesRecorder jsChangeRecorder = new ChangesRecorder(null, null);

    private long lastTableUpdate = -1;
    private final List<Component> tablesToRender = new ArrayList<Component>();
    private SortedSet<FormAnchorInfo> formAnchorInfos;
    private final Map<String, Integer> tabIndexChanges = new HashMap<String, Integer>();
    private boolean anchorInfoChanged = false;
    private StringBuffer componentsThatNeedAnchorRelayout;
    private boolean isResizing = false;

    private final IApplication application;

    private final ArrayList<WebSplitPane> splitPanesToUpdateDivider = new ArrayList<WebSplitPane>();

    public PageContributor(final IApplication application, String id) {
        super(id, new Model());
        this.application = application;
        setOutputMarkupPlaceholderTag(true);

        add(new AbstractServoyDefaultAjaxBehavior() {
            private static final long serialVersionUID = 1L;

            @Override
            protected void respond(AjaxRequestTarget target) {
                String update = getRequestCycle().getRequest().getParameter("update"); //$NON-NLS-1$
                // get the update parameter and check if that is still the same, else wait for the next.
                if (Long.parseLong(update) == lastTableUpdate) {
                    for (int i = 0; i < tablesToRender.size(); i++) {
                        Component comp = tablesToRender.get(i);
                        if (comp.isVisibleInHierarchy()) {
                            target.addComponent(comp);
                        }
                    }
                    tablesToRender.clear();
                    WebEventExecutor.generateResponse(target, findPage());
                } else {
                    Debug.log("IGNORED TABLE REQUEST");
                }
            }

            @Override
            public void renderHead(IHeaderResponse response) {
                super.renderHead(response);
                response.renderOnDomReadyJavascript(getCallbackScript().toString());
            }

            @Override
            public CharSequence getCallbackUrl(boolean onlyTargetActivePage) {
                CharSequence url = super.getCallbackUrl(true);
                url = url + "&update=" + lastTableUpdate; //$NON-NLS-1$
                return url;
            }

            @Override
            public boolean isEnabled(Component component) {
                return tablesToRender.size() > 0 && super.isEnabled(component);
            }
        });
        add(eventCallbackBehavior = new EventCallbackBehavior());
        add(new AbstractServoyDefaultAjaxBehavior() {
            @Override
            public void renderHead(IHeaderResponse response) {
                if (isFormWidthZero()) {
                    response.renderOnLoadJavascript("Servoy.Resize.onWindowResize();"); //$NON-NLS-1$
                }
            }

            @Override
            protected void respond(AjaxRequestTarget target) {
                // not used
            }

            private boolean isFormWidthZero() {
                final boolean[] returnValue = { false };
                Page page = findPage();
                if (page != null) {
                    page.visitChildren(WebForm.class, new Component.IVisitor<WebForm>() {
                        public Object component(WebForm form) {
                            if (form.getFormWidth() == 0 && form.isVisibleInHierarchy()) {
                                IWebFormContainer formContainer = form.findParent(IWebFormContainer.class);
                                if (!(formContainer instanceof WebSplitPane)) {
                                    returnValue[0] = true;
                                    return IVisitor.STOP_TRAVERSAL;
                                }
                            }
                            return IVisitor.CONTINUE_TRAVERSAL;
                        }
                    });
                }
                return returnValue[0];
            }
        });
    }

    @Override
    public void renderHead(HtmlHeaderContainer container) {
        super.renderHead(container);

        IHeaderResponse response = container.getHeaderResponse();

        String djs = getDynamicJavaScript();
        if (djs != null) {
            response.renderOnLoadJavascript(djs);
        }
        addReferences(response);

        Page page = findPage();
        if (page instanceof MainPage) {
            Component focus = ((MainPage) page).getAndResetToFocusComponent();
            if (focus != null) {
                if (focus instanceof WebDataHtmlArea) {
                    response.renderOnLoadJavascript("tinyMCE.activeEditor.focus()");
                } else {
                    response.renderOnLoadJavascript(
                            "setTimeout(\"requestFocus('" + focus.getMarkupId() + "');\",0);"); //$NON-NLS-1$ //$NON-NLS-2$
                }
            }
        }

        if (formAnchorInfos != null && formAnchorInfos.size() != 0 && WebClientSession.get() != null
                && WebClientSession.get().getWebClient() != null && Utils.getAsBoolean(
                        WebClientSession.get().getWebClient().getRuntimeProperties().get("enableAnchors"))) //$NON-NLS-1$
        {
            if (anchorInfoChanged) {
                response.renderJavascriptReference(anchorlayout);
                response.renderOnLoadJavascript("setTimeout(\"layoutEntirePage();\", 10);"); // setTimeout is important here, to let the browser apply CSS styles during Ajax calls //$NON-NLS-1$
                String sb = FormAnchorInfo.generateAnchoringFunctions(formAnchorInfos, getOrientation());
                response.renderJavascript(sb, null);
                anchorInfoChanged = false;
            } else if (componentsThatNeedAnchorRelayout != null && componentsThatNeedAnchorRelayout.length() > 0) {
                response.renderJavascriptReference(anchorlayout);
                response.renderOnLoadJavascript("setTimeout(\"layoutSpecificElements();\", 10);");
                response.renderJavascript("executeLayoutSpecificElements = function()\n{\n"
                        + componentsThatNeedAnchorRelayout.append("\n}"), null);
            }
        }
        if (componentsThatNeedAnchorRelayout != null)
            componentsThatNeedAnchorRelayout.setLength(0);

        if (tabIndexChanges.size() > 0) {
            StringBuffer stringBuffer = new StringBuffer();
            stringBuffer.append("Servoy.TabCycleHandling.setNewTabIndexes(["); //$NON-NLS-1$
            for (String componentID : tabIndexChanges.keySet()) {
                stringBuffer.append("['");//$NON-NLS-1$
                stringBuffer.append(componentID);
                stringBuffer.append("',");//$NON-NLS-1$
                stringBuffer.append(tabIndexChanges.get(componentID));
                stringBuffer.append("]");//$NON-NLS-1$
                stringBuffer.append(",");//$NON-NLS-1$
            }
            stringBuffer.deleteCharAt(stringBuffer.length() - 1);
            stringBuffer.append("]);"); //$NON-NLS-1$
            response.renderOnLoadJavascript(stringBuffer.toString());
            tabIndexChanges.clear();
        }

        if (splitPanesToUpdateDivider.size() > 0) {
            for (WebSplitPane splitPane : splitPanesToUpdateDivider) {
                if (splitPane.findParent(Page.class) != null
                        && !splitPane.getScriptObject().getChangesRecorder().isChanged()
                        && !splitPane.isParentContainerChanged()) {
                    response.renderOnLoadJavascript((new StringBuilder("(function() {") //$NON-NLS-1$
                            .append(splitPane.getDividerLocationJSSetter(true).append("}).call();"))).toString()); //$NON-NLS-1$
                }
            }
            splitPanesToUpdateDivider.clear();
        }

        // Enable this for Firebug debugging under IE/Safari/etc.
        //response.renderJavascriptReference("http://getfirebug.com/releases/lite/1.2/firebug-lite-compressed.js"); //$NON-NLS-1$
    }

    private void addReferences(IHeaderResponse response) {
        ResourceReferences grr = getGlobalResourceReferences();
        if (grr != null) {
            List<Pair<Byte, Object>> resources = grr.getAllResources();
            for (Pair<Byte, Object> resource : resources) {
                String url = null;
                if (resource.getRight() instanceof ResourceReference) {
                    url = RequestCycle.get().urlFor((ResourceReference) resource.getRight()).toString();
                } else if (resource.getRight() instanceof String) {
                    url = (String) resource.getRight();
                    if (url.contains(MediaURLStreamHandler.MEDIA_URL_DEF)) {
                        url = StripHTMLTagsConverter.convertMediaReferences(url,
                                application.getSolution().getName(), new ResourceReference("media"), "", true) //$NON-NLS-1$//$NON-NLS-2$
                                .toString();
                    }
                }
                if (url != null) {
                    if (ResourceReferences.JS.equals(resource.getLeft())) {
                        response.renderJavascriptReference(url);
                    } else if (ResourceReferences.CSS.equals(resource.getLeft())) {
                        response.renderCSSReference(url);
                    }
                }
            }
        }
    }

    private String getOrientation() {
        String orientation = OrientationApplier.getHTMLContainerOrientation(application.getLocale(),
                application.getSolution().getTextOrientation());
        if (orientation.equals(AttributeModifier.VALUELESS_ATTRIBUTE_REMOVE))
            orientation = "ltr"; //$NON-NLS-1$
        return orientation;
    }

    public void removeFormAnchorInfo(FormAnchorInfo fai) {
        if (formAnchorInfos != null)
            formAnchorInfos.remove(fai);
    }

    public void setFormAnchorInfos(SortedSet<FormAnchorInfo> infos) {
        anchorInfoChanged = !Utils.equalObjects(formAnchorInfos, infos);
        if (infos == null) {
            formAnchorInfos = null;
        } else {
            if (anchorInfoChanged) {
                if (!isResizing)
                    getStylePropertyChanges().setChanged();
                formAnchorInfos = infos;
            }
        }
    }

    public void markComponentForAnchorLayoutIfNeeded(Component component) {
        if (formAnchorInfos != null && formAnchorInfos.size() != 0) {
            // see if this component is actually affected by layout or not and generate anchoring properties for it if it is
            String s = FormAnchorInfo.generateAnchoringParams(formAnchorInfos, component);
            if (s != null) {
                if (componentsThatNeedAnchorRelayout == null)
                    componentsThatNeedAnchorRelayout = new StringBuffer();
                componentsThatNeedAnchorRelayout.append("layoutOneElement(").append(s).append(");\n");
                getStylePropertyChanges().setChanged();
            }
        }
    }

    public void setResizing(boolean b) {
        isResizing = b;
    }

    public void addTableToRender(Component comp) {
        getStylePropertyChanges().setChanged();
        if (!tablesToRender.contains(comp))
            tablesToRender.add(comp);
        lastTableUpdate = System.currentTimeMillis();
    }

    public void addTabIndexChange(String componentID, int tabIndex) {
        if (tabIndex != ISupportWebTabSeq.DEFAULT) {
            tabIndexChanges.put(componentID, Integer.valueOf(tabIndex));
        } else {
            tabIndexChanges.remove(componentID);
        }
    }

    public void addBehavior(String name, IBehavior behavior) {
        if (behaviors.put(name, behavior) == null) {
            getStylePropertyChanges().setChanged();
            add(behavior);
        }
    }

    public void removeBehavior(String name) {
        IBehavior behavior = null;
        if ((behavior = behaviors.remove(name)) != null) {
            getStylePropertyChanges().setChanged();
            if (RequestCycle.get() != null) {
                remove(behavior);
            }
        }
    }

    public void addDynamicJavaScript(String js) {
        if (dynamicJS == null)
            dynamicJS = new StringBuffer();
        dynamicJS.append(js);
        getStylePropertyChanges().setChanged();
    }

    private String getDynamicJavaScript() {
        String retval = null;
        if (dynamicJS != null)
            retval = dynamicJS.toString();
        dynamicJS = null;
        return retval;
    }

    protected ResourceReferences getGlobalResourceReferences() {
        ResourceReferences grr = null;
        IBasicFormManager fm = application.getFormManager();
        if (fm instanceof WebFormManager) {
            grr = ((WebFormManager) fm).getGlobalResourceReferences();
        }
        return grr;
    }

    @Override
    public void addGlobalCSSResourceReference(ResourceReference resource) {
        ResourceReferences grr = getGlobalResourceReferences();
        if (grr != null) {
            grr.addGlobalCSSResourceReference(resource);
            getStylePropertyChanges().setChanged();
        }
    }

    @Override
    public void addGlobalJSResourceReference(String url) {
        ResourceReferences grr = getGlobalResourceReferences();
        if (grr != null) {
            grr.addGlobalJSResourceReference(url);
            getStylePropertyChanges().setChanged();
        }
    }

    @Override
    public void addGlobalJSResourceReference(ResourceReference resource) {
        ResourceReferences grr = getGlobalResourceReferences();
        if (grr != null) {
            grr.addGlobalJSResourceReference(resource);
            getStylePropertyChanges().setChanged();
        }
    }

    @Override
    public void addGlobalCSSResourceReference(String url) {
        ResourceReferences grr = getGlobalResourceReferences();
        if (grr != null) {
            grr.addGlobalCSSResourceReference(url);
            getStylePropertyChanges().setChanged();
        }
    }

    @Override
    public void removeGlobalResourceReference(ResourceReference resource) {
        ResourceReferences grr = getGlobalResourceReferences();
        if (grr != null) {
            grr.removeGlobalResourceReference(resource);
            getStylePropertyChanges().setChanged();
        }
    }

    @Override
    public void removeGlobalResourceReference(String url) {
        ResourceReferences grr = getGlobalResourceReferences();
        if (grr != null) {
            grr.removeGlobalResourceReference(url);
            getStylePropertyChanges().setChanged();
        }
    }

    @Override
    public List<Object> getGlobalCSSResources() {
        ResourceReferences grr = getGlobalResourceReferences();
        if (grr != null) {
            return grr.getGlobalCSSResources();
        }
        return Collections.emptyList();
    }

    @Override
    public List<Object> getGlobalJSResources() {
        ResourceReferences grr = getGlobalResourceReferences();
        if (grr != null) {
            return grr.getGlobalJSResources();
        }
        return Collections.emptyList();
    }

    public IBehavior getBehavior(String name) {
        return behaviors.get(name);
    }

    public IStylePropertyChanges getStylePropertyChanges() {
        return jsChangeRecorder;
    }

    @Override
    protected void onRender(MarkupStream markupStream) {
        super.onRender(markupStream);
        getStylePropertyChanges().setRendered();
    }

    public static List<Component> getVisibleChildren(Component component, final boolean onlyChanged) {
        final List<Component> visibleChildren = new ArrayList<Component>();
        if (component.isVisibleInHierarchy() && (!onlyChanged || (component instanceof IProviderStylePropertyChanges
                && ((IProviderStylePropertyChanges) component).getStylePropertyChanges().isChanged()))) {
            visibleChildren.add(component);
        }
        if (component instanceof MarkupContainer) {
            ((MarkupContainer) component).visitChildren(IProviderStylePropertyChanges.class,
                    new IVisitor<Component>() {
                        public Object component(Component stylePropertyChange) {
                            if (!stylePropertyChange.isVisibleInHierarchy()) {
                                return IVisitor.CONTINUE_TRAVERSAL_BUT_DONT_GO_DEEPER;
                            }
                            if (onlyChanged && !((IProviderStylePropertyChanges) stylePropertyChange)
                                    .getStylePropertyChanges().isChanged()) {
                                return IVisitor.CONTINUE_TRAVERSAL;
                            }
                            visibleChildren.add(stylePropertyChange);
                            // add all children from here
                            if (stylePropertyChange instanceof MarkupContainer) {
                                ((MarkupContainer) stylePropertyChange).visitChildren(IComponent.class,
                                        new IVisitor<Component>() {
                                            public Object component(Component fieldComponent) {
                                                if (!fieldComponent.isVisibleInHierarchy()) {
                                                    return IVisitor.CONTINUE_TRAVERSAL_BUT_DONT_GO_DEEPER;
                                                }
                                                visibleChildren.add(fieldComponent);
                                                return IVisitor.CONTINUE_TRAVERSAL;
                                            }
                                        });
                            }
                            return IVisitor.CONTINUE_TRAVERSAL_BUT_DONT_GO_DEEPER;
                        }
                    });
        }
        return visibleChildren;
    }

    private class EventCallbackBehavior extends AbstractServoyDefaultAjaxBehavior {
        private static final long serialVersionUID = 1L;

        @Override
        protected void respond(final AjaxRequestTarget target) {
            if (Debug.tracing())
                Debug.trace("Event response callback " + getRequestCycle().getRequest().getURL()); //$NON-NLS-1$
            final String markupId = getRequestCycle().getRequest().getParameter("id"); //$NON-NLS-1$
            final String event = getRequestCycle().getRequest().getParameter("event"); //$NON-NLS-1$
            if (markupId != null && event != null) {
                final MainPage callback = findParent(MainPage.class);
                if (callback == null) {
                    Debug.trace("Callback handler not found, event=" + event + " id=" + markupId); //$NON-NLS-1$ //$NON-NLS-2$
                } else {
                    IEventDispatcher<WicketEvent> eventDispatcher = ((WebClient) application).getEventDispatcher();
                    if (eventDispatcher != null) {
                        eventDispatcher.addEvent(new WicketEvent((WebClient) application, new Runnable() {
                            public void run() {
                                callback.respond(target, event, markupId);
                            }
                        }));
                        WebEventExecutor.generateResponse(target, getPage());
                    } else {
                        callback.respond(target, event, markupId);
                    }
                }
            } else {
                Debug.error("Missing id or event parameter in callback " + getRequestCycle().getRequest().getURL()); //$NON-NLS-1$
            }
        }

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

    /**
     * @param container
     */
    public void addRepeatingView(IRepeatingView rp) {
        this.repeatingView = rp;
    }

    /*
     * (non-Javadoc)
     *
     * @see com.servoy.j2db.server.headlessclient.IPageContributor#getRepeatingView()
     */
    public IRepeatingView getRepeatingView() {
        return repeatingView;
    }

    /*
     * (non-Javadoc)
     *
     * @see com.servoy.j2db.server.headlessclient.IPageContributorInternal#getEventCallback()
     */
    public AbstractAjaxBehavior getEventCallback() {
        return eventCallbackBehavior;
    }

    public void addSplitPaneToUpdatedDivider(WebSplitPane splitPane) {
        if (splitPanesToUpdateDivider.indexOf(splitPane) == -1)
            splitPanesToUpdateDivider.add(splitPane);
        getStylePropertyChanges().setChanged();
    }
}