freemarker-notifications mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From woon...@apache.org
Subject [08/13] incubator-freemarker git commit: FREEMARKER-55: Adding tests and polishing
Date Thu, 04 Jan 2018 19:49:31 GMT
FREEMARKER-55: Adding tests and polishing


Project: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/commit/045c980c
Tree: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/tree/045c980c
Diff: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/diff/045c980c

Branch: refs/heads/3
Commit: 045c980cfcbc96b258334aaf4671451111914864
Parents: 1f7100c
Author: Woonsan Ko <woonsan@apache.org>
Authored: Tue Jan 2 23:03:55 2018 -0500
Committer: Woonsan Ko <woonsan@apache.org>
Committed: Tue Jan 2 23:03:55 2018 -0500

----------------------------------------------------------------------
 .../AbstractSpringTemplateCallableModel.java    | 15 ++++-
 .../model/SpringTemplateCallableHashModel.java  |  9 ++-
 ...aBoundFormElementTemplateDirectiveModel.java |  2 +-
 .../AbstractFormTemplateDirectiveModel.java     | 15 +++--
 ...stractHtmlElementTemplateDirectiveModel.java |  8 +--
 ...tHtmlInputElementTemplateDirectiveModel.java |  2 +-
 .../model/form/FormTemplateDirectiveModel.java  | 65 ++++++++++++++++++--
 .../model/form/InputTemplateDirectiveModel.java | 34 +++++++++-
 .../SpringFormTemplateCallableHashModel.java    |  1 -
 .../spring/model/form/TagOutputter.java         |  3 +
 .../form/FormTemplateDirectiveModelTest.java    | 41 +++++++++++-
 .../form/InputTemplateDirectiveModelTest.java   | 35 +++++++++++
 .../test/model/form/form-directive-usages.ftlh  | 46 ++++++++++++--
 .../test/model/form/input-directive-usages.ftlh | 52 +++++++++++++++-
 14 files changed, 290 insertions(+), 38 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/045c980c/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/AbstractSpringTemplateCallableModel.java
----------------------------------------------------------------------
diff --git a/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/AbstractSpringTemplateCallableModel.java
b/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/AbstractSpringTemplateCallableModel.java
index f7c75d9..4866557 100644
--- a/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/AbstractSpringTemplateCallableModel.java
+++ b/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/AbstractSpringTemplateCallableModel.java
@@ -134,7 +134,20 @@ public abstract class AbstractSpringTemplateCallableModel implements
TemplateCal
         return (status != null) ? objectWrapperAndUnwrapper.wrap(status) : null;
     }
 
