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.html;
020:
021: import org.apache.beehive.netui.pageflow.scoping.ScopedServletUtils;
022: import org.apache.beehive.netui.tags.ByRef;
023: import org.apache.beehive.netui.tags.HtmlUtils;
024: import org.apache.beehive.netui.tags.internal.PageFlowTagUtils;
025: import org.apache.beehive.netui.tags.javascript.CoreScriptFeature;
026: import org.apache.beehive.netui.tags.javascript.IScriptReporter;
027: import org.apache.beehive.netui.tags.javascript.ScriptRequestState;
028: import org.apache.beehive.netui.tags.rendering.*;
029: import org.apache.beehive.netui.util.Bundle;
030: import org.apache.beehive.netui.util.ParamHelper;
031: import org.apache.beehive.netui.util.internal.InternalStringBuilder;
032: import org.apache.beehive.netui.util.logging.Logger;
033: import org.apache.struts.taglib.html.Constants;
034:
035: import javax.servlet.ServletRequest;
036: import javax.servlet.http.HttpServletRequest;
037: import javax.servlet.http.HttpServletResponse;
038: import javax.servlet.jsp.JspException;
039: import java.net.URISyntaxException;
040: import java.util.HashMap;
041: import java.util.Map;
042:
043: /**
044: * This is the base class that provides most of the features necessary to create an anchor and an area. The Anchor
045: * and Area tags are created as subclasses of this tag. The Area tag is really a subset of the features, so certain
046: * attributes are not defined here, even though the backing fields are defined here and the utility code knows them.
047: * This may not be the best OO design, but the design is optimized for performance of rendering anchor elements.
048: */
049: abstract public class AnchorBase extends HtmlBaseTag implements
050: IUrlParams, IHasPopupSupport {
051: protected static final String REQUIRED_ATTR = "href, action, linkName, clientAction";
052: private static final Logger logger = Logger
053: .getInstance(AnchorBase.class);
054:
055: protected AnchorTag.State _state = new AnchorTag.State();
056: protected String _linkName; // name of the link
057: protected String _clientAction; // The client action (javascript)
058:
059: private String _action;
060: private String _href;
061: private String _targetScope; // target page flow scope; see comments on setTargetScope()
062: private String _location; // anchor to be added to the end of the hyperlink.
063: private Map _params; // Parameters
064: private Form _form; // the nearest form
065: private boolean _formSubmit = false; // should the anchor submit an enclosing form?
066: private PopupSupport _popupSupport = null; // popup support, if the popup attribute is set to true
067: private boolean _disableSecondClick = false; // When clicked, this anchor will disable itself after being clicked.
068:
069: /**
070: * Base support for the attribute tag. This is overridden to prevent setting the <code>href</code>
071: * attribute.
072: * @param name The name of the attribute. This value may not be null or the empty string.
073: * @param value The value of the attribute. This may contain an expression.
074: * @param facet The name of a facet to which the attribute will be applied. This is optional.
075: * @throws JspException A JspException may be thrown if there is an error setting the attribute.
076: */
077: public void setAttribute(String name, String value, String facet)
078: throws JspException {
079: if (name != null && name.equals(HREF)) {
080: String s = Bundle.getString("Tags_AttributeMayNotBeSet",
081: new Object[] { name });
082: registerTagError(s, null);
083: }
084: super .setAttribute(name, value, facet);
085: }
086:
087: /**
088: * Sets <code>shape</code> attribute for the area.
089: * @param shape the window target.
090: * @jsptagref.attributedescription The shape.
091: * @jsptagref.databindable false
092: * @jsptagref.attributesyntaxvalue <i>string_shape</i>
093: * @netui:attribute required="false" rtexprvalue="true"
094: * description="The shape."
095: */
096: public void setShape(String shape) {
097: _state.registerAttribute(AbstractHtmlState.ATTR_GENERAL, SHAPE,
098: shape);
099: }
100:
101: /**
102: * Sets <code>coords</code> attribute for the area.
103: * @param coords the window target.
104: * @jsptagref.attributedescription The coordinates.
105: * @jsptagref.databindable false
106: * @jsptagref.attributesyntaxvalue <i>string_coordinates</i>
107: * @netui:attribute required="false" rtexprvalue="true"
108: * description="The coordinates."
109: */
110: public void setCoords(String coords) {
111: _state.registerAttribute(AbstractHtmlState.ATTR_GENERAL,
112: COORDS, coords);
113: }
114:
115: /**
116: * Set the name of the action for the Area.
117: * @param action the name of the action to set for the Area.
118: * @jsptagref.attributedescription The action method to invoke. The action method must be in the Controller file
119: * of the Page Flow directory.
120: * @jsptagref.databindable false
121: * @jsptagref.attributesyntaxvalue <i>string_action</i>
122: * @netui:attribute required="false" rtexprvalue="true"
123: * description="The action method to invoke. The action method must be in the Controller file of the Page Flow directory."
124: */
125: public void setAction(String action) throws JspException {
126: _action = setRequiredValueAttribute(action, "action");
127: }
128:
129: /**
130: * Sets the href of the Anchor. This attribute will accept the empty String as a legal value.
131: * @param href the hyperlink URI for the Area.
132: * @jsptagref.attributedescription The URL to go to; if the URL needs
133: * parameters, use the parameter tag to get proper encoding of special
134: * characters in the parameters.
135: * @jsptagref.databindable false
136: * @jsptagref.attributesyntaxvalue <i>string_href</i>
137: * @netui:attribute required="false" rtexprvalue="true"
138: * description="The URL to go to."
139: * reftype="url"
140: */
141: public void setHref(String href) throws JspException {
142: _href = href;
143: }
144:
145: /**
146: * Sets the anchor to be added to the end of the generated hyperlink.
147: * @param location the name of the location anchor.
148: * @jsptagref.attributedescription Location within the URI to visit.
149: * @jsptagref.databindable false
150: * @jsptagref.attributesyntaxvalue <i>string_location</i>
151: * @netui:attribute required="false" rtexprvalue="true"
152: * description="Location within the URI to visit."
153: */
154: public void setLocation(String location) {
155: _location = setNonEmptyValueAttribute(location);
156: }
157:
158: /**
159: * Set the target "scope" for the anchor's action. Multiple active page flows may exist concurrently within named
160: * scopes. This attribute selects which named scope to use. If omitted, the default scope is assumed.
161: * @param targetScope the name of the target scope in which the associated action's page flow resides.
162: * @jsptagref.attributedescription The target scope in which the associated action's page flow resides.
163: * @jsptagref.databindable true
164: * @jsptagref.attributesyntaxvalue <i>string_targetScope</i>
165: * @netui:attribute required="false" rtexprvalue="true"
166: * description="The target scope in which the associated action's page flow resides"
167: */
168: public void setTargetScope(String targetScope) {
169: _targetScope = targetScope;
170: }
171:
172: /**
173: * Sets the formSubmit indicator.
174: * @param formSubmit whether or not the enclosing Form should be submitted.
175: * @jsptagref.attributedescription Boolean. If <code>formSubmit</code> is set to true, and the <netui:anchor> tag
176: * is within a <netui:form> tag,
177: * then the form data will be submitted to the method named in the
178: * <netui:form> tag's <code>action</code> attribute.
179: * @jsptagref.databindable false
180: * @jsptagref.attributesyntaxvalue <i>boolean_formSubmit</i>
181: * @netui:attribute required="false" rtexprvalue="true" type="boolean"
182: * description="If formSubmit is set to true, and the <netui:anchor> tag
183: * is within a <netui:form> tag,
184: * then the form data will be submitted to the method named in the
185: * <netui:form> tag's action attribute."
186: */
187: public void setFormSubmit(boolean formSubmit) {
188: _formSubmit = formSubmit;
189: }
190:
191: /**
192: * Sets the popup indicator.
193: * @param popup whether or not the anchor should open a popup window.
194: * @jsptagref.attributedescription Boolean. If <code>popup</code> is set to true,
195: * the anchor will open a popup window.
196: * @jsptagref.databindable true
197: * @jsptagref.attributesyntaxvalue <i>boolean_popup</i>
198: * @netui:attribute required="false" rtexprvalue="true" type="boolean"
199: * description="If popup is set to true, the anchor will open a popup window.
200: */
201: public void setPopup(boolean popup) {
202: _popupSupport = (popup ? new PopupSupport() : null);
203: }
204:
205: /**
206: * When true, this anchor will disable itself after being clicked.
207: * @param disableSecondClick when true, this anchor will disable itself after being clicked.
208: * @jsptagref.attributedescription Boolean. If <code>disableSecondClick</code> is set to true,
209: * the anchor will disable itself after being clicked.
210: * @jsptagref.databindable true
211: * @jsptagref.attributesyntaxvalue <i>boolean_disableSecondClick</i>
212: * @netui:attribute required="false" rtexprvalue="true" type="boolean"
213: * description="When true, this anchor will disable itself after being clicked."
214: */
215: public void setDisableSecondClick(boolean disableSecondClick) {
216: _disableSecondClick = disableSecondClick;
217: }
218:
219: /**
220: * Sets the tabIndex of the rendered html tag.
221: * @param tabindex the tab index.
222: * @jsptagref.attributedescription The tabIndex of the rendered HTML tag. This attribute determines the position of the
223: * rendered HTML tag in the sequence of tags that the user may advance through by pressing the TAB key.
224: * @jsptagref.databindable false
225: * @jsptagref.attributesyntaxvalue <i>string_tabIndex</i>
226: * @netui:attribute required="false" rtexprvalue="true" type="int"
227: * description="The tabIndex of the rendered HTML tag. This attribute determines the position of the
228: * rendered HTML tag in the sequence of tags that the user may advance through by pressing the TAB key."
229: */
230: public void setTabindex(int tabindex) {
231: _state.registerAttribute(AbstractHtmlState.ATTR_GENERAL,
232: TABINDEX, Integer.toString(tabindex));
233: }
234:
235: /**
236: * Sets the accessKey attribute value. This should key value of the
237: * keyboard navigation key. It is recommended not to use the following
238: * values because there are often used by browsers <code>A, C, E, F, G,
239: * H, V, left arrow, and right arrow</code>.
240: * @param accessKey the accessKey value.
241: * @jsptagref.attributedescription The keyboard navigation key for the element.
242: * The following values are not recommended because they
243: * are often used by browsers: <code>A, C, E, F, G,
244: * H, V, left arrow, and right arrow</code>
245: * @jsptagref.databindable false
246: * @jsptagref.attributesyntaxvalue <i>string_accessKey</i>
247: * @netui:attribute required="false" rtexprvalue="true" type="char"
248: * description=" The keyboard navigation key for the element.
249: * The following values are not recommended because they
250: * are often used by browsers: A, C, E, F, G,
251: * H, V, left arrow, and right arrow."
252: */
253: public void setAccessKey(char accessKey) {
254: if (accessKey == 0x00)
255: return;
256: _state.registerAttribute(AbstractHtmlState.ATTR_GENERAL,
257: ACCESSKEY, Character.toString(accessKey));
258: }
259:
260: /**
261: * Sets the onBlur javascript event.
262: * @param onblur the onBlur event.
263: * @jsptagref.attributedescription The onBlur JavaScript event.
264: * @jsptagref.databindable false
265: * @jsptagref.attributesyntaxvalue <i>string_onBlur</i>
266: * @netui:attribute required="false" rtexprvalue="true"
267: * description="The onBlur JavaScript event."
268: */
269: public void setOnBlur(String onblur) {
270: _state.registerAttribute(AbstractHtmlState.ATTR_JAVASCRIPT,
271: ONBLUR, onblur);
272: }
273:
274: /**
275: * Sets the onFocus javascript event.
276: * @param onfocus the onFocus event.
277: * @jsptagref.attributedescription The onFocus JavaScript event.
278: * @jsptagref.databindable false
279: * @jsptagref.attributesyntaxvalue <i>string_onFocus</i>
280: * @netui:attribute required="false" rtexprvalue="true"
281: * description="The onFocus JavaScript event."
282: */
283: public void setOnFocus(String onfocus) {
284: _state.registerAttribute(AbstractHtmlState.ATTR_JAVASCRIPT,
285: ONFOCUS, onfocus);
286: }
287:
288: //**************************** Helper Routines *********************************************
289:
290: /**
291: * Adds a URL parameter to the generated hyperlink.
292: * @param name the name of the parameter to be added.
293: * @param value the value of the parameter to be added (a String or String[]).
294: * @param facet
295: */
296: public void addParameter(String name, Object value, String facet)
297: throws JspException {
298: assert (name != null) : "Parameter 'name' must not be null";
299:
300: if (_params == null) {
301: _params = new HashMap();
302: }
303: ParamHelper.addParam(_params, name, value);
304: }
305:
306: /**
307: * This method will create the <a> portion of an anchor. It is called by subclasses, for example, the
308: * <code>ImageAnchor</code> relies on this code to generate the <a>.
309: * @param scriptRef a <code>ByRef<String></code> that will contain any JavaScript that may need to be added
310: * to the generated HTML response.
311: * @return a boolean value indicating if an error occur creating the anchor.
312: * @throws JspException
313: */
314: protected final boolean createAnchorBeginTag(
315: HttpServletRequest request, ByRef scriptRef,
316: TagRenderingBase trb, AbstractRenderAppender writer,
317: String requiredAttrs) throws JspException {
318: int have = 0;
319: String formAction = null;
320: String idScript = null;
321:
322: if (_formSubmit)
323: _form = getNearestForm();
324:
325: ScriptRequestState srs = ScriptRequestState
326: .getScriptRequestState(request);
327:
328: // check the parameters that the user provided
329: if (_href != null)
330: have++;
331: if (_action != null)
332: have++;
333: if (_clientAction != null)
334: have++;
335: if (_linkName != null)
336: have++;
337:
338: String tagId = getTagId();
339:
340: // if only the _linkName or _tagId is set then we are creating the name attribute only.
341: if (have == 0 && !_formSubmit && tagId != null) {
342: return createNameAnchor(request, trb);
343: }
344:
345: // if the anchor is submitting a consider this a submit level problem
346: // set the action to the form action.
347: if (_formSubmit) {
348: formAction = getFormAction();
349: if ((formAction != null) && (have == 0)) {
350: have++;
351: }
352: if (_action == null) {
353: _action = formAction;
354: _location = getFormLocation();
355: }
356: }
357:
358: // if we have not specified a destination or we've specified too many
359: // then we need to report an error.
360: if (have == 0 || have > 1) {
361: String s = Bundle.getString("Tags_Anchor_InvalidAnchorURI",
362: new Object[] { getTagName(), requiredAttrs });
363: registerTagError(s, null);
364: return false;
365: }
366:
367: if (_linkName != null) {
368: return createPageAnchor(request, trb);
369: }
370:
371: if (_action != null) {
372: // report that action is not an action
373: if (!PageFlowTagUtils.isAction(request, _action)) {
374: String s = null;
375: if (_action.equals("")) {
376: s = Bundle.getString("Tags_NullBadAction", null);
377: } else {
378: s = Bundle.getString("Tags_BadAction", _action);
379: }
380: registerTagError(s, null);
381: } else {
382: //
383: // If the action we're submitting to is checking for double-submits, save a token in the session.
384: // This will be written out as a param (below), and will be checked in PageFlowRequestProcessor.
385: //
386: String token = PageFlowTagUtils.getToken(request,
387: _action);
388: if (token != null) {
389: addParamInternal(Constants.TOKEN_KEY, token);
390: }
391: }
392: }
393:
394: // we assume that tagId will over have override id if both
395: // are defined.
396: if (tagId != null) {
397: _state.id = tagId;
398: idScript = renderNameAndId(request, _state, null);
399: }
400:
401: // Special case for name anchors
402: if (_clientAction != null) {
403: _state.href = "";
404: } else {
405: // Add the scope-ID parameter, if the targetScope attribute is present.
406: if (_targetScope != null) {
407: addParamInternal(ScopedServletUtils.SCOPE_ID_PARAM,
408: _targetScope);
409: }
410:
411: if (_popupSupport != null) {
412: _popupSupport.addParams(this , request);
413: }
414:
415: // Generate the opening anchor element
416: String uri = null;
417: try {
418: if (_action != null) {
419: uri = PageFlowTagUtils.rewriteActionURL(
420: pageContext, _action, _params, _location);
421: } else if (_href != null) {
422: uri = PageFlowTagUtils.rewriteHrefURL(pageContext,
423: _href, _params, _location);
424: }
425: } catch (URISyntaxException e) {
426: // report the error...
427: logger.error(Bundle
428: .getString("Tags_URISyntaxException"));
429: String s = Bundle.getString("Tags_Anchor_URLException",
430: new Object[] { e.getMessage() });
431: registerTagError(s, e);
432: }
433:
434: if (uri == null) {
435: if (hasErrors())
436: return false;
437: } else {
438: HttpServletResponse response = (HttpServletResponse) pageContext
439: .getResponse();
440: _state.href = response.encodeURL(uri);
441: }
442: }
443:
444: // We need to combine the onclick features
445: IScriptReporter sr = getScriptReporter();
446: if (_clientAction != null
447: && srs.isFeatureWritten(CoreScriptFeature.DYNAMIC_INIT)) {
448: //@todo: we need to support onclick chaining here also...
449: String action = HtmlUtils.escapeEscapes(_clientAction);
450: String entry = ScriptRequestState.getString("netuiAction",
451: new Object[] { action });
452: _state.registerAttribute(AbstractHtmlState.ATTR_JAVASCRIPT,
453: ONCLICK, entry);
454: // Jira 299
455: //_state.onClick = entry;
456: }
457:
458: // if the user overrides the onclick we will ignore this
459: String onclick = _state.getAttribute(
460: AbstractHtmlState.ATTR_JAVASCRIPT, ONCLICK);
461: if (onclick == null) {
462: if (_formSubmit && formAction != null) {
463: String realFormName = getFormId();
464: String key = (_disableSecondClick ? "anchorDisableAndSubmitFormAction"
465: : "anchorFormSubmitAction");
466: String entry = ScriptRequestState.getString(key,
467: new Object[] { realFormName, _state.href });
468: _state.registerAttribute(
469: AbstractHtmlState.ATTR_JAVASCRIPT, ONCLICK,
470: entry);
471: // Jira 299
472: //_state.onClick = ScriptRequestState.getString("anchorFormSubmitAction",
473: // new Object[]{realFormName, _state.href});
474: if (_form != null)
475: _form.insureRealId();
476: } else if (_disableSecondClick) {
477: String entry = ScriptRequestState.getString(
478: "anchorDisableAction", null);
479: _state.registerAttribute(
480: AbstractHtmlState.ATTR_JAVASCRIPT, ONCLICK,
481: entry);
482: } else if (_popupSupport != null) {
483: _state.registerAttribute(
484: AbstractHtmlState.ATTR_JAVASCRIPT, ONCLICK,
485: _popupSupport.getOnClick(request, _state.href));
486: // Jira 299
487: //_state.onClick = _popupSupport.getOnClick(_state.href);
488: }
489: }
490:
491: if (hasErrors())
492: return false;
493:
494: trb.doStartTag(writer, _state);
495:
496: // Emit javascript if this anchor needs to sumbit the form or open a popup window
497: if (_formSubmit && formAction != null || idScript != null
498: || _popupSupport != null) {
499: InternalStringBuilder script = new InternalStringBuilder(32);
500: StringBuilderRenderAppender scriptWriter = new StringBuilderRenderAppender(
501: script);
502:
503: if (_formSubmit && formAction != null)
504: srs.writeFeature(sr, scriptWriter,
505: CoreScriptFeature.ANCHOR_SUBMIT, true, false,
506: null);
507: if (_popupSupport != null)
508: _popupSupport.writeScript(request, srs,
509: getScriptReporter(), scriptWriter);
510: if (idScript != null)
511: scriptWriter.append(idScript);
512: scriptRef.setRef(script.toString());
513: }
514:
515: // create the javaScript
516: return true;
517: }
518:
519: private void addParamInternal(String paramName, String paramVal) {
520: if (_params == null) {
521: _params = new HashMap();
522: }
523: _params.put(paramName, paramVal);
524: }
525:
526: /**
527: * @param req
528: * @param trb
529: * @return a boolean indicating if an error has occurred or not
530: */
531: private boolean createNameAnchor(ServletRequest req,
532: TagRenderingBase trb) {
533: assert (_state.id != null) : "tagId must not be nulll";
534:
535: WriteRenderAppender writer = new WriteRenderAppender(
536: pageContext);
537:
538: // the tagId must be qualified and we need to output the lookup methods
539: // @todo: need to output the Script?
540: // @todo: I don't believe that this works. The id is not encoded correctly
541: _state.id = getIdForTagId(_state.id);
542: String script = renderDefaultNameAndId(
543: (HttpServletRequest) req, _state, _state.id, null);
544:
545: //set the name so legacy browsers can support this.
546: _state.name = _state.id;
547:
548: // output the tag.
549: trb = TagRenderingBase.Factory.getRendering(
550: TagRenderingBase.ANCHOR_TAG, req);
551: trb.doStartTag(writer, _state);
552: return !hasErrors();
553: }
554:
555: /**
556: * This method will output an anchor with a fragment identifier. This should
557: * be a valid ID within the document. If the name begins with the "#" we will
558: * not qualify the set link name. If the name doesn't begin with #, then we
559: * will qualify it into the current scope container.
560: * @param req The servlet request.
561: * @param trb The TagRenderer that will output the link
562: * @return return a boolean indicating if an error occurred or not
563: */
564: private boolean createPageAnchor(ServletRequest req,
565: TagRenderingBase trb) {
566: // create the fragment identifier. If the _linkName starts with
567: // '#' then we treat it as if it was fully qualified. Otherwise we
568: // need to qualify it before we add the '#'.
569: _linkName = _linkName.trim();
570: if (_linkName.charAt(0) != '#') {
571: _state.href = "#" + getIdForTagId(_linkName);
572: } else {
573: _state.href = _linkName;
574: }
575:
576: // if the tagId was set then rewrite it and output it.
577: if (_state.id != null) {
578: //_state.id = getIdForTagId(_state.id);
579: _state.name = _state.id;
580: renderNameAndId((HttpServletRequest) req, _state, null);
581: //renderDefaultNameAndId((HttpServletRequest) req, _state, _state.id, _state.id);
582: }
583:
584: // write out the tag.
585: WriteRenderAppender writer = new WriteRenderAppender(
586: pageContext);
587: trb = TagRenderingBase.Factory.getRendering(
588: TagRenderingBase.ANCHOR_TAG, req);
589: trb.doStartTag(writer, _state);
590: return !hasErrors();
591: }
592:
593: /**
594: * Return the action attribute for the nearest form.
595: * @return The action attribute of the enclosing form
596: */
597: private String getFormAction() {
598: if (_form != null)
599: return _form.getAction();
600: return null;
601: }
602:
603: private String getFormLocation() {
604: if (_form != null)
605: return _form.getLocation();
606: return null;
607: }
608:
609: /**
610: * This will get the real name of the form. This is set in the
611: * id attribute.
612: * @return The String real name of the containing form.
613: */
614: private String getFormId() {
615: if (_form != null) {
616: return _form.getRealFormId();
617: }
618: return null;
619: }
620:
621: /**
622: * Release any acquired resources.
623: */
624: protected void localRelease() {
625: super .localRelease();
626:
627: _state.clear();
628: _linkName = null;
629: _clientAction = null;
630:
631: _action = null;
632: _href = null;
633: _targetScope = null;
634: _location = null;
635: _params = null;
636: _form = null;
637: _formSubmit = false;
638: _popupSupport = null;
639: _disableSecondClick = false;
640: }
641:
642: public PopupSupport getPopupSupport() {
643: return _popupSupport;
644: }
645: }
|