001: /*
002: * $Id: FormTag.java 479633 2006-11-27 14:25:35Z pbenedict $
003: *
004: * Licensed to the Apache Software Foundation (ASF) under one
005: * or more contributor license agreements. See the NOTICE file
006: * distributed with this work for additional information
007: * regarding copyright ownership. The ASF licenses this file
008: * to you under the Apache License, Version 2.0 (the
009: * "License"); you may not use this file except in compliance
010: * with the License. You may obtain a copy of the License at
011: *
012: * http://www.apache.org/licenses/LICENSE-2.0
013: *
014: * Unless required by applicable law or agreed to in writing,
015: * software distributed under the License is distributed on an
016: * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017: * KIND, either express or implied. See the License for the
018: * specific language governing permissions and limitations
019: * under the License.
020: */
021: package org.apache.struts.taglib.html;
022:
023: import org.apache.struts.Globals;
024: import org.apache.struts.action.ActionForm;
025: import org.apache.struts.action.ActionMapping;
026: import org.apache.struts.action.ActionServlet;
027: import org.apache.struts.config.ActionConfig;
028: import org.apache.struts.config.FormBeanConfig;
029: import org.apache.struts.config.ModuleConfig;
030: import org.apache.struts.taglib.TagUtils;
031: import org.apache.struts.util.MessageResources;
032: import org.apache.struts.util.RequestUtils;
033:
034: import javax.servlet.http.HttpServletRequest;
035: import javax.servlet.http.HttpServletResponse;
036: import javax.servlet.http.HttpSession;
037: import javax.servlet.jsp.JspException;
038: import javax.servlet.jsp.JspWriter;
039: import javax.servlet.jsp.PageContext;
040: import javax.servlet.jsp.tagext.TagSupport;
041:
042: import java.io.IOException;
043:
044: /**
045: * Custom tag that represents an input form, associated with a bean whose
046: * properties correspond to the various fields of the form.
047: *
048: * @version $Rev: 479633 $ $Date: 2006-11-27 08:25:35 -0600 (Mon, 27 Nov 2006) $
049: */
050: public class FormTag extends TagSupport {
051: /**
052: * The line ending string.
053: */
054: protected static String lineEnd = System
055: .getProperty("line.separator");
056:
057: /**
058: * The message resources for this package.
059: */
060: protected static MessageResources messages = MessageResources
061: .getMessageResources(Constants.Package + ".LocalStrings");
062:
063: // ----------------------------------------------------- Instance Variables
064:
065: /**
066: * The action URL to which this form should be submitted, if any.
067: */
068: protected String action = null;
069:
070: /**
071: * A postback action URL to which this form should be submitted, if any.
072: */
073: private String postbackAction = null;
074:
075: /**
076: * The module configuration for our module.
077: */
078: protected ModuleConfig moduleConfig = null;
079:
080: /**
081: * The content encoding to be used on a POST submit.
082: */
083: protected String enctype = null;
084:
085: /**
086: * The name of the field to receive focus, if any.
087: */
088: protected String focus = null;
089:
090: /**
091: * The index in the focus field array to receive focus. This only applies
092: * if the field given in the focus attribute is actually an array of
093: * fields. This allows a specific field in a radio button array to
094: * receive focus while still allowing indexed field names like
095: * "myRadioButtonField[1]" to be passed in the focus attribute.
096: *
097: * @since Struts 1.1
098: */
099: protected String focusIndex = null;
100:
101: /**
102: * The ActionMapping defining where we will be submitting this form
103: */
104: protected ActionMapping mapping = null;
105:
106: /**
107: * The request method used when submitting this form.
108: */
109: protected String method = null;
110:
111: /**
112: * The onReset event script.
113: */
114: protected String onreset = null;
115:
116: /**
117: * The onSubmit event script.
118: */
119: protected String onsubmit = null;
120:
121: /**
122: * Include language attribute in the focus script's <script>
123: * element. This property is ignored in XHTML mode.
124: *
125: * @since Struts 1.2
126: */
127: protected boolean scriptLanguage = true;
128:
129: /**
130: * The ActionServlet instance we are associated with (so that we can
131: * initialize the <code>servlet</code> property on any form bean that we
132: * create).
133: */
134: protected ActionServlet servlet = null;
135:
136: /**
137: * The style attribute associated with this tag.
138: */
139: protected String style = null;
140:
141: /**
142: * The style class associated with this tag.
143: */
144: protected String styleClass = null;
145:
146: /**
147: * The identifier associated with this tag.
148: */
149: protected String styleId = null;
150:
151: /**
152: * The window target.
153: */
154: protected String target = null;
155:
156: /**
157: * The name of the form bean to (create and) use. This is either the same
158: * as the 'name' attribute, if that was specified, or is obtained from the
159: * associated <code>ActionMapping</code> otherwise.
160: */
161: protected String beanName = null;
162:
163: /**
164: * The scope of the form bean to (create and) use. This is either the same
165: * as the 'scope' attribute, if that was specified, or is obtained from
166: * the associated <code>ActionMapping</code> otherwise.
167: */
168: protected String beanScope = null;
169:
170: /**
171: * The type of the form bean to (create and) use. This is either the same
172: * as the 'type' attribute, if that was specified, or is obtained from the
173: * associated <code>ActionMapping</code> otherwise.
174: */
175: protected String beanType = null;
176:
177: /**
178: * The list of character encodings for input data that the server should
179: * accept.
180: */
181: protected String acceptCharset = null;
182:
183: /**
184: * Controls whether child controls should be 'disabled'.
185: */
186: private boolean disabled = false;
187:
188: /**
189: * Controls whether child controls should be 'readonly'.
190: */
191: protected boolean readonly = false;
192:
193: /**
194: * The language code of this element.
195: */
196: private String lang = null;
197:
198: /**
199: * The direction for weak/neutral text of this element.
200: */
201: private String dir = null;
202:
203: // ------------------------------------------------------------- Properties
204:
205: /**
206: * Return the name of the form bean corresponding to this tag. There is no
207: * corresponding setter method; this method exists so that the nested tag
208: * classes can obtain the actual bean name derived from other attributes
209: * of the tag.
210: */
211: public String getBeanName() {
212: return beanName;
213: }
214:
215: /**
216: * Return the action URL to which this form should be submitted.
217: */
218: public String getAction() {
219: return (this .action);
220: }
221:
222: /**
223: * Set the action URL to which this form should be submitted.
224: *
225: * @param action The new action URL
226: */
227: public void setAction(String action) {
228: this .action = action;
229: }
230:
231: /**
232: * Return the content encoding used when submitting this form.
233: */
234: public String getEnctype() {
235: return (this .enctype);
236: }
237:
238: /**
239: * Set the content encoding used when submitting this form.
240: *
241: * @param enctype The new content encoding
242: */
243: public void setEnctype(String enctype) {
244: this .enctype = enctype;
245: }
246:
247: /**
248: * Return the focus field name for this form.
249: */
250: public String getFocus() {
251: return (this .focus);
252: }
253:
254: /**
255: * Set the focus field name for this form.
256: *
257: * @param focus The new focus field name
258: */
259: public void setFocus(String focus) {
260: this .focus = focus;
261: }
262:
263: /**
264: * Return the request method used when submitting this form.
265: */
266: public String getMethod() {
267: return (this .method);
268: }
269:
270: /**
271: * Set the request method used when submitting this form.
272: *
273: * @param method The new request method
274: */
275: public void setMethod(String method) {
276: this .method = method;
277: }
278:
279: /**
280: * Return the onReset event script.
281: */
282: public String getOnreset() {
283: return (this .onreset);
284: }
285:
286: /**
287: * Set the onReset event script.
288: *
289: * @param onReset The new event script
290: */
291: public void setOnreset(String onReset) {
292: this .onreset = onReset;
293: }
294:
295: /**
296: * Return the onSubmit event script.
297: */
298: public String getOnsubmit() {
299: return (this .onsubmit);
300: }
301:
302: /**
303: * Set the onSubmit event script.
304: *
305: * @param onSubmit The new event script
306: */
307: public void setOnsubmit(String onSubmit) {
308: this .onsubmit = onSubmit;
309: }
310:
311: /**
312: * Return the style attribute for this tag.
313: */
314: public String getStyle() {
315: return (this .style);
316: }
317:
318: /**
319: * Set the style attribute for this tag.
320: *
321: * @param style The new style attribute
322: */
323: public void setStyle(String style) {
324: this .style = style;
325: }
326:
327: /**
328: * Return the style class for this tag.
329: */
330: public String getStyleClass() {
331: return (this .styleClass);
332: }
333:
334: /**
335: * Set the style class for this tag.
336: *
337: * @param styleClass The new style class
338: */
339: public void setStyleClass(String styleClass) {
340: this .styleClass = styleClass;
341: }
342:
343: /**
344: * Return the style identifier for this tag.
345: */
346: public String getStyleId() {
347: return (this .styleId);
348: }
349:
350: /**
351: * Set the style identifier for this tag.
352: *
353: * @param styleId The new style identifier
354: */
355: public void setStyleId(String styleId) {
356: this .styleId = styleId;
357: }
358:
359: /**
360: * Return the window target.
361: */
362: public String getTarget() {
363: return (this .target);
364: }
365:
366: /**
367: * Set the window target.
368: *
369: * @param target The new window target
370: */
371: public void setTarget(String target) {
372: this .target = target;
373: }
374:
375: /**
376: * Return the list of character encodings accepted.
377: */
378: public String getAcceptCharset() {
379: return acceptCharset;
380: }
381:
382: /**
383: * Set the list of character encodings accepted.
384: *
385: * @param acceptCharset The list of character encodings
386: */
387: public void setAcceptCharset(String acceptCharset) {
388: this .acceptCharset = acceptCharset;
389: }
390:
391: /**
392: * Sets the disabled event handler.
393: */
394: public void setDisabled(boolean disabled) {
395: this .disabled = disabled;
396: }
397:
398: /**
399: * Returns the disabled event handler.
400: */
401: public boolean isDisabled() {
402: return disabled;
403: }
404:
405: /**
406: * Sets the readonly event handler.
407: */
408: public void setReadonly(boolean readonly) {
409: this .readonly = readonly;
410: }
411:
412: /**
413: * Returns the readonly event handler.
414: */
415: public boolean isReadonly() {
416: return readonly;
417: }
418:
419: /**
420: * Returns the language code of this element.
421: *
422: * @since Struts 1.3.6
423: */
424: public String getLang() {
425: return this .lang;
426: }
427:
428: /**
429: * Sets the language code of this element.
430: *
431: * @since Struts 1.3.6
432: */
433: public void setLang(String lang) {
434: this .lang = lang;
435: }
436:
437: /**
438: * Returns the direction for weak/neutral text this element.
439: *
440: * @since Struts 1.3.6
441: */
442: public String getDir() {
443: return this .dir;
444: }
445:
446: /**
447: * Sets the direction for weak/neutral text of this element.
448: *
449: * @since Struts 1.3.6
450: */
451: public void setDir(String dir) {
452: this .dir = dir;
453: }
454:
455: // --------------------------------------------------------- Public Methods
456:
457: /**
458: * Render the beginning of this form.
459: *
460: * @throws JspException if a JSP exception has occurred
461: */
462: public int doStartTag() throws JspException {
463:
464: postbackAction = null;
465:
466: // Look up the form bean name, scope, and type if necessary
467: this .lookup();
468:
469: // Create an appropriate "form" element based on our parameters
470: StringBuffer results = new StringBuffer();
471:
472: results.append(this .renderFormStartElement());
473:
474: results.append(this .renderToken());
475:
476: TagUtils.getInstance().write(pageContext, results.toString());
477:
478: // Store this tag itself as a page attribute
479: pageContext.setAttribute(Constants.FORM_KEY, this ,
480: PageContext.REQUEST_SCOPE);
481:
482: this .initFormBean();
483:
484: return (EVAL_BODY_INCLUDE);
485: }
486:
487: /**
488: * Locate or create the bean associated with our form.
489: *
490: * @throws JspException
491: * @since Struts 1.1
492: */
493: protected void initFormBean() throws JspException {
494: int scope = PageContext.SESSION_SCOPE;
495:
496: if ("request".equalsIgnoreCase(beanScope)) {
497: scope = PageContext.REQUEST_SCOPE;
498: }
499:
500: Object bean = pageContext.getAttribute(beanName, scope);
501:
502: if (bean == null) {
503: // New and improved - use the values from the action mapping
504: bean = RequestUtils.createActionForm(
505: (HttpServletRequest) pageContext.getRequest(),
506: mapping, moduleConfig, servlet);
507:
508: if (bean instanceof ActionForm) {
509: ((ActionForm) bean).reset(mapping,
510: (HttpServletRequest) pageContext.getRequest());
511: }
512:
513: if (bean == null) {
514: throw new JspException(messages.getMessage(
515: "formTag.create", beanType));
516: }
517:
518: pageContext.setAttribute(beanName, bean, scope);
519: }
520:
521: pageContext.setAttribute(Constants.BEAN_KEY, bean,
522: PageContext.REQUEST_SCOPE);
523: }
524:
525: /**
526: * Generates the opening <code><form></code> element with
527: * appropriate attributes.
528: *
529: * @since Struts 1.1
530: */
531: protected String renderFormStartElement() throws JspException {
532: StringBuffer results = new StringBuffer("<form");
533:
534: // render attributes
535: renderName(results);
536:
537: renderAttribute(results, "method",
538: (getMethod() == null) ? "post" : getMethod());
539: renderAction(results);
540: renderAttribute(results, "accept-charset", getAcceptCharset());
541: renderAttribute(results, "class", getStyleClass());
542: renderAttribute(results, "dir", getDir());
543: renderAttribute(results, "enctype", getEnctype());
544: renderAttribute(results, "lang", getLang());
545: renderAttribute(results, "onreset", getOnreset());
546: renderAttribute(results, "onsubmit", getOnsubmit());
547: renderAttribute(results, "style", getStyle());
548: renderAttribute(results, "target", getTarget());
549:
550: // Hook for additional attributes
551: renderOtherAttributes(results);
552:
553: results.append(">");
554:
555: return results.toString();
556: }
557:
558: /**
559: * Renders the name of the form. If XHTML is set to true, the name will
560: * be rendered as an 'id' attribute, otherwise as a 'name' attribute.
561: */
562: protected void renderName(StringBuffer results) throws JspException {
563: if (this .isXhtml()) {
564: if (getStyleId() == null) {
565: renderAttribute(results, "id", beanName);
566: } else {
567: throw new JspException(messages
568: .getMessage("formTag.ignoredId"));
569: }
570: } else {
571: renderAttribute(results, "name", beanName);
572: renderAttribute(results, "id", getStyleId());
573: }
574: }
575:
576: /**
577: * Renders the action attribute
578: */
579: protected void renderAction(StringBuffer results) {
580: String calcAction = (this .action == null ? postbackAction
581: : this .action);
582: HttpServletResponse response = (HttpServletResponse) this .pageContext
583: .getResponse();
584:
585: results.append(" action=\"");
586: results.append(response.encodeURL(TagUtils.getInstance()
587: .getActionMappingURL(calcAction, this .pageContext)));
588:
589: results.append("\"");
590: }
591:
592: /**
593: * 'Hook' to enable this tag to be extended and additional attributes
594: * added.
595: */
596: protected void renderOtherAttributes(StringBuffer results) {
597: }
598:
599: /**
600: * Generates a hidden input field with token information, if any. The
601: * field is added within a div element for HTML 4.01 Strict compliance.
602: *
603: * @return A hidden input field containing the token.
604: * @since Struts 1.1
605: */
606: protected String renderToken() {
607: StringBuffer results = new StringBuffer();
608: HttpSession session = pageContext.getSession();
609:
610: if (session != null) {
611: String token = (String) session
612: .getAttribute(Globals.TRANSACTION_TOKEN_KEY);
613:
614: if (token != null) {
615: results.append("<div><input type=\"hidden\" name=\"");
616: results.append(Constants.TOKEN_KEY);
617: results.append("\" value=\"");
618: results.append(token);
619:
620: if (this .isXhtml()) {
621: results.append("\" />");
622: } else {
623: results.append("\">");
624: }
625:
626: results.append("</div>");
627: }
628: }
629:
630: return results.toString();
631: }
632:
633: /**
634: * Renders attribute="value" if not null
635: */
636: protected void renderAttribute(StringBuffer results,
637: String attribute, String value) {
638: if (value != null) {
639: results.append(" ");
640: results.append(attribute);
641: results.append("=\"");
642: results.append(value);
643: results.append("\"");
644: }
645: }
646:
647: /**
648: * Render the end of this form.
649: *
650: * @throws JspException if a JSP exception has occurred
651: */
652: public int doEndTag() throws JspException {
653: // Remove the page scope attributes we created
654: pageContext.removeAttribute(Constants.BEAN_KEY,
655: PageContext.REQUEST_SCOPE);
656: pageContext.removeAttribute(Constants.FORM_KEY,
657: PageContext.REQUEST_SCOPE);
658:
659: // Render a tag representing the end of our current form
660: StringBuffer results = new StringBuffer("</form>");
661:
662: // Render JavaScript to set the input focus if required
663: if (this .focus != null) {
664: results.append(this .renderFocusJavascript());
665: }
666:
667: // Print this value to our output writer
668: JspWriter writer = pageContext.getOut();
669:
670: try {
671: writer.print(results.toString());
672: } catch (IOException e) {
673: throw new JspException(messages.getMessage("common.io", e
674: .toString()));
675: }
676:
677: postbackAction = null;
678:
679: // Continue processing this page
680: return (EVAL_PAGE);
681: }
682:
683: /**
684: * Generates javascript to set the initial focus to the form element given
685: * in the tag's "focus" attribute.
686: *
687: * @since Struts 1.1
688: */
689: protected String renderFocusJavascript() {
690: StringBuffer results = new StringBuffer();
691:
692: results.append(lineEnd);
693: results.append("<script type=\"text/javascript\"");
694:
695: if (!this .isXhtml() && this .scriptLanguage) {
696: results.append(" language=\"JavaScript\"");
697: }
698:
699: results.append(">");
700: results.append(lineEnd);
701:
702: // xhtml script content shouldn't use the browser hiding trick
703: if (!this .isXhtml()) {
704: results.append(" <!--");
705: results.append(lineEnd);
706: }
707:
708: // Construct the control name that will receive focus.
709: // This does not include any index.
710: StringBuffer focusControl = new StringBuffer(
711: "document.forms[\"");
712:
713: focusControl.append(beanName);
714: focusControl.append("\"].elements[\"");
715: focusControl.append(this .focus);
716: focusControl.append("\"]");
717:
718: results.append(" var focusControl = ");
719: results.append(focusControl.toString());
720: results.append(";");
721: results.append(lineEnd);
722: results.append(lineEnd);
723:
724: results.append(" if (focusControl.type != \"hidden\" && ");
725: results.append("!focusControl.disabled && ");
726: results.append("focusControl.style.display != \"none\") {");
727: results.append(lineEnd);
728:
729: // Construct the index if needed and insert into focus statement
730: String index = "";
731:
732: if (this .focusIndex != null) {
733: StringBuffer sb = new StringBuffer("[");
734:
735: sb.append(this .focusIndex);
736: sb.append("]");
737: index = sb.toString();
738: }
739:
740: results.append(" focusControl");
741: results.append(index);
742: results.append(".focus();");
743: results.append(lineEnd);
744:
745: results.append(" }");
746: results.append(lineEnd);
747:
748: if (!this .isXhtml()) {
749: results.append(" // -->");
750: results.append(lineEnd);
751: }
752:
753: results.append("</script>");
754: results.append(lineEnd);
755:
756: return results.toString();
757: }
758:
759: /**
760: * Release any acquired resources.
761: */
762: public void release() {
763: super .release();
764: action = null;
765: moduleConfig = null;
766: enctype = null;
767: dir = null;
768: disabled = false;
769: focus = null;
770: focusIndex = null;
771: lang = null;
772: mapping = null;
773: method = null;
774: onreset = null;
775: onsubmit = null;
776: readonly = false;
777: servlet = null;
778: style = null;
779: styleClass = null;
780: styleId = null;
781: target = null;
782: acceptCharset = null;
783: }
784:
785: // ------------------------------------------------------ Protected Methods
786:
787: /**
788: * Look up values for the <code>name</code>, <code>scope</code>, and
789: * <code>type</code> properties if necessary.
790: *
791: * @throws JspException if a required value cannot be looked up
792: */
793: protected void lookup() throws JspException {
794:
795: // Look up the module configuration information we need
796: moduleConfig = TagUtils.getInstance().getModuleConfig(
797: pageContext);
798:
799: if (moduleConfig == null) {
800: JspException e = new JspException(messages
801: .getMessage("formTag.collections"));
802:
803: pageContext.setAttribute(Globals.EXCEPTION_KEY, e,
804: PageContext.REQUEST_SCOPE);
805: throw e;
806: }
807:
808: String calcAction = this .action;
809:
810: // If the action is not specified, use the original request uri
811: if (this .action == null) {
812: HttpServletRequest request = (HttpServletRequest) pageContext
813: .getRequest();
814: postbackAction = (String) request
815: .getAttribute(Globals.ORIGINAL_URI_KEY);
816:
817: String prefix = moduleConfig.getPrefix();
818: if (postbackAction != null && prefix.length() > 0
819: && postbackAction.startsWith(prefix)) {
820: postbackAction = postbackAction.substring(prefix
821: .length());
822: }
823: calcAction = postbackAction;
824: } else {
825: // Translate the action if it is an actionId
826: ActionConfig actionConfig = moduleConfig
827: .findActionConfigId(this .action);
828: if (actionConfig != null) {
829: this .action = actionConfig.getPath();
830: calcAction = this .action;
831: }
832: }
833:
834: servlet = (ActionServlet) pageContext.getServletContext()
835: .getAttribute(Globals.ACTION_SERVLET_KEY);
836:
837: // Look up the action mapping we will be submitting to
838: String mappingName = TagUtils.getInstance()
839: .getActionMappingName(calcAction);
840:
841: mapping = (ActionMapping) moduleConfig
842: .findActionConfig(mappingName);
843:
844: if (mapping == null) {
845: JspException e = new JspException(messages.getMessage(
846: "formTag.mapping", mappingName));
847:
848: pageContext.setAttribute(Globals.EXCEPTION_KEY, e,
849: PageContext.REQUEST_SCOPE);
850: throw e;
851: }
852:
853: // Look up the form bean definition
854: FormBeanConfig formBeanConfig = moduleConfig
855: .findFormBeanConfig(mapping.getName());
856:
857: if (formBeanConfig == null) {
858: JspException e = null;
859:
860: if (mapping.getName() == null) {
861: e = new JspException(messages.getMessage(
862: "formTag.name", calcAction));
863: } else {
864: e = new JspException(messages.getMessage(
865: "formTag.formBean", mapping.getName(),
866: calcAction));
867: }
868:
869: pageContext.setAttribute(Globals.EXCEPTION_KEY, e,
870: PageContext.REQUEST_SCOPE);
871: throw e;
872: }
873:
874: // Calculate the required values
875: beanName = mapping.getAttribute();
876: beanScope = mapping.getScope();
877: beanType = formBeanConfig.getType();
878: }
879:
880: /**
881: * Returns true if this tag should render as xhtml.
882: */
883: private boolean isXhtml() {
884: return TagUtils.getInstance().isXhtml(this .pageContext);
885: }
886:
887: /**
888: * Returns the focusIndex.
889: *
890: * @return String
891: */
892: public String getFocusIndex() {
893: return focusIndex;
894: }
895:
896: /**
897: * Sets the focusIndex.
898: *
899: * @param focusIndex The focusIndex to set
900: */
901: public void setFocusIndex(String focusIndex) {
902: this .focusIndex = focusIndex;
903: }
904:
905: /**
906: * Gets whether or not the focus script's <script> element will
907: * include the language attribute.
908: *
909: * @return true if language attribute will be included.
910: * @since Struts 1.2
911: */
912: public boolean getScriptLanguage() {
913: return this .scriptLanguage;
914: }
915:
916: /**
917: * Sets whether or not the focus script's <script> element will
918: * include the language attribute.
919: *
920: * @since Struts 1.2
921: */
922: public void setScriptLanguage(boolean scriptLanguage) {
923: this.scriptLanguage = scriptLanguage;
924: }
925: }
|