001    // Copyright 2009, 2010, 2011 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 java.io.IOException;
018    
019    import org.apache.tapestry5.BindingConstants;
020    import org.apache.tapestry5.Block;
021    import org.apache.tapestry5.ComponentResources;
022    import org.apache.tapestry5.EventConstants;
023    import org.apache.tapestry5.EventContext;
024    import org.apache.tapestry5.Link;
025    import org.apache.tapestry5.MarkupWriter;
026    import org.apache.tapestry5.TrackableComponentEventCallback;
027    import org.apache.tapestry5.ajax.MultiZoneUpdate;
028    import org.apache.tapestry5.annotations.Environmental;
029    import org.apache.tapestry5.annotations.Events;
030    import org.apache.tapestry5.annotations.Import;
031    import org.apache.tapestry5.annotations.Parameter;
032    import org.apache.tapestry5.annotations.SupportsInformalParameters;
033    import org.apache.tapestry5.dom.Element;
034    import org.apache.tapestry5.ioc.annotations.Inject;
035    import org.apache.tapestry5.ioc.internal.util.InternalUtils;
036    import org.apache.tapestry5.json.JSONObject;
037    import org.apache.tapestry5.services.javascript.JavaScriptSupport;
038    
039    /**
040     * A component used to implement the <a
041     * href="http://en.wikipedia.org/wiki/Progressive_enhancement">progressive
042     * enhancement</a> web design strategy; the component renders itself with a
043     * simplified initial content (i.e., "loading
044     * ...") and an Ajax request then supplies the component's true body. This
045     * results in much faster page loads. You can
046     * even nest these!
047     * <p/>
048     * The component simply does not render its body on initial render. On the subsequent action event request, it fires a
049     * {@link org.apache.tapestry5.EventConstants#PROGRESSIVE_DISPLAY} event to inform the container about the (optional)
050     * event context. The event handler method may return a renderable object; if not then the component's body is rendered
051     * as the partial markup response.
052     * 
053     * @since 5.1.0.1
054     * @tapestrydoc
055     */
056    @SupportsInformalParameters
057    @Import(library = "ProgressiveDisplay.js")
058    @Events(EventConstants.PROGRESSIVE_DISPLAY)
059    @SuppressWarnings("all")
060    public class ProgressiveDisplay
061    {
062        /**
063         * The initial content to display until the real content arrives. Defaults
064         * to "Loading ..." and an Ajax activity
065         * icon.
066         */
067        @Parameter(defaultPrefix = BindingConstants.LITERAL, value = "block:defaultInitial")
068        private Block initial;
069    
070        /**
071         * If provided, this is the event context, which will be provided via the
072         * {@link org.apache.tapestry5.EventConstants#PROGRESSIVE_DISPLAY event}.
073         */
074        @Parameter
075        private Object[] context;
076    
077        @Inject
078        private ComponentResources resources;
079    
080        @Environmental
081        private JavaScriptSupport jsSupport;
082    
083        @Environmental
084        private TrackableComponentEventCallback eventCallback;
085    
086        /**
087         * Name of a function on the client-side Tapestry.ElementEffect object that
088         * is invoked after the elements's body
089         * content has been updated. If not specified, then the basic "highlight"
090         * method is used, which performs a classic
091         * "yellow fade" to indicate to the user that and update has taken place.
092         */
093        @Parameter(defaultPrefix = BindingConstants.LITERAL)
094        private String update;
095    
096        Block beginRender(MarkupWriter writer)
097        {
098            String clientId = jsSupport.allocateClientId(resources);
099            String elementName = resources.getElementName("div");
100    
101            Element e = writer.element(elementName, "id", clientId);
102    
103            resources.renderInformalParameters(writer);
104    
105            e.addClassName("t-zone");
106    
107            Link link = resources.createEventLink(EventConstants.ACTION, context);
108    
109            JSONObject spec = new JSONObject();
110    
111            if (InternalUtils.isNonBlank(update))
112                spec.put("update", update.toLowerCase());
113    
114            spec.put("element", clientId);
115            spec.put("url", link.toURI());
116    
117            jsSupport.addInitializerCall("progressiveDisplay", spec);
118    
119            return initial;
120        }
121    
122        Object onAction(EventContext context) throws IOException
123        {
124            resources.triggerContextEvent(EventConstants.PROGRESSIVE_DISPLAY, context, eventCallback);
125    
126            if (eventCallback.isAborted())
127                return null;
128    
129            return getBody();
130        }
131    
132        boolean beforeRenderBody()
133        {
134            return false;
135        }
136    
137        void afterRender(MarkupWriter writer)
138        {
139            writer.end();
140        }
141    
142        /**
143         * Returns the body of the ProgressiveDisplay, which is sometimes (in the
144         * context of a {@linkplain MultiZoneUpdate multi-zone update})
145         * the content to be included.
146         * 
147         * @since 5.2.0
148         * @return body of component
149         */
150        public Block getBody()
151        {
152            return resources.getBody();
153        }
154    }