Skip to content

Commit

Permalink
[AGNT-366][C2-20895] add room selector component (#384)
Browse files Browse the repository at this point in the history
* [AGNT-366][C2-20895] add room selector component

* [AGNT-366][C2-20895] consider value attribute as string

* [AGNT-366][C2-20895] use declared variable

* [AGNT-366][C2-20895] fix exception message
  • Loading branch information
mohamed-rojbeni authored Oct 3, 2023
1 parent bda0f94 commit fe22652
Show file tree
Hide file tree
Showing 5 changed files with 606 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
import org.symphonyoss.symphony.messageml.elements.PersonSelector;
import org.symphonyoss.symphony.messageml.elements.Preformatted;
import org.symphonyoss.symphony.messageml.elements.Radio;
import org.symphonyoss.symphony.messageml.elements.RoomSelector;
import org.symphonyoss.symphony.messageml.elements.Select;
import org.symphonyoss.symphony.messageml.elements.Span;
import org.symphonyoss.symphony.messageml.elements.SplittableElement;
Expand Down Expand Up @@ -586,6 +587,9 @@ public Element createElement(org.w3c.dom.Element element, Element parent) throws
case PersonSelector.MESSAGEML_TAG:
return new PersonSelector(parent, messageFormat);

case RoomSelector.MESSAGEML_TAG:
return new RoomSelector(parent, messageFormat);

case DateSelector.MESSAGEML_TAG:
return new DateSelector(parent, messageFormat);

Expand Down Expand Up @@ -787,6 +791,9 @@ private Element createElementFromDiv(org.w3c.dom.Element element, Element parent
} else if (containsAttribute(elementClass, PersonSelector.MESSAGEML_TAG)) {
removeAttribute(element, CLASS_ATTR, PersonSelector.MESSAGEML_TAG);
return new PersonSelector(parent, FormatEnum.PRESENTATIONML);
} else if (containsAttribute(elementClass, RoomSelector.MESSAGEML_TAG)) {
removeAttribute(element, CLASS_ATTR, RoomSelector.MESSAGEML_TAG);
return new RoomSelector(parent, FormatEnum.PRESENTATIONML);
} else if (containsAttribute(elementClass, DateSelector.MESSAGEML_TAG)) {
removeAttribute(element, CLASS_ATTR, DateSelector.MESSAGEML_TAG);
return new DateSelector(parent, FormatEnum.PRESENTATIONML);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ public enum BiFields {
TIMEZONE_PICKER("timezonepicker", BiEventType.MESSAGEML_ELEMENT_SENT),
DATE_SELECTOR("dateselector", BiEventType.MESSAGEML_ELEMENT_SENT),
PERSON_SELECTOR("personselector", BiEventType.MESSAGEML_ELEMENT_SENT),
ROOM_SELECTOR("roomselector", BiEventType.MESSAGEML_ELEMENT_SENT),
SELECT("dropdownmenu", BiEventType.MESSAGEML_ELEMENT_SENT),
CHIME("chimes", BiEventType.MESSAGEML_MESSAGE_SENT, "0"),
CODE("codes", BiEventType.MESSAGEML_MESSAGE_SENT, "0"),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
package org.symphonyoss.symphony.messageml.elements;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.commonmark.node.Node;
import org.symphonyoss.symphony.messageml.MessageMLContext;
import org.symphonyoss.symphony.messageml.MessageMLParser;
import org.symphonyoss.symphony.messageml.bi.BiContext;
import org.symphonyoss.symphony.messageml.bi.BiFields;
import org.symphonyoss.symphony.messageml.bi.BiItem;
import org.symphonyoss.symphony.messageml.exceptions.InvalidInputException;
import org.symphonyoss.symphony.messageml.exceptions.ProcessingException;
import org.symphonyoss.symphony.messageml.markdown.nodes.form.PersonSelectorNode;
import org.symphonyoss.symphony.messageml.markdown.nodes.form.RoomSelectorNode;
import org.symphonyoss.symphony.messageml.util.XmlPrintStream;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.NodeList;

import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
* Class representing a room selector inside a Symphony Elements form.
*/
public class RoomSelector extends FormElement implements LabelableElement, TooltipableElement {
public static final String MESSAGEML_TAG = "room-selector";

private static final String PLACEHOLDER_ATTR = "placeholder";
private static final String REQUIRED_ATTR = "required";
private static final Set<String> VALID_VALUES_FOR_REQUIRED_ATTR =
new HashSet<>(Arrays.asList("true", "false"));
private static final String VALUE_ATTR = "value";

private static final String PRESENTATIONML_TAG = "div";
private static final String PRESENTATIONML_PLACEHOLDER_ATTR = "data-placeholder";
private static final String PRESENTATIONML_REQUIRED_ATTR = "data-required";
private static final String PRESENTATIONML_VALUE_ATTR = "data-value";

private final static String MARKDOWN = "Room Selector";
private static final String CLASS_ATTR = "class";
private static final String PRESENTATIONML_NAME_ATTR = "data-name";

private static final ObjectMapper MAPPER = new ObjectMapper();

public RoomSelector(Element parent, FormatEnum messageFormat) {
super(parent, MESSAGEML_TAG, messageFormat);
}

static {
MAPPER.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true);
}

@Override
public void buildAll(MessageMLParser parser, org.w3c.dom.Element element)
throws InvalidInputException, ProcessingException {
switch (getFormat()) {
case MESSAGEML:
super.buildAll(parser, element);
break;
case PRESENTATIONML:
buildElementFromDiv(parser, element);
this.validate();
break;
default:
throw new InvalidInputException(
String.format("Invalid message format for \"%s\" element", MESSAGEML_TAG));
}
}

@Override
public void validate() throws InvalidInputException {
super.validate();

if (getAttribute(NAME_ATTR) == null) {
throw new InvalidInputException("The attribute \"name\" is required");
}

if (getAttribute(REQUIRED_ATTR) != null) {
assertAttributeValue(REQUIRED_ATTR, VALID_VALUES_FOR_REQUIRED_ATTR);
}

if (getAttribute(VALUE_ATTR) != null) {
String attribute = getAttribute(VALUE_ATTR);
try {
MAPPER.readValue(attribute,
MAPPER.getTypeFactory().constructCollectionType(List.class, String.class));
} catch (JsonProcessingException e) {
throw new InvalidInputException(
String.format(
"Attribute \"%s\" contains an unsupported format, should be an array of room ids",
VALUE_ATTR)
);
}
}

assertNoContent();
assertAttributeNotBlank(NAME_ATTR);
}

@Override
public void asPresentationML(XmlPrintStream out,
MessageMLContext context) {
Map<String, String> presentationAttrs = buildRoomSelectorInputAttributes();
if (isSplittable()) {
// open div + adding splittable elements
presentationAttrs.put("id", splittableAsPresentationML(out, context));
// render element
innerAsPresentationML(out, presentationAttrs);
// close div
out.closeElement();
} else {
innerAsPresentationML(out, presentationAttrs);
}
}

private void innerAsPresentationML(XmlPrintStream out, Map<String, String> presentationAttrs) {
out.openElement(PRESENTATIONML_TAG, presentationAttrs);
out.closeElement();
}

@Override
public Node asMarkdown() {
return new RoomSelectorNode(getAttribute(PLACEHOLDER_ATTR), getAttribute(LABEL),
getAttribute(TITLE));
}

@Override
protected void buildAttribute(MessageMLParser parser,
org.w3c.dom.Node item) throws InvalidInputException {
switch (item.getNodeName()) {
case NAME_ATTR:
case PLACEHOLDER_ATTR:
case REQUIRED_ATTR:
case LABEL:
case TITLE:
case VALUE_ATTR:
setAttribute(item.getNodeName(), getStringAttribute(item));
break;
case ID_ATTR:
if (format != FormatEnum.PRESENTATIONML) {
throwInvalidInputException(item);
}
fillAttributes(parser, item);
break;
default:
throwInvalidInputException(item);
}
}

private Map<String, String> buildRoomSelectorInputAttributes() {
Map<String, String> presentationAttrs = new LinkedHashMap<>();

presentationAttrs.put(CLASS_ATTR, MESSAGEML_TAG);
presentationAttrs.put(PRESENTATIONML_NAME_ATTR, getAttribute(NAME_ATTR));

if (getAttribute(PLACEHOLDER_ATTR) != null) {
presentationAttrs.put(PRESENTATIONML_PLACEHOLDER_ATTR, getAttribute(PLACEHOLDER_ATTR));
}

if (getAttribute(REQUIRED_ATTR) != null) {
presentationAttrs.put(PRESENTATIONML_REQUIRED_ATTR, getAttribute(REQUIRED_ATTR));
}

if (getAttribute(VALUE_ATTR) != null) {
presentationAttrs.put(PRESENTATIONML_VALUE_ATTR, getAttribute(VALUE_ATTR));
}

return presentationAttrs;
}

private void buildElementFromDiv(MessageMLParser parser, org.w3c.dom.Element element)
throws InvalidInputException, ProcessingException {

element.setAttribute(NAME_ATTR, element.getAttribute(PRESENTATIONML_NAME_ATTR));
element.removeAttribute(PRESENTATIONML_NAME_ATTR);

if (element.hasAttribute(PRESENTATIONML_PLACEHOLDER_ATTR)) {
element.setAttribute(PLACEHOLDER_ATTR, element.getAttribute(PRESENTATIONML_PLACEHOLDER_ATTR));
element.removeAttribute(PRESENTATIONML_PLACEHOLDER_ATTR);
}

if (element.hasAttribute(PRESENTATIONML_REQUIRED_ATTR)) {
element.setAttribute(REQUIRED_ATTR, element.getAttribute(PRESENTATIONML_REQUIRED_ATTR));
element.removeAttribute(PRESENTATIONML_REQUIRED_ATTR);
}

if (element.hasAttribute(PRESENTATIONML_VALUE_ATTR)) {
element.setAttribute(VALUE_ATTR, element.getAttribute(PRESENTATIONML_VALUE_ATTR));
element.removeAttribute(PRESENTATIONML_VALUE_ATTR);
}

NamedNodeMap attributes = element.getAttributes();

for (int i = 0; i < attributes.getLength(); i++) {
buildAttribute(parser, attributes.item(i));
}

NodeList children = element.getChildNodes();

for (int i = 0; i < children.getLength(); i++) {
buildNode(parser, children.item(i));
}

}

@Override
public String getPresentationMLTag() {
return PRESENTATIONML_TAG;
}

@Override
public String getElementId() {
return MESSAGEML_TAG;
}

@Override
public void updateBiContext(BiContext context) {
Map<String, Object> attributesMapBi = new HashMap<>();

this.putOneIfPresent(attributesMapBi, BiFields.TITLE.getValue(), TITLE);
this.putOneIfPresent(attributesMapBi, BiFields.LABEL.getValue(), LABEL);
this.putOneIfPresent(attributesMapBi, BiFields.PLACEHOLDER.getValue(), PLACEHOLDER_ATTR);
this.putOneIfPresent(attributesMapBi, BiFields.REQUIRED.getValue(), REQUIRED_ATTR);

context.addItem(new BiItem(BiFields.ROOM_SELECTOR.getValue(), attributesMapBi));
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package org.symphonyoss.symphony.messageml.markdown.nodes.form;


/**
* Class that Represents a Markdown Node for the "RoomSelector" form element.
*
* @author Mohamed Rojbeni
* @since 09/15/2023
*/
public class RoomSelectorNode extends FormElementNode implements PlaceholderLabelTooltipNode {
private final static String MARKDOWN = "Room Selector";

private String placeholder;
private String label;
private String tooltip;

public RoomSelectorNode(String placeholder, String label, String tooltip) {
super(MARKDOWN, placeholder);
this.placeholder = placeholder;
this.label = label;
this.tooltip = tooltip;
}

@Override
public String getText() {
return generateMarkdownPlaceholderLabelAndTooltip(placeholder, label, tooltip);
}
}
Loading

0 comments on commit fe22652

Please sign in to comment.