Repository: freemarker
Updated Branches:
  refs/heads/3 c4f96d918 -> 2d799d492


FREEMARKER-55: Adding option directive, TODO: check option selected.


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

Branch: refs/heads/3
Commit: 2d799d49203617efdd2a3de6fa916f6c67014d05
Parents: c4f96d9
Author: Woonsan Ko <[email protected]>
Authored: Fri Apr 27 00:21:33 2018 -0400
Committer: Woonsan Ko <[email protected]>
Committed: Fri Apr 27 00:21:33 2018 -0400

----------------------------------------------------------------------
 .../form/OptionTemplateDirectiveModel.java      | 154 +++++++++++++++++
 .../form/SelectableValueComparisonUtils.java    | 165 +++++++++++++++++++
 .../SpringFormTemplateCallableHashModel.java    |   1 +
 .../form/SelectTemplateDirectiveModelTest.java  |   6 +
 .../model/form/select-directive-usages.f3ah     |  16 ++
 5 files changed, 342 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/freemarker/blob/2d799d49/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/form/OptionTemplateDirectiveModel.java
----------------------------------------------------------------------
diff --git 
a/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/form/OptionTemplateDirectiveModel.java
 
b/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/form/OptionTemplateDirectiveModel.java
new file mode 100644
index 0000000..7ad31ad
--- /dev/null
+++ 
b/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/form/OptionTemplateDirectiveModel.java
@@ -0,0 +1,154 @@
+/*
+ * 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.util.ObjectUtils;
+import org.springframework.web.servlet.support.RequestContext;
+
+/**
+ * Provides a convenient <code>TemplateModel</code> that allow to supply a 
collection that are to be rendered
+ * as an HTML '{@code option}' element.
+ * <P>
+ * This directive supports the following parameters:
+ * <UL>
+ * <LI><code>items</code>: collection of option items.</LI>
+ * <LI>
+ *   ... TODO ...
+ * </LI>
+ * </UL>
+ * </P>
+ * <P>
+ * Some valid example(s):
+ * </P>
+ * <PRE>
+ *   ...
+ * </PRE>
+ * <P>
+ * <EM>Note:</EM> Unlike Spring Framework's <code>&lt;form:input /&gt;</code> 
JSP Tag Library, this directive
+ * does not support <code>htmlEscape</code> parameter. It always renders 
HTML's without escaping
+ * because it is much easier to control escaping in FreeMarker Template 
expressions.
+ * </P>
+ */
+class OptionTemplateDirectiveModel extends 
AbstractHtmlInputElementTemplateDirectiveModel {
+
+    public static final String NAME = "option";
+
+    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 OptionTemplateDirectiveModel(HttpServletRequest request, 
HttpServletResponse response) {
+        super(request, response);
+    }
+
+    @Override
+    public boolean isNestedContentSupported() {
+        return false;
+    }
+
+    @Override
+    public ArgumentArrayLayout getDirectiveArgumentArrayLayout() {
+        return ARGS_LAYOUT;
+    }
+
+    @Override
+    protected void executeInternal(TemplateModel[] args, CallPlace callPlace, 
Writer out, final Environment env,
+            ObjectWrapperAndUnwrapper objectWrapperAndUnwrapper, 
RequestContext requestContext)
+            throws TemplateException, IOException {
+
+        super.executeInternal(args, callPlace, out, env, 
objectWrapperAndUnwrapper, requestContext);
+
+        value = CallableUtils.getOptionalStringArgument(args, VALUE_PARAM_IDX, 
this);
+        label = CallableUtils.getOptionalStringArgument(args, LABEL_PARAM_IDX, 
this);
+
+        final FormTemplateScope formTemplateScope = 
env.getCustomState(FORM_TEMPLATE_SCOPE_KEY);
+        final SelectTemplateDirectiveModel curSelectDirective = 
formTemplateScope.getCurrentSelectDirective();
+
+        final String selectName = curSelectDirective.getName();
+        final String valueProperty = (getValue() != null
+                ? ObjectUtils.getDisplayString(evaluate("value", getValue()))
+                : null);
+        final String labelProperty = (getLabel() != null
+                ? ObjectUtils.getDisplayString(evaluate("label", getLabel()))
+                : null);
+
+        final TagOutputter tagOut = formTemplateScope.getCurrentTagOutputter();
+
+        tagOut.beginTag("option");
+        writeOptionalAttribute(tagOut, "id", resolveId());
+        writeOptionalAttributes(tagOut);
+        String renderedValue = getDisplayString(value, 
curSelectDirective.getBindStatus().getEditor());
+        renderedValue = processFieldValue(env, selectName, renderedValue, 
"option");
+        tagOut.writeAttribute("value", renderedValue);
+
+        if (SelectableValueComparisonUtils.isEqualValueBoundTo(value, 
curSelectDirective.getBindStatus())) {
+            tagOut.writeAttribute("selected", "selected");
+        }
+
+        if (isDisabled()) {
+            tagOut.writeAttribute("disabled", "disabled");
+        }
+
+        tagOut.appendValue(label);
+        tagOut.endTag();
+    }
+
+    public String getValue() {
+        return value;
+    }
+
+    public String getLabel() {
+        return label;
+    }
+
+    @Override
+    protected String autogenerateId() {
+        return null;
+    }
+}

