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.util.internal.InternalStringBuilder;
022:
023: import org.apache.beehive.netui.core.urls.URLRewriterService;
024: import org.apache.beehive.netui.pageflow.PageFlowUtils;
025: import org.apache.beehive.netui.pageflow.scoping.ScopedServletUtils;
026: import org.apache.beehive.netui.tags.HtmlUtils;
027: import org.apache.beehive.netui.tags.IHtmlAccessable;
028: import org.apache.beehive.netui.tags.internal.PageFlowTagUtils;
029: import org.apache.beehive.netui.tags.javascript.CoreScriptFeature;
030: import org.apache.beehive.netui.tags.javascript.IScriptReporter;
031: import org.apache.beehive.netui.tags.javascript.ScriptRequestState;
032: import org.apache.beehive.netui.tags.rendering.*;
033: import org.apache.beehive.netui.util.Bundle;
034: import org.apache.beehive.netui.util.ParamHelper;
035: import org.apache.beehive.netui.util.logging.Logger;
036: import org.apache.struts.taglib.html.Constants;
037:
038: import javax.servlet.ServletContext;
039: import javax.servlet.http.HttpServletRequest;
040: import javax.servlet.http.HttpServletResponse;
041: import javax.servlet.jsp.JspException;
042: import java.net.URISyntaxException;
043: import java.util.HashMap;
044: import java.util.Map;
045:
046: /**
047: * Generates a button on the page with the specified attributes. The
048: * <netui:button> tag must be enclosed in <netui:form...> ...
049: * </netui:form> tags. You can specify the action that the form will
050: * raise on the <netui:form> tag.
051: * @jsptagref.tagdescription Renders an <input type="button"> tag with the specified attributes.
052: * To submit data or invoke a method on the Controller file, the
053: * <netui:button> tag must have a parent {@link Form} tag.
054: *
055: * <p>The action attribute on the <netui:button> is for the purpose
056: * of overriding the action attribute on the enclosing <netui:form> tag.
057: * If no action attribute is specified on the <netui:button> tag,
058: * the action attribute on the <netui:form> tag will determine which
059: * action method is invoked.
060: * </p>
061: *
062: * <p>This tag can also render a <button> control to allow richer
063: * rendering possibilities. When the <code>renderAsButton</code>
064: * attribute is set to <code>true</code>, we render the markup with
065: * an HTML <button> tag, using the value attribute, and placing
066: * the evaluated body content as the content between the begin and
067: * end HTML button tags.
068: * </p>
069: *
070: * <p>Please note that using the action override and rendering the
071: * markup with a <button> control will not work in Internet Explorer.
072: * Internet Explorer includes the name and value of every <button>
073: * control in the HTML form as parameters in the request query,
074: * regardless of whether the button has been clicked or not. The
075: * action override attribute renders a name attribute on the HTML tag.
076: * This name is used to identified by the NetUI request processor to
077: * determine if an action method other than the one on the
078: * <netui:form> tag should be invoked. With IE, the name attribute
079: * of the <button> control will always be included in the parameters
080: * or the request query and NetUI will assume an action override button
081: * was clicked, leading to incorrect behavior.
082: * </p>
083: * @example In this sample, the <netui:button> submits data to
084: * the Controller file's <code>processData</code> action method (specified on the <netui:form>'s action
085: * attribute).
086: * <pre>
087: * <netui:form action="processData">
088: * <!--
089: * input elements here
090: * -->
091: * <netui:button value="Submit" type="submit"/>
092: * </netui:form></pre>
093: * @netui:tag name="button" description="Create a button on your JSP page."
094: */
095: public class Button extends HtmlFocusBaseTag implements IUrlParams,
096: IHtmlAccessable, IHasPopupSupport {
097: private static final Logger logger = Logger
098: .getInstance(Button.class);
099:
100: public static final String ACTION_OVERRIDE = "actionOverride:";
101:
102: private InputSubmitTag.State _state = new ButtonTag.State();
103:
104: private String _action; // The action which will override the action on the form
105: private String _value; // The text of the button (this will override any body text).
106: private String _text; // The body content of this tag (if any).
107: private Map _params; // Any parameters to the submit
108: private String _targetScope; // Target page flow scope; see comments on setTargetScope()
109: private PopupSupport _popupSupport = null; // popup support, if the popup attribute is set to true
110: private boolean _disableSecondClick = false; // When clicked, this button will disable itself before submitting.
111: private boolean _renderAsButton = false; // Write the HTML markup using the <button> element.
112:
113: /**
114: * Return the name of the Tag.
115: */
116: public String getTagName() {
117: return "Button";
118: }
119:
120: /**
121: * This method will return the state associated with the tag. This is used by this
122: * base class to access the individual state objects created by the tags.
123: * @return a subclass of the <code>AbstractHtmlState</code> class.
124: */
125: protected AbstractHtmlState getState() {
126: return _state;
127: }
128:
129: /**
130: * Base support for the attribute tag. This is overridden to prevent setting the <code>type</code>
131: * and <code>value</code> attributes.
132: * @param name The name of the attribute. This value may not be null or the empty string.
133: * @param value The value of the attribute.
134: * @param facet The name of a facet to which the attribute will be applied. This is optional.
135: * @throws JspException A JspException may be thrown if there is an error setting the attribute.
136: */
137: public void setAttribute(String name, String value, String facet)
138: throws JspException {
139: if (name != null && (name.equals(TYPE) || name.equals(VALUE))) {
140: String s = Bundle.getString("Tags_AttributeMayNotBeSet",
141: new Object[] { name });
142: registerTagError(s, null);
143: }
144: super .setAttribute(name, value, facet);
145: }
146:
147: /**
148: * Set the name of the action for the Button.
149: * @param action the name of the action to set for the Button.
150: * @jsptagref.attributedescription The action method invoked. The value of this attribute will override
151: * the <code>action</code>
152: * attribute of the parent <netui:form> tag.
153: * @jsptagref.databindable false
154: * @jsptagref.attributesyntaxvalue <i>string_action</i>
155: * @netui:attribute required="false" rtexprvalue="true"
156: * description="The action method invoked. The value of this attribute will override
157: * the action attribute of the parent <netui:form> tag."
158: */
159: public void setAction(String action) throws JspException {
160: _action = setRequiredValueAttribute(action, "action");
161: }
162:
163: /**
164: * Set the target "scope" for the button's action. Multiple active page flows may exist concurrently within named
165: * scopes. This attribute selects which named scope to use. If omitted, the default scope is assumed.
166: * @param targetScope the name of the target scope in which the associated action's page flow resides.
167: * @jsptagref.attributedescription The target scope in which the associated action's page flow resides.
168: * @jsptagref.databindable true
169: * @jsptagref.attributesyntaxvalue <i>string_targetScope</i>
170: * @netui:attribute required="false" rtexprvalue="true"
171: * description="The target scope in which the associated action's page flow resides"
172: */
173: public void setTargetScope(String targetScope) {
174: _targetScope = targetScope;
175: }
176:
177: /**
178: * Set the type of the Button (submit, button, or reset).
179: * @param type the type of the Button.
180: * @jsptagref.attributedescription The type of the button. Possible values are <code>submit</code>, <code>button</code>, or <code>reset</code>.
181: * The default value is <code>submit</code>.
182: * @jsptagref.databindable false
183: * @jsptagref.attributesyntaxvalue <i>string_type</i>
184: * @netui:attribute required="false" rtexprvalue="true"
185: * description="The type of the button. Possible values are submit, button, or reset.
186: * The default value is submit."
187: */
188: public void setType(String type) throws JspException {
189: if (INPUT_SUBMIT.equals(type) || INPUT_BUTTON.equals(type)
190: || INPUT_RESET.equals(type)) {
191: _state.type = type;
192: return;
193: }
194: String s = Bundle.getString("Tags_ButtonTypeError",
195: new Object[] { type });
196: registerTagError(s, null);
197: }
198:
199: /**
200: * Set the value of the Button's text.
201: * @param value the value of the Button's text.
202: * @jsptagref.attributedescription The text displayed by the rendered HTML button.
203: * @jsptagref.databindable Read Only
204: * @jsptagref.attributesyntaxvalue <i>string_value</i>
205: * @netui:attribute required="false" rtexprvalue="true"
206: * description="The text displayed by the rendered HTML button."
207: */
208: public void setValue(String value) throws JspException {
209: _value = setNonEmptyValueAttribute(value);
210: }
211:
212: /**
213: * Sets the popup indicator.
214: * @param popup whether or not the button should open a popup window.
215: * @jsptagref.attributedescription Boolean. If <code>popup</code> is set to true,
216: * the button will open a popup window.
217: * @jsptagref.databindable true
218: * @jsptagref.attributesyntaxvalue <i>boolean_popup</i>
219: * @netui:attribute required="false" rtexprvalue="true" type="boolean"
220: * description="If popup is set to true, the button will open a popup window."
221: */
222: public void setPopup(boolean popup) {
223: _popupSupport = (popup ? new PopupSupport() : null);
224: }
225:
226: /**
227: * When true, this button will disable itself before submitting.
228: * @param disableSecondClick when true, this button will disable itself before submitting.
229: * @jsptagref.attributedescription Boolean. If <code>disableSecondClick</code> is set to true,
230: * the button will disable itself before submitting.
231: * @jsptagref.databindable true
232: * @jsptagref.attributesyntaxvalue <i>boolean_disableSecondClick</i>
233: * @netui:attribute required="false" rtexprvalue="true" type="boolean"
234: * description="When true, this button will disable itself before submitting."
235: */
236: public void setDisableSecondClick(boolean disableSecondClick) {
237: _disableSecondClick = disableSecondClick;
238: }
239:
240: /**
241: * When set to true, this tag will render the <button> control in the HTML markup,
242: * rather than the <input type="..."> element. The evaluated content between
243: * the start and end tags will be written out as the content between the rendered
244: * HTML <button> and </button>.
245: *
246: * <p>Please note that using an action override and rendering the
247: * markup with a <button> control will not work in Internet Explorer.
248: * Internet Explorer includes the name and value of every <button>
249: * control in the HTML form as parameters in the request query,
250: * regardless of whether the button has been clicked or not.
251: * </p>
252: * @param renderAsButton whether or not to render a <button> rather than an <input> tag.
253: * @jsptagref.attributedescription Boolean. If <code>renderAsButton</code> is set to true,
254: * render a <button> rather than an <input> tag.
255: * @jsptagref.databindable true
256: * @jsptagref.attributesyntaxvalue <i>boolean_renderAsButton</i>
257: * @netui:attribute required="false" rtexprvalue="true" type="boolean"
258: * description="When true, render a <button> rather than an <input> tag."
259: */
260: public void setRenderAsButton(boolean renderAsButton) {
261: _renderAsButton = renderAsButton;
262: }
263:
264: /**
265: * Adds a URL parameter to the generated hyperlink.
266: * @param name the name of the parameter to be added.
267: * @param value the value of the parameter to be added (a String or String[]).
268: * @param facet
269: */
270: public void addParameter(String name, Object value, String facet)
271: throws JspException {
272: assert (name != null) : "Parameter 'name' must not be null";
273:
274: if (_params == null) {
275: _params = new HashMap();
276: }
277: ParamHelper.addParam(_params, name, value);
278: }
279:
280: /**
281: * Process the start of the Button.
282: * @throws JspException if a JSP exception has occurred
283: */
284: public int doStartTag() throws JspException {
285: return EVAL_BODY_BUFFERED;
286: }
287:
288: /**
289: * Save the associated button label from the body content (if any).
290: * By default, we render the HTML markup with an <input> tag and
291: * if the value attribute is null then we will use the body content
292: * as the value attribute.
293: *
294: * <p>However, if the <code>renderAsButton</code> attribute
295: * is set to true, then we render the HTML markup with an HTML
296: * <button> tag, using the value attribute, and placing the
297: * evaluated body content as the content between the begin and
298: * end HTML button tags.
299: * </p>
300: *
301: * @throws JspException if a JSP exception has occurred
302: */
303: public int doAfterBody() throws JspException {
304: if (bodyContent != null) {
305: String text = bodyContent.getString().trim();
306: if (text.length() <= 0)
307: text = null;
308:
309: if (text != null) {
310: if (_renderAsButton) {
311: _text = text;
312: } else {
313: if (_value == null) {
314: // use the <input> element with text as the value
315: _value = text;
316: }
317: }
318: }
319: bodyContent.clearBody();
320: }
321: return SKIP_BODY;
322: }
323:
324: /**
325: * Render the button.
326: * @throws JspException if a JSP exception has occurred
327: */
328: public int doEndTag() throws JspException {
329: String idScript = null;
330:
331: HttpServletRequest request = (HttpServletRequest) pageContext
332: .getRequest();
333: HttpServletResponse response = (HttpServletResponse) pageContext
334: .getResponse();
335:
336: // Acquire the label value we will be generating
337: if (_value == null)
338: _value = Bundle.getString("Tags_ButtonText", null);
339: _state.value = _value;
340:
341: // Generate an HTML element
342: _state.disabled = isDisabled();
343:
344: // Add parameters for popup window support.
345: if (_popupSupport != null) {
346: _popupSupport.addParams(this , request);
347: }
348:
349: // targetScope implies an extra parameter. If there's no action on this button, get the action from the
350: // nearest form, so we can construct an action url with our extra parameter.
351: if (_targetScope != null && _action == null) {
352: Form parentForm = getNearestForm();
353: if (parentForm != null) {
354: _action = parentForm.getAction();
355: }
356: }
357:
358: if (_action == null && _params != null && _popupSupport == null) {
359: Form parentForm = getNearestForm();
360: if (parentForm != null) {
361: _action = parentForm.getAction();
362: }
363: }
364:
365: if (_action != null) {
366: boolean isAction = PageFlowTagUtils.isAction(request,
367: _action);
368: if (isAction) {
369:
370: // If the action we're submitting to is checking for double-submits, save a token in the session.
371: // This will be written out as a param (below), and will be checked in PageFlowRequestProcessor.
372: String token = PageFlowTagUtils.getToken(request,
373: _action);
374: if (token != null) {
375: if (_params == null) {
376: _params = new HashMap();
377: }
378: _params.put(Constants.TOKEN_KEY, token);
379: }
380:
381: // Add the scope ID parameter if there's one on the tag, or if there's one in the request.
382: if (_targetScope != null) {
383: if (_params == null) {
384: _params = new HashMap();
385: }
386: _params.put(ScopedServletUtils.SCOPE_ID_PARAM,
387: _targetScope);
388: }
389:
390: String overrideAction = ACTION_OVERRIDE + _action;
391: overrideAction = HtmlUtils.addParams(overrideAction,
392: _params, response.getCharacterEncoding());
393: String buttonOutput = URLRewriterService.getNamePrefix(
394: pageContext.getServletContext(), pageContext
395: .getRequest(), overrideAction)
396: + overrideAction;
397: if (buttonOutput.indexOf(";") > -1) {
398: buttonOutput = buttonOutput.substring(0,
399: buttonOutput.indexOf(";"));
400: }
401: _state.name = buttonOutput;
402:
403: // don't write the id attribute
404: Form parentForm = getNearestForm();
405: idScript = renderNameAndId(request, _state, parentForm);
406: } else {
407: // set the error because the action is invalid
408: registerTagError(Bundle.getString("Tags_BadAction",
409: _action), null);
410: }
411: } else {
412: Form parentForm = getNearestForm();
413: idScript = renderNameAndId(request, _state, parentForm);
414: }
415:
416: boolean buttonDisableAndSubmit = false;
417: boolean buttonDisable = false;
418:
419: // if the user overrides the onclick we will ignore this
420: if (getOnClick() == null) {
421: if (_disableSecondClick) {
422: Form parentForm = getNearestForm();
423: String href = getActionUrl(request, response);
424: String entry;
425: if (parentForm != null
426: && href != null
427: && (_state.type == null || _state.type == INPUT_SUBMIT)) {
428: entry = ScriptRequestState.getString(
429: "buttonDisableAndSubmitFormAction",
430: new Object[] { parentForm.getRealFormId(),
431: href });
432: buttonDisableAndSubmit = true;
433: } else {
434: entry = ScriptRequestState.getString(
435: "buttonDisableAction", null);
436: buttonDisable = true;
437: }
438: _state.registerAttribute(
439: AbstractHtmlState.ATTR_JAVASCRIPT, ONCLICK,
440: entry);
441: if (parentForm != null)
442: parentForm.insureRealId();
443: } else if (_popupSupport != null) {
444: String href = getActionUrl(request, response);
445:
446: if (href != null) {
447: href = response.encodeURL(href);
448: setOnClick(_popupSupport.getOnClick(request, href));
449: }
450: }
451: }
452:
453: // report any errors that may have been generated.
454: if (hasErrors())
455: return reportAndExit(EVAL_PAGE);
456:
457: WriteRenderAppender writer = new WriteRenderAppender(
458: pageContext);
459: TagRenderingBase br = null;
460: if (_renderAsButton) {
461: br = TagRenderingBase.Factory.getRendering(
462: TagRenderingBase.BUTTON_TAG, request);
463: br.doStartTag(writer, _state);
464: // add the body content
465: if (_text != null)
466: write(_text);
467: else if (_value != null)
468: write(_value);
469: } else {
470: br = TagRenderingBase.Factory.getRendering(
471: TagRenderingBase.INPUT_SUBMIT_TAG, request);
472: br.doStartTag(writer, _state);
473: }
474: br.doEndTag(writer);
475:
476: //Emit javascript if this button needs to sumbit the form or open a popup window
477: if (idScript != null || _popupSupport != null || buttonDisable
478: || buttonDisableAndSubmit) {
479: ScriptRequestState srs = ScriptRequestState
480: .getScriptRequestState(request);
481: InternalStringBuilder script = new InternalStringBuilder(32);
482: StringBuilderRenderAppender scriptWriter = new StringBuilderRenderAppender(
483: script);
484: IScriptReporter sr = getScriptReporter();
485:
486: if (buttonDisableAndSubmit)
487: srs.writeFeature(sr, scriptWriter,
488: CoreScriptFeature.BUTTON_DISABLE_AND_SUBMIT,
489: true, false, null);
490: if (buttonDisable)
491: srs.writeFeature(sr, scriptWriter,
492: CoreScriptFeature.BUTTON_DISABLE, true, false,
493: null);
494: if (_popupSupport != null)
495: _popupSupport.writeScript(request, srs,
496: getScriptReporter(), scriptWriter);
497: if (idScript != null)
498: scriptWriter.append(idScript);
499: write(script.toString());
500: }
501:
502: // Evaluate the remainder of this page
503: localRelease();
504: return EVAL_PAGE;
505: }
506:
507: private String getActionUrl(HttpServletRequest request,
508: HttpServletResponse response) throws JspException {
509: String href = null;
510:
511: if (_action != null) {
512: ServletContext servletContext = pageContext
513: .getServletContext();
514: boolean forXML = TagRenderingBase.Factory.isXHTML(request);
515: try {
516: href = PageFlowUtils.getRewrittenActionURI(
517: servletContext, request, response, _action,
518: _params, null, forXML);
519: } catch (URISyntaxException e) {
520: // report the error...
521: logger.error(Bundle
522: .getString("Tags_URISyntaxException"));
523: String s = Bundle.getString("Tags_Button_URLException",
524: new Object[] { _action, e.getMessage() });
525: registerTagError(s, e);
526: }
527: } else {
528: Form parentForm = getNearestForm();
529: if (parentForm != null)
530: href = HtmlUtils.addParams(parentForm.getActionUrl(),
531: _params, response.getCharacterEncoding());
532: }
533:
534: return href;
535: }
536:
537: /**
538: * Release any acquired resources.
539: */
540: protected void localRelease() {
541: super .localRelease();
542:
543: _state.clear();
544: _action = null;
545: _value = null;
546: _text = null;
547: _params = null;
548: _targetScope = null;
549: _popupSupport = null;
550: _disableSecondClick = false;
551: _renderAsButton = false;
552: }
553:
554: /* ==================================================================
555: *
556: * This tag's publically exposed HTML, CSS, and JavaScript attributes
557: *
558: * ==================================================================
559: */
560:
561: /**
562: * Sets the accessKey attribute value. This should key value of the
563: * keyboard navigation key. It is recommended not to use the following
564: * values because there are often used by browsers <code>A, C, E, F, G,
565: * H, V, left arrow, and right arrow</code>.
566: * @param accessKey the accessKey value.
567: * @jsptagref.attributedescription The keyboard navigation key for the element.
568: * The following values are not recommended because they
569: * are often used by browsers: <code>A, C, E, F, G,
570: * H, V, left arrow, and right arrow</code>
571: * @jsptagref.databindable false
572: * @jsptagref.attributesyntaxvalue <i>string_accessKey</i>
573: * @netui:attribute required="false" rtexprvalue="true" type="char"
574: * description="The keyboard navigation key for the element.
575: * The following values are not recommended because they
576: * are often used by browsers: A, C, E, F, G,
577: * H, V, left arrow, and right arrow"
578: */
579: public void setAccessKey(char accessKey) {
580: if (accessKey == 0x00)
581: return;
582: _state.registerAttribute(AbstractHtmlState.ATTR_GENERAL,
583: ACCESSKEY, Character.toString(accessKey));
584: }
585:
586: /**
587: * Sets the alt attribute value.
588: * @param alt the alt value.
589: * @jsptagref.attributedescription The alt attribute of the element.
590: * @jsptagref.databindable Read Only
591: * @jsptagref.attributesyntaxvalue <i>string_alt</i>
592: * @netui:attribute required="false" rtexprvalue="true"
593: * description="The alt attribute of the element."
594: */
595: public void setAlt(String alt) {
596: _state.registerAttribute(AbstractHtmlState.ATTR_GENERAL, ALT,
597: alt);
598: }
599:
600: /**
601: * Sets the tabIndex of the rendered html tag.
602: * @param tabindex the tab index.
603: * @jsptagref.attributedescription The tabIndex of the rendered HTML tag. This attribute determines the position of the
604: * tag in the sequence of page elements that the user may advance through by pressing the TAB key.
605: * @jsptagref.databindable false
606: * @jsptagref.attributesyntaxvalue <i>string_tabIndex</i>
607: * @netui:attribute required="false" rtexprvalue="true" type="int"
608: * description="The tabIndex of the rendered HTML tag. This attribute determines the position of the
609: * tag in the sequence of page elements that the user may advance through by pressing the TAB key."
610: */
611: public void setTabindex(int tabindex) {
612: _state.registerAttribute(AbstractHtmlState.ATTR_GENERAL,
613: TABINDEX, Integer.toString(tabindex));
614: }
615:
616: public PopupSupport getPopupSupport() {
617: return _popupSupport;
618: }
619: }
|