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 }