Repository: freemarker Updated Branches: refs/heads/3 cb072d7dc -> d389b6cce
FREEMARKER-55: Adding radiobutton directive Project: http://git-wip-us.apache.org/repos/asf/freemarker/repo Commit: http://git-wip-us.apache.org/repos/asf/freemarker/commit/d389b6cc Tree: http://git-wip-us.apache.org/repos/asf/freemarker/tree/d389b6cc Diff: http://git-wip-us.apache.org/repos/asf/freemarker/diff/d389b6cc Branch: refs/heads/3 Commit: d389b6cce3bab38227949c454b5404aa32d79b0a Parents: cb072d7 Author: Woonsan Ko <woon...@apache.org> Authored: Mon Jul 23 16:53:28 2018 -0400 Committer: Woonsan Ko <woon...@apache.org> Committed: Mon Jul 23 16:53:28 2018 -0400 ---------------------------------------------------------------------- .../form/RadioButtonTemplateDirectiveModel.java | 131 +++++++++++++++++++ .../SpringFormTemplateCallableHashModel.java | 1 + .../spring/example/mvc/users/User.java | 16 ++- .../example/mvc/users/UserRepository.java | 3 + .../RadioButtonTemplateDirectiveModelTest.java | 87 ++++++++++++ .../form/radiobutton-directive-usages.f3ah | 44 +++++++ 6 files changed, 279 insertions(+), 3 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/freemarker/blob/d389b6cc/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/form/RadioButtonTemplateDirectiveModel.java ---------------------------------------------------------------------- diff --git a/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/form/RadioButtonTemplateDirectiveModel.java b/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/form/RadioButtonTemplateDirectiveModel.java new file mode 100644 index 0000000..d6b8e16 --- /dev/null +++ b/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/form/RadioButtonTemplateDirectiveModel.java @@ -0,0 +1,131 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.freemarker.spring.model.form; + +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.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.web.servlet.support.RequestContext; + +/** + * Provides <code>TemplateModel</code> for data-binding-aware HTML '{@code <input type="radio"/>}' element. + * This tag is provided for completeness if the application relies on a + * <code>org.springframework.web.servlet.support.RequestDataValueProcessor</code>. + * <P> + * This directive supports the following parameters: + * <UL> + * <LI> + * ... TODO ... + * </LI> + * </UL> + * </P> + * <P> + * Some valid example(s): + * </P> + * <PRE> + * <@form.radio 'user.gender' value='U'/>Unspecified + * </PRE> + * <P> + * <EM>Note:</EM> Unlike Spring Framework's <code><form:button /></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 RadioButtonTemplateDirectiveModel extends AbstractSingleCheckedElementTemplateDirectiveModel { + + public static final String NAME = "radiobutton"; + + private static final int NAMED_ARGS_OFFSET = AbstractHtmlInputElementTemplateDirectiveModel.ARGS_LAYOUT + .getPredefinedNamedArgumentsEndIndex(); + + private static final int VALUE_PARAM_IDX = NAMED_ARGS_OFFSET; + private static final String VALUE_PARAM_NAME = "value"; + + private static final int LABEL_PARAM_IDX = NAMED_ARGS_OFFSET + 1; + private static final String LABEL_PARAM_NAME = "label"; + + protected static final ArgumentArrayLayout ARGS_LAYOUT = ArgumentArrayLayout.create(1, false, + StringToIndexMap.of(AbstractHtmlInputElementTemplateDirectiveModel.ARGS_LAYOUT.getPredefinedNamedArgumentsMap(), + new StringToIndexMap.Entry(VALUE_PARAM_NAME, VALUE_PARAM_IDX), + new StringToIndexMap.Entry(LABEL_PARAM_NAME, LABEL_PARAM_IDX)), + true); + + private String value; + private String label; + + protected RadioButtonTemplateDirectiveModel(HttpServletRequest request, HttpServletResponse response) { + super(request, response); + } + + @Override + public ArgumentArrayLayout getDirectiveArgumentArrayLayout() { + return ARGS_LAYOUT; + } + + @Override + public boolean isNestedContentSupported() { + return false; + } + + @Override + protected void executeInternal(TemplateModel[] args, CallPlace callPlace, Writer out, Environment env, + ObjectWrapperAndUnwrapper objectWrapperAndUnwrapper, RequestContext requestContext) + throws TemplateException, IOException { + value = CallableUtils.getOptionalStringArgument(args, VALUE_PARAM_IDX, this); + label = CallableUtils.getOptionalStringArgument(args, LABEL_PARAM_IDX, this); + + super.executeInternal(args, callPlace, out, env, objectWrapperAndUnwrapper, requestContext); + } + + @Override + public String getValue() { + return value; + } + + @Override + public String getLabel() { + return label; + } + + @Override + protected void writeAdditionalDetails(Environment env, TagOutputter tagOut) throws TemplateException, IOException { + tagOut.writeAttribute("type", getInputType()); + Object resolvedValue = evaluate("value", getValue()); + renderFromValue(env, resolvedValue, tagOut); + } + + @Override + protected String getInputType() { + return "radio"; + } + +} http://git-wip-us.apache.org/repos/asf/freemarker/blob/d389b6cc/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 680fe29..a2750dc 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 @@ -58,6 +58,7 @@ public final class SpringFormTemplateCallableHashModel implements TemplateHashMo modelsMap.put(ErrorsTemplateDirectiveModel.NAME, new ErrorsTemplateDirectiveModel(request, response)); modelsMap.put(CheckboxTemplateDirectiveModel.NAME, new CheckboxTemplateDirectiveModel(request, response)); modelsMap.put(CheckboxesTemplateDirectiveModel.NAME, new CheckboxesTemplateDirectiveModel(request, response)); + modelsMap.put(RadioButtonTemplateDirectiveModel.NAME, new RadioButtonTemplateDirectiveModel(request, response)); } @Override http://git-wip-us.apache.org/repos/asf/freemarker/blob/d389b6cc/freemarker-spring/src/test/java/org/apache/freemarker/spring/example/mvc/users/User.java ---------------------------------------------------------------------- diff --git a/freemarker-spring/src/test/java/org/apache/freemarker/spring/example/mvc/users/User.java b/freemarker-spring/src/test/java/org/apache/freemarker/spring/example/mvc/users/User.java index 56330e6..9bfa43d 100644 --- a/freemarker-spring/src/test/java/org/apache/freemarker/spring/example/mvc/users/User.java +++ b/freemarker-spring/src/test/java/org/apache/freemarker/spring/example/mvc/users/User.java @@ -28,6 +28,7 @@ public class User { private String email; private String firstName; private String lastName; + private String gender = "U"; // 'F': female, 'M': male, 'U': unspecified, ... private Date birthDate; private String description; private String favoriteSport; @@ -77,6 +78,14 @@ public class User { this.lastName = lastName; } + public String getGender() { + return gender; + } + + public void setGender(String gender) { + this.gender = gender; + } + public Date getBirthDate() { return birthDate; } @@ -125,8 +134,9 @@ public class User { @Override public String toString() { - return super.toString() + " {id=" + id + ", firstName='" + firstName + "', lastName='" + lastName + "', email='" - + email + "', birthDate='" + birthDate + "', description='" + description + "', favoriteSport='" - + favoriteSport + "', receiveNewsletter=" + receiveNewsletter + ", favoriteFood=" + favoriteFood + "}"; + return super.toString() + " {id=" + id + ", firstName='" + firstName + "', lastName='" + lastName + + "', gender='" + gender + ", email='" + email + "', birthDate='" + birthDate + "', description='" + + description + "', favoriteSport='" + favoriteSport + "', receiveNewsletter=" + receiveNewsletter + + ", favoriteFood=" + favoriteFood + "}"; } } http://git-wip-us.apache.org/repos/asf/freemarker/blob/d389b6cc/freemarker-spring/src/test/java/org/apache/freemarker/spring/example/mvc/users/UserRepository.java ---------------------------------------------------------------------- diff --git a/freemarker-spring/src/test/java/org/apache/freemarker/spring/example/mvc/users/UserRepository.java b/freemarker-spring/src/test/java/org/apache/freemarker/spring/example/mvc/users/UserRepository.java index 57af289..667ee50 100644 --- a/freemarker-spring/src/test/java/org/apache/freemarker/spring/example/mvc/users/UserRepository.java +++ b/freemarker-spring/src/test/java/org/apache/freemarker/spring/example/mvc/users/UserRepository.java @@ -50,6 +50,7 @@ public class UserRepository { user.setPassword("johnpass"); user.setFirstName("John"); user.setLastName("Doe"); + user.setGender("M"); Calendar birthDate = Calendar.getInstance(); birthDate.set(Calendar.YEAR, 1973); birthDate.set(Calendar.MONTH, Calendar.JANUARY); @@ -68,6 +69,7 @@ public class UserRepository { user.setPassword("janepass"); user.setFirstName("Jane"); user.setLastName("Doe"); + user.setGender("F"); birthDate = Calendar.getInstance(); birthDate.set(Calendar.YEAR, 1970); birthDate.set(Calendar.MONTH, Calendar.FEBRUARY); @@ -135,6 +137,7 @@ public class UserRepository { clone.setEmail(source.getEmail()); clone.setFirstName(source.getFirstName()); clone.setLastName(source.getLastName()); + clone.setGender(source.getGender()); clone.setBirthDate(source.getBirthDate()); clone.setDescription(source.getDescription()); clone.setFavoriteSport(source.getFavoriteSport()); http://git-wip-us.apache.org/repos/asf/freemarker/blob/d389b6cc/freemarker-spring/src/test/java/org/apache/freemarker/spring/model/form/RadioButtonTemplateDirectiveModelTest.java ---------------------------------------------------------------------- diff --git a/freemarker-spring/src/test/java/org/apache/freemarker/spring/model/form/RadioButtonTemplateDirectiveModelTest.java b/freemarker-spring/src/test/java/org/apache/freemarker/spring/model/form/RadioButtonTemplateDirectiveModelTest.java new file mode 100644 index 0000000..7c3d209 --- /dev/null +++ b/freemarker-spring/src/test/java/org/apache/freemarker/spring/model/form/RadioButtonTemplateDirectiveModelTest.java @@ -0,0 +1,87 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.freemarker.spring.model.form; + +import org.apache.freemarker.spring.example.mvc.users.User; +import org.apache.freemarker.spring.example.mvc.users.UserRepository; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.context.WebApplicationContext; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +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") +@ContextConfiguration(locations = { "classpath:org/apache/freemarker/spring/example/mvc/users/users-mvc-context.xml" }) +public class RadioButtonTemplateDirectiveModelTest { + + @Autowired + private WebApplicationContext wac; + + @Autowired + private UserRepository userRepository; + + private MockMvc mockMvc; + + @Before + public void setUp() { + mockMvc = MockMvcBuilders.webAppContextSetup(wac).build(); + } + + @Test + public void testSingleCheckboxWithFemaleUser() throws Exception { + final User user = userRepository.getUserByEmail("j...@example.com"); + mockMvc.perform(get("/users/{userId}/", user.getId()).param("viewName", "test/model/form/radiobutton-directive-usages") + .accept(MediaType.parseMediaType("text/html"))).andExpect(status().isOk()) + .andExpect(content().contentTypeCompatibleWith("text/html")).andDo(print()) + .andExpect(xpath("//form[@id='form1']//input[@type='radio' and @id='gender1' and @name='gender' and @value='F']").exists()) + .andExpect(xpath("//form[@id='form1']//input[@type='radio' and @id='gender1' and @name='gender' and @value='F']/@checked").string("checked")) + .andExpect(xpath("//form[@id='form1']//input[@type='radio' and @id='gender2' and @name='gender' and @value='M']").exists()) + .andExpect(xpath("//form[@id='form1']//input[@type='radio' and @id='gender2' and @name='gender' and @value='M']/@checked").doesNotExist()) + .andExpect(xpath("//form[@id='form1']//input[@type='radio' and @id='gender3' and @name='gender' and @value='U']").exists()) + .andExpect(xpath("//form[@id='form1']//input[@type='radio' and @id='gender3' and @name='gender' and @value='U']/@checked").doesNotExist()); + } + + @Test + public void testSingleCheckboxWithMaleUser() throws Exception { + final User user = userRepository.getUserByEmail("j...@example.com"); + mockMvc.perform(get("/users/{userId}/", user.getId()).param("viewName", "test/model/form/radiobutton-directive-usages") + .accept(MediaType.parseMediaType("text/html"))).andExpect(status().isOk()) + .andExpect(content().contentTypeCompatibleWith("text/html")).andDo(print()) + .andExpect(xpath("//form[@id='form1']//input[@type='radio' and @id='gender1' and @name='gender' and @value='F']").exists()) + .andExpect(xpath("//form[@id='form1']//input[@type='radio' and @id='gender1' and @name='gender' and @value='F']/@checked").doesNotExist()) + .andExpect(xpath("//form[@id='form1']//input[@type='radio' and @id='gender2' and @name='gender' and @value='M']").exists()) + .andExpect(xpath("//form[@id='form1']//input[@type='radio' and @id='gender2' and @name='gender' and @value='M']/@checked").string("checked")) + .andExpect(xpath("//form[@id='form1']//input[@type='radio' and @id='gender3' and @name='gender' and @value='U']").exists()) + .andExpect(xpath("//form[@id='form1']//input[@type='radio' and @id='gender3' and @name='gender' and @value='U']/@checked").doesNotExist()); + } +} http://git-wip-us.apache.org/repos/asf/freemarker/blob/d389b6cc/freemarker-spring/src/test/resources/META-INF/web-resources/views/test/model/form/radiobutton-directive-usages.f3ah ---------------------------------------------------------------------- diff --git a/freemarker-spring/src/test/resources/META-INF/web-resources/views/test/model/form/radiobutton-directive-usages.f3ah b/freemarker-spring/src/test/resources/META-INF/web-resources/views/test/model/form/radiobutton-directive-usages.f3ah new file mode 100644 index 0000000..ca4192d --- /dev/null +++ b/freemarker-spring/src/test/resources/META-INF/web-resources/views/test/model/form/radiobutton-directive-usages.f3ah @@ -0,0 +1,44 @@ +<#-- + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. +--> +<html> +<body> + + <h1>Form 1</h1> + <hr/> + <form id="form1"> + <table> + <tr> + <th>User:</th> + <td> + ${user.firstName} ${user.lastName} (${user.email}) + </td> + </tr> + <tr> + <th>Gender</th> + <td> + <@form.radiobutton "user.gender" value="F" />Female + <@form.radiobutton "user.gender" value="M" />Male + <@form.radiobutton "user.gender" value="U" />Unspecified + </td> + </tr> + </table> + </form> + +</body> +</html>