FREEMARKER-55: Adding tests and polishing
Project: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/commit/045c980c Tree: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/tree/045c980c Diff: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/diff/045c980c Branch: refs/heads/3 Commit: 045c980cfcbc96b258334aaf4671451111914864 Parents: 1f7100c Author: Woonsan Ko <[email protected]> Authored: Tue Jan 2 23:03:55 2018 -0500 Committer: Woonsan Ko <[email protected]> Committed: Tue Jan 2 23:03:55 2018 -0500 ---------------------------------------------------------------------- .../AbstractSpringTemplateCallableModel.java | 15 ++++- .../model/SpringTemplateCallableHashModel.java | 9 ++- ...aBoundFormElementTemplateDirectiveModel.java | 2 +- .../AbstractFormTemplateDirectiveModel.java | 15 +++-- ...stractHtmlElementTemplateDirectiveModel.java | 8 +-- ...tHtmlInputElementTemplateDirectiveModel.java | 2 +- .../model/form/FormTemplateDirectiveModel.java | 65 ++++++++++++++++++-- .../model/form/InputTemplateDirectiveModel.java | 34 +++++++++- .../SpringFormTemplateCallableHashModel.java | 1 - .../spring/model/form/TagOutputter.java | 3 + .../form/FormTemplateDirectiveModelTest.java | 41 +++++++++++- .../form/InputTemplateDirectiveModelTest.java | 35 +++++++++++ .../test/model/form/form-directive-usages.ftlh | 46 ++++++++++++-- .../test/model/form/input-directive-usages.ftlh | 52 +++++++++++++++- 14 files changed, 290 insertions(+), 38 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/045c980c/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/AbstractSpringTemplateCallableModel.java ---------------------------------------------------------------------- diff --git a/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/AbstractSpringTemplateCallableModel.java b/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/AbstractSpringTemplateCallableModel.java index f7c75d9..4866557 100644 --- a/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/AbstractSpringTemplateCallableModel.java +++ b/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/AbstractSpringTemplateCallableModel.java @@ -134,7 +134,20 @@ public abstract class AbstractSpringTemplateCallableModel implements TemplateCal return (status != null) ? objectWrapperAndUnwrapper.wrap(status) : null; } - // TODO: Javadocs + /** + * Find {@link BindStatus} with no {@code htmlEscape} option from {@link RequestContext} by the {@code path}. + * <P> + * <EM>NOTE:</EM> In FreeMarker, there is no need to depend on <code>BindStatus#htmlEscape</code> option + * as FreeMarker template expressions can easily set escape option by themselves. + * Therefore, this method always get a {@link BindStatus} with {@code htmlEscape} option set to {@code false}. + * @param env Environment + * @param objectWrapperAndUnwrapper ObjectWrapperAndUnwrapper + * @param requestContext Spring RequestContext + * @param path bind path + * @param ignoreNestedPath flag whether or not to ignore the nested path + * @return a {@link BindStatus} with no {@code htmlEscape} option from {@link RequestContext} by the {@code path} + * @throws TemplateException if template exception occurs + */ protected final BindStatus getBindStatus(Environment env, ObjectWrapperAndUnwrapper objectWrapperAndUnwrapper, RequestContext requestContext, String path, boolean ignoreNestedPath) throws TemplateException { http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/045c980c/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/SpringTemplateCallableHashModel.java ---------------------------------------------------------------------- diff --git a/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/SpringTemplateCallableHashModel.java b/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/SpringTemplateCallableHashModel.java index 762222c..a859821 100644 --- a/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/SpringTemplateCallableHashModel.java +++ b/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/SpringTemplateCallableHashModel.java @@ -35,7 +35,6 @@ import org.apache.freemarker.spring.model.form.SpringFormTemplateCallableHashMod /** * TemplateHashModel wrapper for templates using Spring directives, functions and internal models. */ -//TODO [FM3] Shouldn't this be a TemplateHashModelEx? public final class SpringTemplateCallableHashModel implements TemplateHashModel, Serializable { private static final long serialVersionUID = 1L; @@ -77,19 +76,19 @@ public final class SpringTemplateCallableHashModel implements TemplateHashModel, return modelsMap.get(key); } - TemplateStringModel getNestedPathModel() throws TemplateException { + public TemplateStringModel getNestedPathModel() throws TemplateException { return (TemplateStringModel) get(NESTED_PATH_MODEL); } - void setNestedPathModel(TemplateStringModel nestedPathModel) { + public void setNestedPathModel(TemplateStringModel nestedPathModel) { modelsMap.put(NESTED_PATH_MODEL, nestedPathModel); } - TemplateModel getEvaluationContextModel() throws TemplateException { + public TemplateModel getEvaluationContextModel() throws TemplateException { return get(EVALUATION_CONTEXT_MODEL); } - void setEvaluationContextModel(TemplateModel evaluationContextModel) { + public void setEvaluationContextModel(TemplateModel evaluationContextModel) { modelsMap.put(EVALUATION_CONTEXT_MODEL, evaluationContextModel); } http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/045c980c/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/form/AbstractDataBoundFormElementTemplateDirectiveModel.java ---------------------------------------------------------------------- diff --git a/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/form/AbstractDataBoundFormElementTemplateDirectiveModel.java b/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/form/AbstractDataBoundFormElementTemplateDirectiveModel.java index 27bc4fd..d416fd4 100644 --- a/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/form/AbstractDataBoundFormElementTemplateDirectiveModel.java +++ b/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/form/AbstractDataBoundFormElementTemplateDirectiveModel.java @@ -43,7 +43,7 @@ import org.springframework.web.servlet.support.RequestDataValueProcessor; /** * Corresponds to <code>org.springframework.web.servlet.tags.form.AbstractDataBoundFormElementTag</code>. */ -public abstract class AbstractDataBoundFormElementTemplateDirectiveModel extends AbstractFormTemplateDirectiveModel { +abstract class AbstractDataBoundFormElementTemplateDirectiveModel extends AbstractFormTemplateDirectiveModel { private static final int PATH_PARAM_IDX = 0; http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/045c980c/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/form/AbstractFormTemplateDirectiveModel.java ---------------------------------------------------------------------- diff --git a/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/form/AbstractFormTemplateDirectiveModel.java b/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/form/AbstractFormTemplateDirectiveModel.java index 45fa896..00c9f42 100644 --- a/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/form/AbstractFormTemplateDirectiveModel.java +++ b/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/form/AbstractFormTemplateDirectiveModel.java @@ -28,12 +28,11 @@ import javax.servlet.http.HttpServletResponse; import org.apache.freemarker.core.TemplateException; import org.apache.freemarker.spring.model.AbstractSpringTemplateDirectiveModel; import org.springframework.util.ObjectUtils; -import org.springframework.web.util.HtmlUtils; /** * Corresponds to <code>org.springframework.web.servlet.tags.form.AbstractFormTag</code>. */ -public abstract class AbstractFormTemplateDirectiveModel extends AbstractSpringTemplateDirectiveModel { +abstract class AbstractFormTemplateDirectiveModel extends AbstractSpringTemplateDirectiveModel { protected AbstractFormTemplateDirectiveModel(HttpServletRequest request, HttpServletResponse response) { super(request, response); @@ -43,32 +42,32 @@ public abstract class AbstractFormTemplateDirectiveModel extends AbstractSpringT return value; } - public static String getDisplayString(Object value, boolean htmlEscape) { + public static String getDisplayString(Object value) { String displayValue = ObjectUtils.getDisplayString(value); - return (htmlEscape ? HtmlUtils.htmlEscape(displayValue) : displayValue); + return displayValue; } - public static String getDisplayString(Object value, PropertyEditor propertyEditor, boolean htmlEscape) { + public static String getDisplayString(Object value, PropertyEditor propertyEditor) { if (propertyEditor != null && !(value instanceof String)) { try { propertyEditor.setValue(value); String text = propertyEditor.getAsText(); if (text != null) { - return getDisplayString(text, htmlEscape); + return getDisplayString(text); } } catch (Throwable ex) { // Ignore error if the PropertyEditor doesn't support this text value. } } - return getDisplayString(value, htmlEscape); + return getDisplayString(value); } protected final void writeOptionalAttribute(TagOutputter tagOut, String attrName, Object attrValue) throws TemplateException, IOException { if (attrValue != null) { - tagOut.writeOptionalAttributeValue(attrName, getDisplayString(evaluate(attrName, attrValue), false)); + tagOut.writeOptionalAttributeValue(attrName, getDisplayString(evaluate(attrName, attrValue))); } } http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/045c980c/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/form/AbstractHtmlElementTemplateDirectiveModel.java ---------------------------------------------------------------------- diff --git a/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/form/AbstractHtmlElementTemplateDirectiveModel.java b/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/form/AbstractHtmlElementTemplateDirectiveModel.java index f711f3a..87b2394 100644 --- a/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/form/AbstractHtmlElementTemplateDirectiveModel.java +++ b/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/form/AbstractHtmlElementTemplateDirectiveModel.java @@ -48,7 +48,7 @@ import org.springframework.web.servlet.support.RequestContext; /** * Corresponds to <code>org.springframework.web.servlet.tags.form.AbstractHtmlElementTag</code>. */ -public abstract class AbstractHtmlElementTemplateDirectiveModel +abstract class AbstractHtmlElementTemplateDirectiveModel extends AbstractDataBoundFormElementTemplateDirectiveModel { private static final int NAMED_ARGS_OFFSET = AbstractDataBoundFormElementTemplateDirectiveModel.NAMED_ARGS_ENTRY_LIST @@ -107,7 +107,6 @@ public abstract class AbstractHtmlElementTemplateDirectiveModel private static final int CSSERRORCLASS_PARAM_IDX = NAMED_ARGS_OFFSET + 16; private static final String CSSERRORCLASS_PARAM_NAME = "cssErrorClass"; - // TODO: It's a problem to see NAMED_ARGS_ENTRY_LIST is visible from child classes! @SuppressWarnings("unchecked") protected static List<StringToIndexMap.Entry> NAMED_ARGS_ENTRY_LIST = _CollectionUtils.mergeImmutableLists(false, @@ -196,7 +195,7 @@ public abstract class AbstractHtmlElementTemplateDirectiveModel onkeydown = CallableUtils.getOptionalStringArgument(args, ONKEYDOWN_PARAM_IDX, this); cssErrorClass = CallableUtils.getOptionalStringArgument(args, CSSERRORCLASS_PARAM_IDX, this); - final int attrsVarargsIndex = ARGS_LAYOUT.getNamedVarargsArgumentIndex(); + final int attrsVarargsIndex = getDirectiveArgumentArrayLayout().getNamedVarargsArgumentIndex(); final TemplateHashModelEx attrsHashModel = (TemplateHashModelEx) args[attrsVarargsIndex]; if (attrsHashModel != null && !attrsHashModel.isEmptyHash()) { @@ -342,8 +341,7 @@ public abstract class AbstractHtmlElementTemplateDirectiveModel if (!unmodifiableDynamicAttributes.isEmpty()) { for (String attr : unmodifiableDynamicAttributes.keySet()) { - tagOut.writeOptionalAttributeValue(attr, - getDisplayString(unmodifiableDynamicAttributes.get(attr), false)); + tagOut.writeOptionalAttributeValue(attr, getDisplayString(unmodifiableDynamicAttributes.get(attr))); } } } http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/045c980c/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/form/AbstractHtmlInputElementTemplateDirectiveModel.java ---------------------------------------------------------------------- diff --git a/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/form/AbstractHtmlInputElementTemplateDirectiveModel.java b/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/form/AbstractHtmlInputElementTemplateDirectiveModel.java index ef81c1d..997ffc5 100644 --- a/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/form/AbstractHtmlInputElementTemplateDirectiveModel.java +++ b/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/form/AbstractHtmlInputElementTemplateDirectiveModel.java @@ -38,7 +38,7 @@ import org.apache.freemarker.core.util.StringToIndexMap; import org.apache.freemarker.core.util._CollectionUtils; import org.springframework.web.servlet.support.RequestContext; -public abstract class AbstractHtmlInputElementTemplateDirectiveModel extends AbstractHtmlElementTemplateDirectiveModel { +abstract class AbstractHtmlInputElementTemplateDirectiveModel extends AbstractHtmlElementTemplateDirectiveModel { private static final int NAMED_ARGS_OFFSET = AbstractHtmlElementTemplateDirectiveModel.NAMED_ARGS_ENTRY_LIST.size() + 1; http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/045c980c/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/form/FormTemplateDirectiveModel.java ---------------------------------------------------------------------- diff --git a/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/form/FormTemplateDirectiveModel.java b/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/form/FormTemplateDirectiveModel.java index 565245b..a35699c 100644 --- a/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/form/FormTemplateDirectiveModel.java +++ b/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/form/FormTemplateDirectiveModel.java @@ -34,9 +34,12 @@ import org.apache.freemarker.core.TemplateException; import org.apache.freemarker.core.model.ArgumentArrayLayout; import org.apache.freemarker.core.model.ObjectWrapperAndUnwrapper; import org.apache.freemarker.core.model.TemplateModel; +import org.apache.freemarker.core.model.TemplateStringModel; import org.apache.freemarker.core.util.CallableUtils; import org.apache.freemarker.core.util.StringToIndexMap; import org.apache.freemarker.core.util._CollectionUtils; +import org.apache.freemarker.spring.model.SpringTemplateCallableHashModel; +import org.springframework.beans.PropertyAccessor; import org.springframework.http.HttpMethod; import org.springframework.util.StringUtils; import org.springframework.web.servlet.support.RequestContext; @@ -44,7 +47,34 @@ import org.springframework.web.servlet.support.RequestDataValueProcessor; import org.springframework.web.util.HtmlUtils; import org.springframework.web.util.UriUtils; -public class FormTemplateDirectiveModel extends AbstractHtmlElementTemplateDirectiveModel { +/** + * Provides <code>TemplateModel</code> for data-binding-aware HTML '{@code form}' element whose inner directives + * are bound to properties on a <em>form object</em>. + * <P> + * This directive supports the following parameters: + * <UL> + * <LI><code>modelAttribute</code>: The first positional parameter pointing to the bean or bean property as its form object.</LI> + * <LI> + * ... TODO ... + * </LI> + * </UL> + * </P> + * <P> + * Some valid example(s): + * </P> + * <PRE> + * <#assign form=spring.form /> + * <@form.form "user"> + * <div>First name: <@form.input 'firstName' /></div> + * </@form.form> + * </PRE> + * <P> + * <EM>Note:</EM> Unlike Spring Framework's <code><form:input /></code> JSP Tag Library, this directive + * does not support <code>htmlEscape</code> parameter. It always renders HTML's without escaping + * because it is much easier to control escaping in FreeMarker Template expressions. + * </P> + */ +class FormTemplateDirectiveModel extends AbstractHtmlElementTemplateDirectiveModel { public static final String NAME = "form"; @@ -205,10 +235,22 @@ public class FormTemplateDirectiveModel extends AbstractHtmlElementTemplateDirec tagOut.endTag(); } - // TODO: expose the form object name for nested tags... + final String modelAttribute = getModelAttribute(); - // TODO: save previous nestedPath value, build and expose current nestedPath value. + // save previous nestedPath value, build and expose current nestedPath value. + final SpringTemplateCallableHashModel springTemplateModel = getSpringTemplateCallableHashModel(env); + final TemplateStringModel prevNestedPathModel = springTemplateModel.getNestedPathModel(); + final String newNestedPath = modelAttribute + PropertyAccessor.NESTED_PROPERTY_SEPARATOR; + final TemplateStringModel newNestedPathModel = (TemplateStringModel) objectWrapperAndUnwrapper + .wrap(newNestedPath); + try { + springTemplateModel.setNestedPathModel(newNestedPathModel); + callPlace.executeNestedContent(null, out, env); + } finally { + springTemplateModel.setNestedPathModel(prevNestedPathModel); + tagOut.endTag(); + } } protected String getModelAttribute() { @@ -271,13 +313,22 @@ public class FormTemplateDirectiveModel extends AbstractHtmlElementTemplateDirec return ("get".equalsIgnoreCase(method) || "post".equalsIgnoreCase(method)); } + /** + * Resolve the form action attribute. + * <p>If the {@code action} attribute is specified, then that value is used. + * If the {@code servletRelativeAction} is specified, then the value is prepended with context and servlet paths. + * Otherwise, the {@link org.springframework.web.servlet.support.RequestContext#getRequestUri() originating URI} is used. + * @param env environment + * @return the value that is to be used for the form action attribute + * @throws TemplateException if template exception occurs + */ protected String resolveAction(Environment env) throws TemplateException { RequestContext requestContext = getRequestContext(env, false); String action = getAction(); String servletRelativeAction = getServletRelativeAction(); if (StringUtils.hasText(action)) { - action = getDisplayString(evaluate(ACTION_PARAM_NAME, action), false); + action = getDisplayString(evaluate(ACTION_PARAM_NAME, action)); return processAction(env, action); } else if (StringUtils.hasText(servletRelativeAction)) { String pathToServlet = requestContext.getPathToServlet(); @@ -287,7 +338,7 @@ public class FormTemplateDirectiveModel extends AbstractHtmlElementTemplateDirec servletRelativeAction = pathToServlet + servletRelativeAction; } - servletRelativeAction = getDisplayString(evaluate(ACTION_PARAM_NAME, servletRelativeAction), false); + servletRelativeAction = getDisplayString(evaluate(ACTION_PARAM_NAME, servletRelativeAction)); return processAction(env, servletRelativeAction); } else { String requestUri = requestContext.getRequestUri(); @@ -296,7 +347,7 @@ public class FormTemplateDirectiveModel extends AbstractHtmlElementTemplateDirec try { requestUri = UriUtils.encodePath(requestUri, encoding); } catch (UnsupportedEncodingException ex) { - // shouldn't happen - if it does, proceed with requestUri as-is + // According to Spring MVC Javadoc, it shouldn't happen. } HttpServletResponse response = getResponse(); @@ -337,9 +388,11 @@ public class FormTemplateDirectiveModel extends AbstractHtmlElementTemplateDirec private String processAction(Environment env, String action) throws TemplateException { RequestDataValueProcessor processor = getRequestContext(env, false).getRequestDataValueProcessor(); HttpServletRequest request = getRequest(); + if (processor != null && request != null) { action = processor.processAction((HttpServletRequest) request, action, getHttpMethod()); } + return action; } http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/045c980c/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/form/InputTemplateDirectiveModel.java ---------------------------------------------------------------------- diff --git a/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/form/InputTemplateDirectiveModel.java b/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/form/InputTemplateDirectiveModel.java index e348da8..f155054 100644 --- a/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/form/InputTemplateDirectiveModel.java +++ b/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/form/InputTemplateDirectiveModel.java @@ -38,7 +38,37 @@ import org.apache.freemarker.core.util.StringToIndexMap; import org.apache.freemarker.core.util._CollectionUtils; import org.springframework.web.servlet.support.RequestContext; -public class InputTemplateDirectiveModel extends AbstractHtmlInputElementTemplateDirectiveModel { +/** + * Provides <code>TemplateModel</code> for data-binding-aware HTML '{@code input}' element with a '{@code type}' + * of '{@code text}'. + * <P> + * This directive supports the following parameters: + * <UL> + * <LI><code>path</code>: The first positional parameter pointing to the bean or bean property to bind status information for.</LI> + * <LI> + * ... TODO ... + * </LI> + * </UL> + * </P> + * <P> + * Some valid example(s): + * </P> + * <PRE> + * <#assign form=spring.form /> + * ... + * <@form.input 'user.firstName' /> + * + * <@form.input 'user.email' id="customEmailId" /> + * + * ... + * </PRE> + * <P> + * <EM>Note:</EM> Unlike Spring Framework's <code><form:input /></code> JSP Tag Library, this directive + * does not support <code>htmlEscape</code> parameter. It always renders HTML's without escaping + * because it is much easier to control escaping in FreeMarker Template expressions. + * </P> + */ +class InputTemplateDirectiveModel extends AbstractHtmlInputElementTemplateDirectiveModel { public static final String NAME = "input"; @@ -161,7 +191,7 @@ public class InputTemplateDirectiveModel extends AbstractHtmlInputElementTemplat } protected void writeValue(Environment env, TagOutputter tagOut) throws TemplateException, IOException { - String value = getDisplayString(getBindStatus().getValue(), getBindStatus().getEditor(), false); + String value = getDisplayString(getBindStatus().getValue(), getBindStatus().getEditor()); String type = hasDynamicTypeAttribute() ? (String) getDynamicAttributes().get("type") : getType(); tagOut.writeAttribute("value", processFieldValue(env, getName(), value, type)); } http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/045c980c/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/form/SpringFormTemplateCallableHashModel.java ---------------------------------------------------------------------- diff --git a/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/form/SpringFormTemplateCallableHashModel.java b/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/form/SpringFormTemplateCallableHashModel.java index 3c9becd..c7b1f5a 100644 --- a/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/form/SpringFormTemplateCallableHashModel.java +++ b/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/form/SpringFormTemplateCallableHashModel.java @@ -33,7 +33,6 @@ import org.apache.freemarker.core.model.TemplateModel; /** * TemplateHashModel wrapper for templates using Spring directives, functions and internal models. */ -//TODO [FM3] Shouldn't this be a TemplateHashModelEx? public final class SpringFormTemplateCallableHashModel implements TemplateHashModel, Serializable { private static final long serialVersionUID = 1L; http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/045c980c/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/form/TagOutputter.java ---------------------------------------------------------------------- diff --git a/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/form/TagOutputter.java b/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/form/TagOutputter.java index 8569ad1..4e127b8 100644 --- a/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/form/TagOutputter.java +++ b/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/form/TagOutputter.java @@ -26,6 +26,9 @@ import java.util.Stack; import org.apache.freemarker.core.TemplateException; import org.springframework.util.StringUtils; +/** + * Utility to writing HTML content. + */ class TagOutputter { private final Writer out; http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/045c980c/freemarker-spring/src/test/java/org/apache/freemarker/spring/model/form/FormTemplateDirectiveModelTest.java ---------------------------------------------------------------------- diff --git a/freemarker-spring/src/test/java/org/apache/freemarker/spring/model/form/FormTemplateDirectiveModelTest.java b/freemarker-spring/src/test/java/org/apache/freemarker/spring/model/form/FormTemplateDirectiveModelTest.java index 2795171..8ac8413 100644 --- a/freemarker-spring/src/test/java/org/apache/freemarker/spring/model/form/FormTemplateDirectiveModelTest.java +++ b/freemarker-spring/src/test/java/org/apache/freemarker/spring/model/form/FormTemplateDirectiveModelTest.java @@ -37,6 +37,7 @@ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilder import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.xpath; @RunWith(SpringJUnit4ClassRunner.class) @WebAppConfiguration("classpath:META-INF/web-resources") @@ -62,7 +63,45 @@ public class FormTemplateDirectiveModelTest { final User user = userRepository.getUser(userId); mockMvc.perform(get("/users/{userId}/", userId).param("viewName", "test/model/form/form-directive-usages") .accept(MediaType.parseMediaType("text/html"))).andExpect(status().isOk()) - .andExpect(content().contentTypeCompatibleWith("text/html")).andDo(print()); + .andExpect(content().contentTypeCompatibleWith("text/html")).andDo(print()) + .andExpect(xpath("//form[@id='form1']/@method").string("post")) + .andExpect(xpath("//form[@id='form1']//input[@name='firstName']/@value").string(user.getFirstName())) + .andExpect(xpath("//form[@id='form1']//input[@name='lastName']/@value").string(user.getLastName())); + } + + @Test + public void testDefaultAttributes() throws Exception { + final Long userId = userRepository.getUserIds().iterator().next(); + final User user = userRepository.getUser(userId); + mockMvc.perform(get("/users/{userId}/", userId).param("viewName", "test/model/form/form-directive-usages") + .accept(MediaType.parseMediaType("text/html"))).andExpect(status().isOk()) + .andExpect(content().contentTypeCompatibleWith("text/html")).andDo(print()) + .andExpect(xpath("//form[@id='form2']/@class").string("my_cssClass")) + .andExpect(xpath("//form[@id='form2']/@style").string("my_cssStyle")) + .andExpect(xpath("//form[@id='form2']/@lang").string("my_lang")) + .andExpect(xpath("//form[@id='form2']/@title").string("my_title")) + .andExpect(xpath("//form[@id='form2']/@dir").string("my_dir")) + .andExpect(xpath("//form[@id='form2']/@tabindex").string("my_tabindex")) + .andExpect(xpath("//form[@id='form2']/@onclick").string("my_onclick()")) + .andExpect(xpath("//form[@id='form2']/@ondblclick").string("my_ondblclick()")) + .andExpect(xpath("//form[@id='form2']/@onmousedown").string("my_onmousedown()")) + .andExpect(xpath("//form[@id='form2']/@onmouseup").string("my_onmouseup()")) + .andExpect(xpath("//form[@id='form2']/@onmouseover").string("my_onmouseover()")) + .andExpect(xpath("//form[@id='form2']/@onmousemove").string("my_onmousemove()")) + .andExpect(xpath("//form[@id='form2']/@onmouseout").string("my_onmouseout()")) + .andExpect(xpath("//form[@id='form2']/@onkeypress").string("my_onkeypress()")) + .andExpect(xpath("//form[@id='form2']/@onkeyup").string("my_onkeyup()")) + .andExpect(xpath("//form[@id='form2']/@onkeydown").string("my_onkeydown()")) + .andExpect(xpath("//form[@id='form2']/@action").string("my_action")) + .andExpect(xpath("//form[@id='form2']/@method").string("post")) + .andExpect(xpath("//form[@id='form2']/@target").string("my_target")) + .andExpect(xpath("//form[@id='form2']/@enctype").string("my_enctype")) + .andExpect(xpath("//form[@id='form2']/@acceptCharset").string("my_acceptCharset")) + .andExpect(xpath("//form[@id='form2']/@onsubmit").string("my_onsubmit()")) + .andExpect(xpath("//form[@id='form2']/@onreset").string("my_onreset()")) + .andExpect(xpath("//form[@id='form2']/@autocomplete").string("my_autocomplete")) + .andExpect(xpath("//form[@id='form2']/@name").string("my_name")) + .andExpect(xpath("//form[@id='form2']/@autocomplete").string("my_autocomplete")); } } http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/045c980c/freemarker-spring/src/test/java/org/apache/freemarker/spring/model/form/InputTemplateDirectiveModelTest.java ---------------------------------------------------------------------- diff --git a/freemarker-spring/src/test/java/org/apache/freemarker/spring/model/form/InputTemplateDirectiveModelTest.java b/freemarker-spring/src/test/java/org/apache/freemarker/spring/model/form/InputTemplateDirectiveModelTest.java index 9d8a6e2..b7a0d91 100644 --- a/freemarker-spring/src/test/java/org/apache/freemarker/spring/model/form/InputTemplateDirectiveModelTest.java +++ b/freemarker-spring/src/test/java/org/apache/freemarker/spring/model/form/InputTemplateDirectiveModelTest.java @@ -69,4 +69,39 @@ public class InputTemplateDirectiveModelTest { .andExpect(xpath("//form[@id='form1']//input[@id='lastName' and @name='lastName']/@value").string(user.getLastName())); } + @Test + public void testDefaultAttributes() throws Exception { + final Long userId = userRepository.getUserIds().iterator().next(); + mockMvc.perform(get("/users/{userId}/", userId).param("viewName", "test/model/form/input-directive-usages") + .accept(MediaType.parseMediaType("text/html"))).andExpect(status().isOk()) + .andExpect(content().contentTypeCompatibleWith("text/html")).andDo(print()) + .andExpect(xpath("//form[@id='form2']//input/@name").string("firstName")) + .andExpect(xpath("//form[@id='form2']//input/@class").string("my_cssClass")) + .andExpect(xpath("//form[@id='form2']//input/@style").string("my_cssStyle")) + .andExpect(xpath("//form[@id='form2']//input/@lang").string("my_lang")) + .andExpect(xpath("//form[@id='form2']//input/@title").string("my_title")) + .andExpect(xpath("//form[@id='form2']//input/@dir").string("my_dir")) + .andExpect(xpath("//form[@id='form2']//input/@tabindex").string("my_tabindex")) + .andExpect(xpath("//form[@id='form2']//input/@onclick").string("my_onclick()")) + .andExpect(xpath("//form[@id='form2']//input/@ondblclick").string("my_ondblclick()")) + .andExpect(xpath("//form[@id='form2']//input/@onmousedown").string("my_onmousedown()")) + .andExpect(xpath("//form[@id='form2']//input/@onmouseup").string("my_onmouseup()")) + .andExpect(xpath("//form[@id='form2']//input/@onmouseover").string("my_onmouseover()")) + .andExpect(xpath("//form[@id='form2']//input/@onmousemove").string("my_onmousemove()")) + .andExpect(xpath("//form[@id='form2']//input/@onmouseout").string("my_onmouseout()")) + .andExpect(xpath("//form[@id='form2']//input/@onkeypress").string("my_onkeypress()")) + .andExpect(xpath("//form[@id='form2']//input/@onkeyup").string("my_onkeyup()")) + .andExpect(xpath("//form[@id='form2']//input/@onkeydown").string("my_onkeydown()")) + .andExpect(xpath("//form[@id='form2']//input/@onfocus").string("my_onfocus()")) + .andExpect(xpath("//form[@id='form2']//input/@onblur").string("my_onblur()")) + .andExpect(xpath("//form[@id='form2']//input/@onchange").string("my_onchange()")) + .andExpect(xpath("//form[@id='form2']//input/@accesskey").string("my_accesskey")) + .andExpect(xpath("//form[@id='form2']//input/@disabled").string("disabled")) + .andExpect(xpath("//form[@id='form2']//input/@readonly").string("readonly")) + .andExpect(xpath("//form[@id='form2']//input/@size").string("my_size")) + .andExpect(xpath("//form[@id='form2']//input/@maxlength").string("my_maxlength")) + .andExpect(xpath("//form[@id='form2']//input/@alt").string("my_alt")) + .andExpect(xpath("//form[@id='form2']//input/@onselect").string("my_onselect()")) + .andExpect(xpath("//form[@id='form2']//input/@autocomplete").string("my_autocomplete")); + } } http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/045c980c/freemarker-spring/src/test/resources/META-INF/web-resources/views/test/model/form/form-directive-usages.ftlh ---------------------------------------------------------------------- diff --git a/freemarker-spring/src/test/resources/META-INF/web-resources/views/test/model/form/form-directive-usages.ftlh b/freemarker-spring/src/test/resources/META-INF/web-resources/views/test/model/form/form-directive-usages.ftlh index 2338ed2..f70a4aa 100644 --- a/freemarker-spring/src/test/resources/META-INF/web-resources/views/test/model/form/form-directive-usages.ftlh +++ b/freemarker-spring/src/test/resources/META-INF/web-resources/views/test/model/form/form-directive-usages.ftlh @@ -19,24 +19,62 @@ <html> <body> + <#assign form=spring.form /> + <h1>Form 1</h1> <hr/> - <@spring.form.form "user"> + <@form.form "user" id="form1"> <table> <tr> <th>First name:</th> <td> - <@spring.form.input 'user.firstName' /> + <@form.input 'firstName' /> </td> </tr> <tr> <th>Last name:</th> <td> - <@spring.form.input 'user.lastName' /> + <@form.input 'lastName' /> </td> </tr> </table> - </@spring.form.form> + </@form.form> + + <hr/> + + <h2>Testing default attributes</h2> + <@form.form "user" id="form2" + cssClass="my_cssClass" + cssStyle="my_cssStyle" + lang="my_lang" + title="my_title" + dir="my_dir" + tabindex="my_tabindex" + onclick="my_onclick()" + ondblclick="my_ondblclick()" + onmousedown="my_onmousedown()" + onmouseup="my_onmouseup()" + onmouseover="my_onmouseover()" + onmousemove="my_onmousemove()" + onmouseout="my_onmouseout()" + onkeypress="my_onkeypress()" + onkeyup="my_onkeyup()" + onkeydown="my_onkeydown()" + cssErrorClass="my_cssErrorClass" + action="my_action" + method="post" + target="my_target" + enctype="my_enctype" + acceptCharset="my_acceptCharset" + onsubmit="my_onsubmit()" + onreset="my_onreset()" + autocomplete="my_autocomplete" + name="my_name" + value="my_value" + type="my_type" + servletRelativeAction="my_servletRelativeAction" + methodParam="my_methodParam"> + </@form.form> </body> </html> http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/045c980c/freemarker-spring/src/test/resources/META-INF/web-resources/views/test/model/form/input-directive-usages.ftlh ---------------------------------------------------------------------- diff --git a/freemarker-spring/src/test/resources/META-INF/web-resources/views/test/model/form/input-directive-usages.ftlh b/freemarker-spring/src/test/resources/META-INF/web-resources/views/test/model/form/input-directive-usages.ftlh index 614d2ae..b575ea0 100644 --- a/freemarker-spring/src/test/resources/META-INF/web-resources/views/test/model/form/input-directive-usages.ftlh +++ b/freemarker-spring/src/test/resources/META-INF/web-resources/views/test/model/form/input-directive-usages.ftlh @@ -19,6 +19,8 @@ <html> <body> + <#assign form=spring.form /> + <h1>Form 1</h1> <hr/> <form id="form1"> @@ -26,19 +28,63 @@ <tr> <th>E-Mail:</th> <td> - <@spring.form.input 'user.email' id="customEmailId" /> + <@form.input 'user.email' id="customEmailId" /> </td> </tr> <tr> <th>First name:</th> <td> - <@spring.form.input 'user.firstName' /> + <@form.input 'user.firstName' /> </td> </tr> <tr> <th>Last name:</th> <td> - <@spring.form.input 'user.lastName' /> + <@form.input 'user.lastName' /> + </td> + </tr> + </table> + </form> + + <hr/> + + <h2>Testing default attributes</h2> + <form id="form2"> + <table> + <tr> + <th>First name:</th> + <td> + <@form.input "user.firstName" + id="my_id" + cssClass="my_cssClass" + cssStyle="my_cssStyle" + lang="my_lang" + title="my_title" + dir="my_dir" + tabindex="my_tabindex" + onclick="my_onclick()" + ondblclick="my_ondblclick()" + onmousedown="my_onmousedown()" + onmouseup="my_onmouseup()" + onmouseover="my_onmouseover()" + onmousemove="my_onmousemove()" + onmouseout="my_onmouseout()" + onkeypress="my_onkeypress()" + onkeyup="my_onkeyup()" + onkeydown="my_onkeydown()" + cssErrorClass="my_cssErrorClass" + onfocus="my_onfocus()" + onblur="my_onblur()" + onchange="my_onchange()" + accesskey="my_accesskey" + disabled=true + readonly=true + size="my_size" + maxlength="my_maxlength" + alt="my_alt" + onselect="my_onselect()" + autocomplete="my_autocomplete" + /> </td> </tr> </table>
