/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * $Header:$ */ package org.apache.beehive.netui.tags.html; import org.apache.beehive.netui.util.internal.InternalStringBuilder; import org.apache.beehive.netui.core.urls.MutableURI; import org.apache.beehive.netui.core.urls.URIContext; import org.apache.beehive.netui.core.urls.URLType; import org.apache.beehive.netui.core.urls.URLRewriterService; import org.apache.beehive.netui.core.urls.FreezableMutableURI; import org.apache.beehive.netui.pageflow.FlowController; import org.apache.beehive.netui.pageflow.PageFlowUtils; import org.apache.beehive.netui.pageflow.handler.Handlers; import org.apache.beehive.netui.pageflow.internal.InternalConstants; import org.apache.beehive.netui.pageflow.internal.InternalUtils; import org.apache.beehive.netui.pageflow.internal.URIContextFactory; import org.apache.beehive.netui.pageflow.scoping.ScopedServletUtils; import org.apache.beehive.netui.script.common.ImplicitObjectUtil; import org.apache.beehive.netui.tags.TagConfig; import org.apache.beehive.netui.tags.internal.PageFlowTagUtils; import org.apache.beehive.netui.tags.javascript.CoreScriptFeature; import org.apache.beehive.netui.tags.javascript.IScriptReporter; import org.apache.beehive.netui.tags.javascript.ScriptRequestState; import org.apache.beehive.netui.tags.rendering.*; import org.apache.beehive.netui.util.Bundle; import org.apache.beehive.netui.util.ParamHelper; import org.apache.beehive.netui.util.logging.Logger; import org.apache.struts.Globals; import org.apache.struts.action.ActionForm; import org.apache.struts.action.ActionMapping; import org.apache.struts.action.ActionServlet; import org.apache.struts.config.FormBeanConfig; import org.apache.struts.config.ModuleConfig; import org.apache.struts.taglib.html.Constants; import javax.servlet.ServletContext; import javax.servlet.ServletRequest; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import javax.servlet.jsp.JspException; import javax.servlet.jsp.PageContext; import java.net.URISyntaxException; import java.util.*; /** * This tag represents an input form, associated with a bean whose * properties correspond to the various fields of the form. * @jsptagref.tagdescription Renders an HTML form that can be submitted to a Java method * in the Controller file for processesing. * *
Submitting Data
* *When a <netui:form> is submitted, the form data is passed to a method * for processessing. The data is passed as a Form Bean instance. * The <netui:form>'s input fields correspond to the properties of the Form Bean. * When the form is submitted the following sequence of events occurs: * (1) a new Form Bean instance is created, (2) the form data is loaded into the * corresponding Form Bean properties, * and (3) the Form Bean instance is passed to the method * where the data is processed. * *
The action
attribute determines the target method of the submission.
* The parameter of the target method determines the Form Bean instance
* that carries the submitted data.
*
*
For example, if a <netui:form>'s target method is someAction
...
*
*
<netui:form action="someAction"> * // * // input fields go here * // * <netui:button value="Submit" type="submit"/> * </netui:form>* *
...and the someAction
method takes a Form Bean parameter of
* type SomeFormBean
...
*
*
@Jpf.Action( * forwards={ * @Jpf.Forward(name="success", path="showData.jsp") * } * ) * protected Forward someAction(SomeFormBean form)* *
...then an instance of SomeFormBean
will carry the submitted data.
*
*
Pre-populating Form Fields with the Session Object
* *The name
, type
, and scope
attributes can
* be used together to pre-populate
* the form fields with default values when they are first rendered in the browser.
*
*
In the Controller file, instantiate the appropriate Form Bean, set default values, and * store the Form Bean instance in the Session object. * *
protected void onCreate() * { * // Create a new Form Bean instance * ProcessDataForm formInstance = new ProcessDataForm(); * * // Set default values. * formInstance.setAge(32); * formInstance.setName("John"); * * // Store the instance in the Session object. * getSession().setAttribute("defaultValues", formInstance); * }* *
Then, use the name
, type
, and scope
attributes to pre-populate the
* form fields.
*
*
<netui:form * action="processData" * name="defaultValues" * type="tagSamples.netui.form.FormController$ProcessDataForm" * scope="session">* *
Note: when the data is submitted, the data is passed as a Request-scoped Form
* Bean, *not* as the Session-scoped Form Bean used to pre-populate the fields. However, you
* may pass the data as a Page Flow-scoped Form Bean, if the annotation
* {@link org.apache.beehive.netui.pageflow.annotations.Jpf.Action @Jpf.Action}(
* {@link org.apache.beehive.netui.pageflow.annotations.Jpf.Action#useFormBean useFormBean}="someFormBeanMemberVariable"
*
...)
is set on the receiving method.
*
*
Pre-populating Form Fields By Passing a Form Bean Instance to the JSP Page
* *As an alternative to the pre-population technique above, you can set the * pre-population values in a Form Bean instance and then pass that instance to the JSP page. * For example, assume that index.jsp contains the <netui:form> and input elements. * The following action method sets the pre-population values in a Form Bean instance * and passes that instance to the * <netui:form> and its input elements. * Note that the Forward object returned by the method has two parameters, the String "success" * and the pre-populated form. * *
@Jpf.Action( * forwards={ * @Jpf.Forward(name="success", path="index.jsp") * } * ) * protected Forward begin(ProcessDataForm form) * { * form.setAge(44); * form.setName("Mark"); * return new Forward("success", form); * }* @example In this first sample, the * <netui:form> tag invokes the
processData
* action method in the Controller file when the form is submitted.
* <netui:form action="processData"> * Name: * <netui:textBox dataSource="actionForm.name"/> * Age: * <netui:textBox dataSource="actionForm.age"/> * <netui:button value="Submit" type="submit"/> * </netui:form>* *
Notice that the processData action method takes a parameter of
* type ProcessDataForm
.
@Jpf.Action( * forwards={ * @Jpf.Forward(name="success", path="showData.jsp") * } * ) * protected Forward processData(ProcessDataForm form) * { * // * // Process the submitted data here. * // * * return new Forward("success"); * }* *
This means that the submitted data is loaded into an * instance of ProcessDataForm before * it is passed to the action method. * *
In this next sample, the form fields are pre-populated based upon default values * stored in the Session object. *
* <netui:form action="Action" type="corp.Controller$NameBean" * scope="session" name="nameBean"> * Name: <netui:textBox dataSource="actionForm.name" /> * <netui:button value="Submit"/> * </netui:form>* @netui:tag name="form" description="Represents an input form, associated with a bean whose properties correspond to the various fields of the form." */ public class Form extends HtmlBaseTag implements IUrlParams { private static final Logger logger = Logger.getInstance(Form.class); // unique name of the form private static String FORM_ID = "Netui_Form_"; // The form state private FormTag.State _state = new FormTag.State(); // state variable for any hidden values private InputHiddenTag.State _hiddenState = new InputHiddenTag.State(); private String _focus = null; // The name of the field to receive focus, if any. private String _location = null; // The location hash to append to the url. private String _text = null; // The body content of this tag (if any). private String _beanName = null; // The name of the form bean to (create and) use. private boolean _explicitBeanType = false; // Flag indicating that the BeanType was set by setting the attribute private String _beanType = null; // The type of the form bean to (create and) use. private String _beanScope = null; // The scope of the form bean to (create and) use. private String _realName = null; // This is the real name of the form if it is to be output private boolean _setRealName = false; // Force the RealName to be output. Contained tags can cause this private String _targetScope; // target page flow scope; see comments on setTargetScope() private ActionMapping _mapping = null; // The ActionMapping defining where we will be submitting this form private ActionServlet _servlet = null; // The ActionServlet instance we are associated with private HashMap _focusMap; // Map allowing setting focus on one of the controls private ModuleConfig _appConfig = null; // The application configuration for our module. private FlowController _flowController = null; // The flow controller (page flow or shared flow). private boolean _formSubmit = false; private Map _params; private String _actionUrl; // The generated action URL. private LinkedHashMap/*
AbstractHtmlState
class.
*/
protected AbstractHtmlState getState()
{
return _state;
}
/**
* Base support for the attribute tag. This is overridden to prevent setting the action
,
* and method
attributes.
* @param name The name of the attribute. This value may not be null or the empty string.
* @param value The value of the attribute. This may contain an expression.
* @param facet The name of a facet to which the attribute will be applied. This is optional.
* @throws JspException A JspException may be thrown if there is an error setting the attribute.
*/
public void setAttribute(String name, String value, String facet)
throws JspException
{
if (name != null) {
if (name.equals(ACTION) || name.equals(METHOD)) {
String s = Bundle.getString("Tags_AttributeMayNotBeSet", new Object[]{name});
registerTagError(s, null);
}
}
super.setAttribute(name, value, facet);
}
/**
* Return the action of the Form.
* @return a String representing the action name of the Form.
*/
public String getAction()
{
return _state.action;
}
/**
* Set the name of the action for the Form.
* @param action the name of the action to set for the Form.
* @jsptagref.attributedescription Required. The action method invoked on form submit. Form data is passed to this method.
* @jsptagref.databindable false
* @jsptagref.attributesyntaxvalue string_action
* @netui:attribute required="true" rtexprvalue="true"
* description="The action method invoked on form submit. Form data is passed to this method."
*/
public void setAction(String action)
throws JspException
{
_state.action = setRequiredValueAttribute(action, "action");
}
/**
* Set the content encoding to be used on a post submit.
* @param enctype the content encoding type.
* @jsptagref.attributedescription The content encoding to be used on a POST submit.
* @jsptagref.databindable false
* @jsptagref.attributesyntaxvalue string_enctype
* @netui:attribute required="false" rtexprvalue="true"
* description="The content encoding to be used on a POST submit."
*/
public void setEnctype(String enctype)
{
_state.registerAttribute(AbstractHtmlState.ATTR_GENERAL, ENCTYPE, enctype);
}
/**
* This method will return the real id that will be written out by the form. This is
* either the current state qualified tagId or the _realName if tagId is not specified.
* @return the value of the id attribute written by the form.
*/
public String getRealFormId()
{
return _realName;
}
/**
* This method will insure that a real id is written out even if a tagId is not
* set for the form.
*/
public void insureRealId()
{
_setRealName = true;
}
/**
* Set the name of the field to receive focus.
* @param focus the focus field name.
* @jsptagref.attributedescription The tagID
of an input field which should receive initial focus.
* @jsptagref.databindable false
* @jsptagref.attributesyntaxvalue string_focus
* @netui:attribute required="false" rtexprvalue="true"
* description="The tagID of an input field which should receive initial focus."
*/
public void setFocus(String focus)
{
_focus = setNonEmptyValueAttribute(focus);
insureRealId();
}
/**
* Get the focus flag.
* @return true
if the focus has been set. false
otherwise.
*/
public boolean isFocusSet() {
return (_focus != null);
}
/**
* Set the location hash to append to the url.
* @param location the location hash
* @jsptagref.attributedescription The location hash to append to the URL.
* @jsptagref.databindable false
* @jsptagref.attributesyntaxvalue string_location
* @netui:attribute required="false" rtexprvalue="true"
* description="The location hash to append to the URL."
*/
public void setLocation(String location)
{
_location = location;
}
/**
* Return the value of the location.
* @return The value of the location.
*/
public String getLocation() {
return _location;
}
/**
* Set the request method used ("get" or "post") when submitting this form.
* @param method the request method
* @jsptagref.attributedescription The request method used when submitting this form.
* @jsptagref.databindable false
* @jsptagref.attributesyntaxvalue string_method
* @netui:attribute required="false" rtexprvalue="true"
* description="The request method used when submitting this form."
*/
public void setMethod(String method)
throws JspException
{
if (FORM_POST.equals(method) || FORM_GET.equals(method)) {
_state.method = method;
return;
}
String s = Bundle.getString("Tags_FormMethodError", new Object[]{method});
registerTagError(s, null);
}
/**
* Set the attribute key under which our associated bean is stored.
* @param name the attribute key name
* @jsptagref.attributedescription The attribute key under which the associated Form Bean used to populate the input form is stored.
* This Form Bean is found in the scope defined by the scope
attribute.
* @jsptagref.databindable false
* @jsptagref.attributesyntaxvalue string_name
* @netui:attribute required="false" rtexprvalue="true"
* description="The attribute key under which the associated Form Bean used to populate the input form is stored.
* This Form Bean is found in the scope defined by the scope attribute."
*/
public void setBeanName(String name)
throws JspException
{
// @todo: we need to verify what happens if this is null and type is set.
_beanName = setRequiredValueAttribute(name, "beanName");
}
/**
* Set the target "scope" for the form's action. Multiple active page flows may exist concurrently within named
* scopes. This attribute selects which named scope to use. If omitted, the default scope is assumed.
* @param targetScope the name of the target scope in which the associated action's page flow resides.
* @jsptagref.attributedescription The target scope in which the associated action's page flow resides.
* @jsptagref.databindable true
* @jsptagref.attributesyntaxvalue string_targetScope
* @netui:attribute required="false" rtexprvalue="true"
* description="The target scope in which the associated action's page flow resides"
*/
public void setTargetScope(String targetScope)
{
_targetScope = targetScope;
}
/**
* Sets the onReset javascript event.
* @param onReset the onReset event.
* @jsptagref.attributedescription The JavaScript onReset event.
* @jsptagref.databindable false
* @jsptagref.attributesyntaxvalue string_onSubmit
* @netui:attribute required="false" rtexprvalue="true"
* description="The JavaScript onReset event."
*/
public void setOnReset(String onReset)
{
_state.registerAttribute(AbstractHtmlState.ATTR_JAVASCRIPT, ONRESET, onReset);
}
/**
* Sets the onSubmit javascript event.
* @param onSubmit the onReset event.
* @jsptagref.attributedescription The JavaScript onSubmit event.
* @jsptagref.databindable false
* @jsptagref.attributesyntaxvalue string_onSumbit
* @netui:attribute required="false" rtexprvalue="true"
* description="The JavaScript onSubmit event."
*/
public void setOnSubmit(String onSubmit)
{
_state.registerAttribute(AbstractHtmlState.ATTR_JAVASCRIPT, ONSUBMIT, onSubmit);
}
/**
* Sets the scope (request or session) under which the associated bean
* is stored.
* @param scope the scope.
* @jsptagref.attributedescription The scope (request
or session
) under which the associated Form Bean
* used to populate the form input fields is stored.
* Using the name
, type
and scope
attributes defines
* the Form Bean used.
* @jsptagref.databindable false
* @jsptagref.attributesyntaxvalue string_scope
* @netui:attribute required="false" rtexprvalue="true"
* description="The scope (request or session) under which the associated Form Bean
* used to populate the form input fields is stored.
* Using the name, type and scope attributes defines
* the Form Bean used."
*/
public void setBeanScope(String scope)
throws JspException
{
_beanScope = setRequiredValueAttribute(scope, "beanScope");
}
/**
* Sets the window target.
* @param target the window target.
* @jsptagref.attributedescription The window target
* @jsptagref.databindable false
* @jsptagref.attributesyntaxvalue string_windowTarget
* @netui:attribute required="false" rtexprvalue="true"
* description="The window target"
*/
public void setTarget(String target)
{
_state.registerAttribute(AbstractHtmlState.ATTR_GENERAL, TARGET, target);
}
/**
* Sets the Java class name of the bean to be created, if necessary.
* @param type the class name
* @jsptagref.attributedescription The Java class name of the Form Bean to be created, if necessary.
* This Form Bean will be created if the name
and scope
attributes are set.
* The Form Bean is then used to populate the form input fields.
* @jsptagref.databindable false
* @jsptagref.attributesyntaxvalue string_type
* @netui:attribute required="false" rtexprvalue="true"
* description="The Java class name of the Form Bean to be created, if necessary.
* This Form Bean will be created if the name and scope attributes are set.
* The Form Bean is then used to populate the form input fields."
*/
public void setBeanType(String type)
throws JspException
{
_beanType = setRequiredValueAttribute(type, "beanType");
_explicitBeanType = true;
}
/**
* Generate the JavaScript which will submit a form. This is usually generated if the
* form contains anchors that are used to submit the form. This will make sure the JavaScript is
* always generated.
* @param formSubmit boolean value indicating that the form submit JavaScript should be generated. Default is false.
* @jsptagref.attributedescription Generate the form submit JavaScript even if the form does not
* contain anchors. Default is false
.
* @jsptagref.databindable false
* @jsptagref.attributesyntaxvalue boolean_formSubmit
* @netui:attribute required="false" rtexprvalue="true" type="boolean"
* description="Generate the form submit JavaScript even if the form does not contain anchors."
*/
public void setGenJavaScriptFormSubmit(boolean formSubmit)
{
_formSubmit = formSubmit;
_setRealName = true;
}
/**
* Adds a URL parameter to the generated hyperlink.
* @param name the name of the parameter to be added.
* @param value the value of the parameter to be added (a String or String[]).
* @param facet
*/
public void addParameter(String name, Object value, String facet) throws JspException
{
assert(name != null) : "Parameter 'name' must not be null";
if (_params == null) {
_params = new HashMap();
}
ParamHelper.addParam(_params, name, value);
}
/**
* Adds a tagId and name to the Form's focusMap.
* @param tagID the tagID of a child tag.
* @param name the name of a child tag.
*/
public void addTagID(String tagID, String name)
{
if (_focusMap == null) {
_focusMap = new HashMap();
}
_focusMap.put(tagID, name);
}
//********************************************* Do the Work *******************************************************
/**
* Render the beginning of this form.
* @throws JspException if a JSP exception has occurred
*/
public int doStartTag() throws JspException
{
if (hasErrors())
return SKIP_BODY;
// Error out if there is a parent form
if (getNearestForm() != null) {
registerTagError(Bundle.getString("Tags_FormParentForm"), null);
}
// Look up the form bean name, scope, and type if necessary
HttpServletRequest request = (HttpServletRequest) pageContext.getRequest();
ServletContext servletContext = pageContext.getServletContext();
// find the beanName, beanType and beanScope
lookupBeanScopeAndName(request, servletContext);
if (hasErrors())
return SKIP_BODY;
// Generate the real name. We generate it here so we can return it to any contained tags if
// they need it. If they access it, the most likely will cause the realName to be output
if (_state.id != null) {
_realName = getIdForTagId(_state.id);
}
else {
String formId = FORM_ID + getNextId(request);
_realName = getIdForTagId(formId);
}
// Store this tag itself as a page attribute
pageContext.setAttribute(Constants.FORM_KEY, this, PageContext.REQUEST_SCOPE);
// Locate or create the bean associated with our form
int scope = PageContext.SESSION_SCOPE;
if ("request".equals(_beanScope)) {
scope = PageContext.REQUEST_SCOPE;
}
// setup the Bean
Object bean = null;
if (_beanName != null)
bean = pageContext.getAttribute(_beanName, scope);
if (bean == null) {
if (_explicitBeanType) {
// Backwards compatibility - use explicitly specified values
try {
Handlers handlers = Handlers.get(pageContext.getServletContext());
bean = handlers.getReloadableClassHandler().newInstance(_beanType);
if (bean != null) {
((ActionForm) bean).setServlet(_servlet);
}
}
catch (Exception e) {
registerTagError(Bundle.getString("Tags_FormNameBadType"), e);
}
}
else {
// New and improved - use the values from the action mapping
// First, check to see if this is a page flow-scoped form bean. If so, use the current value
// from the member field in the page flow (or shared flow).
if (_flowController != null) {
bean = _flowController.getFormBean(_mapping);
}
if (bean == null) {
bean = InternalUtils.createActionForm(_mapping, _appConfig, _servlet, servletContext);
}
}
if (hasErrors())
return SKIP_BODY;
// Call the reset method if we have an ActionForm
if (bean instanceof ActionForm) {
((ActionForm) bean).reset(_mapping, request);
}
if (bean != null) {
pageContext.setAttribute(_beanName, bean, scope);
}
}
if (bean != null) {
pageContext.setAttribute(Constants.BEAN_KEY, bean, PageContext.REQUEST_SCOPE);
ImplicitObjectUtil.loadActionForm(pageContext, bean);
}
// Create the action URL here, so child tags can access it.
try {
HttpServletResponse response = (HttpServletResponse) pageContext.getResponse();
_extraHiddenParams = new LinkedHashMap/*name
, scope
, and
* type
properties if necessary.
* @throws JspException if a required value cannot be looked up
*/
private void lookupBeanScopeAndName(HttpServletRequest request, ServletContext servletContext)
throws JspException
{
// Look up the application module configuration information we need
_appConfig = (ModuleConfig) request.getAttribute(Globals.MODULE_KEY);
if (_appConfig == null) { // Backwards compatibility hack
_appConfig = (ModuleConfig) servletContext.getAttribute(Globals.MODULE_KEY);
}
if (_appConfig == null) {
registerTagError(Bundle.getString("Tags_FormNoApplicationConfig"), null);
return;
}
_servlet = (ActionServlet) servletContext.getAttribute(Globals.ACTION_SERVLET_KEY);
_flowController = PageFlowUtils.getCurrentPageFlow(request, pageContext.getServletContext());
// check to see if this is a bad action
PageFlowTagUtils.MappingAndController mac = PageFlowTagUtils.getActionMapping(request, _flowController, _state.action);
if (mac == null) {
FlowController globalApp = PageFlowUtils.getSharedFlow(InternalConstants.GLOBALAPP_CLASSNAME, request);
mac = PageFlowTagUtils.getActionMapping(request, globalApp, _state.action);
}
if (mac == null) {
registerTagError(Bundle.getString("Tags_BadAction", _state.action), null);
return;
}
_flowController = mac.controller;
_mapping = mac.mapping;
assert (_mapping != null) : "Mapping not found for controller";
_appConfig = _mapping.getModuleConfig();
// Were the required values already specified?
if (_beanName != null) {
if (!_explicitBeanType) {
registerTagError(Bundle.getString("Tags_FormNameNoType"), null);
return;
}
if (_beanScope == null)
_beanScope = "session";
return;
}
// Look up the form bean definition
FormBeanConfig formBeanConfig = _appConfig.findFormBeanConfig(_mapping.getName());
if (formBeanConfig == null) {
// clear any _beanType
_beanType = null;
}
else {
// Calculate the required values
_beanName = _mapping.getAttribute();
_beanScope = _mapping.getScope();
_beanType = formBeanConfig.getType();
}
}
}