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 <woon...@apache.org> Authored: Thu Dec 28 01:26:54 2017 -0500 Committer: Woonsan Ko <woon...@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>