001    // Copyright 2005 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.tapestry.util;
016    
017    import java.util.ArrayList;
018    import java.util.HashMap;
019    import java.util.List;
020    import java.util.Map;
021    
022    import org.apache.hivemind.Locatable;
023    import org.apache.hivemind.Location;
024    import org.apache.hivemind.Resource;
025    import org.apache.hivemind.util.Defense;
026    import org.apache.tapestry.IAsset;
027    import org.apache.tapestry.IMarkupWriter;
028    import org.apache.tapestry.IRequestCycle;
029    import org.apache.tapestry.PageRenderSupport;
030    import org.apache.tapestry.Tapestry;
031    import org.apache.tapestry.asset.AssetFactory;
032    
033    /**
034     * Implementation of {@link org.apache.tapestry.PageRenderSupport}. The
035     * {@link org.apache.tapestry.html.Body} component uses an instance of this class.
036     * 
037     * @author Howard M. Lewis Ship
038     * @since 4.0
039     */
040    public class PageRenderSupportImpl implements Locatable, PageRenderSupport
041    {
042        private final AssetFactory _assetFactory;
043    
044        private final Location _location;
045    
046        // Lines that belong inside the onLoad event handler for the <body> tag.
047        private StringBuffer _initializationScript;
048    
049        // Any other scripting desired
050    
051        private StringBuffer _bodyScript;
052    
053        // Contains text lines related to image initializations
054    
055        private StringBuffer _imageInitializations;
056    
057        /**
058         * Map of URLs to Strings (preloaded image references).
059         */
060    
061        private Map _imageMap;
062    
063        /**
064         * List of included scripts. Values are Strings.
065         * 
066         * @since 1.0.5
067         */
068    
069        private List _externalScripts;
070    
071        private final IdAllocator _idAllocator;
072    
073        private final String _preloadName;
074    
075        public PageRenderSupportImpl(AssetFactory assetFactory, String namespace, Location location)
076        {
077            Defense.notNull(assetFactory, "assetService");
078    
079            _assetFactory = assetFactory;
080            _location = location;
081            _idAllocator = new IdAllocator(namespace);
082    
083            _preloadName = (namespace.equals("") ? "tapestry" : namespace) + "_preload";
084        }
085    
086        /**
087         * Returns the location, which may be used in error messages. In practical terms, this is the
088         * location of the {@link org.apache.tapestry.html.Body} component.
089         */
090    
091        public Location getLocation()
092        {
093            return _location;
094        }
095    
096        public String getPreloadedImageReference(String URL)
097        {
098            if (_imageMap == null)
099                _imageMap = new HashMap();
100    
101            String reference = (String) _imageMap.get(URL);
102    
103            if (reference == null)
104            {
105                int count = _imageMap.size();
106                String varName = _preloadName + "[" + count + "]";
107                reference = varName + ".src";
108    
109                if (_imageInitializations == null)
110                    _imageInitializations = new StringBuffer();
111    
112                _imageInitializations.append("  ");
113                _imageInitializations.append(varName);
114                _imageInitializations.append(" = new Image();\n");
115                _imageInitializations.append("  ");
116                _imageInitializations.append(reference);
117                _imageInitializations.append(" = \"");
118                _imageInitializations.append(URL);
119                _imageInitializations.append("\";\n");
120    
121                _imageMap.put(URL, reference);
122            }
123    
124            return reference;
125        }
126    
127        public void addBodyScript(String script)
128        {
129            if (_bodyScript == null)
130                _bodyScript = new StringBuffer(script.length());
131    
132            _bodyScript.append(script);
133        }
134    
135        public void addInitializationScript(String script)
136        {
137            if (_initializationScript == null)
138                _initializationScript = new StringBuffer(script.length() + 1);
139    
140            _initializationScript.append(script);
141            _initializationScript.append('\n');
142        }
143    
144        public void addExternalScript(Resource scriptLocation)
145        {
146            if (_externalScripts == null)
147                _externalScripts = new ArrayList();
148    
149            if (_externalScripts.contains(scriptLocation))
150                return;
151    
152            // Record the Resource so we don't include it twice.
153    
154            _externalScripts.add(scriptLocation);
155    
156        }
157    
158        public String getUniqueString(String baseValue)
159        {
160            return _idAllocator.allocateId(baseValue);
161        }
162    
163        private void writeExternalScripts(IMarkupWriter writer, IRequestCycle cycle)
164        {
165            int count = Tapestry.size(_externalScripts);
166            for (int i = 0; i < count; i++)
167            {
168                Resource scriptLocation = (Resource) _externalScripts.get(i);
169    
170                IAsset asset = _assetFactory.createAsset(scriptLocation, null);
171    
172                String url = asset.buildURL();
173    
174                // Note: important to use begin(), not beginEmpty(), because browser don't
175                // interpret <script .../> properly.
176    
177                writer.begin("script");
178                writer.attribute("type", "text/javascript");
179                writer.attribute("src", url);
180                writer.end();
181                writer.println();
182            }
183        }
184    
185        /**
186         * Writes a single large JavaScript block containing:
187         * <ul>
188         * <li>Any image initializations (via {@link #getPreloadedImageReference(String)}).
189         * <li>Any included scripts (via {@link #addExternalScript(Resource)}).
190         * <li>Any contributions (via {@link #addBodyScript(String)}).
191         * </ul>
192         * 
193         * @see #writeInitializationScript(IMarkupWriter)
194         */
195    
196        public void writeBodyScript(IMarkupWriter writer, IRequestCycle cycle)
197        {
198            if (!Tapestry.isEmpty(_externalScripts))
199                writeExternalScripts(writer, cycle);
200    
201            if (!(any(_bodyScript) || any(_imageInitializations)))
202                return;
203    
204            writer.begin("script");
205            writer.attribute("type", "text/javascript");
206            writer.printRaw("<!--");
207    
208            if (any(_imageInitializations))
209            {
210                writer.printRaw("\n\nvar " + _preloadName + " = new Array();\n");
211                writer.printRaw("if (document.images)\n");
212                writer.printRaw("{\n");
213                writer.printRaw(_imageInitializations.toString());
214                writer.printRaw("}\n");
215            }
216    
217            if (any(_bodyScript))
218            {
219                writer.printRaw("\n\n");
220                writer.printRaw(_bodyScript.toString());
221            }
222    
223            writer.printRaw("\n\n// -->");
224            writer.end();
225        }
226    
227        /**
228         * Writes any image initializations; this should be invoked at the end of the render, after all
229         * the related HTML will have already been streamed to the client and parsed by the web browser.
230         * Earlier versions of Tapestry uses a <code>window.onload</code> event handler.
231         */
232    
233        public void writeInitializationScript(IMarkupWriter writer)
234        {
235            if (!any(_initializationScript))
236                return;
237    
238            writer.begin("script");
239            writer.attribute("language", "JavaScript");
240            writer.attribute("type", "text/javascript");
241            writer.printRaw("<!--\n");
242    
243            writer.printRaw(_initializationScript.toString());
244    
245            writer.printRaw("\n// -->");
246            writer.end();
247        }
248    
249        private boolean any(StringBuffer buffer)
250        {
251            return buffer != null && buffer.length() > 0;
252        }
253    }