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 }