http://git-wip-us.apache.org/repos/asf/freemarker/blob/2d799d49/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/form/SelectableValueComparisonUtils.java
----------------------------------------------------------------------
diff --git 
a/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/form/SelectableValueComparisonUtils.java
 
b/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/form/SelectableValueComparisonUtils.java
new file mode 100644
index 0000000..e17ab05
--- /dev/null
+++ 
b/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/form/SelectableValueComparisonUtils.java
@@ -0,0 +1,165 @@
+/*
+ * 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.beans.PropertyEditor;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.springframework.util.CollectionUtils;
+import org.springframework.util.ObjectUtils;
+import org.springframework.web.servlet.support.BindStatus;
+
+class SelectableValueComparisonUtils {
+
+    private SelectableValueComparisonUtils() {
+    }
+
+    public static boolean isEqualValueBoundTo(final Object value, final 
BindStatus bindStatus) {
+        if (value == null && bindStatus == null) {
+            return true;
+        }
+
+        Object boundValue = bindStatus.getValue();
+
+        if (ObjectUtils.nullSafeEquals(boundValue, value)) {
+            return true;
+        }
+
+        final Object actualValue = bindStatus.getActualValue();
+
+        if (actualValue != null && actualValue != boundValue && 
ObjectUtils.nullSafeEquals(actualValue, value)) {
+            return true;
+        }
+
+        if (actualValue != null) {
+            boundValue = actualValue;
+        } else if (boundValue == null) {
+            return false;
+        }
+
+        boolean equal = false;
+
+        if (boundValue.getClass().isArray()) {
+            equal = 
isValueInCollection(CollectionUtils.arrayToList(boundValue), value, bindStatus);
+        } else if (boundValue instanceof Collection) {
+            equal = isValueInCollection((Collection<?>) boundValue, value, 
bindStatus);
+        } else if (boundValue instanceof Map) {
+            equal = isValueInMapKeys((Map<?, ?>) boundValue, value, 
bindStatus);
+        }
+
+        if (!equal) {
+            equal = isEqualValuesComparingWithEditorValue(boundValue, value, 
bindStatus.getEditor(), null);
+        }
+
+        return equal;
+    }
+
+    private static boolean isValueInCollection(Collection<?> boundCollection, 
Object value,
+            BindStatus bindStatus) {
+        try {
+            if (boundCollection.contains(value)) {
+                return true;
+            }
+        } catch (ClassCastException ignoreEx) {
+        }
+
+        return isValueInCollectionComparingWithEditorValue(boundCollection, 
value, bindStatus);
+    }
+
+    private static boolean isValueInMapKeys(Map<?, ?> boundMap, Object value, 
BindStatus bindStatus) {
+        try {
+            if (boundMap.containsKey(value)) {
+                return true;
+            }
+        } catch (ClassCastException ignoreEx) {
+        }
+
+        return isValueInCollectionComparingWithEditorValue(boundMap.keySet(), 
value, bindStatus);
+    }
+
+    private static boolean 
isValueInCollectionComparingWithEditorValue(Collection<?> collection, Object 
value,
+            BindStatus bindStatus) {
+
+        final Map<PropertyEditor, Object> convertedValueCache = new 
HashMap<PropertyEditor, Object>(1);
+        PropertyEditor editor = null;
+        final boolean isValueString = (value instanceof String);
+
+        if (!isValueString) {
+            editor = bindStatus.findEditor(value.getClass());
+        }
+
+        for (Object element : collection) {
+            if (editor == null && element != null && isValueString) {
+                editor = bindStatus.findEditor(element.getClass());
+            }
+
+            if (isEqualValuesComparingWithEditorValue(element, value, editor, 
convertedValueCache)) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    private static boolean isEqualValuesComparingWithEditorValue(Object 
boundValue, Object value, PropertyEditor editor,
+            Map<PropertyEditor, Object> convertedValueCache) {
+
+        String valueDisplayString = 
AbstractFormTemplateDirectiveModel.getDisplayString(value, editor);
+
+        if (boundValue != null && boundValue.getClass().isEnum()) {
+            Enum<?> boundEnum = (Enum<?>) boundValue;
+            String enumCodeAsString = 
ObjectUtils.getDisplayString(boundEnum.name());
+
+            if (enumCodeAsString.equals(valueDisplayString)) {
+                return true;
+            }
+
+            String enumLabelAsString = 
ObjectUtils.getDisplayString(boundEnum.toString());
+
+            if (enumLabelAsString.equals(valueDisplayString)) {
+                return true;
+            }
+        } else if 
(ObjectUtils.getDisplayString(boundValue).equals(valueDisplayString)) {
+            return true;
+        } else if (editor != null && value instanceof String) {
+            String valueAsString = (String) value;
+            Object valueAsEditorValue;
+
+            if (convertedValueCache != null && 
convertedValueCache.containsKey(editor)) {
+                valueAsEditorValue = convertedValueCache.get(editor);
+            } else {
+                editor.setAsText(valueAsString);
+                valueAsEditorValue = editor.getValue();
+
+                if (convertedValueCache != null) {
+                    convertedValueCache.put(editor, valueAsEditorValue);
+                }
+            }
+
+            if (ObjectUtils.nullSafeEquals(boundValue, valueAsEditorValue)) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+}

http://git-wip-us.apache.org/repos/asf/freemarker/blob/2d799d49/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 31e1591..b460c4c 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
@@ -54,6 +54,7 @@ public final class SpringFormTemplateCallableHashModel 
implements TemplateHashMo
         modelsMap.put(LabelTemplateDirectiveModel.NAME, new 
LabelTemplateDirectiveModel(request, response));
         modelsMap.put(SelectTemplateDirectiveModel.NAME, new 
SelectTemplateDirectiveModel(request, response));
         modelsMap.put(OptionsTemplateDirectiveModel.NAME, new 
OptionsTemplateDirectiveModel(request, response));
+        modelsMap.put(OptionTemplateDirectiveModel.NAME, new 
OptionTemplateDirectiveModel(request, response));
         modelsMap.put(ErrorsTemplateDirectiveModel.NAME, new 
ErrorsTemplateDirectiveModel(request, response));
     }
 

http://git-wip-us.apache.org/repos/asf/freemarker/blob/2d799d49/freemarker-spring/src/test/java/org/apache/freemarker/spring/model/form/SelectTemplateDirectiveModelTest.java
----------------------------------------------------------------------
diff --git 
a/freemarker-spring/src/test/java/org/apache/freemarker/spring/model/form/SelectTemplateDirectiveModelTest.java
 
b/freemarker-spring/src/test/java/org/apache/freemarker/spring/model/form/SelectTemplateDirectiveModelTest.java
index c07fb8d..eafd098 100644
--- 
a/freemarker-spring/src/test/java/org/apache/freemarker/spring/model/form/SelectTemplateDirectiveModelTest.java
+++ 
b/freemarker-spring/src/test/java/org/apache/freemarker/spring/model/form/SelectTemplateDirectiveModelTest.java
@@ -82,5 +82,11 @@ public class SelectTemplateDirectiveModelTest {
             String sport = UserController.ALL_SPORTS.get(i);
             
resultAcctions.andExpect(xpath("//form[@id='form3']//select[@name='favoriteSport']//option["
 + (i + 1) + "]").string(sport));
         }
+
+        
resultAcctions.andExpect(xpath("//form[@id='form4']//select[@name='favoriteSport']//option[1]").string("---
 Select ---"));
+        for (int i = 0; i < UserController.OUTDOOR_SPORTS.size(); i++) {
+            String sport = UserController.OUTDOOR_SPORTS.get(i);
+            
resultAcctions.andExpect(xpath("//form[@id='form4']//select[@name='favoriteSport']//option["
 + (i + 2) + "]").string(sport));
+        }
     }
 }

http://git-wip-us.apache.org/repos/asf/freemarker/blob/2d799d49/freemarker-spring/src/test/resources/META-INF/web-resources/views/test/model/form/select-directive-usages.f3ah
----------------------------------------------------------------------
diff --git 
a/freemarker-spring/src/test/resources/META-INF/web-resources/views/test/model/form/select-directive-usages.f3ah
 
b/freemarker-spring/src/test/resources/META-INF/web-resources/views/test/model/form/select-directive-usages.f3ah
index 0794c2c..327bf4a 100644
--- 
a/freemarker-spring/src/test/resources/META-INF/web-resources/views/test/model/form/select-directive-usages.f3ah
+++ 
b/freemarker-spring/src/test/resources/META-INF/web-resources/views/test/model/form/select-directive-usages.f3ah
@@ -62,5 +62,21 @@
     </table>
   </form>
 
+  <h1>Form 4</h1>
+  <hr/>
+  <form id="form4">
+    <table>
+      <tr>
+        <th>Favorite Sport:</th>
+        <td>
+          <@form.select 'user.favoriteSport'>
+            <@form.option value="" label="--- Select ---" />
+            <@form.options items=outdoorSports />
+          </@form.select>
+        </td>
+      </tr>
+    </table>
+  </form>
+
 </body>
 </html>

Reply via email to