001    // Copyright 2009 The Apache Software Foundation
002    //
003    // Licensed under the Apache License, Version 2.0 (the "License");
004    // you may not use this file except in compliance with the License.
005    // You may obtain a copy of the License at
006    //
007    //     http://www.apache.org/licenses/LICENSE-2.0
008    //
009    // Unless required by applicable law or agreed to in writing, software
010    // distributed under the License is distributed on an "AS IS" BASIS,
011    // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012    // See the License for the specific language governing permissions and
013    // limitations under the License.
014    
015    package org.apache.tapestry5.corelib.components;
016    
017    import org.apache.tapestry5.*;
018    import org.apache.tapestry5.annotations.*;
019    import org.apache.tapestry5.dom.Element;
020    import org.apache.tapestry5.internal.services.ComponentResultProcessorWrapper;
021    import org.apache.tapestry5.ioc.annotations.Inject;
022    import org.apache.tapestry5.ioc.internal.util.InternalUtils;
023    import org.apache.tapestry5.json.JSONObject;
024    import org.apache.tapestry5.services.ComponentEventResultProcessor;
025    
026    import java.io.IOException;
027    
028    /**
029     * A component used to implement the <a href="http://en.wikipedia.org/wiki/Progressive_enhancement">progressive
030     * enhancement</a> web design strategy; the component renders itself with a simplified initial content (i.e., "loading
031     * ...") and an Ajax request then supplies the component's true body. This results in much faster page loads. You can
032     * even nest these!
033     * <p/>
034     * The component simply does not render its body on initial render. On the subsequent action event request, it fires a
035     * {@link org.apache.tapestry5.EventConstants#PROGRESSIVE_DISPLAY} event to inform the container about the (optional)
036     * event context. The event handler method may return a renderable object; if not then the component's body is rendered
037     * as the partial markup response.
038     *
039     * @since 5.1.0.1
040     */
041    @SupportsInformalParameters
042    @IncludeJavaScriptLibrary("ProgressiveDisplay.js")
043    @Events(EventConstants.PROGRESSIVE_DISPLAY)
044    public class ProgressiveDisplay
045    {
046        /**
047         * The initial content to display until the real content arrives. Defaults to "Loading ..." and an Ajax activity
048         * icon.
049         */
050        @Parameter(defaultPrefix = BindingConstants.LITERAL, value = "block:defaultInitial")
051        private Block initial;
052    
053        /**
054         * If provided, this is the event context, which will be provided via the {@link
055         * org.apache.tapestry5.EventConstants#PROGRESSIVE_DISPLAY event}.
056         */
057        @Parameter
058        private Object[] context;
059    
060        @Inject
061        private ComponentResources resources;
062    
063        @Environmental
064        private RenderSupport renderSupport;
065    
066        @Environmental
067        private ComponentEventResultProcessor resultProcessor;
068    
069        /**
070         * Name of a function on the client-side Tapestry.ElementEffect object that is invoked after the elements's body
071         * content has been updated. If not specified, then the basic "highlight" method is used, which performs a classic
072         * "yellow fade" to indicate to the user that and update has taken place.
073         */
074        @Parameter(defaultPrefix = BindingConstants.LITERAL)
075        private String update;
076    
077        Block beginRender(MarkupWriter writer)
078        {
079            String clientId = renderSupport.allocateClientId(resources);
080            String elementName = resources.getElementName("div");
081    
082            Element e = writer.element(elementName, "id", clientId);
083    
084            resources.renderInformalParameters(writer);
085    
086            e.addClassName("t-zone");
087    
088            Link link = resources.createEventLink(EventConstants.ACTION, context);
089    
090            JSONObject spec = new JSONObject();
091    
092            if (InternalUtils.isNonBlank(update))
093                spec.put("update", update.toLowerCase());
094    
095            spec.put("element", clientId);
096            spec.put("url", link.toAbsoluteURI());
097    
098            renderSupport.addInit("progressiveDisplay", spec);
099    
100            return initial;
101        }
102    
103        Object onAction(EventContext context) throws IOException
104        {
105            ComponentResultProcessorWrapper wrapper = new ComponentResultProcessorWrapper(resultProcessor);
106    
107            resources.triggerContextEvent(EventConstants.PROGRESSIVE_DISPLAY, context, wrapper);
108    
109            if (wrapper.isAborted())
110                return null;
111    
112            return resources.getBody();
113        }
114    
115        boolean beforeRenderBody()
116        {
117            return false;
118        }
119    
120        void afterRender(MarkupWriter writer)
121        {
122            writer.end();
123        }
124    }