001 // Copyright 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.internal.services.ajax; 016 017 import java.util.List; 018 import java.util.Map; 019 import java.util.Set; 020 021 import org.apache.tapestry5.Asset; 022 import org.apache.tapestry5.ComponentResources; 023 import org.apache.tapestry5.FieldFocusPriority; 024 import org.apache.tapestry5.func.F; 025 import org.apache.tapestry5.func.Worker; 026 import org.apache.tapestry5.internal.InternalConstants; 027 import org.apache.tapestry5.internal.services.DocumentLinker; 028 import org.apache.tapestry5.internal.services.javascript.JavaScriptStackPathConstructor; 029 import org.apache.tapestry5.ioc.internal.util.CollectionFactory; 030 import org.apache.tapestry5.ioc.internal.util.InternalUtils; 031 import org.apache.tapestry5.ioc.util.IdAllocator; 032 import org.apache.tapestry5.json.JSONArray; 033 import org.apache.tapestry5.json.JSONObject; 034 import org.apache.tapestry5.services.javascript.InitializationPriority; 035 import org.apache.tapestry5.services.javascript.JavaScriptStack; 036 import org.apache.tapestry5.services.javascript.JavaScriptStackSource; 037 import org.apache.tapestry5.services.javascript.JavaScriptSupport; 038 import org.apache.tapestry5.services.javascript.StylesheetLink; 039 040 public class JavaScriptSupportImpl implements JavaScriptSupport 041 { 042 private final IdAllocator idAllocator; 043 044 private final DocumentLinker linker; 045 046 // Using a Map as a case-insensitive set of stack names. 047 048 private final Map<String, Boolean> addedStacks = CollectionFactory.newCaseInsensitiveMap(); 049 050 private final List<String> stackLibraries = CollectionFactory.newList(); 051 052 private final List<String> otherLibraries = CollectionFactory.newList(); 053 054 private final Set<String> importedStylesheetURLs = CollectionFactory.newSet(); 055 056 private final List<StylesheetLink> stylesheetLinks = CollectionFactory.newList(); 057 058 private final Map<InitializationPriority, JSONObject> inits = CollectionFactory.newMap(); 059 060 private final JavaScriptStackSource javascriptStackSource; 061 062 private final JavaScriptStackPathConstructor stackPathConstructor; 063 064 private final boolean partialMode; 065 066 private FieldFocusPriority focusPriority; 067 068 private String focusFieldId; 069 070 public JavaScriptSupportImpl(DocumentLinker linker, JavaScriptStackSource javascriptStackSource, 071 JavaScriptStackPathConstructor stackPathConstructor) 072 { 073 this(linker, javascriptStackSource, stackPathConstructor, new IdAllocator(), false); 074 } 075 076 /** 077 * @param linker 078 * responsible for assembling all the information gathered by JavaScriptSupport and 079 * attaching it to the Document (for a full page render) or to the JSON response (in a partial render) 080 * @param javascriptStackSource 081 * source of information about {@link JavaScriptStack}s, used when handling the import 082 * of libraries and stacks (often, to handle transitive dependencies) 083 * @param stackPathConstructor 084 * encapsulates the knowledge of how to represent a stack (which may be converted 085 * from a series of JavaScript libraries into a single virtual JavaScript library) 086 * @param idAllocator 087 * used when allocating unique ids (it is usually pre-initialized in an Ajax request to ensure 088 * that newly allocated ids do not conflict with previous renders and partial updates) 089 * @param partialMode 090 * if true, then the JSS configures itself for a partial page render (part of an Ajax request) 091 * which automatically assumes the "core" library has been added (to the original page render) 092 * and makes other minor changes to behavior. 093 */ 094 public JavaScriptSupportImpl(DocumentLinker linker, JavaScriptStackSource javascriptStackSource, 095 JavaScriptStackPathConstructor stackPathConstructor, IdAllocator idAllocator, boolean partialMode) 096 { 097 this.linker = linker; 098 this.idAllocator = idAllocator; 099 this.javascriptStackSource = javascriptStackSource; 100 this.stackPathConstructor = stackPathConstructor; 101 this.partialMode = partialMode; 102 103 // In partial mode, assume that the infrastructure stack is already present 104 // (from the original page render). 105 106 if (partialMode) 107 addedStacks.put(InternalConstants.CORE_STACK_NAME, true); 108 } 109 110 public void commit() 111 { 112 if (focusFieldId != null) 113 addInitializerCall("activate", focusFieldId); 114 115 F.flow(stylesheetLinks).each(new Worker<StylesheetLink>() 116 { 117 public void work(StylesheetLink value) 118 { 119 linker.addStylesheetLink(value); 120 } 121 }); 122 123 Worker<String> linkLibrary = new Worker<String>() 124 { 125 public void work(String value) 126 { 127 linker.addScriptLink(value); 128 } 129 }; 130 131 F.flow(stackLibraries).each(linkLibrary); 132 F.flow(otherLibraries).each(linkLibrary); 133 134 for (InitializationPriority p : InitializationPriority.values()) 135 { 136 JSONObject init = inits.get(p); 137 138 if (init != null) 139 linker.setInitialization(p, init); 140 } 141 } 142 143 public void addInitializerCall(InitializationPriority priority, String functionName, JSONObject parameter) 144 { 145 storeInitializerCall(priority, functionName, parameter); 146 } 147 148 public void addInitializerCall(String functionName, JSONArray parameter) 149 { 150 storeInitializerCall(InitializationPriority.NORMAL, functionName, parameter); 151 } 152 153 public void addInitializerCall(InitializationPriority priority, String functionName, JSONArray parameter) 154 { 155 storeInitializerCall(priority, functionName, parameter); 156 } 157 158 private void storeInitializerCall(InitializationPriority priority, String functionName, Object parameter) 159 { 160 assert priority != null; 161 assert parameter != null; 162 assert InternalUtils.isNonBlank(functionName); 163 addCoreStackIfNeeded(); 164 165 JSONObject init = inits.get(priority); 166 167 if (init == null) 168 { 169 init = new JSONObject(); 170 inits.put(priority, init); 171 } 172 173 JSONArray invocations = init.has(functionName) ? init.getJSONArray(functionName) : null; 174 175 if (invocations == null) 176 { 177 invocations = new JSONArray(); 178 init.put(functionName, invocations); 179 } 180 181 invocations.put(parameter); 182 } 183 184 public void addInitializerCall(String functionName, JSONObject parameter) 185 { 186 addInitializerCall(InitializationPriority.NORMAL, functionName, parameter); 187 } 188 189 public void addInitializerCall(InitializationPriority priority, String functionName, String parameter) 190 { 191 storeInitializerCall(priority, functionName, parameter); 192 } 193 194 public void addInitializerCall(String functionName, String parameter) 195 { 196 addInitializerCall(InitializationPriority.NORMAL, functionName, parameter); 197 } 198 199 public void addScript(InitializationPriority priority, String format, Object... arguments) 200 { 201 assert priority != null; 202 assert InternalUtils.isNonBlank(format); 203 204 addCoreStackIfNeeded(); 205 206 String newScript = arguments.length == 0 ? format : String.format(format, arguments); 207 208 if (partialMode) 209 { 210 addInitializerCall(priority, "evalScript", newScript); 211 } 212 else 213 { 214 linker.addScript(priority, newScript); 215 } 216 } 217 218 public void addScript(String format, Object... arguments) 219 { 220 addScript(InitializationPriority.NORMAL, format, arguments); 221 } 222 223 public String allocateClientId(ComponentResources resources) 224 { 225 return allocateClientId(resources.getId()); 226 } 227 228 public String allocateClientId(String id) 229 { 230 return idAllocator.allocateId(id); 231 } 232 233 public void importJavaScriptLibrary(Asset asset) 234 { 235 assert asset != null; 236 237 importJavaScriptLibrary(asset.toClientURL()); 238 } 239 240 public void importJavaScriptLibrary(String libraryURL) 241 { 242 addCoreStackIfNeeded(); 243 244 String stackName = findStackForLibrary(libraryURL); 245 246 if (stackName != null) 247 { 248 importStack(stackName); 249 return; 250 } 251 252 if (otherLibraries.contains(libraryURL)) 253 return; 254 255 otherLibraries.add(libraryURL); 256 } 257 258 private Map<String, String> libraryURLToStackName; 259 260 /** 261 * Locates the name of the stack that includes the library URL. Returns the stack, 262 * or null if the library is free-standing. 263 */ 264 private String findStackForLibrary(String libraryURL) 265 { 266 return getLibraryURLToStackName().get(libraryURL); 267 } 268 269 private Map<String, String> getLibraryURLToStackName() 270 { 271 if (libraryURLToStackName == null) 272 { 273 libraryURLToStackName = CollectionFactory.newMap(); 274 275 for (String stackName : javascriptStackSource.getStackNames()) 276 { 277 for (Asset library : javascriptStackSource.getStack(stackName).getJavaScriptLibraries()) 278 { 279 libraryURLToStackName.put(library.toClientURL(), stackName); 280 } 281 } 282 } 283 284 return libraryURLToStackName; 285 } 286 287 private void addCoreStackIfNeeded() 288 { 289 addAssetsFromStack(InternalConstants.CORE_STACK_NAME); 290 } 291 292 private void addAssetsFromStack(String stackName) 293 { 294 if (addedStacks.containsKey(stackName)) 295 return; 296 297 JavaScriptStack stack = javascriptStackSource.getStack(stackName); 298 299 for (String dependentStackname : stack.getStacks()) 300 { 301 addAssetsFromStack(dependentStackname); 302 } 303 304 stackLibraries.addAll(stackPathConstructor.constructPathsForJavaScriptStack(stackName)); 305 306 stylesheetLinks.addAll(stack.getStylesheets()); 307 308 addedStacks.put(stackName, true); 309 310 String initialization = stack.getInitialization(); 311 312 if (initialization != null) 313 addScript(InitializationPriority.IMMEDIATE, initialization); 314 } 315 316 public void importStylesheet(Asset stylesheet) 317 { 318 assert stylesheet != null; 319 importStylesheet(new StylesheetLink(stylesheet)); 320 } 321 322 public void importStylesheet(StylesheetLink stylesheetLink) 323 { 324 assert stylesheetLink != null; 325 String stylesheetURL = stylesheetLink.getURL(); 326 327 if (importedStylesheetURLs.contains(stylesheetURL)) 328 return; 329 330 importedStylesheetURLs.add(stylesheetURL); 331 332 stylesheetLinks.add(stylesheetLink); 333 } 334 335 public void importStack(String stackName) 336 { 337 assert InternalUtils.isNonBlank(stackName); 338 addCoreStackIfNeeded(); 339 340 addAssetsFromStack(stackName); 341 } 342 343 public void autofocus(FieldFocusPriority priority, String fieldId) 344 { 345 assert priority != null; 346 assert InternalUtils.isNonBlank(fieldId); 347 348 if (focusFieldId == null || priority.compareTo(focusPriority) > 0) 349 { 350 this.focusPriority = priority; 351 focusFieldId = fieldId; 352 } 353 } 354 355 }