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 }