-    // TODO: Javadocs
+    /**
+     * Find {@link BindStatus} with no {@code htmlEscape} option from {@link RequestContext}
by the {@code path}.
+     * <P>
+     * <EM>NOTE:</EM> In FreeMarker, there is no need to depend on <code>BindStatus#htmlEscape</code>
option
+     * as FreeMarker template expressions can easily set escape option by themselves.
+     * Therefore, this method always get a {@link BindStatus} with {@code htmlEscape} option
set to {@code false}.
+     * @param env Environment
+     * @param objectWrapperAndUnwrapper ObjectWrapperAndUnwrapper
+     * @param requestContext Spring RequestContext
+     * @param path bind path
+     * @param ignoreNestedPath flag whether or not to ignore the nested path
+     * @return a {@link BindStatus} with no {@code htmlEscape} option from {@link RequestContext}
by the {@code path}
+     * @throws TemplateException if template exception occurs
+     */
     protected final BindStatus getBindStatus(Environment env,
             ObjectWrapperAndUnwrapper objectWrapperAndUnwrapper, RequestContext requestContext,
String path,
             boolean ignoreNestedPath) throws TemplateException {

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/045c980c/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/SpringTemplateCallableHashModel.java
----------------------------------------------------------------------
diff --git a/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/SpringTemplateCallableHashModel.java
b/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/SpringTemplateCallableHashModel.java
index 762222c..a859821 100644
--- a/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/SpringTemplateCallableHashModel.java
+++ b/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/SpringTemplateCallableHashModel.java
@@ -35,7 +35,6 @@ import org.apache.freemarker.spring.model.form.SpringFormTemplateCallableHashMod
 /**
  * TemplateHashModel wrapper for templates using Spring directives, functions and internal
models.
  */
-//TODO [FM3] Shouldn't this be a TemplateHashModelEx?
 public final class SpringTemplateCallableHashModel implements TemplateHashModel, Serializable
{
 
     private static final long serialVersionUID = 1L;
@@ -77,19 +76,19 @@ public final class SpringTemplateCallableHashModel implements TemplateHashModel,
         return modelsMap.get(key);
     }
 
-    TemplateStringModel getNestedPathModel() throws TemplateException {
+    public TemplateStringModel getNestedPathModel() throws TemplateException {
         return (TemplateStringModel) get(NESTED_PATH_MODEL);
     }
 
-    void setNestedPathModel(TemplateStringModel nestedPathModel) {
+    public void setNestedPathModel(TemplateStringModel nestedPathModel) {
         modelsMap.put(NESTED_PATH_MODEL, nestedPathModel);
     }
 
-    TemplateModel getEvaluationContextModel() throws TemplateException {
+    public TemplateModel getEvaluationContextModel() throws TemplateException {
         return get(EVALUATION_CONTEXT_MODEL);
     }
 
-    void setEvaluationContextModel(TemplateModel evaluationContextModel) {
+    public void setEvaluationContextModel(TemplateModel evaluationContextModel) {
         modelsMap.put(EVALUATION_CONTEXT_MODEL, evaluationContextModel);
     }
 

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/045c980c/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/form/AbstractDataBoundFormElementTemplateDirectiveModel.java
----------------------------------------------------------------------
diff --git a/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/form/AbstractDataBoundFormElementTemplateDirectiveModel.java
b/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/form/AbstractDataBoundFormElementTemplateDirectiveModel.java
index 27bc4fd..d416fd4 100644
--- a/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/form/AbstractDataBoundFormElementTemplateDirectiveModel.java
+++ b/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/form/AbstractDataBoundFormElementTemplateDirectiveModel.java
@@ -43,7 +43,7 @@ import org.springframework.web.servlet.support.RequestDataValueProcessor;
 /**
  * Corresponds to <code>org.springframework.web.servlet.tags.form.AbstractDataBoundFormElementTag</code>.
  */
-public abstract class AbstractDataBoundFormElementTemplateDirectiveModel extends AbstractFormTemplateDirectiveModel
{
+abstract class AbstractDataBoundFormElementTemplateDirectiveModel extends AbstractFormTemplateDirectiveModel
{
 
     private static final int PATH_PARAM_IDX = 0;
 

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/045c980c/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/form/AbstractFormTemplateDirectiveModel.java
----------------------------------------------------------------------
diff --git a/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/form/AbstractFormTemplateDirectiveModel.java
b/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/form/AbstractFormTemplateDirectiveModel.java
index 45fa896..00c9f42 100644
--- a/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/form/AbstractFormTemplateDirectiveModel.java
+++ b/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/form/AbstractFormTemplateDirectiveModel.java
@@ -28,12 +28,11 @@ import javax.servlet.http.HttpServletResponse;
 import org.apache.freemarker.core.TemplateException;
 import org.apache.freemarker.spring.model.AbstractSpringTemplateDirectiveModel;
 import org.springframework.util.ObjectUtils;
-import org.springframework.web.util.HtmlUtils;
 
 /**
  * Corresponds to <code>org.springframework.web.servlet.tags.form.AbstractFormTag</code>.
  */
-public abstract class AbstractFormTemplateDirectiveModel extends AbstractSpringTemplateDirectiveModel
{
+abstract class AbstractFormTemplateDirectiveModel extends AbstractSpringTemplateDirectiveModel
{
 
     protected AbstractFormTemplateDirectiveModel(HttpServletRequest request, HttpServletResponse
response) {
         super(request, response);
@@ -43,32 +42,32 @@ public abstract class AbstractFormTemplateDirectiveModel extends AbstractSpringT
         return value;
     }
 
-    public static String getDisplayString(Object value, boolean htmlEscape) {
+    public static String getDisplayString(Object value) {
         String displayValue = ObjectUtils.getDisplayString(value);
-        return (htmlEscape ? HtmlUtils.htmlEscape(displayValue) : displayValue);
+        return displayValue;
     }
 
-    public static String getDisplayString(Object value, PropertyEditor propertyEditor, boolean
htmlEscape) {
+    public static String getDisplayString(Object value, PropertyEditor propertyEditor) {
         if (propertyEditor != null && !(value instanceof String)) {
             try {
                 propertyEditor.setValue(value);
                 String text = propertyEditor.getAsText();
 
                 if (text != null) {
-                    return getDisplayString(text, htmlEscape);
+                    return getDisplayString(text);
                 }
             } catch (Throwable ex) {
                 // Ignore error if the PropertyEditor doesn't support this text value.
             }
         }
 
-        return getDisplayString(value, htmlEscape);
+        return getDisplayString(value);
     }
 
     protected final void writeOptionalAttribute(TagOutputter tagOut, String attrName, Object
attrValue)
             throws TemplateException, IOException {
         if (attrValue != null) {
-            tagOut.writeOptionalAttributeValue(attrName, getDisplayString(evaluate(attrName,
attrValue), false));
+            tagOut.writeOptionalAttributeValue(attrName, getDisplayString(evaluate(attrName,
attrValue)));
         }
     }
 

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/045c980c/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/form/AbstractHtmlElementTemplateDirectiveModel.java
----------------------------------------------------------------------
diff --git a/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/form/AbstractHtmlElementTemplateDirectiveModel.java
b/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/form/AbstractHtmlElementTemplateDirectiveModel.java
index f711f3a..87b2394 100644
--- a/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/form/AbstractHtmlElementTemplateDirectiveModel.java
+++ b/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/form/AbstractHtmlElementTemplateDirectiveModel.java
@@ -48,7 +48,7 @@ import org.springframework.web.servlet.support.RequestContext;
 /**
  * Corresponds to <code>org.springframework.web.servlet.tags.form.AbstractHtmlElementTag</code>.
  */
-public abstract class AbstractHtmlElementTemplateDirectiveModel
+abstract class AbstractHtmlElementTemplateDirectiveModel
         extends AbstractDataBoundFormElementTemplateDirectiveModel {
 
     private static final int NAMED_ARGS_OFFSET = AbstractDataBoundFormElementTemplateDirectiveModel.NAMED_ARGS_ENTRY_LIST
@@ -107,7 +107,6 @@ public abstract class AbstractHtmlElementTemplateDirectiveModel
     private static final int CSSERRORCLASS_PARAM_IDX = NAMED_ARGS_OFFSET + 16;
     private static final String CSSERRORCLASS_PARAM_NAME = "cssErrorClass";
 
-    // TODO: It's a problem to see NAMED_ARGS_ENTRY_LIST is visible from child classes!
     @SuppressWarnings("unchecked")
     protected static List<StringToIndexMap.Entry> NAMED_ARGS_ENTRY_LIST =
             _CollectionUtils.mergeImmutableLists(false,
@@ -196,7 +195,7 @@ public abstract class AbstractHtmlElementTemplateDirectiveModel
         onkeydown = CallableUtils.getOptionalStringArgument(args, ONKEYDOWN_PARAM_IDX, this);
         cssErrorClass = CallableUtils.getOptionalStringArgument(args, CSSERRORCLASS_PARAM_IDX,
this);
 
-        final int attrsVarargsIndex = ARGS_LAYOUT.getNamedVarargsArgumentIndex();
+        final int attrsVarargsIndex = getDirectiveArgumentArrayLayout().getNamedVarargsArgumentIndex();
         final TemplateHashModelEx attrsHashModel = (TemplateHashModelEx) args[attrsVarargsIndex];
 
         if (attrsHashModel != null && !attrsHashModel.isEmptyHash()) {
@@ -342,8 +341,7 @@ public abstract class AbstractHtmlElementTemplateDirectiveModel
 
         if (!unmodifiableDynamicAttributes.isEmpty()) {
             for (String attr : unmodifiableDynamicAttributes.keySet()) {
-                tagOut.writeOptionalAttributeValue(attr,
-                        getDisplayString(unmodifiableDynamicAttributes.get(attr), false));
+                tagOut.writeOptionalAttributeValue(attr, getDisplayString(unmodifiableDynamicAttributes.get(attr)));
             }
         }
     }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/045c980c/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/form/AbstractHtmlInputElementTemplateDirectiveModel.java
----------------------------------------------------------------------
diff --git a/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/form/AbstractHtmlInputElementTemplateDirectiveModel.java
b/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/form/AbstractHtmlInputElementTemplateDirectiveModel.java
index ef81c1d..997ffc5 100644
--- a/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/form/AbstractHtmlInputElementTemplateDirectiveModel.java
+++ b/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/form/AbstractHtmlInputElementTemplateDirectiveModel.java
@@ -38,7 +38,7 @@ import org.apache.freemarker.core.util.StringToIndexMap;
 import org.apache.freemarker.core.util._CollectionUtils;
 import org.springframework.web.servlet.support.RequestContext;
 
-public abstract class AbstractHtmlInputElementTemplateDirectiveModel extends AbstractHtmlElementTemplateDirectiveModel
{
+abstract class AbstractHtmlInputElementTemplateDirectiveModel extends AbstractHtmlElementTemplateDirectiveModel
{
 
     private static final int NAMED_ARGS_OFFSET = AbstractHtmlElementTemplateDirectiveModel.NAMED_ARGS_ENTRY_LIST.size()
             + 1;

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/045c980c/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/form/FormTemplateDirectiveModel.java
----------------------------------------------------------------------
diff --git a/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/form/FormTemplateDirectiveModel.java
b/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/form/FormTemplateDirectiveModel.java
index 565245b..a35699c 100644
--- a/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/form/FormTemplateDirectiveModel.java
+++ b/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/form/FormTemplateDirectiveModel.java
@@ -34,9 +34,12 @@ import org.apache.freemarker.core.TemplateException;
 import org.apache.freemarker.core.model.ArgumentArrayLayout;
 import org.apache.freemarker.core.model.ObjectWrapperAndUnwrapper;
 import org.apache.freemarker.core.model.TemplateModel;
+import org.apache.freemarker.core.model.TemplateStringModel;
 import org.apache.freemarker.core.util.CallableUtils;
 import org.apache.freemarker.core.util.StringToIndexMap;
 import org.apache.freemarker.core.util._CollectionUtils;
+import org.apache.freemarker.spring.model.SpringTemplateCallableHashModel;
+import org.springframework.beans.PropertyAccessor;
 import org.springframework.http.HttpMethod;
 import org.springframework.util.StringUtils;
 import org.springframework.web.servlet.support.RequestContext;
@@ -44,7 +47,34 @@ import org.springframework.web.servlet.support.RequestDataValueProcessor;
 import org.springframework.web.util.HtmlUtils;
 import org.springframework.web.util.UriUtils;
 
-public class FormTemplateDirectiveModel extends AbstractHtmlElementTemplateDirectiveModel
{
+/**
+ * Provides <code>TemplateModel</code> for data-binding-aware HTML '{@code form}'
element whose inner directives
+ * are bound to properties on a <em>form object</em>.
+ * <P>
+ * This directive supports the following parameters:
+ * <UL>
+ * <LI><code>modelAttribute</code>: The first positional parameter pointing
to the bean or bean property as its form object.</LI>
+ * <LI>
+ *   ... TODO ...
+ * </LI>
+ * </UL>
+ * </P>
+ * <P>
+ * Some valid example(s):
+ * </P>
+ * <PRE>
+ * &lt;#assign form=spring.form /&gt;
+ * &lt;@form.form "user"&gt;
+ *   &lt;div&gt;First name: &lt;@form.input 'firstName' /&gt;&lt;/div&gt;
+ * &lt;/@form.form&gt;
+ * </PRE>
+ * <P>
+ * <EM>Note:</EM> Unlike Spring Framework's <code>&lt;form:input /&gt;</code>
JSP Tag Library, this directive
+ * does not support <code>htmlEscape</code> parameter. It always renders HTML's
without escaping
+ * because it is much easier to control escaping in FreeMarker Template expressions.
+ * </P>
+ */
+class FormTemplateDirectiveModel extends AbstractHtmlElementTemplateDirectiveModel {
 
     public static final String NAME = "form";
 
@@ -205,10 +235,22 @@ public class FormTemplateDirectiveModel extends AbstractHtmlElementTemplateDirec
             tagOut.endTag();
         }
 
-        // TODO: expose the form object name for nested tags...
+        final String modelAttribute = getModelAttribute();
 
-        // TODO: save previous nestedPath value, build and expose current nestedPath value.
+        // save previous nestedPath value, build and expose current nestedPath value.
+        final SpringTemplateCallableHashModel springTemplateModel = getSpringTemplateCallableHashModel(env);
+        final TemplateStringModel prevNestedPathModel = springTemplateModel.getNestedPathModel();
+        final String newNestedPath = modelAttribute + PropertyAccessor.NESTED_PROPERTY_SEPARATOR;
+        final TemplateStringModel newNestedPathModel = (TemplateStringModel) objectWrapperAndUnwrapper
+                .wrap(newNestedPath);
 
+        try {
+            springTemplateModel.setNestedPathModel(newNestedPathModel);
+            callPlace.executeNestedContent(null, out, env);
+        } finally {
+            springTemplateModel.setNestedPathModel(prevNestedPathModel);
+            tagOut.endTag();
+        }
     }
 
     protected String getModelAttribute() {
@@ -271,13 +313,22 @@ public class FormTemplateDirectiveModel extends AbstractHtmlElementTemplateDirec
         return ("get".equalsIgnoreCase(method) || "post".equalsIgnoreCase(method));
     }
 
+    /**
+     * Resolve the form action attribute.
+     * <p>If the {@code action} attribute is specified, then that value is used.
+     * If the {@code servletRelativeAction} is specified, then the value is prepended with
context and servlet paths.
+     * Otherwise, the {@link org.springframework.web.servlet.support.RequestContext#getRequestUri()
originating URI} is used.
+     * @param env environment
+     * @return the value that is to be used for the form action attribute
+     * @throws TemplateException if template exception occurs
+     */
     protected String resolveAction(Environment env) throws TemplateException {
         RequestContext requestContext = getRequestContext(env, false);
         String action = getAction();
         String servletRelativeAction = getServletRelativeAction();
 
         if (StringUtils.hasText(action)) {
-            action = getDisplayString(evaluate(ACTION_PARAM_NAME, action), false);
+            action = getDisplayString(evaluate(ACTION_PARAM_NAME, action));
             return processAction(env, action);
         } else if (StringUtils.hasText(servletRelativeAction)) {
             String pathToServlet = requestContext.getPathToServlet();
@@ -287,7 +338,7 @@ public class FormTemplateDirectiveModel extends AbstractHtmlElementTemplateDirec
                 servletRelativeAction = pathToServlet + servletRelativeAction;
             }
 
-            servletRelativeAction = getDisplayString(evaluate(ACTION_PARAM_NAME, servletRelativeAction),
false);
+            servletRelativeAction = getDisplayString(evaluate(ACTION_PARAM_NAME, servletRelativeAction));
             return processAction(env, servletRelativeAction);
         } else {
             String requestUri = requestContext.getRequestUri();
@@ -296,7 +347,7 @@ public class FormTemplateDirectiveModel extends AbstractHtmlElementTemplateDirec
             try {
                 requestUri = UriUtils.encodePath(requestUri, encoding);
             } catch (UnsupportedEncodingException ex) {
-                // shouldn't happen - if it does, proceed with requestUri as-is
+                // According to Spring MVC Javadoc, it shouldn't happen.
             }
 
             HttpServletResponse response = getResponse();
@@ -337,9 +388,11 @@ public class FormTemplateDirectiveModel extends AbstractHtmlElementTemplateDirec
     private String processAction(Environment env, String action) throws TemplateException
{
         RequestDataValueProcessor processor = getRequestContext(env, false).getRequestDataValueProcessor();
         HttpServletRequest request = getRequest();
+
         if (processor != null && request != null) {
             action = processor.processAction((HttpServletRequest) request, action, getHttpMethod());
         }
+
         return action;
     }
 

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/045c980c/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/form/InputTemplateDirectiveModel.java
----------------------------------------------------------------------
diff --git a/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/form/InputTemplateDirectiveModel.java
b/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/form/InputTemplateDirectiveModel.java
index e348da8..f155054 100644
--- a/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/form/InputTemplateDirectiveModel.java
+++ b/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/form/InputTemplateDirectiveModel.java
@@ -38,7 +38,37 @@ import org.apache.freemarker.core.util.StringToIndexMap;
 import org.apache.freemarker.core.util._CollectionUtils;
 import org.springframework.web.servlet.support.RequestContext;
 
-public class InputTemplateDirectiveModel extends AbstractHtmlInputElementTemplateDirectiveModel
{
+/**
+ * Provides <code>TemplateModel</code> for data-binding-aware HTML '{@code input}'
element with a '{@code type}'
+ * of '{@code text}'.
+ * <P>
+ * This directive supports the following parameters:
+ * <UL>
+ * <LI><code>path</code>: The first positional parameter pointing to the
bean or bean property to bind status information for.</LI>
+ * <LI>
+ *   ... TODO ...
+ * </LI>
+ * </UL>
+ * </P>
+ * <P>
+ * Some valid example(s):
+ * </P>
+ * <PRE>
+ *   &lt;#assign form=spring.form /&gt;
+ *   ...
+ *   &lt;@form.input 'user.firstName' /&gt;
+ *   
+ *   &lt;@form.input 'user.email' id="customEmailId" /&gt;
+ *   
+ *   ...
+ * </PRE>
+ * <P>
+ * <EM>Note:</EM> Unlike Spring Framework's <code>&lt;form:input /&gt;</code>
JSP Tag Library, this directive
+ * does not support <code>htmlEscape</code> parameter. It always renders HTML's
without escaping
+ * because it is much easier to control escaping in FreeMarker Template expressions.
+ * </P>
+ */
+class InputTemplateDirectiveModel extends AbstractHtmlInputElementTemplateDirectiveModel
{
 
     public static final String NAME = "input";
 
@@ -161,7 +191,7 @@ public class InputTemplateDirectiveModel extends AbstractHtmlInputElementTemplat
     }
 
     protected void writeValue(Environment env, TagOutputter tagOut) throws TemplateException,
IOException {
-        String value = getDisplayString(getBindStatus().getValue(), getBindStatus().getEditor(),
false);
+        String value = getDisplayString(getBindStatus().getValue(), getBindStatus().getEditor());
         String type = hasDynamicTypeAttribute() ? (String) getDynamicAttributes().get("type")
: getType();
         tagOut.writeAttribute("value", processFieldValue(env, getName(), value, type));
     }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/045c980c/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/form/SpringFormTemplateCallableHashModel.java
----------------------------------------------------------------------
diff --git a/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/form/SpringFormTemplateCallableHashModel.java
b/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/form/SpringFormTemplateCallableHashModel.java
index 3c9becd..c7b1f5a 100644
--- a/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/form/SpringFormTemplateCallableHashModel.java
+++ b/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/form/SpringFormTemplateCallableHashModel.java
@@ -33,7 +33,6 @@ import org.apache.freemarker.core.model.TemplateModel;
 /**
  * TemplateHashModel wrapper for templates using Spring directives, functions and internal
models.
  */
-//TODO [FM3] Shouldn't this be a TemplateHashModelEx?
 public final class SpringFormTemplateCallableHashModel implements TemplateHashModel, Serializable
{
 
     private static final long serialVersionUID = 1L;

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/045c980c/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/form/TagOutputter.java
----------------------------------------------------------------------
diff --git a/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/form/TagOutputter.java
b/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/form/TagOutputter.java
index 8569ad1..4e127b8 100644
--- a/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/form/TagOutputter.java
+++ b/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/form/TagOutputter.java
@@ -26,6 +26,9 @@ import java.util.Stack;
 import org.apache.freemarker.core.TemplateException;
 import org.springframework.util.StringUtils;
 
+/**
+ * Utility to writing HTML content.
+ */
 class TagOutputter {
 
     private final Writer out;

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/045c980c/freemarker-spring/src/test/java/org/apache/freemarker/spring/model/form/FormTemplateDirectiveModelTest.java
----------------------------------------------------------------------
diff --git a/freemarker-spring/src/test/java/org/apache/freemarker/spring/model/form/FormTemplateDirectiveModelTest.java
b/freemarker-spring/src/test/java/org/apache/freemarker/spring/model/form/FormTemplateDirectiveModelTest.java
index 2795171..8ac8413 100644
--- a/freemarker-spring/src/test/java/org/apache/freemarker/spring/model/form/FormTemplateDirectiveModelTest.java
+++ b/freemarker-spring/src/test/java/org/apache/freemarker/spring/model/form/FormTemplateDirectiveModelTest.java
@@ -37,6 +37,7 @@ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilder
 import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
 import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
 import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.xpath;
 
 @RunWith(SpringJUnit4ClassRunner.class)
 @WebAppConfiguration("classpath:META-INF/web-resources")
@@ -62,7 +63,45 @@ public class FormTemplateDirectiveModelTest {
         final User user = userRepository.getUser(userId);
         mockMvc.perform(get("/users/{userId}/", userId).param("viewName", "test/model/form/form-directive-usages")
                 .accept(MediaType.parseMediaType("text/html"))).andExpect(status().isOk())
-                .andExpect(content().contentTypeCompatibleWith("text/html")).andDo(print());
+                .andExpect(content().contentTypeCompatibleWith("text/html")).andDo(print())
+                .andExpect(xpath("//form[@id='form1']/@method").string("post"))
+                .andExpect(xpath("//form[@id='form1']//input[@name='firstName']/@value").string(user.getFirstName()))
+                .andExpect(xpath("//form[@id='form1']//input[@name='lastName']/@value").string(user.getLastName()));
+    }
+
+    @Test
+    public void testDefaultAttributes() throws Exception {
+        final Long userId = userRepository.getUserIds().iterator().next();
+        final User user = userRepository.getUser(userId);
+        mockMvc.perform(get("/users/{userId}/", userId).param("viewName", "test/model/form/form-directive-usages")
+                .accept(MediaType.parseMediaType("text/html"))).andExpect(status().isOk())
+                .andExpect(content().contentTypeCompatibleWith("text/html")).andDo(print())
+                .andExpect(xpath("//form[@id='form2']/@class").string("my_cssClass"))
+                .andExpect(xpath("//form[@id='form2']/@style").string("my_cssStyle"))
+                .andExpect(xpath("//form[@id='form2']/@lang").string("my_lang"))
+                .andExpect(xpath("//form[@id='form2']/@title").string("my_title"))
+                .andExpect(xpath("//form[@id='form2']/@dir").string("my_dir"))
+                .andExpect(xpath("//form[@id='form2']/@tabindex").string("my_tabindex"))
+                .andExpect(xpath("//form[@id='form2']/@onclick").string("my_onclick()"))
+                .andExpect(xpath("//form[@id='form2']/@ondblclick").string("my_ondblclick()"))
+                .andExpect(xpath("//form[@id='form2']/@onmousedown").string("my_onmousedown()"))
+                .andExpect(xpath("//form[@id='form2']/@onmouseup").string("my_onmouseup()"))
+                .andExpect(xpath("//form[@id='form2']/@onmouseover").string("my_onmouseover()"))
+                .andExpect(xpath("//form[@id='form2']/@onmousemove").string("my_onmousemove()"))
+                .andExpect(xpath("//form[@id='form2']/@onmouseout").string("my_onmouseout()"))
+                .andExpect(xpath("//form[@id='form2']/@onkeypress").string("my_onkeypress()"))
+                .andExpect(xpath("//form[@id='form2']/@onkeyup").string("my_onkeyup()"))
+                .andExpect(xpath("//form[@id='form2']/@onkeydown").string("my_onkeydown()"))
+                .andExpect(xpath("//form[@id='form2']/@action").string("my_action"))
+                .andExpect(xpath("//form[@id='form2']/@method").string("post"))
+                .andExpect(xpath("//form[@id='form2']/@target").string("my_target"))
+                .andExpect(xpath("//form[@id='form2']/@enctype").string("my_enctype"))
+                .andExpect(xpath("//form[@id='form2']/@acceptCharset").string("my_acceptCharset"))
+                .andExpect(xpath("//form[@id='form2']/@onsubmit").string("my_onsubmit()"))
+                .andExpect(xpath("//form[@id='form2']/@onreset").string("my_onreset()"))
+                .andExpect(xpath("//form[@id='form2']/@autocomplete").string("my_autocomplete"))
+                .andExpect(xpath("//form[@id='form2']/@name").string("my_name"))
+                .andExpect(xpath("//form[@id='form2']/@autocomplete").string("my_autocomplete"));
     }
 
 }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/045c980c/freemarker-spring/src/test/java/org/apache/freemarker/spring/model/form/InputTemplateDirectiveModelTest.java
----------------------------------------------------------------------
diff --git a/freemarker-spring/src/test/java/org/apache/freemarker/spring/model/form/InputTemplateDirectiveModelTest.java
b/freemarker-spring/src/test/java/org/apache/freemarker/spring/model/form/InputTemplateDirectiveModelTest.java
index 9d8a6e2..b7a0d91 100644
--- a/freemarker-spring/src/test/java/org/apache/freemarker/spring/model/form/InputTemplateDirectiveModelTest.java
+++ b/freemarker-spring/src/test/java/org/apache/freemarker/spring/model/form/InputTemplateDirectiveModelTest.java
@@ -69,4 +69,39 @@ public class InputTemplateDirectiveModelTest {
                 .andExpect(xpath("//form[@id='form1']//input[@id='lastName' and @name='lastName']/@value").string(user.getLastName()));
     }
 
+    @Test
+    public void testDefaultAttributes() throws Exception {
+        final Long userId = userRepository.getUserIds().iterator().next();
+        mockMvc.perform(get("/users/{userId}/", userId).param("viewName", "test/model/form/input-directive-usages")
+                .accept(MediaType.parseMediaType("text/html"))).andExpect(status().isOk())
+                .andExpect(content().contentTypeCompatibleWith("text/html")).andDo(print())
+                .andExpect(xpath("//form[@id='form2']//input/@name").string("firstName"))
+                .andExpect(xpath("//form[@id='form2']//input/@class").string("my_cssClass"))
+                .andExpect(xpath("//form[@id='form2']//input/@style").string("my_cssStyle"))
+                .andExpect(xpath("//form[@id='form2']//input/@lang").string("my_lang"))
+                .andExpect(xpath("//form[@id='form2']//input/@title").string("my_title"))
+                .andExpect(xpath("//form[@id='form2']//input/@dir").string("my_dir"))
+                .andExpect(xpath("//form[@id='form2']//input/@tabindex").string("my_tabindex"))
+                .andExpect(xpath("//form[@id='form2']//input/@onclick").string("my_onclick()"))
+                .andExpect(xpath("//form[@id='form2']//input/@ondblclick").string("my_ondblclick()"))
+                .andExpect(xpath("//form[@id='form2']//input/@onmousedown").string("my_onmousedown()"))
+                .andExpect(xpath("//form[@id='form2']//input/@onmouseup").string("my_onmouseup()"))
+                .andExpect(xpath("//form[@id='form2']//input/@onmouseover").string("my_onmouseover()"))
+                .andExpect(xpath("//form[@id='form2']//input/@onmousemove").string("my_onmousemove()"))
+                .andExpect(xpath("//form[@id='form2']//input/@onmouseout").string("my_onmouseout()"))
+                .andExpect(xpath("//form[@id='form2']//input/@onkeypress").string("my_onkeypress()"))
+                .andExpect(xpath("//form[@id='form2']//input/@onkeyup").string("my_onkeyup()"))
+                .andExpect(xpath("//form[@id='form2']//input/@onkeydown").string("my_onkeydown()"))
+                .andExpect(xpath("//form[@id='form2']//input/@onfocus").string("my_onfocus()"))
+                .andExpect(xpath("//form[@id='form2']//input/@onblur").string("my_onblur()"))
+                .andExpect(xpath("//form[@id='form2']//input/@onchange").string("my_onchange()"))
+                .andExpect(xpath("//form[@id='form2']//input/@accesskey").string("my_accesskey"))
+                .andExpect(xpath("//form[@id='form2']//input/@disabled").string("disabled"))
+                .andExpect(xpath("//form[@id='form2']//input/@readonly").string("readonly"))
+                .andExpect(xpath("//form[@id='form2']//input/@size").string("my_size"))
+                .andExpect(xpath("//form[@id='form2']//input/@maxlength").string("my_maxlength"))
+                .andExpect(xpath("//form[@id='form2']//input/@alt").string("my_alt"))
+                .andExpect(xpath("//form[@id='form2']//input/@onselect").string("my_onselect()"))
+                .andExpect(xpath("//form[@id='form2']//input/@autocomplete").string("my_autocomplete"));
+    }
 }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/045c980c/freemarker-spring/src/test/resources/META-INF/web-resources/views/test/model/form/form-directive-usages.ftlh
----------------------------------------------------------------------
diff --git a/freemarker-spring/src/test/resources/META-INF/web-resources/views/test/model/form/form-directive-usages.ftlh
b/freemarker-spring/src/test/resources/META-INF/web-resources/views/test/model/form/form-directive-usages.ftlh
index 2338ed2..f70a4aa 100644
--- a/freemarker-spring/src/test/resources/META-INF/web-resources/views/test/model/form/form-directive-usages.ftlh
+++ b/freemarker-spring/src/test/resources/META-INF/web-resources/views/test/model/form/form-directive-usages.ftlh
@@ -19,24 +19,62 @@
 <html>
 <body>
 
+  <#assign form=spring.form />
+
   <h1>Form 1</h1>
   <hr/>
-  <@spring.form.form "user">
+  <@form.form "user" id="form1">
     <table>
       <tr>
         <th>First name:</th>
         <td>
-          <@spring.form.input 'user.firstName' />
+          <@form.input 'firstName' />
         </td>
       </tr>
       <tr>
         <th>Last name:</th>
         <td>
-          <@spring.form.input 'user.lastName' />
+          <@form.input 'lastName' />
         </td>
       </tr>
     </table>
-  </@spring.form.form>
+  </@form.form>
+
+  <hr/>
+
+  <h2>Testing default attributes</h2>
+  <@form.form "user" id="form2"
+                     cssClass="my_cssClass"
+                     cssStyle="my_cssStyle"
+                     lang="my_lang"
+                     title="my_title"
+                     dir="my_dir"
+                     tabindex="my_tabindex"
+                     onclick="my_onclick()"
+                     ondblclick="my_ondblclick()"
+                     onmousedown="my_onmousedown()"
+                     onmouseup="my_onmouseup()"
+                     onmouseover="my_onmouseover()"
+                     onmousemove="my_onmousemove()"
+                     onmouseout="my_onmouseout()"
+                     onkeypress="my_onkeypress()"
+                     onkeyup="my_onkeyup()"
+                     onkeydown="my_onkeydown()"
+                     cssErrorClass="my_cssErrorClass"
+                     action="my_action"
+                     method="post"
+                     target="my_target"
+                     enctype="my_enctype"
+                     acceptCharset="my_acceptCharset"
+                     onsubmit="my_onsubmit()"
+                     onreset="my_onreset()"
+                     autocomplete="my_autocomplete"
+                     name="my_name"
+                     value="my_value"
+                     type="my_type"
+                     servletRelativeAction="my_servletRelativeAction"
+                     methodParam="my_methodParam">
+  </@form.form>
 
 </body>
 </html>

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/045c980c/freemarker-spring/src/test/resources/META-INF/web-resources/views/test/model/form/input-directive-usages.ftlh
----------------------------------------------------------------------
diff --git a/freemarker-spring/src/test/resources/META-INF/web-resources/views/test/model/form/input-directive-usages.ftlh
b/freemarker-spring/src/test/resources/META-INF/web-resources/views/test/model/form/input-directive-usages.ftlh
index 614d2ae..b575ea0 100644
--- a/freemarker-spring/src/test/resources/META-INF/web-resources/views/test/model/form/input-directive-usages.ftlh
+++ b/freemarker-spring/src/test/resources/META-INF/web-resources/views/test/model/form/input-directive-usages.ftlh
@@ -19,6 +19,8 @@
 <html>
 <body>
 
+  <#assign form=spring.form />
+
   <h1>Form 1</h1>
   <hr/>
   <form id="form1">
@@ -26,19 +28,63 @@
       <tr>
         <th>E-Mail:</th>
         <td>
-          <@spring.form.input 'user.email' id="customEmailId" />
+          <@form.input 'user.email' id="customEmailId" />
         </td>
       </tr>
       <tr>
         <th>First name:</th>
         <td>
-          <@spring.form.input 'user.firstName' />
+          <@form.input 'user.firstName' />
         </td>
       </tr>
       <tr>
         <th>Last name:</th>
         <td>
-          <@spring.form.input 'user.lastName' />
+          <@form.input 'user.lastName' />
+        </td>
+      </tr>
+    </table>
+  </form>
+
+  <hr/>
+
+  <h2>Testing default attributes</h2>
+  <form id="form2">
+    <table>
+      <tr>
+        <th>First name:</th>
+        <td>
+          <@form.input "user.firstName"
+                      id="my_id"
+                      cssClass="my_cssClass"
+                      cssStyle="my_cssStyle"
+                      lang="my_lang"
+                      title="my_title"
+                      dir="my_dir"
+                      tabindex="my_tabindex"
+                      onclick="my_onclick()"
+                      ondblclick="my_ondblclick()"
+                      onmousedown="my_onmousedown()"
+                      onmouseup="my_onmouseup()"
+                      onmouseover="my_onmouseover()"
+                      onmousemove="my_onmousemove()"
+                      onmouseout="my_onmouseout()"
+                      onkeypress="my_onkeypress()"
+                      onkeyup="my_onkeyup()"
+                      onkeydown="my_onkeydown()"
+                      cssErrorClass="my_cssErrorClass"
+                      onfocus="my_onfocus()"
+                      onblur="my_onblur()"
+                      onchange="my_onchange()"
+                      accesskey="my_accesskey"
+                      disabled=true
+                      readonly=true
+                      size="my_size"
+                      maxlength="my_maxlength"
+                      alt="my_alt"
+                      onselect="my_onselect()"
+                      autocomplete="my_autocomplete"
+          />
         </td>
       </tr>
     </table>



Mime
View raw message