Package org.apache.tapestry.services.impl

Source Code of org.apache.tapestry.services.impl.ComponentEventConnectionWorker

// Copyright May 20, 2006 The Apache Software Foundation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package org.apache.tapestry.services.impl;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.hivemind.ClassResolver;
import org.apache.hivemind.Resource;
import org.apache.hivemind.util.ClasspathResource;
import org.apache.tapestry.IComponent;
import org.apache.tapestry.IDirectEvent;
import org.apache.tapestry.IForm;
import org.apache.tapestry.IRequestCycle;
import org.apache.tapestry.PageRenderSupport;
import org.apache.tapestry.TapestryUtils;
import org.apache.tapestry.dojo.IWidget;
import org.apache.tapestry.engine.DirectEventServiceParameter;
import org.apache.tapestry.engine.IEngineService;
import org.apache.tapestry.engine.IScriptSource;
import org.apache.tapestry.html.Body;
import org.apache.tapestry.internal.event.ComponentEventProperty;
import org.apache.tapestry.internal.event.EventBoundListener;
import org.apache.tapestry.internal.event.IComponentEventInvoker;
import org.apache.tapestry.services.ComponentRenderWorker;
import org.apache.tapestry.spec.IEventListener;
import org.apache.tapestry.util.ScriptUtils;


/**
* Implementation that handles connecting events to listener
* method invocations.
*
* @author jkuhnert
*/
public class ComponentEventConnectionWorker implements ComponentRenderWorker
{
    /** Stored in {@link IRequestCycle} with associated forms. */
    public static final String FORM_NAME_LIST =  "org.apache.tapestry.services.impl.ComponentEventConnectionFormNames-";
   
    // holds mapped event listener info
    private IComponentEventInvoker _invoker;
   
    // generates links for scripts
    private IEngineService _eventEngine;
   
    // handles resolving and loading different component event
    // connection script types
    private IScriptSource _scriptSource;
   
    // script path references
    private String _componentScript;
    private String _widgetScript;
    private String _elementScript;
   
    // resolves classpath relative resources
    private ClassResolver _resolver;
   
    // wrappers around resolved script templates
    private ClasspathResource _componentResource;
    private ClasspathResource _widgetResource;
    private ClasspathResource _elementResource;
   
    // For event connections referencing forms that have not
    // been rendered yet.
    private Map _deferredFormConnections = new HashMap();
   
    /**
     * {@inheritDoc}
     */
    public void renderComponent(IRequestCycle cycle, IComponent component)
    {
        if (cycle.isRewinding()
                || TapestryUtils.getOptionalPageRenderSupport(cycle) == null)
            return;
       
        // Don't render fields being pre-rendered, otherwise we'll render twice
        IComponent field = (IComponent)cycle.getAttribute(TapestryUtils.FIELD_PRERENDER);
        if (field != null && field == component)
            return;
       
        linkComponentEvents(cycle, component);
       
        linkElementEvents(cycle, component);
       
        if (IForm.class.isInstance(component))
            mapFormNames(cycle, (IForm)component);
       
        if (isDeferredForm(component))
            linkDeferredForm(cycle, (IForm)component);
    }
   
    void linkComponentEvents(IRequestCycle cycle, IComponent component)
    {
        ComponentEventProperty[] props = getComponentEvents(component);
        if (props == null)
            return;
       
        for (int i=0; i < props.length; i++) {
           
            String clientId = component.getClientId();
           
            Map parms = new HashMap();
            parms.put("clientId", clientId);
            parms.put("component", component);
           
            Object[][] events = getEvents(props[i], clientId);
            Object[][] formEvents = filterFormEvents(props[i], parms, cycle);
           
            if (events.length < 1 && formEvents.length < 1)
                return;
           
            DirectEventServiceParameter dsp =
                new DirectEventServiceParameter((IDirectEvent)component, new Object[] {}, new String[] {}, false);
           
            parms.put("url", _eventEngine.getLink(false, dsp).getURL());
            parms.put("events", events);
            parms.put("formEvents", formEvents);

            PageRenderSupport prs = TapestryUtils.getPageRenderSupport(cycle, component);
            Resource resource = getScript(component);

            _scriptSource.getScript(resource).execute(component, cycle, prs, parms);
        }
    }
   
    ComponentEventProperty[] getComponentEvents(IComponent comp)
    {
        List listeners = _invoker.getEventListeners(comp.getId());
        if (listeners == null)
            return null;
       
        List ret = new ArrayList();
       
        for (int i=0; i < listeners.size(); i++) {
           
            IEventListener listener = (IEventListener)listeners.get(i);
           
            ret.add(listener.getComponentEvents(comp.getId()));
        }
       
        return (ComponentEventProperty[])ret.toArray(new ComponentEventProperty[ret.size()]);
    }
   
