diff --git a/src/main/java/org/symphonyoss/symphony/messageml/bi/BiFields.java b/src/main/java/org/symphonyoss/symphony/messageml/bi/BiFields.java
index 8bb11935..49ece01d 100644
--- a/src/main/java/org/symphonyoss/symphony/messageml/bi/BiFields.java
+++ b/src/main/java/org/symphonyoss/symphony/messageml/bi/BiFields.java
@@ -67,6 +67,7 @@ public enum BiFields {
HIGHLIGHTED_OPTIONS("highlighted_options", BiEventType.MESSAGEML_ELEMENT_SENT),
VALIDATION_STRICT("validation_strict", BiEventType.MESSAGEML_ELEMENT_SENT),
REQUIRED("required", BiEventType.MESSAGEML_ELEMENT_SENT),
+ MULTI_SELECT("is_multi_select", BiEventType.MESSAGEML_ELEMENT_SENT),
MESSAGE_LENGTH("message_length", BiEventType.MESSAGEML_MESSAGE_SENT),
ENTITY_JSON_SIZE("entities_json_size", BiEventType.MESSAGEML_MESSAGE_SENT),
FREEMARKER("use_freemarker", BiEventType.MESSAGEML_MESSAGE_SENT),
diff --git a/src/main/java/org/symphonyoss/symphony/messageml/elements/Element.java b/src/main/java/org/symphonyoss/symphony/messageml/elements/Element.java
index 00382d94..b5164309 100644
--- a/src/main/java/org/symphonyoss/symphony/messageml/elements/Element.java
+++ b/src/main/java/org/symphonyoss/symphony/messageml/elements/Element.java
@@ -989,4 +989,20 @@ protected void throwInvalidInputException(org.w3c.dom.Node item) throws InvalidI
throw new InvalidInputException("Attribute \"" + item.getNodeName()
+ "\" is not allowed in \"" + getMessageMLTag() + "\"");
}
+
+ protected int checkIntegerAttribute(String attributeName, int minValue, String errorMessage)
+ throws InvalidInputException {
+ int value = 0;
+ if (getAttribute(attributeName) != null) {
+ try {
+ value = Integer.parseInt(getAttribute(attributeName));
+ if (value < minValue) {
+ throw new InvalidInputException(errorMessage);
+ }
+ } catch (NumberFormatException e) {
+ throw new InvalidInputException(errorMessage, e);
+ }
+ }
+ return value;
+ }
}
diff --git a/src/main/java/org/symphonyoss/symphony/messageml/elements/Select.java b/src/main/java/org/symphonyoss/symphony/messageml/elements/Select.java
index 2e163f3b..7e4a3f04 100644
--- a/src/main/java/org/symphonyoss/symphony/messageml/elements/Select.java
+++ b/src/main/java/org/symphonyoss/symphony/messageml/elements/Select.java
@@ -30,11 +30,24 @@
import java.util.Map;
/**
- * Class representing dropdown menu - Symphony Elements.
- *
- * @author lumoura
- * @since 3/22/18
+ * This class represents the Symphony Element Dialog which is represented with tag name "select" (drop down menu).
+ * The messageML representation of the select element can contain the following attributes:
+ *
+ * - name (required) -> to identify this element
+ * - required -> true/false, to enforce a non empty option to be selected
+ * - data-placeholder -> string, text displayed in the dropdown menu before an option is selected
+ * - title -> string, the description that will be displayed when clicking the tooltip icon
+ * - label -> string, definition of the label that will be displayed on top of the Masked Text Field Element
+ * - multiple -> true/false, to allow for multiple options to be selected
+ * - min -> integer, minimum number of options to select if multiple=true
+ * - max -> integer, maximum number of options to select if multiple=true
+ *
+ * It can contain the following child tags:
+ *
+ * - option (required) -> the possible options to select {@link Option}
+ *
*/
+
public class Select extends FormElement implements LabelableElement, TooltipableElement {
public static final String MESSAGEML_TAG = "select";
@@ -42,6 +55,11 @@ public class Select extends FormElement implements LabelableElement, Tooltipable
private static final String REQUIRED_ATTR = "required";
private static final String OPTION_SELECTED_ATTR = "selected";
private static final String DATA_PLACEHOLDER_ATTR = "data-placeholder";
+ private static final String MULTIPLE_ATTR = "multiple";
+ private static final String MML_MIN_ATTR = "min";
+ private static final String MIN_ATTR = "data-min";
+ private static final String MML_MAX_ATTR = "max";
+ private static final String MAX_ATTR = "data-max";
public Select(Element parent) {
super(parent, MESSAGEML_TAG);
@@ -59,31 +77,64 @@ public void validate() throws InvalidInputException {
if (getAttribute(NAME_ATTR) == null) {
throw new InvalidInputException("The attribute \"name\" is required");
}
+ assertAttributeNotBlank(NAME_ATTR);
assertContentModel(Collections.singleton(Option.class));
assertContainsChildOfType(Collections.singleton(Option.class));
- if(getAttribute(REQUIRED_ATTR) != null) {
+ if (getAttribute(REQUIRED_ATTR) != null) {
assertAttributeValue(REQUIRED_ATTR, Arrays.asList(Boolean.TRUE.toString(), Boolean.FALSE.toString()));
}
- assertOnlyOneOptionSelected();
- assertAttributeNotBlank(NAME_ATTR);
+ if (getAttribute(MULTIPLE_ATTR) != null) {
+ assertAttributeValue(MULTIPLE_ATTR, Arrays.asList(Boolean.TRUE.toString(), Boolean.FALSE.toString()));
+ }
+
+ boolean multipleAttributeValue = Boolean.parseBoolean(getAttribute(MULTIPLE_ATTR));
+ if (getAttribute(MIN_ATTR) != null && !multipleAttributeValue) {
+ throw new InvalidInputException("Attribute \"min\" is not allowed. Attribute \"multiple\" missing");
+ }
+
+ if (getAttribute(MAX_ATTR) != null && !multipleAttributeValue) {
+ throw new InvalidInputException("Attribute \"max\" is not allowed. Attribute \"multiple\" missing");
+ }
+
+ int min = checkIntegerAttribute(MIN_ATTR, 0, "Attribute \"min\" is not valid, it must be >= 0");
+ int max = checkIntegerAttribute(MAX_ATTR, 1, "Attribute \"max\" is not valid, it must be >= 1");
+ if (max > 0 && min > max) {
+ throw new InvalidInputException("Attribute \"min\" is greater than attribute \"max\"");
+ }
+
+ if (multipleAttributeValue && Boolean.parseBoolean(getAttribute(REQUIRED_ATTR))
+ && getAttribute(MIN_ATTR) != null && min == 0) {
+ // multiple=true required=true min=0
+ throw new InvalidInputException("Attribute \"min\" cannot be 0 if \"required\" is true");
+ }
+
+ if (!multipleAttributeValue) {
+ assertOnlyOneOptionSelected();
+ }
}
@Override
- protected void buildAttribute(MessageMLParser parser,
- Node item) throws InvalidInputException {
+ protected void buildAttribute(MessageMLParser parser, Node item) throws InvalidInputException {
switch (item.getNodeName()) {
case NAME_ATTR:
case REQUIRED_ATTR:
case DATA_PLACEHOLDER_ATTR:
case LABEL:
case TITLE:
+ case MULTIPLE_ATTR:
setAttribute(item.getNodeName(), getStringAttribute(item));
break;
+ case MML_MIN_ATTR:
+ setAttribute(MIN_ATTR, getStringAttribute(item));
+ break;
+ case MML_MAX_ATTR:
+ setAttribute(MAX_ATTR, getStringAttribute(item));
+ break;
case ID_ATTR:
- if(format != FormatEnum.PRESENTATIONML){
+ if (format != FormatEnum.PRESENTATIONML) {
throwInvalidInputException(item);
}
fillAttributes(parser, item);
@@ -94,7 +145,7 @@ protected void buildAttribute(MessageMLParser parser,
}
@Override
- public String getElementId(){
+ public String getElementId() {
return ELEMENT_ID;
}
@@ -106,6 +157,7 @@ public void updateBiContext(BiContext context) {
this.putOneIfPresent(attributesMapBi, BiFields.LABEL.getValue(), LABEL);
this.putOneIfPresent(attributesMapBi, BiFields.PLACEHOLDER.getValue(), DATA_PLACEHOLDER_ATTR);
this.putOneIfPresent(attributesMapBi, BiFields.REQUIRED.getValue(), REQUIRED_ATTR);
+ this.putOneIfPresent(attributesMapBi, BiFields.MULTI_SELECT.getValue(), MULTIPLE_ATTR);
context.addItem(new BiItem(BiFields.SELECT.getValue(), attributesMapBi));
}
@@ -116,7 +168,7 @@ private void assertOnlyOneOptionSelected() throws InvalidInputException {
.filter(selectedAttr -> selectedAttr != null && selectedAttr.equalsIgnoreCase(Boolean.TRUE.toString()))
.count();
- if(numberOfSelectedOptions > 1) {
+ if (numberOfSelectedOptions > 1) {
throw new InvalidInputException("Element \"select\" can only have one selected \"option\"");
}
}
diff --git a/src/test/java/org/symphonyoss/symphony/messageml/elements/form/SelectOptionTest.java b/src/test/java/org/symphonyoss/symphony/messageml/elements/form/SelectOptionTest.java
index 9b034d4f..1f047eec 100644
--- a/src/test/java/org/symphonyoss/symphony/messageml/elements/form/SelectOptionTest.java
+++ b/src/test/java/org/symphonyoss/symphony/messageml/elements/form/SelectOptionTest.java
@@ -1,5 +1,9 @@
package org.symphonyoss.symphony.messageml.elements.form;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.symphonyoss.symphony.messageml.markdown.MarkdownRenderer.addEscapeCharacter;
+
import org.junit.Test;
import org.symphonyoss.symphony.messageml.MessageMLContext;
import org.symphonyoss.symphony.messageml.bi.BiFields;
@@ -21,10 +25,6 @@
import java.util.stream.Collectors;
import java.util.stream.Stream;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNull;
-import static org.symphonyoss.symphony.messageml.markdown.MarkdownRenderer.addEscapeCharacter;
-
public class SelectOptionTest extends ElementTest {
private static final String NAME_ATTR = "name";
@@ -41,7 +41,9 @@ public void testCompleteRequiredSelect() throws Exception {
String name = "complete-required-id";
boolean required = true;
String placeholder = "placeholder-here";
- String input = "";
context.parseMessageML(input, null, MessageML.MESSAGEML_VERSION);
@@ -60,9 +62,11 @@ public void testCompleteRequiredSelectWithLabelAndTooltip() throws Exception {
String title = "tooltip";
boolean required = true;
String placeholder = "placeholder-here";
- String input = "";
+ String input =
+ "";
context.parseMessageML(input, null, MessageML.MESSAGEML_VERSION);
Element messageML = context.getMessageML();
@@ -80,7 +84,9 @@ public void testSelectWithUnderscore() throws Exception {
String title = "tooltip-here";
boolean required = true;
String placeholder = "placeholder-here";
- String input = "";
context.parseMessageML(input, null, MessageML.MESSAGEML_VERSION);
@@ -92,12 +98,13 @@ public void testSelectWithUnderscore() throws Exception {
assertEquals("Select class", Select.class, select.getClass());
verifySelectPresentation((Select) select, name, true, required, placeholder, true, true);
}
+
@Test
public void testCompleteNotRequiredSelect() throws Exception {
String name = "complete-id";
boolean required = false;
String input = "" + ACTION_BTN_ELEMENT + "";
+ "\">" + ACTION_BTN_ELEMENT + "";
context.parseMessageML(input, null, MessageML.MESSAGEML_VERSION);
Element messageML = context.getMessageML();
@@ -111,7 +118,8 @@ public void testCompleteNotRequiredSelect() throws Exception {
@Test
public void testSimpleSelect() throws Exception {
String name = "simple-id";
- String input = "" + ACTION_BTN_ELEMENT
+ String input = "" + ACTION_BTN_ELEMENT
+ "";
context.parseMessageML(input, null, MessageML.MESSAGEML_VERSION);
@@ -120,14 +128,15 @@ public void testSimpleSelect() throws Exception {
Element select = form.getChildren().get(0);
assertEquals("Select class", Select.class, select.getClass());
- verifySelectPresentation((Select) select, name, false,false, null, false, false);
+ verifySelectPresentation((Select) select, name, false, false, null, false, false);
}
@Test
public void testDoubleOptionSelect() throws Exception {
String name = "simple-id";
- String input = "" + ACTION_BTN_ELEMENT + "";
+ String input = "" + ACTION_BTN_ELEMENT + "";
context.parseMessageML(input, null, MessageML.MESSAGEML_VERSION);
Element messageML = context.getMessageML();
@@ -135,13 +144,14 @@ public void testDoubleOptionSelect() throws Exception {
Element select = form.getChildren().get(0);
assertEquals("Select class", Select.class, select.getClass());
- verifySelectPresentation((Select) select, name, false,false, null, false, false);
+ verifySelectPresentation((Select) select, name, false, false, null, false, false);
}
@Test
public void testOptionWithSelectedAttr() throws Exception {
String name = "simple-id";
- String input = "