You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@freemarker.apache.org by wo...@apache.org on 2018/01/04 19:49:27 UTC

[04/13] incubator-freemarker git commit: FREEMARKER-55: make attribute arguments explicit

FREEMARKER-55: make attribute arguments explicit


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

Branch: refs/heads/3
Commit: 2b5e9b7cb8bda5065e475339f3ec9dd854848b0a
Parents: ff2feb0
Author: Woonsan Ko <wo...@apache.org>
Authored: Thu Dec 28 01:26:54 2017 -0500
Committer: Woonsan Ko <wo...@apache.org>
Committed: Thu Dec 28 01:26:54 2017 -0500

----------------------------------------------------------------------
 ...aBoundFormElementTemplateDirectiveModel.java |  76 +++-
 .../AbstractFormTemplateDirectiveModel.java     |  41 ++-
 ...stractHtmlElementTemplateDirectiveModel.java | 349 +++++++++++++------
 ...tHtmlInputElementTemplateDirectiveModel.java | 126 +++++++
 .../model/form/InputTemplateDirectiveModel.java | 152 +++++---
 .../spring/model/form/TagOutputter.java         |  11 +-
 .../test/model/form/input-directive-usages.ftlh |   2 +-
 7 files changed, 582 insertions(+), 175 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/2b5e9b7c/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 7bfac9b..f50f81d 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
@@ -20,38 +20,88 @@
 package org.apache.freemarker.spring.model.form;
 
 import java.io.IOException;
+import java.io.Writer;
+import java.util.Arrays;
+import java.util.List;
 
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
+import org.apache.freemarker.core.CallPlace;
 import org.apache.freemarker.core.Environment;
 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.util.CallableUtils;
+import org.apache.freemarker.core.util.StringToIndexMap;
 import org.springframework.util.StringUtils;
 import org.springframework.web.servlet.support.BindStatus;
 import org.springframework.web.servlet.support.RequestContext;