    void linkElementEvents(IRequestCycle cycle, IComponent component)
    {
        if (!component.getSpecification().hasElementEvents())
            return;
       
        DirectEventServiceParameter dsp =
            new DirectEventServiceParameter((IDirectEvent)component, new Object[] {}, new String[] {}, false);
       
        String url = _eventEngine.getLink(false, dsp).getURL();
       
        PageRenderSupport prs = TapestryUtils.getPageRenderSupport(cycle, component);
        Resource resource = getElementScript();
       
        Map elements = component.getSpecification().getElementEvents();
        Iterator keys = elements.keySet().iterator();
       
        // build our list of targets / events
        while (keys.hasNext()) {
           
            Map parms = new HashMap();
           
            String target = (String)keys.next();
           
            ComponentEventProperty prop = (ComponentEventProperty)elements.get(target);
           
            parms.put("component", component);
            parms.put("target", target);
            parms.put("url", url);
            parms.put("events", getEvents(prop, target));
            parms.put("formEvents", filterFormEvents(prop, parms, cycle));
           
            _scriptSource.getScript(resource).execute(component, cycle, prs, parms);
        }
    }
   
    /**
     * {@inheritDoc}
     */
    public void renderBody(IRequestCycle cycle, Body component)
    {
        if (cycle.isRewinding())
            return;
       
        renderComponent(cycle, component);
       
        // just in case
        _deferredFormConnections.clear();
    }
   
    void mapFormNames(IRequestCycle cycle, IForm form)
    {
        List names = (List)cycle.getAttribute(FORM_NAME_LIST + form.getId());
       
        if (names == null) {
            names = new ArrayList();
           
            cycle.setAttribute(FORM_NAME_LIST + form.getId(), names);
        }
       
        names.add(form.getName());
    }
   
    void linkDeferredForm(IRequestCycle cycle, IForm form)
    {
        List deferred = (List)_deferredFormConnections.remove(form.getId());
       
        for (int i=0; i < deferred.size(); i++) {
           
            Object[] val = (Object[])deferred.get(i);
           
            Map scriptParms = (Map)val[0];
           
            // don't want any events accidently connected again
            scriptParms.remove("events");
           
            IComponent component = (IComponent)scriptParms.get("component");
           
            // fire off element based events first
           
            linkElementEvents(cycle, component);
           
            ComponentEventProperty[] props = getComponentEvents(component);
            if (props == null)
                continue;
           
            for (int e=0; e < props.length; e++) {
               
                Object[][] formEvents = buildFormEvents(cycle, form.getId(),
                        props[e].getFormEvents(), (Boolean)val[1], (Boolean)val[2], val[3]);
               
                scriptParms.put("formEvents", formEvents);
               
                // execute script
               
                PageRenderSupport prs = TapestryUtils.getPageRenderSupport(cycle, component);
                Resource resource = getScript(component);
               
                _scriptSource.getScript(resource).execute(form, cycle, prs, scriptParms);
            }
        }
    }
   
    /**
     * Generates a two dimensional array containing the event name in the first
     * index and a unique hashcode for the event binding in the second.
     *
     * @param prop The component event properties object the events are managed in.
     * @return A two dimensional array containing all events, or empty array if none exist.
     */
    Object[][] getEvents(ComponentEventProperty prop, String clientId)
    {
        Set events = prop.getEvents();
        List ret = new ArrayList();
       
        Iterator it = events.iterator();
        while (it.hasNext()) {
           
            String event = (String)it.next();
           
            int hash = 0;
            List listeners = prop.getEventListeners(event);
            for (int i=0; i < listeners.size(); i++){
                hash += listeners.get(i).hashCode();
            }
           
            ret.add(new Object[]{ event, ScriptUtils.functionHash(event + hash + clientId) });
        }
       
        return (Object[][])ret.toArray(new Object[ret.size()][2]);
    }
   
    Object[][] buildFormEvents(IRequestCycle cycle, String formId,
            Set events, Boolean async, Boolean validate, Object uniqueHash)
    {
        List formNames = (List)cycle.getAttribute(FORM_NAME_LIST + formId);
        List retval = new ArrayList();
       
        Iterator it = events.iterator();
       
        while (it.hasNext()) {
           
            String event = (String)it.next();
           
            retval.add(new Object[]{event, formNames, async,
                    validate, ScriptUtils.functionHash(new String(uniqueHash + event)) });
        }
       
        return (Object[][])retval.toArray(new Object[retval.size()][5]);
    }
   
    Resource getScript(IComponent component)
    {
        if (IWidget.class.isInstance(component)) {
           
            if (_widgetResource == null)
                _widgetResource = new ClasspathResource(_resolver, _widgetScript);
           
            return _widgetResource;
        }
       
        if (_componentResource == null)
            _componentResource = new ClasspathResource(_resolver, _componentScript);
       
        return _componentResource;
    }
   
