1 /*
2 * $Id: FormTag.java 331056 2005-11-06 01:29:01Z niallp $
3 *
4 * Copyright 1999-2005 The Apache Software Foundation.
5 *
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
17 */
18
19 package org.apache.struts.taglib.html;
20
21 import java.io.IOException;
22
23 import javax.servlet.http.HttpServletRequest;
24 import javax.servlet.http.HttpServletResponse;
25 import javax.servlet.http.HttpSession;
26 import javax.servlet.jsp.JspException;
27 import javax.servlet.jsp.JspWriter;
28 import javax.servlet.jsp.PageContext;
29 import javax.servlet.jsp.tagext.TagSupport;
30
31 import org.apache.struts.Globals;
32 import org.apache.struts.action.ActionForm;
33 import org.apache.struts.action.ActionMapping;
34 import org.apache.struts.action.ActionServlet;
35 import org.apache.struts.config.FormBeanConfig;
36 import org.apache.struts.config.ModuleConfig;
37 import org.apache.struts.taglib.TagUtils;
38 import org.apache.struts.util.MessageResources;
39 import org.apache.struts.util.RequestUtils;
40
41 /**
42 * Custom tag that represents an input form, associated with a bean whose
43 * properties correspond to the various fields of the form.
44 *
45 * @version $Rev: 331056 $ $Date: 2005-11-06 01:29:01 +0000 (Sun, 06 Nov 2005) $
46 */
47 public class FormTag extends TagSupport {
48
49 // ----------------------------------------------------- Instance Variables
50
51 /**
52 * The action URL to which this form should be submitted, if any.
53 */
54 protected String action = null;
55
56 /**
57 * The module configuration for our module.
58 */
59 protected ModuleConfig moduleConfig = null;
60
61 /**
62 * The content encoding to be used on a POST submit.
63 */
64 protected String enctype = null;
65
66 /**
67 * The name of the field to receive focus, if any.
68 */
69 protected String focus = null;
70
71 /**
72 * The index in the focus field array to receive focus. This only applies if the field
73 * given in the focus attribute is actually an array of fields. This allows a specific
74 * field in a radio button array to receive focus while still allowing indexed field
75 * names like "myRadioButtonField[1]" to be passed in the focus attribute.
76 * @since Struts 1.1
77 */
78 protected String focusIndex = null;
79
80 /**
81 * The line ending string.
82 */
83 protected static String lineEnd = System.getProperty("line.separator");
84
85 /**
86 * The ActionMapping defining where we will be submitting this form
87 */
88 protected ActionMapping mapping = null;
89
90 /**
91 * The message resources for this package.
92 */
93 protected static MessageResources messages =
94 MessageResources.getMessageResources(Constants.Package + ".LocalStrings");
95
96 /**
97 * The request method used when submitting this form.
98 */
99 protected String method = null;
100
101 /**
102 * The onReset event script.
103 */
104 protected String onreset = null;
105
106 /**
107 * The onSubmit event script.
108 */
109 protected String onsubmit = null;
110
111 /**
112 * Include language attribute in the focus script's <script> element. This
113 * property is ignored in XHTML mode.
114 * @since Struts 1.2
115 */
116 protected boolean scriptLanguage = true;
117
118 /**
119 * The ActionServlet instance we are associated with (so that we can
120 * initialize the <code>servlet</code> property on any form bean that
121 * we create).
122 */
123 protected ActionServlet servlet = null;
124
125 /**
126 * The style attribute associated with this tag.
127 */
128 protected String style = null;
129
130 /**
131 * The style class associated with this tag.
132 */
133 protected String styleClass = null;
134
135 /**
136 * The identifier associated with this tag.
137 */
138 protected String styleId = null;
139
140 /**
141 * The window target.
142 */
143 protected String target = null;
144
145 /**
146 * The name of the form bean to (create and) use. This is either the same
147 * as the 'name' attribute, if that was specified, or is obtained from the
148 * associated <code>ActionMapping</code> otherwise.
149 */
150 protected String beanName = null;
151
152 /**
153 * The scope of the form bean to (create and) use. This is either the same
154 * as the 'scope' attribute, if that was specified, or is obtained from the
155 * associated <code>ActionMapping</code> otherwise.
156 */
157 protected String beanScope = null;
158
159 /**
160 * The type of the form bean to (create and) use. This is either the same
161 * as the 'type' attribute, if that was specified, or is obtained from the
162 * associated <code>ActionMapping</code> otherwise.
163 */
164 protected String beanType = null;
165
166 /**
167 * The list of character encodings for input data that the server should
168 * accept.
169 */
170 protected String acceptCharset = null;
171
172 /** Controls whether child controls should be 'disabled'. */
173 private boolean disabled = false;
174
175 /** Controls whether child controls should be 'readonly'. */
176 protected boolean readonly = false;
177
178 // ------------------------------------------------------------- Properties
179
180 /**
181 * Return the name of the form bean corresponding to this tag. There is
182 * no corresponding setter method; this method exists so that the nested
183 * tag classes can obtain the actual bean name derived from other
184 * attributes of the tag.
185 */
186 public String getBeanName() {
187
188 return beanName;
189
190 }
191
192 /**
193 * Return the action URL to which this form should be submitted.
194 */
195 public String getAction() {
196
197 return (this.action);
198
199 }
200
201 /**
202 * Set the action URL to which this form should be submitted.
203 *
204 * @param action The new action URL
205 */
206 public void setAction(String action) {
207
208 this.action = action;
209
210 }
211
212 /**
213 * Return the content encoding used when submitting this form.
214 */
215 public String getEnctype() {
216
217 return (this.enctype);
218
219 }
220
221 /**
222 * Set the content encoding used when submitting this form.
223 *
224 * @param enctype The new content encoding
225 */
226 public void setEnctype(String enctype) {
227
228 this.enctype = enctype;
229
230 }
231
232 /**
233 * Return the focus field name for this form.
234 */
235 public String getFocus() {
236
237 return (this.focus);
238
239 }
240
241 /**
242 * Set the focus field name for this form.
243 *
244 * @param focus The new focus field name
245 */
246 public void setFocus(String focus) {
247
248 this.focus = focus;
249
250 }
251
252 /**
253 * Return the request method used when submitting this form.
254 */
255 public String getMethod() {
256
257 return (this.method);
258
259 }
260
261 /**
262 * Set the request method used when submitting this form.
263 *
264 * @param method The new request method
265 */
266 public void setMethod(String method) {
267
268 this.method = method;
269
270 }
271
272 /**
273 * Return the onReset event script.
274 */
275 public String getOnreset() {
276
277 return (this.onreset);
278
279 }
280
281 /**
282 * Set the onReset event script.
283 *
284 * @param onReset The new event script
285 */
286 public void setOnreset(String onReset) {
287
288 this.onreset = onReset;
289
290 }
291
292 /**
293 * Return the onSubmit event script.
294 */
295 public String getOnsubmit() {
296
297 return (this.onsubmit);
298
299 }
300
301 /**
302 * Set the onSubmit event script.
303 *
304 * @param onSubmit The new event script
305 */
306 public void setOnsubmit(String onSubmit) {
307
308 this.onsubmit = onSubmit;
309
310 }
311
312 /**
313 * Return the style attribute for this tag.
314 */
315 public String getStyle() {
316
317 return (this.style);
318
319 }
320
321 /**
322 * Set the style attribute for this tag.
323 *
324 * @param style The new style attribute
325 */
326 public void setStyle(String style) {
327
328 this.style = style;
329
330 }
331
332 /**
333 * Return the style class for this tag.
334 */
335 public String getStyleClass() {
336
337 return (this.styleClass);
338
339 }
340
341 /**
342 * Set the style class for this tag.
343 *
344 * @param styleClass The new style class
345 */
346 public void setStyleClass(String styleClass) {
347
348 this.styleClass = styleClass;
349
350 }
351
352 /**
353 * Return the style identifier for this tag.
354 */
355 public String getStyleId() {
356
357 return (this.styleId);
358
359 }
360
361 /**
362 * Set the style identifier for this tag.
363 *
364 * @param styleId The new style identifier
365 */
366 public void setStyleId(String styleId) {
367
368 this.styleId = styleId;
369
370 }
371
372 /**
373 * Return the window target.
374 */
375 public String getTarget() {
376
377 return (this.target);
378
379 }
380
381 /**
382 * Set the window target.
383 *
384 * @param target The new window target
385 */
386 public void setTarget(String target) {
387
388 this.target = target;
389
390 }
391
392 /**
393 * Return the list of character encodings accepted.
394 */
395 public String getAcceptCharset() {
396
397 return acceptCharset;
398
399 }
400
401 /**
402 * Set the list of character encodings accepted.
403 *
404 * @param acceptCharset The list of character encodings
405 */
406 public void setAcceptCharset(String acceptCharset) {
407
408 this.acceptCharset= acceptCharset;
409
410 }
411
412 /** Sets the disabled event handler. */
413 public void setDisabled(boolean disabled) {
414 this.disabled = disabled;
415 }
416
417 /** Returns the disabled event handler. */
418 public boolean isDisabled() {
419 return disabled;
420 }
421
422 /** Sets the readonly event handler. */
423 public void setReadonly(boolean readonly) {
424 this.readonly = readonly;
425 }
426
427 /** Returns the readonly event handler. */
428 public boolean isReadonly() {
429 return readonly;
430 }
431
432
433 // --------------------------------------------------------- Public Methods
434
435 /**
436 * Render the beginning of this form.
437 *
438 * @exception JspException if a JSP exception has occurred
439 */
440 public int doStartTag() throws JspException {
441
442 // Look up the form bean name, scope, and type if necessary
443 this.lookup();
444
445 // Create an appropriate "form" element based on our parameters
446 StringBuffer results = new StringBuffer();
447
448 results.append(this.renderFormStartElement());
449
450 results.append(this.renderToken());
451
452 TagUtils.getInstance().write(pageContext, results.toString());
453
454 // Store this tag itself as a page attribute
455 pageContext.setAttribute(Constants.FORM_KEY, this, PageContext.REQUEST_SCOPE);
456
457 this.initFormBean();
458
459 return (EVAL_BODY_INCLUDE);
460
461 }
462
463 /**
464 * Locate or create the bean associated with our form.
465 * @throws JspException
466 * @since Struts 1.1
467 */
468 protected void initFormBean() throws JspException {
469 int scope = PageContext.SESSION_SCOPE;
470 if ("request".equalsIgnoreCase(beanScope)) {
471 scope = PageContext.REQUEST_SCOPE;
472 }
473
474 Object bean = pageContext.getAttribute(beanName, scope);
475 if (bean == null) {
476 // New and improved - use the values from the action mapping
477 bean =
478 RequestUtils.createActionForm(
479 (HttpServletRequest) pageContext.getRequest(),
480 mapping,
481 moduleConfig,
482 servlet);
483 if (bean instanceof ActionForm) {
484 ((ActionForm) bean).reset(mapping, (HttpServletRequest) pageContext.getRequest());
485 }
486 if (bean == null) {
487 throw new JspException(messages.getMessage("formTag.create", beanType));
488 }
489 pageContext.setAttribute(beanName, bean, scope);
490 }
491 pageContext.setAttribute(Constants.BEAN_KEY, bean, PageContext.REQUEST_SCOPE);
492 }
493
494 /**
495 * Generates the opening <code><form></code> element with appropriate
496 * attributes.
497 * @since Struts 1.1
498 */
499 protected String renderFormStartElement() {
500
501 StringBuffer results = new StringBuffer("<form");
502
503 // render attributes
504 renderName(results);
505
506 renderAttribute(results, "method", getMethod() == null ? "post" : getMethod());
507 renderAction(results);
508 renderAttribute(results, "accept-charset", getAcceptCharset());
509 renderAttribute(results, "class", getStyleClass());
510 renderAttribute(results, "enctype", getEnctype());
511 renderAttribute(results, "onreset", getOnreset());
512 renderAttribute(results, "onsubmit", getOnsubmit());
513 renderAttribute(results, "style", getStyle());
514 renderAttribute(results, "target", getTarget());
515
516 // Hook for additional attributes
517 renderOtherAttributes(results);
518
519 results.append(">");
520 return results.toString();
521 }
522
523 /**
524 * Renders the name of the form. If XHTML is set to true, the name will
525 * be rendered as an 'id' attribute, otherwise as a 'name' attribute.
526 */
527 protected void renderName(StringBuffer results) {
528 if (this.isXhtml()) {
529 if (getStyleId() == null) {
530 renderAttribute(results, "id", beanName);
531 } else {
532 renderAttribute(results, "id", getStyleId());
533 }
534 } else {
535 renderAttribute(results, "name", beanName);
536 renderAttribute(results, "id", getStyleId());
537 }
538 }
539
540 /**
541 * Renders the action attribute
542 */
543 protected void renderAction(StringBuffer results) {
544
545 HttpServletResponse response =
546 (HttpServletResponse) this.pageContext.getResponse();
547
548 results.append(" action=\"");
549 results.append(
550 response.encodeURL(
551 TagUtils.getInstance().getActionMappingURL(
552 this.action,
553 this.pageContext)));
554
555 results.append("\"");
556 }
557
558 /**
559 * 'Hook' to enable this tag to be extended and
560 * additional attributes added.
561 */
562 protected void renderOtherAttributes(StringBuffer results) {
563 }
564
565 /**
566 * Generates a hidden input field with token information, if any. The
567 * field is added within a div element for HTML 4.01 Strict compliance.
568 * @return A hidden input field containing the token.
569 * @since Struts 1.1
570 */
571 protected String renderToken() {
572 StringBuffer results = new StringBuffer();
573 HttpSession session = pageContext.getSession();
574
575 if (session != null) {
576 String token =
577 (String) session.getAttribute(Globals.TRANSACTION_TOKEN_KEY);
578
579 if (token != null) {
580 results.append("<div><input type=\"hidden\" name=\"");
581 results.append(Constants.TOKEN_KEY);
582 results.append("\" value=\"");
583 results.append(token);
584 if (this.isXhtml()) {
585 results.append("\" />");
586 } else {
587 results.append("\">");
588 }
589 results.append("</div>");
590 }
591 }
592
593 return results.toString();
594 }
595
596 /**
597 * Renders attribute="value" if not null
598 */
599 protected void renderAttribute(StringBuffer results, String attribute, String value) {
600 if (value != null) {
601 results.append(" ");
602 results.append(attribute);
603 results.append("=\"");
604 results.append(value);
605 results.append("\"");
606 }
607 }
608
609 /**
610 * Render the end of this form.
611 *
612 * @exception JspException if a JSP exception has occurred
613 */
614 public int doEndTag() throws JspException {
615
616 // Remove the page scope attributes we created
617 pageContext.removeAttribute(Constants.BEAN_KEY, PageContext.REQUEST_SCOPE);
618 pageContext.removeAttribute(Constants.FORM_KEY, PageContext.REQUEST_SCOPE);
619
620 // Render a tag representing the end of our current form
621 StringBuffer results = new StringBuffer("</form>");
622
623 // Render JavaScript to set the input focus if required
624 if (this.focus != null) {
625 results.append(this.renderFocusJavascript());
626 }
627
628 // Print this value to our output writer
629 JspWriter writer = pageContext.getOut();
630 try {
631 writer.print(results.toString());
632 } catch (IOException e) {
633 throw new JspException(messages.getMessage("common.io", e.toString()));
634 }
635
636 // Continue processing this page
637 return (EVAL_PAGE);
638
639 }
640
641 /**
642 * Generates javascript to set the initial focus to the form element given in the
643 * tag's "focus" attribute.
644 * @since Struts 1.1
645 */
646 protected String renderFocusJavascript() {
647 StringBuffer results = new StringBuffer();
648
649 results.append(lineEnd);
650 results.append("<script type=\"text/javascript\"");
651 if (!this.isXhtml() && this.scriptLanguage) {
652 results.append(" language=\"JavaScript\"");
653 }
654 results.append(">");
655 results.append(lineEnd);
656
657 // xhtml script content shouldn't use the browser hiding trick
658 if (!this.isXhtml()) {
659 results.append(" <!--");
660 results.append(lineEnd);
661 }
662
663 // Construct the control name that will receive focus.
664 // This does not include any index.
665 StringBuffer focusControl = new StringBuffer("document.forms[\"");
666 focusControl.append(beanName);
667 focusControl.append("\"].elements[\"");
668 focusControl.append(this.focus);
669 focusControl.append("\"]");
670
671 results.append(" var focusControl = ");
672 results.append(focusControl.toString());
673 results.append(";");
674 results.append(lineEnd);
675 results.append(lineEnd);
676
677 results.append(" if (focusControl.type != \"hidden\" && !focusControl.disabled) {");
678 results.append(lineEnd);
679
680 // Construct the index if needed and insert into focus statement
681 String index = "";
682 if (this.focusIndex != null) {
683 StringBuffer sb = new StringBuffer("[");
684 sb.append(this.focusIndex);
685 sb.append("]");
686 index = sb.toString();
687 }
688 results.append(" focusControl");
689 results.append(index);
690 results.append(".focus();");
691 results.append(lineEnd);
692
693 results.append(" }");
694 results.append(lineEnd);
695
696 if (!this.isXhtml()) {
697 results.append(" // -->");
698 results.append(lineEnd);
699 }
700
701 results.append("</script>");
702 results.append(lineEnd);
703 return results.toString();
704 }
705
706 /**
707 * Release any acquired resources.
708 */
709 public void release() {
710
711 super.release();
712 action = null;
713 moduleConfig = null;
714 enctype = null;
715 disabled = false;
716 focus = null;
717 focusIndex = null;
718 mapping = null;
719 method = null;
720 onreset = null;
721 onsubmit = null;
722 readonly = false;
723 servlet = null;
724 style = null;
725 styleClass = null;
726 styleId = null;
727 target = null;
728 acceptCharset = null;
729
730 }
731
732 // ------------------------------------------------------ Protected Methods
733
734
735 /**
736 * Look up values for the <code>name</code>, <code>scope</code>, and
737 * <code>type</code> properties if necessary.
738 *
739 * @exception JspException if a required value cannot be looked up
740 */
741 protected void lookup() throws JspException {
742
743 // Look up the module configuration information we need
744 moduleConfig = TagUtils.getInstance().getModuleConfig(pageContext);
745
746 if (moduleConfig == null) {
747 JspException e = new JspException(messages.getMessage("formTag.collections"));
748 pageContext.setAttribute(Globals.EXCEPTION_KEY, e, PageContext.REQUEST_SCOPE);
749 throw e;
750 }
751 servlet =
752 (ActionServlet) pageContext.getServletContext().getAttribute(
753 Globals.ACTION_SERVLET_KEY);
754
755 // Look up the action mapping we will be submitting to
756 String mappingName = TagUtils.getInstance().getActionMappingName(action);
757 mapping = (ActionMapping) moduleConfig.findActionConfig(mappingName);
758 if (mapping == null) {
759 JspException e = new JspException(messages.getMessage("formTag.mapping", mappingName));
760 pageContext.setAttribute(Globals.EXCEPTION_KEY, e, PageContext.REQUEST_SCOPE);
761 throw e;
762 }
763
764 // Look up the form bean definition
765 FormBeanConfig formBeanConfig = moduleConfig.findFormBeanConfig(mapping.getName());
766 if (formBeanConfig == null) {
767 JspException e =
768 new JspException(messages.getMessage("formTag.formBean", mapping.getName(), action));
769 pageContext.setAttribute(Globals.EXCEPTION_KEY, e, PageContext.REQUEST_SCOPE);
770 throw e;
771 }
772
773 // Calculate the required values
774 beanName = mapping.getAttribute();
775 beanScope = mapping.getScope();
776 beanType = formBeanConfig.getType();
777 }
778
779 /**
780 * Returns true if this tag should render as xhtml.
781 */
782 private boolean isXhtml() {
783 return TagUtils.getInstance().isXhtml(this.pageContext);
784 }
785
786 /**
787 * Returns the focusIndex.
788 * @return String
789 */
790 public String getFocusIndex() {
791 return focusIndex;
792 }
793
794 /**
795 * Sets the focusIndex.
796 * @param focusIndex The focusIndex to set
797 */
798 public void setFocusIndex(String focusIndex) {
799 this.focusIndex = focusIndex;
800 }
801
802 /**
803 * Gets whether or not the focus script's <script> element will include the
804 * language attribute.
805 * @return true if language attribute will be included.
806 * @since Struts 1.2
807 */
808 public boolean getScriptLanguage() {
809 return this.scriptLanguage;
810 }
811
812 /**
813 * Sets whether or not the focus script's <script> element will include the
814 * language attribute.
815 * @since Struts 1.2
816 */
817 public void setScriptLanguage(boolean scriptLanguage) {
818 this.scriptLanguage = scriptLanguage;
819 }
820
821 }