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
015package org.apache.tapestry.util;
016
017import java.util.ArrayList;
018import java.util.HashMap;
019import java.util.List;
020import java.util.Map;
021
022import org.apache.hivemind.Locatable;
023import org.apache.hivemind.Location;
024import org.apache.hivemind.Resource;
025import org.apache.hivemind.util.Defense;
026import org.apache.tapestry.IAsset;
027import org.apache.tapestry.IMarkupWriter;
028import org.apache.tapestry.IRequestCycle;
029import org.apache.tapestry.PageRenderSupport;
030import org.apache.tapestry.Tapestry;
031import 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 */
040public 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}&nbsp;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}