    Resource getElementScript()
    {
        if (_elementResource == null)
            _elementResource = new ClasspathResource(_resolver, _elementScript);
       
        return _elementResource;
    }
   
    boolean isDeferredForm(IComponent component)
    {
        if (IForm.class.isInstance(component)
                && _deferredFormConnections.get(((IForm)component).getId()) != null)
            return true;
       
        return false;
    }
   
    /**
     * For each form event attempts to find a rendered form name list that corresponds
     * to the actual client ids that the form can be connected to. If the form hasn't been
     * rendered yet the events will be filtered out and deferred for execution <i>after</i>
     * the form has rendererd.
     *
     * @param prop
     * @param scriptParms
     * @param cycle
     *
     * @return A set of events that can be connected "now".
     */
    Object[][] filterFormEvents(ComponentEventProperty prop, Map scriptParms, IRequestCycle cycle)
    {
        Set events = prop.getFormEvents();
       
        if (events.size() < 1)
            return new Object[0][0];
       
        List retval = new ArrayList();
       
        Iterator it = events.iterator();
        while (it.hasNext()) {
           
            String event = (String)it.next();
            Iterator lit = prop.getFormEventListeners(event).iterator();
           
            while (lit.hasNext()) {
                EventBoundListener listener = (EventBoundListener)lit.next();
               
                String formId = listener.getFormId();
                List formNames = (List)cycle.getAttribute(FORM_NAME_LIST + formId);
               
                // defer connection until form is rendered
                if (formNames == null) {
                   
                    deferFormConnection(formId, scriptParms,
                            listener.isAsync(),
                            listener.isValidateForm(),
                            ScriptUtils.functionHash(listener));
                    continue;
                }
               
                // form has been rendered so go ahead
                retval.add(new Object[] {
                        event, formNames,
                        Boolean.valueOf(listener.isAsync()),
                        Boolean.valueOf(listener.isValidateForm()),
                        ScriptUtils.functionHash(listener)
                });
            }
        }
       
        return (Object[][])retval.toArray(new Object[retval.size()][5]);
    }
   
    /**
     * Temporarily stores the data needed to perform script evaluations that
     * connect a component event to submitting a particular form that hasn't
     * been rendered yet. We can't reliably connect to a form until its name has
     * been set by a render, which could happen multiple times if it's in a list.
     *
     * <p>
     * The idea here is that when the form actually ~is~ rendered we will look for
     * any pending deferred operations and run them while also clearing out our
     * deferred list.
     * </p>
     *
     * @param formId The form to defer event connection for.
     * @param scriptParms The initial map of parameters for the connection @Script component.
     * @param async Whether or not the action taken should be asynchronous.
     * @param validate Whether or not the form should have client side validation run befor submitting.
     * @param uniqueHash Represents a hashcode() value that will help make client side function name
     *                  unique.
     */
    void deferFormConnection(String formId, Map scriptParms,
            boolean async, boolean validate, String uniqueHash)
    {
        List deferred = (List)_deferredFormConnections.get(formId);
       
        if (deferred == null) {
           
            deferred = new ArrayList();
           
            _deferredFormConnections.put(formId, deferred);
        }
       
        deferred.add(new Object[] {scriptParms, Boolean.valueOf(async),
                Boolean.valueOf(validate), uniqueHash});
    }
   
    // for testing
    Map getDefferedFormConnections()
    {
        return _deferredFormConnections;
    }
   
    /**
     * Sets the invoker to use/manage event connections.
     * @param invoker
     */
    public void setEventInvoker(IComponentEventInvoker invoker)
    {
        _invoker = invoker;
    }
   
    /**
     * Sets the engine service that will be used to construct callback
     * URL references to invoke the specified components event listener.
     *
     * @param eventEngine
     */
    public void setEventEngine(IEngineService eventEngine)
    {
        _eventEngine = eventEngine;
    }
   
    /**
     * The javascript that will be used to connect the component
     * to its configured events. (if any)
     * @param script
     */
    public void setComponentScript(String script)
    {
        _componentScript = script;
    }
   
    /**
     * The javascript that will be used to connect the widget component
     * to its configured events. (if any)
     * @param script
     */
    public void setWidgetScript(String script)
    {
        _widgetScript = script;
    }
   
    /**
     * The javascript that connects html elements to direct
     * listener methods.
     * @param script
     */
    public void setElementScript(String script)
    {
        _elementScript = script;
    }
   
    /**
     * The service that parses script files.
     * @param scriptSource
     */
    public void setScriptSource(IScriptSource scriptSource)
    {
        _scriptSource = scriptSource;
    }
   
    public void setClassResolver(ClassResolver resolver)
    {
        _resolver = resolver;
    }
}
TOP

Related Classes of org.apache.tapestry.services.impl.ComponentEventConnectionWorker

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.