001: /*
002: * Version: MPL 1.1/GPL 2.0/LGPL 2.1
003: *
004: * "The contents of this file are subject to the Mozilla Public License
005: * Version 1.1 (the "License"); you may not use this file except in
006: * compliance with the License. You may obtain a copy of the License at
007: * http://www.mozilla.org/MPL/
008: *
009: * Software distributed under the License is distributed on an "AS IS"
010: * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
011: * License for the specific language governing rights and limitations under
012: * the License.
013: *
014: * The Original Code is ICEfaces 1.5 open source software code, released
015: * November 5, 2006. The Initial Developer of the Original Code is ICEsoft
016: * Technologies Canada, Corp. Portions created by ICEsoft are Copyright (C)
017: * 2004-2006 ICEsoft Technologies Canada, Corp. All Rights Reserved.
018: *
019: * Contributor(s): _____________________.
020: *
021: * Alternatively, the contents of this file may be used under the terms of
022: * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"
023: * License), in which case the provisions of the LGPL License are
024: * applicable instead of those above. If you wish to allow use of your
025: * version of this file only under the terms of the LGPL License and not to
026: * allow others to use your version of this file under the MPL, indicate
027: * your decision by deleting the provisions above and replace them with
028: * the notice and other provisions required by the LGPL License. If you do
029: * not delete the provisions above, a recipient may use your version of
030: * this file under either the MPL or the LGPL License."
031: *
032: */
033:
034: package com.icesoft.faces.context.effects;
035:
036: import com.icesoft.faces.application.D2DViewHandler;
037: import com.icesoft.faces.application.StartupTime;
038: import com.icesoft.faces.context.BridgeFacesContext;
039:
040: import javax.faces.component.UIComponent;
041: import javax.faces.context.ExternalContext;
042: import javax.faces.context.FacesContext;
043: import java.util.ArrayList;
044: import java.util.Collections;
045: import java.util.HashMap;
046: import java.util.Iterator;
047: import java.util.List;
048: import java.util.Map;
049: import java.util.Random;
050:
051: /**
052: *
053: * Used to send Javascript to the browser
054: */
055: public class JavascriptContext {
056: private static final Random RANDOM = new Random();
057:
058: /**
059: * Request scope key, used to store javascript code to be sent to the browser
060: */
061: private static final String REQUEST_KEY = "icesoft_javascript_request_key_7698193";
062: /**
063: * Effects scope key, used to store the fired effects in the request scope
064: */
065: private static final String EFFECTS_REQUEST_KEY = "icesoft_javascript_effects_request_key_9072451";
066: /**
067: * Request scope key for componenet focus
068: */
069: private static final String FOCUS_COMP_KEY = "icesoft_javascript_focus_comp";
070: /**
071: * Request Scope Key, storing the focus
072: */
073: private static final String FOCUS_APP_KEY = "icesoft_javascript_focus_app";
074: /**
075: * Request Scope Key, indicating that extras needs to be included
076: */
077: public static final String LIB_KEY = "icesoft_javascript_required_libs_897241";
078: /**
079: * ID of script node used to send Javascript in a dom update
080: */
081: public static final String DYNAMIC_CODE_ID = "dynamic-code";
082:
083: /**
084: * URL of the ICE Bridge lib
085: */
086: public static final String ICE_BRIDGE = "/xmlhttp"
087: + StartupTime.getStartupInc() + "icefaces-d2d.js";
088:
089: /**
090: * URL of the ICE Extras lib
091: */
092: public static final String ICE_EXTRAS = "/xmlhttp"
093: + StartupTime.getStartupInc() + "ice-extras.js";
094:
095: /**
096: * Include a script tag in the <head> section of the page with the src
097: * attribute set to libname. This will insure that all icefaces pages
098: * contain this script for the remainder of the session.
099: *
100: * @param libname the source of the javascript file
101: * @param facesContext The facescontext this file is needed for
102: */
103: public static void includeLib(String libname,
104: FacesContext facesContext) {
105: if (facesContext == null)
106: return;
107: ExternalContext externalContext = facesContext
108: .getExternalContext();
109: if (externalContext == null)
110: return;
111: Map sessionMap = externalContext.getSessionMap();
112: if (sessionMap == null)
113: return;
114: List libs = (List) sessionMap.get(LIB_KEY);
115: if (libs == null) {
116: libs = new ArrayList();
117: sessionMap.put(LIB_KEY, libs);
118: }
119: if (!libs.contains(libname))
120: libs.add(libname);
121: }
122:
123: /**
124: * Get the included javascript libraries
125: *
126: * @param facesContext
127: * @return
128: */
129: public static String[] getIncludedLibs(FacesContext facesContext) {
130: List libs = (List) facesContext.getExternalContext()
131: .getSessionMap().get(LIB_KEY);
132: String[] result = new String[0];
133: if (libs != null) {
134: result = new String[libs.size()];
135: libs.toArray(result);
136: }
137: return result;
138: }
139:
140: /**
141: * Add a javascript call to be executed on the browser. Code will be
142: * executed at the end of the current render cycle.
143: * <p/>
144: * Note: When sending function definitions you must specify the function
145: * name as this['functionName'] = function(){} For example: <code>
146: * helloWorld(name}{ alert('Hello [' + name + ']'); }</code>
147: * <p/>
148: * Would need to be written as <code> this['helloWorld'] = function(name){
149: * alert('Hello [' + name + ']'); }</code>
150: *
151: * @param facesContext
152: * @param call Javascript code to execute
153: */
154: public static void addJavascriptCall(FacesContext facesContext,
155: String call) {
156: Map map = facesContext.getExternalContext().getRequestMap();
157: addJavascriptCall(map, call);
158: }
159:
160: /**
161: * Add a javascript call to the request map
162: * @param map
163: * @param call
164: */
165: private static void addJavascriptCall(Map map, String call) {
166: String currentValue = (String) map.get(REQUEST_KEY);
167: if (currentValue == null) {
168: map.put(REQUEST_KEY, call);
169: } else {
170: map.put(REQUEST_KEY, currentValue + call);
171: }
172: }
173:
174: /**
175: * Add a javascript call to this request
176: * @param facesContext
177: * @return
178: */
179: public static String getJavascriptCalls(FacesContext facesContext) {
180: Map map = facesContext.getExternalContext().getRequestMap();
181: return getJavascriptCalls(map);
182: }
183:
184: /**
185: * Get javascript calls from the Request map
186: * @param map
187: * @return
188: */
189: public static String getJavascriptCalls(Map map) {
190: addtEffectJavascriptCalls(map);
191: String code = (String) map.get(REQUEST_KEY);
192: map.put(REQUEST_KEY, "");
193: String focus = getFocusCall(map);
194: code = replaceDupes(code == null || "".equals(code) ? focus
195: : code + ';' + focus);
196: // Hack so that javascript will be called from both the bridge and the html parser
197: // Remove when we have a better way
198: if ("".equals(code)) {
199: return "";
200: } else {
201: return "var c = function(){"
202: + code
203: + "};if(window.application){c();}else{window.onLoad(c)};"
204: + randomComment();
205: }
206: }
207:
208: /**
209: * Get the focus call from the request map
210: * @param map
211: * @return
212: */
213: private static String getFocusCall(Map map) {
214: String focus = (String) map.get(FOCUS_APP_KEY);
215: if (focus == null) {
216: focus = (String) map.get(FOCUS_COMP_KEY);
217: }
218: if (focus == null) {
219: return "";
220: } else {
221: map.remove(FOCUS_APP_KEY);
222: map.remove(FOCUS_COMP_KEY);
223: return "Ice.Focus.setFocus('" + focus + "');";
224: }
225: }
226:
227: /**
228: * Wrap the effect in a javascript method to be called later. Returns the
229: * method name. Used in local effects.
230: *
231: * @param effect
232: * @param id
233: * @param context
234: * @return The name of the method that wraps the effect
235: */
236: public static String applyEffect(Effect effect, String id,
237: FacesContext context) {
238: //Get the real ID if a JSF component
239: //UIComponent uiComponent = context.getViewRoot().findComponent(id);
240:
241: UIComponent uiComponent = D2DViewHandler.findComponent(id,
242: context.getViewRoot());
243: if (uiComponent != null) {
244: id = uiComponent.getClientId(context);
245: }
246:
247: String name = genFunctionName(id, effect);
248:
249: String call = "window['" + name + "'] = function (){"
250: + "id = '" + id + "';" + effect.toString() + "};";
251:
252: addJavascriptCall(context, call);
253: return name + "();";
254: }
255:
256: /**
257: * Fire an effect at the end of the current render cycle. Fired from server, non local.
258: *
259: * @param effect
260: * @param id Target element of the effect
261: * @param context
262: */
263: public static void fireEffect(Effect effect, String id,
264: FacesContext context) {
265: if (effect == null || effect.isFired())
266: return;
267: effect.setFired(true);
268: Object viewRoot = context.getViewRoot();
269: try {
270:
271: UIComponent uiComponent = D2DViewHandler.findComponent(id,
272: context.getViewRoot());
273: if (uiComponent != null) {
274: id = uiComponent.getClientId(context);
275: }
276: } catch (Exception e) {
277: /*Class clazz = context.getViewRoot().getClass();
278: Method[] methods =clazz.getMethods();
279: for(int i = 0; i < methods.length; i++){
280: Method m = methods[i];
281: System.err.println("Method [" + m.getName() + "]");
282: Class[] args = m.getParameterTypes();
283: for(int a =0; a<args.length;a++){
284: System.err.println("Arg [" + args[a].getName() + "]");
285: }
286: } */
287: System.err.println("View Root is ["
288: + viewRoot.getClass().getName() + "]");
289: e.printStackTrace();
290: }
291: effect.setId(id);
292: addEffect(effect, context);
293:
294: }
295:
296: /**
297: * Fire an effect at the end of the current render cycle
298: *
299: * @param effect
300: * @param component
301: */
302: public static void fireEffect(Effect effect, UIComponent component) {
303: if (effect == null || effect.isFired())
304: return;
305: FacesContext facesContext = FacesContext.getCurrentInstance();
306: fireEffect(effect, component, facesContext);
307: }
308:
309: /**
310: * Fire an effect at the end of the current render cycle
311: *
312: * @param effect
313: * @param component
314: * @param facesContext
315: */
316: public static void fireEffect(Effect effect, UIComponent component,
317: FacesContext facesContext) {
318: if (effect == null || effect.isFired())
319: return;
320: String id = component.getClientId(facesContext);
321: fireEffect(effect, id, facesContext);
322: }
323:
324: /**
325: * Fire an effect at the end of the current render cycle
326: *
327: * @param uiComponent
328: * @param facesContext
329: */
330:
331: public static void fireEffect(UIComponent uiComponent,
332: FacesContext facesContext) {
333: Effect effect = (Effect) uiComponent.getAttributes().get(
334: "effect");
335: if (effect != null)
336: fireEffect(effect, uiComponent, facesContext);
337: }
338:
339: /**
340: * Get the Effect function for a given event
341: * @param uiComponent
342: * @param event
343: * @param id
344: * @param facesContext
345: * @return
346: */
347: public static String getEffectFunctionForEvent(
348: UIComponent uiComponent, String event, String id,
349: FacesContext facesContext) {
350: String result = null;
351: Effect fx = getEffectForEvent(uiComponent, event);
352: if (fx != null) {
353: result = applyEffect(fx, id, facesContext);
354: }
355: return result;
356: }
357:
358: /**
359: * Get the Effect for a givin function
360: * @param uiComponent
361: * @param event
362: * @return
363: */
364: private static Effect getEffectForEvent(UIComponent uiComponent,
365: String event) {
366: Effect result = null;
367: Object o = uiComponent.getAttributes().get(event);
368: if (o != null && o instanceof Effect) {
369: result = (Effect) o;
370: }
371: return result;
372: }
373:
374: /**
375: * Generate a unique function name for local effects
376: *
377: * @param id
378: * @param effect
379: * @return
380: */
381: private static String genFunctionName(String id, Effect effect) {
382: StringBuffer sb = new StringBuffer("iceEffect");
383: char[] ca = id.toCharArray();
384: // Make sure no invalid characters are in the function name
385: //65 - 90 A - Z
386: //97 - 122 a - z
387: //48 - 57 / 0 - 9
388: for (int i = 0; i < ca.length; i++) {
389: int c = (int) ca[i];
390: if ((c > 64 && c < 91) || (c > 96 && c < 123)
391: || (c > 47 && c < 58)) {
392: sb.append(ca[i]);
393: } else {
394: sb.append('_');
395: }
396: }
397: sb.append(effect.hashCode());
398: return sb.toString();
399: }
400:
401: /**
402: * Remove duplicate semi-colons from a givin string
403: * @param s
404: * @return
405: */
406: private static String replaceDupes(String s) {
407: if (s.indexOf(";;") == -1)
408: return s;
409: s = s.replaceAll(";;", ";");
410: return s;
411: }
412:
413: /**
414: * \ Set focus on an HTML element.
415: *
416: * @param context
417: * @param id
418: */
419: public static void focus(FacesContext context, String id) {
420: //this method relies on XMLRenderer to create these "script" elements
421: if (!id.equals("")) {
422: Map map = context.getExternalContext().getRequestMap();
423: map.put(FOCUS_COMP_KEY, id);
424: }
425: }
426:
427: /**
428: * Set the application focus for the current request, overrides and setFocus call.
429: * Generally setFocus is used by components, while setApplicationFocus is used by the application.
430: * @param id
431: */
432: public static void applicationFocus(FacesContext facesContext,
433: String id) {
434: if (!id.equals("")) {
435:
436: Map map = facesContext.getExternalContext().getRequestMap();
437: map.put(FOCUS_APP_KEY, id);
438: }
439: }
440:
441: /**
442: * Get the focus for the current request
443: * @param context
444: * @return
445: */
446: public static String getFocus(FacesContext context) {
447: String focusId = ((BridgeFacesContext) context).getFocusId();
448: Map map = context.getExternalContext().getRequestMap();
449: if (map.containsKey(FOCUS_COMP_KEY))
450: focusId = (String) map.get(FOCUS_COMP_KEY);
451: if (map.containsKey(FOCUS_APP_KEY))
452: focusId = (String) map.get(FOCUS_APP_KEY);
453: return focusId;
454: }
455:
456: /**
457: * Add an effect call for the current request
458: * @param effect
459: * @param facesContext
460: */
461: private static void addEffect(Effect effect,
462: FacesContext facesContext) {
463: Map map = facesContext.getExternalContext().getRequestMap();
464: List list = (ArrayList) map.get(EFFECTS_REQUEST_KEY);
465: if (list == null) {
466: list = new ArrayList();
467: map.put(EFFECTS_REQUEST_KEY, list);
468: }
469: list.add(effect);
470: }
471:
472: /**
473: * Add fired effects as javascript calls
474: * @param map
475: */
476: private static void addtEffectJavascriptCalls(Map map) {
477:
478: List list = (ArrayList) map.get(EFFECTS_REQUEST_KEY);
479: if (list == null) {
480: return;
481: }
482: Map sequencedEffects = new HashMap();
483: Iterator iter = list.iterator();
484: while (iter.hasNext()) {
485: Effect effect = (Effect) iter.next();
486: if (effect.getSequence() == null
487: && !(effect instanceof EffectQueue)) {
488: String call = "id = '" + effect.getId() + "';"
489: + effect.toString();
490: addJavascriptCall(map, call);
491: } else {
492:
493: extractEffectSequence(effect, sequencedEffects);
494: }
495: }
496:
497: if (sequencedEffects.size() > 0) {
498: iter = sequencedEffects.values().iterator();
499: int sequence = 0;
500: while (iter.hasNext()) {
501: list = (List) iter.next();
502: Collections.sort(list, new EffectComparator());
503: String call = buildSequenceEffectCall(list, sequence);
504: addJavascriptCall(map, call);
505: sequence++;
506: }
507: }
508: map.remove(EFFECTS_REQUEST_KEY);
509: }
510:
511: /**
512: * Build sequence of javascript effect calls
513: * @param list
514: * @param sequence
515: * @return
516: */
517: private static String buildSequenceEffectCall(List list,
518: int sequence) {
519: StringBuffer sb = new StringBuffer();
520: Iterator effectIter = list.iterator();
521: int effect = 0;
522: String lastCall = null;
523: while (effectIter.hasNext()) {
524: Effect fx = (Effect) effectIter.next();
525: String var = getVariableName(sequence, effect);
526: lastCall = "var " + var + " = '" + fx.getId() + "';"
527: + fx.toString(var, lastCall);
528: effect++;
529: }
530: return lastCall;
531: }
532:
533: /**
534: * Extra Javascript Effect Sequence from a map of effects
535: * @param effect
536: * @param sequencedEffects
537: */
538: private static void extractEffectSequence(Effect effect,
539: Map sequencedEffects) {
540: String seqName = effect.getSequence();
541:
542: List seq = (List) sequencedEffects.get(seqName);
543: if (seq == null) {
544: seq = new ArrayList();
545: sequencedEffects.put(seqName, seq);
546: }
547: if (effect instanceof EffectQueue) {
548: List eqList = ((EffectQueue) effect).getEffects();
549: Iterator eqIter = eqList.iterator();
550: int seqId = 0;
551: while (eqIter.hasNext()) {
552: Effect fx = (Effect) eqIter.next();
553: fx.setSequence(seqName);
554: fx.setSequenceId(seqId);
555: fx.setId(effect.getId());
556: seq.add(fx);
557: seqId++;
558:
559: }
560: } else {
561: seq.add(effect);
562: }
563: }
564:
565: /**
566: * Get variable name for an effect in a sequence
567: * @param sequence
568: * @param effect
569: * @return
570: */
571: private static String getVariableName(int sequence, int effect) {
572: return "s" + sequence + "e" + effect;
573: }
574:
575: /**
576: * Generate random Javascript comment to force a diff on the 'script'
577: * elements, thus triggering their evaluation in the browser.
578: */
579: private static String randomComment() {
580: return "//" + RANDOM.nextInt();
581: }
582: }
|