001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: *
017: * $Header:$
018: */
019: package org.apache.beehive.netui.tags.javascript;
020:
021: import org.apache.beehive.netui.util.internal.InternalStringBuilder;
022:
023: import org.apache.beehive.netui.tags.AbstractClassicTag;
024: import org.apache.beehive.netui.tags.TagConfig;
025: import org.apache.beehive.netui.tags.RequestUtils;
026: import org.apache.beehive.netui.tags.rendering.AbstractRenderAppender;
027: import org.apache.beehive.netui.tags.rendering.ScriptTag;
028: import org.apache.beehive.netui.tags.rendering.TagRenderingBase;
029: import org.apache.beehive.netui.tags.rendering.WriteRenderAppender;
030: import org.apache.beehive.netui.pageflow.scoping.ScopedRequest;
031: import org.apache.beehive.netui.pageflow.scoping.ScopedServletUtils;
032:
033: import javax.servlet.http.HttpServletRequest;
034: import javax.servlet.jsp.JspException;
035: import javax.servlet.jsp.tagext.SimpleTagSupport;
036: import javax.servlet.jsp.tagext.Tag;
037: import javax.servlet.ServletRequest;
038: import java.util.ArrayList;
039: import java.util.HashMap;
040: import java.util.Iterator;
041:
042: /**
043: * Acts as a container that will bundle up JavaScript created by other NetUI tags,
044: * and output it within a single <script> tag. This is especially needed for
045: * Portal web applications, because they often cannot rely on having
046: * <html> ... </html> tags to provide a default container. In a portlet,
047: * some JSP pages might be included into other JSP pages. Having redundant
048: * <html> ... </html> tags in the rendered portlet JSP can result in display
049: * problems for some browsers. However, omitting the <html> tag (and the
050: * container it provides) can result in cluttered code, especially where Javascript
051: * appears in the file. To solve this issue, Beehive provides the
052: * <netui:scriptContainer> tag.
053: *
054: * @jsptagref.tagdescription Acts as a container that will bundle up JavaScript created by other <netui...> tags,
055: * and outputs it within a single <script> tag. This is especially useful for
056: * Portal web applications, because they often cannot rely on having
057: * <html> ... </html> tags to provide a default container. In a Portlet,
058: * some JSP pages might be included in other JSP pages. Having redundant
059: * <html> ... </html> tags in the rendered Portlet JSP can result in display
060: * problems for some browsers. On the other hand, omitting the <html> tag (and the
061: * container it provides) can result in cluttered code, especially where JavaScript
062: * appears in the file. To solve this issue, Beehive provides the
063: * <netui:scriptContainer> tag.
064: *
065: * <p>The <netui:scriptContainer> ... </netui:scriptContainer> tag set should
066: * enclose those <netui:...> tags that you want included in the script container.
067: * The first <netui:scriptContainer> tag should appear after the JSP's <body> tag.
068: * The closing </netui:scriptContainer> tag should appear before the JSP's </body> tag.
069: * @example The <netui:scriptContainer> ... </netui:scriptContainer> tag set simply
070: * encloses other NetUI tags that you want to belong to that script container.
071: * The first <netui:scriptContainer> tag should appear after the JSP's <body> tag.
072: * The closing </netui:scriptContainer> tag should appear before the JSP's </body> tag.
073: * @netui:tag name="scriptContainer" description="ScriptContainers defines a container that will gather all of the JavaScript of their children and output it in a single <script> tag. In addition, they providing scoping of tagIds."
074: */
075: public class ScriptContainer extends AbstractClassicTag implements
076: IScriptReporter {
077: public final static String SCOPE_ID = "netui:scopeId";
078:
079: private String _idScope = null;
080: private ArrayList/*<String>*/_funcBlocks;
081: private ArrayList/*<String>*/_codeBefore;
082: private ArrayList/*<String>*/_codeAfter;
083: private HashMap/*<String, String>*/_idMap;
084: private HashMap/*<String, String>*/_idToNameMap;
085: private boolean _genScope = false;
086: private boolean _writeScript = false;
087: private boolean _writeId = false;
088:
089: /**
090: * Returns the name of the Tag.
091: */
092: public String getTagName() {
093: return "ScriptContainer";
094: }
095:
096: /////////////////////////// ScriptReporter Interface ////////////////////////////
097:
098: /**
099: * This method will add Script as a function.
100: * @param placement
101: * @param script the text of the function. This value must not be null.
102: */
103: public void addScriptFunction(ScriptPlacement placement,
104: String script) {
105: assert (script != null) : "The paramter 'script' must not be null";
106: IScriptReporter sr = getParentScriptReporter();
107: if (sr != null) {
108: sr.addScriptFunction(placement, script);
109: return;
110: }
111:
112: // get the list of function blocks and add this script to it.
113: if (placement == null
114: || placement == ScriptPlacement.PLACE_INFRAMEWORK) {
115: if (_funcBlocks == null) {
116: _funcBlocks = new ArrayList/*<String>*/();
117: }
118: assert (_funcBlocks != null) : "_funcBlocks should not be null";
119: _funcBlocks.add(script);
120: } else if (placement == ScriptPlacement.PLACE_BEFORE) {
121: if (_codeBefore == null)
122: _codeBefore = new ArrayList/*<String>*/();
123: _codeBefore.add(script);
124: } else if (placement == ScriptPlacement.PLACE_AFTER) {
125: if (_codeAfter == null)
126: _codeAfter = new ArrayList/*<String>*/();
127: _codeAfter.add(script);
128: } else {
129: assert (false) : "unsupported placement:" + placement;
130: }
131: }
132:
133: /**
134: * Adds a tagID and tagName to the Html's getId javascript function.
135: * @param tagId the id of a child tag.
136: * @param tagName the name of a child tag.
137: */
138: public void addLegacyTagIdMappings(String tagId, String tagName) {
139: assert (tagId != null) : "The parameter 'tagId' must not be null";
140: assert (tagName != null) : "The parameter 'tagName' must not be null";
141:
142: if (_idMap == null) {
143: _idMap = new HashMap/*<String, String>*/();
144: }
145:
146: assert (_idMap != null) : "_idMap should not be null";
147: _idMap.put(tagId, tagName);
148: }
149:
150: /**
151: * This will add the mapping between the tagId and the real name to the NameMap hashmap.
152: * @param tagId
153: * @param realId
154: * @param realName
155: */
156: public void addTagIdMappings(String tagId, String realId,
157: String realName) {
158: assert (tagId != null) : "The parameter 'tagId' must not be null";
159: assert (realId != null) : "The parameter 'realId' must not be null";
160:
161: _writeId = true;
162:
163: if (realName != null) {
164: if (_idToNameMap == null)
165: _idToNameMap = new HashMap/*<String, String>*/();
166: _idToNameMap.put(tagId, realName);
167: }
168: }
169:
170: /**
171: * This method will output all of the Script associated with the script reporter.
172: * @param sb The script is written into the provided InternalStringBuilder. This value must not be null.
173: */
174: public void writeScript(AbstractRenderAppender sb) {
175: assert (sb != null) : "The paramter 'sb' must not be null;";
176: if (_writeScript)
177: return;
178:
179: _writeScript = true;
180: IScriptReporter sr = getParentScriptReporter();
181: if (sr != null) {
182: sr.writeScript(sb);
183: return;
184: }
185:
186: writeBeforeBlocks(sb);
187: writeFrameworkScript(sb);
188: writeAfterBlocks(sb);
189: }
190:
191: /////////////////////////// Attributes ////////////////////////////
192:
193: /**
194: * Set the idScope associated with the code methods
195: * @jsptagref.attributedescription The id that is associated with the script methods.
196: * @jsptagref.databindable false
197: * @jsptagref.attributesyntaxvalue <i>string_scopeId</i>
198: * @netui:attribute required="false" rtexprvalue="true"
199: * description="The id that is associated with the script methods."
200: */
201: public void setIdScope(String idScope) {
202: _idScope = idScope;
203: }
204:
205: /**
206: * return the scopeId associated with the ScriptContainer
207: */
208: public String getIdScope() {
209: return _idScope;
210:
211: }
212:
213: /**
214: * If true generate a scope id for this script container. If this is set to true
215: * and a scopeId is also set, the scopeId set will be written.
216: * @jsptagref.attributedescription Automatically generate a scopeId for this script container.
217: * @jsptagref.databindable true
218: * @jsptagref.attributesyntaxvalue <i>string_generateScopeId</i>
219: * @netui:attribute required="false" rtexprvalue="true"
220: * description="Automatically generate a ScopeId."
221: */
222: public void setGenerateIdScope(boolean genScopeValue) {
223: _genScope = genScopeValue;
224: }
225:
226: ///////////////////////////////// Tag Methods ////////////////////////////////////////
227:
228: public int doStartTag() throws JspException {
229: String scope = getRealIdScope();
230: pushIdScope();
231:
232: WriteRenderAppender writer = new WriteRenderAppender(
233: pageContext);
234: writeBeforeBlocks(writer);
235:
236: // if there is a scopeId, then we need to create a div to contains everything
237: if (_idScope != null) {
238: write("<div");
239: write(" netui:idScope=\"");
240: write(scope);
241: write("\" ");
242: write(">");
243: }
244:
245: return EVAL_BODY_INCLUDE;
246: }
247:
248: /**
249: * Write out the body content and report any errors that occured.
250: * @throws JspException if a JSP exception has occurred
251: */
252: public int doEndTag() throws JspException {
253:
254: popIdScope();
255:
256: // writeout the script.
257: WriteRenderAppender writer = new WriteRenderAppender(
258: pageContext);
259:
260: // if we wrote out the scopeId then we end it.
261: if (_idScope != null) {
262: writer.append("</div>");
263: }
264:
265: writeFrameworkScript(writer);
266: writeAfterBlocks(writer);
267: localRelease();
268: return EVAL_PAGE;
269: }
270:
271: /////////////////////////////////// Protected Support ////////////////////////////////////
272:
273: protected void pushIdScope() {
274: if (_idScope != null) {
275: HttpServletRequest req = (HttpServletRequest) pageContext
276: .getRequest();
277: ArrayList/*<String>*/list = (ArrayList/*<String>*/) RequestUtils
278: .getOuterAttribute(req, SCOPE_ID);
279: if (list == null) {
280: list = new ArrayList/*<String>*/();
281: RequestUtils.setOuterAttribute(req, SCOPE_ID, list);
282: }
283: list.add(_idScope);
284:
285: }
286: }
287:
288: protected void popIdScope() {
289: if (_idScope != null) {
290: HttpServletRequest req = (HttpServletRequest) pageContext
291: .getRequest();
292: ArrayList/*<String>*/list = (ArrayList/*<String>*/) RequestUtils
293: .getOuterAttribute(req, SCOPE_ID);
294: assert (list != null);
295: list.remove(list.size() - 1);
296: }
297: }
298:
299: /**
300: * This method will return the real scope id for the script container.
301: * @return String
302: */
303: protected String getRealIdScope() {
304: ServletRequest request = pageContext.getRequest();
305:
306: // default to the set idScope.
307: String idScope = _idScope;
308:
309: // if there isn't a set idScope and generate scope is on, generate the scope id.
310: if (_idScope == null && _genScope) {
311: int id = getNextId(request);
312: idScope = "n" + Integer.toString(id);
313: _idScope = idScope;
314: }
315:
316: // if there's still no idScope and we're in a ScopedRequest, use the scope-key from the request.
317: if (_idScope == null) {
318: ScopedRequest scopedRequest = ScopedServletUtils
319: .unwrapRequest(request);
320: if (scopedRequest != null) {
321: _idScope = scopedRequest.getScopeKey().toString();
322: idScope = _idScope;
323: }
324: }
325:
326: return idScope;
327: }
328:
329: protected void writeBeforeBlocks(AbstractRenderAppender sb) {
330: if (_codeBefore == null || _codeBefore.size() == 0)
331: return;
332:
333: InternalStringBuilder s = new InternalStringBuilder(256);
334: for (Iterator i = _codeBefore.iterator(); i.hasNext();) {
335: String code = (String) i.next();
336: s.append(code);
337: s.append("\n");
338: }
339: ScriptRequestState.writeScriptBlock(pageContext.getRequest(),
340: sb, s.toString());
341:
342: }
343:
344: protected void writeAfterBlocks(AbstractRenderAppender sb) {
345: if (_codeAfter == null || _codeAfter.size() == 0)
346: return;
347:
348: InternalStringBuilder s = new InternalStringBuilder(256);
349: for (Iterator i = _codeAfter.iterator(); i.hasNext();) {
350: String code = (String) i.next();
351: s.append(code);
352: s.append("\n");
353: }
354: ScriptRequestState.writeScriptBlock(pageContext.getRequest(),
355: sb, s.toString());
356: }
357:
358: /**
359: * This will write the script block.
360: */
361: protected void writeFrameworkScript(AbstractRenderAppender sb) {
362: boolean script = false;
363: ScriptRequestState jsu = ScriptRequestState
364: .getScriptRequestState((HttpServletRequest) pageContext
365: .getRequest());
366:
367: boolean writeLegacy = false;
368: boolean writeName = false;
369: String val;
370:
371: // if we are writing out legacy JavaScript support output the idMap
372: if (TagConfig.isLegacyJavaScript()) {
373: val = processIdMap(_idMap, "idMappingEntry", _idScope);
374: if (val != null) {
375: writeIdMap(this , "idMappingTable", val);
376: writeLegacy = true;
377: }
378: }
379:
380: // if we are writing out default JavaScript support we create the name map
381: if (TagConfig.isDefaultJavaScript()) {
382: String idScope = getJavaScriptId();
383: if (idScope.equals(""))
384: idScope = null;
385: val = processIdMap(_idToNameMap, "tagIdNameMappingEntry",
386: idScope);
387: if (val != null) {
388: writeIdMap(this , "tagIdNameMappingTable", val);
389: writeName = true;
390: }
391: }
392:
393: if (writeLegacy || _writeId || writeName)
394: jsu.writeNetuiNameFunctions(this , writeLegacy, _writeId,
395: writeName);
396:
397: ScriptTag.State state = null;
398: ScriptTag br = null;
399: if (_funcBlocks != null && _funcBlocks.size() > 0) {
400: if (!script) {
401: state = new ScriptTag.State();
402: state.suppressComments = false;
403: br = (ScriptTag) TagRenderingBase.Factory.getRendering(
404: TagRenderingBase.SCRIPT_TAG, pageContext
405: .getRequest());
406: br.doStartTag(sb, state);
407: script = true;
408: }
409: String s = ScriptRequestState.getString("functionComment",
410: null);
411: sb.append(s);
412: int cnt = _funcBlocks.size();
413: for (int i = 0; i < cnt; i++) {
414: sb.append((String) _funcBlocks.get(i));
415: if (i != cnt - 1) {
416: sb.append("\n");
417: }
418: }
419: }
420:
421: if (script) {
422: assert (br != null);
423: br.doEndTag(sb, false);
424: }
425: }
426:
427: /////////////////////////////////// Private Support ////////////////////////////////////
428:
429: /**
430: * @param scriptRepoter
431: * @param mapObj
432: * @param entries
433: * @return returns a string containing JavaScript if there isn't a ScriptReporter
434: */
435: private String writeIdMap(IScriptReporter scriptRepoter,
436: String mapObj, String entries) {
437: String s = ScriptRequestState.getString(mapObj,
438: new Object[] { entries });
439: if (scriptRepoter != null) {
440: scriptRepoter.addScriptFunction(null, s);
441: return null;
442: }
443: return s;
444: }
445:
446: private String getJavaScriptId() {
447: String idScope = "";
448: Tag tag = this ;
449: while (tag != null) {
450: if (tag instanceof ScriptContainer) {
451: String sid = ((ScriptContainer) tag).getIdScope();
452: if (sid != null) {
453: idScope = sid + "_" + idScope;
454: }
455: }
456: tag = tag.getParent();
457: }
458: return idScope;
459: }
460:
461: private String processIdMap(HashMap/*<String, String>*/map,
462: String mapEntry, String idScope) {
463: // if no map or empty then return
464: if (map == null || map.size() == 0)
465: return null;
466:
467: InternalStringBuilder results = new InternalStringBuilder(128);
468: Iterator/*<String>*/ids = map.keySet().iterator();
469: while (ids.hasNext()) {
470: String id = (String) ids.next();
471: String value = (String) map.get(id);
472: if (idScope != null)
473: id = idScope + "__" + id;
474: String entry = ScriptRequestState.getString(mapEntry,
475: new Object[] { id, value });
476: results.append(entry);
477: }
478: return results.toString();
479: }
480:
481: /////////////////////////////////// Local Release ////////////////////////////////////
482: private IScriptReporter getParentScriptReporter() {
483: Tag parent = getParent();
484: if (parent == null)
485: return null;
486: return (IScriptReporter) SimpleTagSupport
487: .findAncestorWithClass(parent, IScriptReporter.class);
488: }
489:
490: /**
491: * Release any acquired resources.
492: */
493: protected void localRelease() {
494: super .localRelease();
495:
496: _idScope = null;
497: _writeScript = false;
498: _genScope = false;
499: _writeId = false;
500:
501: if (_funcBlocks != null)
502: _funcBlocks.clear();
503: if (_codeBefore != null)
504: _codeBefore.clear();
505: if (_codeAfter != null)
506: _codeAfter.clear();
507:
508: if (_idMap != null)
509: _idMap.clear();
510: if (_idToNameMap != null)
511: _idToNameMap.clear();
512: }
513:
514: }
|