+import org.springframework.web.servlet.support.RequestDataValueProcessor;
 
 /**
  * Corresponds to <code>org.springframework.web.servlet.tags.form.AbstractDataBoundFormElementTag</code>.
  */
 public abstract class AbstractDataBoundFormElementTemplateDirectiveModel extends AbstractFormTemplateDirectiveModel {
 
+    private static final int PATH_PARAM_IDX = 0;
+
+    private static final int ID_PARAM_IDX = 1;
+
+    private static final String ID_PARAM_NAME = "id";
+
+    protected static List<StringToIndexMap.Entry> NAMED_ARGS_ENTRY_LIST = Arrays.asList(
+            new StringToIndexMap.Entry(ID_PARAM_NAME, ID_PARAM_IDX)
+    );
+
+    private static final ArgumentArrayLayout ARGS_LAYOUT =
+            ArgumentArrayLayout.create(
+                    1,
+                    false,
+                    StringToIndexMap.of(NAMED_ARGS_ENTRY_LIST.toArray(new StringToIndexMap.Entry[NAMED_ARGS_ENTRY_LIST.size()])),
+                    true
+                    );
+
+    private String path;
     private String id;
 
+    private BindStatus bindStatus;
+
+    protected AbstractDataBoundFormElementTemplateDirectiveModel(HttpServletRequest request,
+            HttpServletResponse response) {
+        super(request, response);
+    }
+
+    public String getPath() {
+        return path;
+    }
+
     public String getId() {
         return id;
     }
 
-    public void setId(String id) {
-        this.id = id;
+    @Override
+    public ArgumentArrayLayout getDirectiveArgumentArrayLayout() {
+        return ARGS_LAYOUT;
     }
 
-    protected AbstractDataBoundFormElementTemplateDirectiveModel(HttpServletRequest request,
-            HttpServletResponse response) {
-        super(request, response);
+    @Override
+    protected void executeInternal(TemplateModel[] args, CallPlace callPlace, Writer out, Environment env,
+            ObjectWrapperAndUnwrapper objectWrapperAndUnwrapper, RequestContext requestContext)
+            throws TemplateException, IOException {
+
+        path = CallableUtils.getOptionalStringArgument(args, PATH_PARAM_IDX, this);
+        id = CallableUtils.getOptionalStringArgument(args, ID_PARAM_IDX, this);
+
+        bindStatus = getBindStatus(env, objectWrapperAndUnwrapper, requestContext, path, false);
+    }
+
+    protected BindStatus getBindStatus() {
+        return bindStatus;
     }
 
-    protected void writeDefaultHtmlElementAttributes(TagOutputter tagOut) throws TemplateException, IOException {
+    protected void writeDefaultAttributes(TagOutputter tagOut) throws TemplateException, IOException {
         // FIXME
         writeOptionalAttribute(tagOut, "id", resolveId());
         writeOptionalAttribute(tagOut, "name", getName());
@@ -86,4 +136,18 @@ public abstract class AbstractDataBoundFormElementTemplateDirectiveModel extends
         return (expression != null ? expression : "");
     }
 
+    protected final String processFieldValue(Environment env, String name, String value, String type) throws TemplateException {
+        RequestContext requestContext = getRequestContext(env, false);
+        RequestDataValueProcessor processor = requestContext.getRequestDataValueProcessor();
+
+        // FIXME
+//        ServletRequest request = this.pageContext.getRequest();
+//
+//        if (processor != null && (request instanceof HttpServletRequest)) {
+//            value = processor.processFormFieldValue((HttpServletRequest) request, name, value, type);
+//        }
+
+        return value;
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/2b5e9b7c/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 756fdb7..28dd1bf 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
@@ -19,20 +19,16 @@
 
 package org.apache.freemarker.spring.model.form;
 
+import java.beans.PropertyEditor;
 import java.io.IOException;
-import java.io.Writer;
 
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
-import org.apache.freemarker.core.CallPlace;
-import org.apache.freemarker.core.Environment;
 import org.apache.freemarker.core.TemplateException;
-import org.apache.freemarker.core.model.ObjectWrapperAndUnwrapper;
-import org.apache.freemarker.core.model.TemplateModel;
 import org.apache.freemarker.spring.model.AbstractSpringTemplateDirectiveModel;
 import org.springframework.util.ObjectUtils;
-import org.springframework.web.servlet.support.RequestContext;
+import org.springframework.web.util.HtmlUtils;
 
 /**
  * Corresponds to <code>org.springframework.web.servlet.tags.form.AbstractFormTag</code>.
@@ -43,31 +39,34 @@ public abstract class AbstractFormTemplateDirectiveModel extends AbstractSpringT
         super(request, response);
     }
 
-    @Override
-    protected final void executeInternal(TemplateModel[] args, CallPlace callPlace, Writer out, Environment env,
-            ObjectWrapperAndUnwrapper objectWrapperAndUnwrapper, RequestContext requestContext)
-            throws TemplateException, IOException {
-        final TagOutputter tagOut = new TagOutputter(out);
-        writeDirectiveContent(args, callPlace, tagOut, env, objectWrapperAndUnwrapper, requestContext);
-    }
-
-    protected abstract void writeDirectiveContent(TemplateModel[] args, CallPlace callPlace, TagOutputter tagOut,
-            Environment env, ObjectWrapperAndUnwrapper objectWrapperAndUnwrapper, RequestContext requestContext)
-            throws TemplateException;
-
     protected Object evaluate(String attributeName, Object value) throws TemplateException {
         return value;
     }
 
-    protected String getDisplayString(Object value) {
+    public static String getDisplayString(Object value, boolean htmlEscape) {
         String displayValue = ObjectUtils.getDisplayString(value);
-        return displayValue;
+        return (htmlEscape ? HtmlUtils.htmlEscape(displayValue) : displayValue);
+    }
+
+    public static String getDisplayString(Object value, PropertyEditor propertyEditor, boolean htmlEscape) {
+        if (propertyEditor != null && !(value instanceof String)) {
+            try {
+                propertyEditor.setValue(value);
+                String text = propertyEditor.getAsText();
+                if (text != null) {
+                    return getDisplayString(text, htmlEscape);
+                }
+            } catch (Throwable ex) {
+                // The PropertyEditor might not support this value... pass through.
+            }
+        }
+        return getDisplayString(value, htmlEscape);
     }
 
     protected final void writeOptionalAttribute(TagOutputter tagOut, String attrName, Object attrValue)
             throws TemplateException, IOException {
         if (attrValue != null) {
-            tagOut.writeOptionalAttributeValue(attrName, getDisplayString(evaluate(attrName, attrValue)));
+            tagOut.writeOptionalAttributeValue(attrName, getDisplayString(evaluate(attrName, attrValue), false));
         }
     }
 

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/2b5e9b7c/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 a91a67c..d982111 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
@@ -20,14 +20,18 @@
 package org.apache.freemarker.spring.model.form;
 
 import java.io.IOException;
+import java.io.Writer;
+import java.util.Arrays;
 import java.util.Collections;
-import java.util.HashMap;
 import java.util.LinkedHashMap;
+import java.util.List;
 import java.util.Map;
 
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
+import org.apache.freemarker.core.CallPlace;
+import org.apache.freemarker.core.Environment;
 import org.apache.freemarker.core.TemplateException;
 import org.apache.freemarker.core.model.ArgumentArrayLayout;
 import org.apache.freemarker.core.model.ObjectWrapperAndUnwrapper;
@@ -35,6 +39,11 @@ import org.apache.freemarker.core.model.TemplateHashModelEx;
 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.springframework.util.ObjectUtils;
+import org.springframework.util.StringUtils;
+import org.springframework.web.servlet.support.RequestContext;
 
 /**
  * Corresponds to <code>org.springframework.web.servlet.tags.form.AbstractHtmlElementTag</code>.
@@ -42,46 +51,110 @@ import org.apache.freemarker.core.util.CallableUtils;
 public abstract class AbstractHtmlElementTemplateDirectiveModel
         extends AbstractDataBoundFormElementTemplateDirectiveModel {
 
-    protected static Map<String, String> createAttributeKeyNamePairsMap(String ... attrNames) {
-        Map<String, String> map = new HashMap<>();
-        for (String attrName : attrNames) {
-            map.put(attrName.toUpperCase(), attrName);
-        }
-        return map;
-    }
-
-    private static final Map<String, String> REGISTERED_ATTRIBUTES = Collections.unmodifiableMap(
-            createAttributeKeyNamePairsMap(
-                    "class",
-                    "style",
-                    "lang",
-                    "title",
-                    "dir",
-                    "tabindex",
-                    "onclick",
-                    "ondblclick",
-                    "onmousedown",
-                    "onmouseup",
-                    "onmouseover",
-                    "onmousemove",
-                    "onmouseout",
-                    "onkeypress",
-                    "onkeyup",
-                    "onkeydown")
-            );
-
-    private static final int PATH_PARAM_IDX = 0;
+    private static final int NAMED_ARGS_OFFSET = AbstractDataBoundFormElementTemplateDirectiveModel.NAMED_ARGS_ENTRY_LIST
+            .size() + 1;
+
+    private static final int CSS_CLASS_PARAM_IDX = NAMED_ARGS_OFFSET;
+    private static final String CSS_CLASS_PARAM_NAME = "cssClass";
+
+    private static final int CSS_STYLE_PARAM_IDX = NAMED_ARGS_OFFSET + 1;
+    private static final String CSS_STYLE_PARAM_NAME = "cssStyle";
+
+    private static final int LANG_PARAM_IDX = NAMED_ARGS_OFFSET + 2;
+    private static final String LANG_PARAM_NAME = "lang";
+
+    private static final int TITLE_PARAM_IDX = NAMED_ARGS_OFFSET + 3;
+    private static final String TITLE_PARAM_NAME = "title";
+
+    private static final int DIR_PARAM_IDX = NAMED_ARGS_OFFSET + 4;
+    private static final String DIR_PARAM_NAME = "dir";
+
+    private static final int TABINDEX_PARAM_IDX = NAMED_ARGS_OFFSET + 5;
+    private static final String TABINDEX_PARAM_NAME = "tabindex";
+
+    private static final int ONCLICK_PARAM_IDX = NAMED_ARGS_OFFSET + 6;
+    private static final String ONCLICK_PARAM_NAME = "onclick";
+
+    private static final int ONDBLCLICK_PARAM_IDX = NAMED_ARGS_OFFSET + 7;
+    private static final String ONDBLCLICK_PARAM_NAME = "ondblclick";
+
+    private static final int ONMOUSEDOWN_PARAM_IDX = NAMED_ARGS_OFFSET + 8;
+    private static final String ONMOUSEDOWN_PARAM_NAME = "onmousedown";
+
+    private static final int ONMOUSEUP_PARAM_IDX = NAMED_ARGS_OFFSET + 9;
+    private static final String ONMOUSEUP_PARAM_NAME = "onmouseup";
+
+    private static final int ONMOUSEOVER_PARAM_IDX = NAMED_ARGS_OFFSET + 10;
+    private static final String ONMOUSEOVER_PARAM_NAME = "onmouseover";
+
+    private static final int ONMOUSEMOVE_PARAM_IDX = NAMED_ARGS_OFFSET + 11;
+    private static final String ONMOUSEMOVE_PARAM_NAME = "onmousemove";
+
+    private static final int ONMOUSEOUT_PARAM_IDX = NAMED_ARGS_OFFSET + 12;
+    private static final String ONMOUSEOUT_PARAM_NAME = "onmouseout";
+
+    private static final int ONKEYPRESS_PARAM_IDX = NAMED_ARGS_OFFSET + 13;
+    private static final String ONKEYPRESS_PARAM_NAME = "onkeypress";
+
+    private static final int ONKEYUP_PARAM_IDX = NAMED_ARGS_OFFSET + 14;
+    private static final String ONKEYUP_PARAM_NAME = "onkeyup";
+
+    private static final int ONKEYDOWN_PARAM_IDX = NAMED_ARGS_OFFSET + 15;
+    private static final String ONKEYDOWN_PARAM_NAME = "onkeydown";
+
+    private static final int CSSERRORCLASS_PARAM_IDX = NAMED_ARGS_OFFSET + 16;
+    private static final String CSSERRORCLASS_PARAM_NAME = "cssErrorClass";
+
+    protected static List<StringToIndexMap.Entry> NAMED_ARGS_ENTRY_LIST =
+            _CollectionUtils.mergeImmutableLists(false,
+                    AbstractDataBoundFormElementTemplateDirectiveModel.NAMED_ARGS_ENTRY_LIST,
+                    Arrays.asList(
+                            new StringToIndexMap.Entry(CSS_CLASS_PARAM_NAME, CSS_CLASS_PARAM_IDX),
+                            new StringToIndexMap.Entry(CSS_STYLE_PARAM_NAME, CSS_STYLE_PARAM_IDX),
+                            new StringToIndexMap.Entry(LANG_PARAM_NAME, LANG_PARAM_IDX),
+                            new StringToIndexMap.Entry(TITLE_PARAM_NAME, TITLE_PARAM_IDX),
+                            new StringToIndexMap.Entry(DIR_PARAM_NAME, DIR_PARAM_IDX),
+                            new StringToIndexMap.Entry(TABINDEX_PARAM_NAME, TABINDEX_PARAM_IDX),
+                            new StringToIndexMap.Entry(ONCLICK_PARAM_NAME, ONCLICK_PARAM_IDX),
+                            new StringToIndexMap.Entry(ONDBLCLICK_PARAM_NAME, ONDBLCLICK_PARAM_IDX),
+                            new StringToIndexMap.Entry(ONMOUSEDOWN_PARAM_NAME, ONMOUSEDOWN_PARAM_IDX),
+                            new StringToIndexMap.Entry(ONMOUSEUP_PARAM_NAME, ONMOUSEUP_PARAM_IDX),
+                            new StringToIndexMap.Entry(ONMOUSEOVER_PARAM_NAME, ONMOUSEOVER_PARAM_IDX),
+                            new StringToIndexMap.Entry(ONMOUSEMOVE_PARAM_NAME, ONMOUSEMOVE_PARAM_IDX),
+                            new StringToIndexMap.Entry(ONMOUSEOUT_PARAM_NAME, ONMOUSEOUT_PARAM_IDX),
+                            new StringToIndexMap.Entry(ONKEYPRESS_PARAM_NAME, ONKEYPRESS_PARAM_IDX),
+                            new StringToIndexMap.Entry(ONKEYUP_PARAM_NAME, ONKEYUP_PARAM_IDX),
+                            new StringToIndexMap.Entry(ONKEYDOWN_PARAM_NAME, ONKEYDOWN_PARAM_IDX),
+                            new StringToIndexMap.Entry(CSSERRORCLASS_PARAM_NAME, CSSERRORCLASS_PARAM_IDX)
+                            )
+                    );
 
     private static final ArgumentArrayLayout ARGS_LAYOUT =
             ArgumentArrayLayout.create(
                     1,
                     false,
-                    null,
+                    StringToIndexMap.of(NAMED_ARGS_ENTRY_LIST.toArray(new StringToIndexMap.Entry[NAMED_ARGS_ENTRY_LIST.size()])),
                     true
                     );
 
-    private Map<String, Object> registeredAttributes;
-    private Map<String, Object> unmodifiableRegisteredAttributes = Collections.emptyMap();
+    private String cssClass;
+    private String cssStyle;
+    private String lang;
+    private String title;
+    private String dir;
+    private String tabindex;
+    private String onclick;
+    private String ondblclick;
+    private String onmousedown;
+    private String onmouseup;
+    private String onmouseover;
+    private String onmousemove;
+    private String onmouseout;
+    private String onkeypress;
+    private String onkeyup;
+    private String onkeydown;
+    private String cssErrorClass;
+
     private Map<String, Object> dynamicAttributes;
     private Map<String, Object> unmodifiableDynamicAttributes = Collections.emptyMap();
 
@@ -94,32 +167,131 @@ public abstract class AbstractHtmlElementTemplateDirectiveModel
         return ARGS_LAYOUT;
     }
 
-    public Map<String, Object> getRegisteredAttributes() {
-        return unmodifiableRegisteredAttributes;
+    @Override
+    protected void executeInternal(TemplateModel[] args, CallPlace callPlace, Writer out, Environment env,
+            ObjectWrapperAndUnwrapper objectWrapperAndUnwrapper, RequestContext requestContext)
+            throws TemplateException, IOException {
+
+        super.executeInternal(args, callPlace, out, env, objectWrapperAndUnwrapper, requestContext);
+
+        cssClass = CallableUtils.getOptionalStringArgument(args, CSS_CLASS_PARAM_IDX, this);
+        cssStyle = CallableUtils.getOptionalStringArgument(args, CSS_STYLE_PARAM_IDX, this);
+        lang = CallableUtils.getOptionalStringArgument(args, LANG_PARAM_IDX, this);
+        title = CallableUtils.getOptionalStringArgument(args, TITLE_PARAM_IDX, this);
+        dir = CallableUtils.getOptionalStringArgument(args, DIR_PARAM_IDX, this);
+        tabindex = CallableUtils.getOptionalStringArgument(args, TABINDEX_PARAM_IDX, this);
+        onclick = CallableUtils.getOptionalStringArgument(args, ONCLICK_PARAM_IDX, this);
+        ondblclick = CallableUtils.getOptionalStringArgument(args, ONDBLCLICK_PARAM_IDX, this);
+        onmousedown = CallableUtils.getOptionalStringArgument(args, ONMOUSEDOWN_PARAM_IDX, this);
+        onmouseup = CallableUtils.getOptionalStringArgument(args, ONMOUSEUP_PARAM_IDX, this);
+        onmouseover = CallableUtils.getOptionalStringArgument(args, ONMOUSEOVER_PARAM_IDX, this);
+        onmousemove = CallableUtils.getOptionalStringArgument(args, ONMOUSEMOVE_PARAM_IDX, this);
+        onmouseout = CallableUtils.getOptionalStringArgument(args, ONMOUSEOUT_PARAM_IDX, this);
+        onkeypress = CallableUtils.getOptionalStringArgument(args, ONKEYPRESS_PARAM_IDX, this);
+        onkeyup = CallableUtils.getOptionalStringArgument(args, ONKEYUP_PARAM_IDX, this);
+        onkeydown = CallableUtils.getOptionalStringArgument(args, ONKEYDOWN_PARAM_IDX, this);
+        cssErrorClass = CallableUtils.getOptionalStringArgument(args, CSSERRORCLASS_PARAM_IDX, this);
+
+        final int attrsVarargsIndex = ARGS_LAYOUT.getNamedVarargsArgumentIndex();
+        final TemplateHashModelEx attrsHashModel = (TemplateHashModelEx) args[attrsVarargsIndex];
+
+        if (attrsHashModel != null && !attrsHashModel.isEmptyHash()) {
+            for (TemplateHashModelEx.KeyValuePairIterator attrIt = attrsHashModel.keyValuePairIterator(); attrIt.hasNext();) {
+                TemplateHashModelEx.KeyValuePair pair = attrIt.next();
+                TemplateModel attrNameModel = pair.getKey();
+                TemplateModel attrValueModel = pair.getValue();
+
+                if (!(attrNameModel instanceof TemplateStringModel)) {
+                    throw CallableUtils.newArgumentValueException(attrsVarargsIndex,
+                            "Attribute name must be a string.", this);
+                }
+
+                String attrName = ((TemplateStringModel) attrNameModel).getAsString();
+
+                if (attrName.isEmpty()) {
+                    throw CallableUtils.newArgumentValueException(attrsVarargsIndex,
+                            "Attribute name must be a non-blank string.", this);
+                }
+
+                final Object attrValue = objectWrapperAndUnwrapper.unwrap(attrValueModel);
+                setDynamicAttribute(attrName, attrValue);
+            }
+        }
     }
 
-    public Map<String, Object> getDynamicAttributes() {
-        return unmodifiableDynamicAttributes;
+    public String getCssClass() {
+        return cssClass;
     }
 
-    public void setRegisteredAttribute(String localName, Object value) {
-        if (localName == null) {
-            throw new IllegalArgumentException("Attribute name must not be null.");
-        }
+    public String getCssStyle() {
+        return cssStyle;
+    }
 
-        if (!isRegisteredAttribute(localName, value)) {
-            throw new IllegalArgumentException("Invalid attribute: " + localName + "=" + value);
-        }
+    public String getLang() {
+        return lang;
+    }
 
-        if (registeredAttributes == null) {
-            registeredAttributes = new LinkedHashMap<String, Object>();
-            unmodifiableRegisteredAttributes = Collections.unmodifiableMap(registeredAttributes);
-        }
+    public String getTitle() {
+        return title;
+    }
 
-        registeredAttributes.put(localName, value);
+    public String getDir() {
+        return dir;
     }
 
-    public void setDynamicAttribute(String localName, Object value) {
+    public String getTabindex() {
+        return tabindex;
+    }
+
+    public String getOnclick() {
+        return onclick;
+    }
+
+    public String getOndblclick() {
+        return ondblclick;
+    }
+
+    public String getOnmousedown() {
+        return onmousedown;
+    }
+
+    public String getOnmouseup() {
+        return onmouseup;
+    }
+
+    public String getOnmouseover() {
+        return onmouseover;
+    }
+
+    public String getOnmousemove() {
+        return onmousemove;
+    }
+
+    public String getOnmouseout() {
+        return onmouseout;
+    }
+
+    public String getOnkeypress() {
+        return onkeypress;
+    }
+
+    public String getOnkeyup() {
+        return onkeyup;
+    }
+
+    public String getOnkeydown() {
+        return onkeydown;
+    }
+
+    public String getCssErrorClass() {
+        return cssErrorClass;
+    }
+
+    public Map<String, Object> getDynamicAttributes() {
+        return unmodifiableDynamicAttributes;
+    }
+
+    private void setDynamicAttribute(String localName, Object value) {
         if (localName == null) {
             throw new IllegalArgumentException("Attribute name must not be null.");
         }
@@ -136,62 +308,45 @@ public abstract class AbstractHtmlElementTemplateDirectiveModel
         dynamicAttributes.put(localName, value);
     }
 
-    protected String getPathArgument(TemplateModel[] args) throws TemplateException {
-        final String path = CallableUtils.getStringArgument(args, PATH_PARAM_IDX, this);
-        return path;
-    }
-
-    protected boolean isRegisteredAttribute(String localName, Object value) {
-        return REGISTERED_ATTRIBUTES.containsKey(localName.toUpperCase());
-    }
-
     protected boolean isValidDynamicAttribute(String localName, Object value) {
         return true;
     }
 
-    protected void readRegisteredAndDynamicAttributes(TemplateModel[] args, ObjectWrapperAndUnwrapper objectWrapperAndUnwrapper) throws TemplateException {
-        final int attrsVarargsIndex = getDirectiveArgumentArrayLayout().getNamedVarargsArgumentIndex();
-        final TemplateHashModelEx attrsHashModel = (TemplateHashModelEx) args[attrsVarargsIndex];
-
-        if (!attrsHashModel.isEmptyHash()) {
-            for (TemplateHashModelEx.KeyValuePairIterator attrIt = attrsHashModel.keyValuePairIterator(); attrIt.hasNext();) {
-                TemplateHashModelEx.KeyValuePair pair = attrIt.next();
-                TemplateModel attrNameModel = pair.getKey();
-                TemplateModel attrValueModel = pair.getValue();
-
-                if (!(attrNameModel instanceof TemplateStringModel)) {
-                    throw CallableUtils.newArgumentValueException(attrsVarargsIndex,
-                            "Parameter name must be a string.", this);
-                }
-
-                String attrName = ((TemplateStringModel) attrNameModel).getAsString();
-
-                if (attrName.isEmpty()) {
-                    throw CallableUtils.newArgumentValueException(attrsVarargsIndex,
-                            "Attribute name must be a non-blank string.", this);
-                }
-
-                final Object attrValue = objectWrapperAndUnwrapper.unwrap(attrValueModel);
+    protected void writeDefaultAttributes(TagOutputter tagOut) throws TemplateException, IOException {
+        super.writeDefaultAttributes(tagOut);
+        writeOptionalAttributes(tagOut);
+    }
 
-                if (isRegisteredAttribute(attrName, attrValue)) {
-                    setRegisteredAttribute(attrName.toUpperCase(), attrValue);
-                } else {
-                    setDynamicAttribute(attrName, attrValue);
-                }
+    protected void writeOptionalAttributes(TagOutputter tagOut) throws TemplateException, IOException {
+        tagOut.writeOptionalAttributeValue("class", resolveCssClass());
+        tagOut.writeOptionalAttributeValue("style", ObjectUtils.getDisplayString(evaluate("cssStyle", getCssStyle())));
+        writeOptionalAttribute(tagOut, LANG_PARAM_NAME, getLang());
+        writeOptionalAttribute(tagOut, TITLE_PARAM_NAME, getTitle());
+        writeOptionalAttribute(tagOut, DIR_PARAM_NAME, getDir());
+        writeOptionalAttribute(tagOut, TABINDEX_PARAM_NAME, getTabindex());
+        writeOptionalAttribute(tagOut, ONCLICK_PARAM_NAME, getOnclick());
+        writeOptionalAttribute(tagOut, ONDBLCLICK_PARAM_NAME, getOndblclick());
+        writeOptionalAttribute(tagOut, ONMOUSEDOWN_PARAM_NAME, getOnmousedown());
+        writeOptionalAttribute(tagOut, ONMOUSEUP_PARAM_NAME, getOnmouseup());
+        writeOptionalAttribute(tagOut, ONMOUSEOVER_PARAM_NAME, getOnmouseover());
+        writeOptionalAttribute(tagOut, ONMOUSEMOVE_PARAM_NAME, getOnmousemove());
+        writeOptionalAttribute(tagOut, ONMOUSEOUT_PARAM_NAME, getOnmouseout());
+        writeOptionalAttribute(tagOut, ONKEYPRESS_PARAM_NAME, getOnkeypress());
+        writeOptionalAttribute(tagOut, ONKEYUP_PARAM_NAME, getOnkeyup());
+        writeOptionalAttribute(tagOut, ONKEYDOWN_PARAM_NAME, getOnkeydown());
+
+        if (!this.unmodifiableDynamicAttributes.isEmpty()) {
+            for (String attr : this.dynamicAttributes.keySet()) {
+                tagOut.writeOptionalAttributeValue(attr, getDisplayString(this.dynamicAttributes.get(attr), false));
             }
         }
-        
-        System.out.println("$$$$$ dynamicAttributes: " + this.getDynamicAttributes());
     }
 
-    protected void writeDefaultHtmlElementAttributes(TagOutputter tagOut) throws TemplateException, IOException {
-        super.writeDefaultHtmlElementAttributes(tagOut);
-
-        for (Map.Entry<String, String> entry : REGISTERED_ATTRIBUTES.entrySet()) {
-            String attrKey = entry.getKey();
-            String attrName = entry.getValue();
-            Object attrValue = getRegisteredAttributes().get(attrKey);
-            writeOptionalAttribute(tagOut, attrName, attrValue);
+    protected String resolveCssClass() throws TemplateException {
+        if (getBindStatus().isError() && StringUtils.hasText(getCssErrorClass())) {
+            return ObjectUtils.getDisplayString(evaluate("cssErrorClass", getCssErrorClass()));
+        } else {
+            return ObjectUtils.getDisplayString(evaluate("cssClass", getCssClass()));
         }
     }
 

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/2b5e9b7c/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 e5ebf93..b77bc9d 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
@@ -19,14 +19,140 @@
 
 package org.apache.freemarker.spring.model.form;
 
+import java.io.IOException;
+import java.io.Writer;
+import java.util.Arrays;
+import java.util.List;
+
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
+import org.apache.freemarker.core.CallPlace;
+import org.apache.freemarker.core.Environment;
+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.util.CallableUtils;
+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 {
 
+    private static final int NAMED_ARGS_OFFSET = AbstractHtmlElementTemplateDirectiveModel.NAMED_ARGS_ENTRY_LIST.size()
+            + 1;
+
+    private static final int ONFOCUS_PARAM_IDX = NAMED_ARGS_OFFSET;
+    private static final String ONFOCUS_PARAM_NAME = "onfocus";
+
+    private static final int ONBLUR_PARAM_IDX = NAMED_ARGS_OFFSET + 1;
+    private static final String ONBLUR_PARAM_NAME = "onblur";
+
+    private static final int ONCHANGE_PARAM_IDX = NAMED_ARGS_OFFSET + 2;
+    private static final String ONCHANGE_PARAM_NAME = "onchange";
+
+    private static final int ACCESSKEY_PARAM_IDX = NAMED_ARGS_OFFSET + 3;
+    private static final String ACCESSKEY_PARAM_NAME = "accesskey";
+
+    private static final int DISABLED_PARAM_IDX = NAMED_ARGS_OFFSET + 4;
+    private static final String DISABLED_PARAM_NAME = "disabled";
+
+    private static final int READONLY_PARAM_IDX = NAMED_ARGS_OFFSET + 5;
+    private static final String READONLY_PARAM_NAME = "readonly";
+
+    protected static List<StringToIndexMap.Entry> NAMED_ARGS_ENTRY_LIST =
+            _CollectionUtils.mergeImmutableLists(false,
+                    AbstractHtmlElementTemplateDirectiveModel.NAMED_ARGS_ENTRY_LIST,
+                    Arrays.asList(
+                        new StringToIndexMap.Entry(ONFOCUS_PARAM_NAME, ONFOCUS_PARAM_IDX),
+                        new StringToIndexMap.Entry(ONBLUR_PARAM_NAME, ONBLUR_PARAM_IDX),
+                        new StringToIndexMap.Entry(ONCHANGE_PARAM_NAME, ONCHANGE_PARAM_IDX),
+                        new StringToIndexMap.Entry(ACCESSKEY_PARAM_NAME, ACCESSKEY_PARAM_IDX),
+                        new StringToIndexMap.Entry(DISABLED_PARAM_NAME, DISABLED_PARAM_IDX),
+                        new StringToIndexMap.Entry(READONLY_PARAM_NAME, READONLY_PARAM_IDX)
+                        )
+                    );
+
+    private static final ArgumentArrayLayout ARGS_LAYOUT =
+            ArgumentArrayLayout.create(
+                    1,
+                    false,
+                    StringToIndexMap.of(NAMED_ARGS_ENTRY_LIST.toArray(new StringToIndexMap.Entry[NAMED_ARGS_ENTRY_LIST.size()])),
+                    true
+                    );
+
+    private String onfocus;
+    private String onblur;
+    private String onchange;
+    private String accesskey;
+    private boolean disabled;
+    private boolean readonly;
+
     protected AbstractHtmlInputElementTemplateDirectiveModel(HttpServletRequest request,
             HttpServletResponse response) {
         super(request, response);
     }
 
+    @Override
+    public ArgumentArrayLayout getDirectiveArgumentArrayLayout() {
+        return ARGS_LAYOUT;
+    }
+
+    @Override
+    protected void executeInternal(TemplateModel[] args, CallPlace callPlace, Writer out, Environment env,
+            ObjectWrapperAndUnwrapper objectWrapperAndUnwrapper, RequestContext requestContext)
+            throws TemplateException, IOException {
+
+        super.executeInternal(args, callPlace, out, env, objectWrapperAndUnwrapper, requestContext);
+
+        onfocus = CallableUtils.getOptionalStringArgument(args, ONFOCUS_PARAM_IDX, this);
+        onblur = CallableUtils.getOptionalStringArgument(args, ONBLUR_PARAM_IDX, this);
+        onchange = CallableUtils.getOptionalStringArgument(args, ONCHANGE_PARAM_IDX, this);
+        accesskey = CallableUtils.getOptionalStringArgument(args, ACCESSKEY_PARAM_IDX, this);
+        disabled = CallableUtils.getOptionalBooleanArgument(args, DISABLED_PARAM_IDX, this, false);
+        readonly = CallableUtils.getOptionalBooleanArgument(args, READONLY_PARAM_IDX, this, false);
+    }
+
+    @Override
+    protected void writeOptionalAttributes(TagOutputter tagOut) throws TemplateException, IOException {
+        super.writeOptionalAttributes(tagOut);
+
+        writeOptionalAttribute(tagOut, ONFOCUS_PARAM_NAME, getOnfocus());
+        writeOptionalAttribute(tagOut, ONBLUR_PARAM_NAME, getOnblur());
+        writeOptionalAttribute(tagOut, ONCHANGE_PARAM_NAME, getOnchange());
+        writeOptionalAttribute(tagOut, ACCESSKEY_PARAM_NAME, getAccesskey());
+
+        if (isDisabled()) {
+            tagOut.writeAttribute(DISABLED_PARAM_NAME, "disabled");
+        }
+        if (isReadonly()) {
+            writeOptionalAttribute(tagOut, READONLY_PARAM_NAME, "readonly");
+        }
+    }
+
+    public String getOnfocus() {
+        return onfocus;
+    }
+
+    public String getOnblur() {
+        return onblur;
+    }
+
+    public String getOnchange() {
+        return onchange;
+    }
+
+    public String getAccesskey() {
+        return accesskey;
+    }
+
+    public boolean isDisabled() {
+        return disabled;
+    }
+
+    public boolean isReadonly() {
+        return readonly;
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/2b5e9b7c/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 4540006..e53eb0c 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
@@ -20,8 +20,9 @@
 package org.apache.freemarker.spring.model.form;
 
 import java.io.IOException;
-import java.util.Collections;
-import java.util.Map;
+import java.io.Writer;
+import java.util.Arrays;
+import java.util.List;
 
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
@@ -29,22 +30,61 @@ import javax.servlet.http.HttpServletResponse;
 import org.apache.freemarker.core.CallPlace;
 import org.apache.freemarker.core.Environment;
 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.util.CallableUtils;
+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 AbstractHtmlElementTemplateDirectiveModel {
+public class InputTemplateDirectiveModel extends AbstractHtmlInputElementTemplateDirectiveModel {
 
     public static final String NAME = "input";
 
-    private static final Map<String, String> REGISTERED_ATTRIBUTES = Collections.unmodifiableMap(
-            createAttributeKeyNamePairsMap(
-                    "size",
-                    "maxlength",
-                    "alt",
-                    "onselect",
-                    "readonly",
-                    "autocomplete"));
+    private static final int NAMED_ARGS_OFFSET = AbstractHtmlInputElementTemplateDirectiveModel.NAMED_ARGS_ENTRY_LIST
+            .size() + 1;
+
+    private static final int SIZE_PARAM_IDX = NAMED_ARGS_OFFSET;
+    private static final String SIZE_PARAM_NAME = "size";
+
+    private static final int MAXLENGTH_PARAM_IDX = NAMED_ARGS_OFFSET + 1;
+    private static final String MAXLENGTH_PARAM_NAME = "maxlength";
+
+    private static final int ALT_PARAM_IDX = NAMED_ARGS_OFFSET + 2;
+    private static final String ALT_PARAM_NAME = "alt";
+
+    private static final int ONSELECT_PARAM_IDX = NAMED_ARGS_OFFSET + 3;
+    private static final String ONSELECT_PARAM_NAME = "onselect";
+
+    private static final int AUTOCOMPLETE_PARAM_IDX = NAMED_ARGS_OFFSET + 4;
+    private static final String AUTOCOMPLETE_PARAM_NAME = "autocomplete";
+
+    protected static List<StringToIndexMap.Entry> NAMED_ARGS_ENTRY_LIST =
+            _CollectionUtils.mergeImmutableLists(false,
+                    AbstractHtmlInputElementTemplateDirectiveModel.NAMED_ARGS_ENTRY_LIST,
+                    Arrays.asList(
+                        new StringToIndexMap.Entry(SIZE_PARAM_NAME, SIZE_PARAM_IDX),
+                        new StringToIndexMap.Entry(MAXLENGTH_PARAM_NAME, MAXLENGTH_PARAM_IDX),
+                        new StringToIndexMap.Entry(ALT_PARAM_NAME, ALT_PARAM_IDX),
+                        new StringToIndexMap.Entry(ONSELECT_PARAM_NAME, ONSELECT_PARAM_IDX),
+                        new StringToIndexMap.Entry(AUTOCOMPLETE_PARAM_NAME, AUTOCOMPLETE_PARAM_IDX)
+                        )
+                    );
+
+    private static final ArgumentArrayLayout ARGS_LAYOUT =
+            ArgumentArrayLayout.create(
+                    1,
+                    false,
+                    StringToIndexMap.of(NAMED_ARGS_ENTRY_LIST.toArray(new StringToIndexMap.Entry[NAMED_ARGS_ENTRY_LIST.size()])),
+                    true
+                    );
+
+    private String size;
+    private String maxlength;
+    private String alt;
+    private String onselect;
+    private String autocomplete;
 
     protected InputTemplateDirectiveModel(HttpServletRequest request, HttpServletResponse response) {
         super(request, response);
@@ -56,59 +96,77 @@ public class InputTemplateDirectiveModel extends AbstractHtmlElementTemplateDire
     }
 
     @Override
-    protected void writeDirectiveContent(TemplateModel[] args, CallPlace callPlace, TagOutputter tagOut,
-            Environment env, ObjectWrapperAndUnwrapper objectWrapperAndUnwrapper, RequestContext requestContext)
-            throws TemplateException {
-
-        final String path = getPathArgument(args);
+    public ArgumentArrayLayout getDirectiveArgumentArrayLayout() {
+        return ARGS_LAYOUT;
+    }
 
-        try {
-            readRegisteredAndDynamicAttributes(args, objectWrapperAndUnwrapper);
+    @Override
+    protected void executeInternal(TemplateModel[] args, CallPlace callPlace, Writer out, Environment env,
+            ObjectWrapperAndUnwrapper objectWrapperAndUnwrapper, RequestContext requestContext)
+            throws TemplateException, IOException {
 
-            tagOut.beginTag(NAME);
+        super.executeInternal(args, callPlace, out, env, objectWrapperAndUnwrapper, requestContext);
 
-            writeDefaultHtmlElementAttributes(tagOut);
+        size = CallableUtils.getOptionalStringArgument(args, SIZE_PARAM_IDX, this);
+        maxlength = CallableUtils.getOptionalStringArgument(args, MAXLENGTH_PARAM_IDX, this);
+        alt = CallableUtils.getOptionalStringArgument(args, ALT_PARAM_IDX, this);
+        onselect = CallableUtils.getOptionalStringArgument(args, ONSELECT_PARAM_IDX, this);
+        autocomplete = CallableUtils.getOptionalStringArgument(args, AUTOCOMPLETE_PARAM_IDX, this);
 
-            if (!hasDynamicTypeAttribute()) {
-                tagOut.writeAttribute("type", (String) getRegisteredAttributes().get("type"));
-            }
+        TagOutputter tagOut = new TagOutputter(out);
 
-            writeValue(tagOut);
+        tagOut.beginTag(NAME);
 
-            // custom optional attributes
-            for (Map.Entry<String, String> entry : REGISTERED_ATTRIBUTES.entrySet()) {
-                String attrKey = entry.getKey();
-                String attrName = entry.getValue();
-                Object attrValue = getRegisteredAttributes().get(attrKey);
-                writeOptionalAttribute(tagOut, attrName, attrValue);
-            }
+        writeDefaultAttributes(tagOut);
 
-            tagOut.endTag();
-        } catch (IOException e) {
-            throw new TemplateException(e);
+        if (!hasDynamicTypeAttribute()) {
+            tagOut.writeAttribute("type", getType());
         }
+
+        writeValue(env, tagOut);
+
+        // more optional attributes by this tag
+        writeOptionalAttribute(tagOut, SIZE_PARAM_NAME, getSize());
+        writeOptionalAttribute(tagOut, MAXLENGTH_PARAM_NAME, getMaxlength());
+        writeOptionalAttribute(tagOut, ALT_PARAM_NAME, getAlt());
+        writeOptionalAttribute(tagOut, ONSELECT_PARAM_NAME, getOnselect());
+        writeOptionalAttribute(tagOut, AUTOCOMPLETE_PARAM_NAME, getAutocomplete());
+
+        tagOut.endTag();
     }
 
-    @Override
-    protected boolean isRegisteredAttribute(String localName, Object value) {
-        return super.isRegisteredAttribute(localName, value) && REGISTERED_ATTRIBUTES.containsKey(localName);
+    public String getSize() {
+        return size;
+    }
+
+    public String getMaxlength() {
+        return maxlength;
+    }
+
+    public String getAlt() {
+        return alt;
+    }
+
+    public String getOnselect() {
+        return onselect;
+    }
+
+    public String getAutocomplete() {
+        return autocomplete;
     }
 
     private boolean hasDynamicTypeAttribute() {
         return getDynamicAttributes().containsKey("type");
     }
 
-    protected void writeValue(TagOutputter tagOut) throws TemplateException {
-//        String value = getDisplayString(getBoundValue(), getPropertyEditor());
-//        String type = hasDynamicTypeAttribute() ? (String) getDynamicAttributes().get("type") : getType();
-//        tagWriter.writeAttribute("value", processFieldValue(getName(), value, type));
+    protected void writeValue(Environment env, TagOutputter tagOut) throws TemplateException, IOException {
+        String value = getDisplayString(getBindStatus().getValue(), getBindStatus().getEditor(), false);
+        String type = hasDynamicTypeAttribute() ? (String) getDynamicAttributes().get("type") : getType();
+        tagOut.writeAttribute("value", processFieldValue(env, getName(), value, type));
+    }
 
-        //FIXME
-        try {
-            tagOut.writeAttribute("value", "value");
-        } catch (IOException e) {
-            throw new TemplateException(e);
-        }
+    protected String getType() {
+        return "text";
     }
 
 }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/2b5e9b7c/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 dd20fe2..8569ad1 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
@@ -77,7 +77,7 @@ class TagOutputter {
         out.write(value);
     }
 
-    public void forceBlock() throws TemplateException, IOException {
+    public void forceBlock() throws TemplateException {
         TagEntry current = tagStack.peek();
 
         if (current.isBlockTag()) {
@@ -117,12 +117,17 @@ class TagOutputter {
         tagStack.pop();
     }
 
-    private void closeAndMarkAsBlockTag() throws TemplateException, IOException {
+    private void closeAndMarkAsBlockTag() throws TemplateException {
         TagEntry current = tagStack.peek();
 
         if (!current.isBlockTag()) {
             current.markAsBlockTag();
-            out.write(">");
+
+            try {
+                out.write(">");
+            } catch (IOException e) {
+                throw new TemplateException("Failed to write output.", e);
+            }
         }
     }
 

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/2b5e9b7c/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 801b234..40beea0 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
@@ -20,7 +20,7 @@
 <body>
 
   <div id="userEmail">
-    <@spring.form.input 'user.email' type='text' value='${user.email!}' />
+    <@spring.form.input 'user.email' value='${user.email!}' />
   </div>
 
 </body>