Creating custom traits with complex fields
This page provides code and configuration examples of creating custom traits with multi and complex fields.
| The rule field is implicitly wrapped inside MultiFieldin Magnolia 6.2.x. Please keep this in mind when configuring fields and use the examples below as a reference. | 
Creating a trait with a multi field
Definition
#traitClass has to be unique, creating multiple traits with multiField requires using different List implementation per trait
traitClass: java.util.List
converterClass: info.magnolia.personalization.preview.parameter.ListParameterConverter
voterClass: info.magnolia.personalization.preview.ui.app.field.ListVoter
defaultPreviewTrait: true
valueField:
  $type: multiValueField
  name: multiFieldTrait
  canRemoveItems: false
  field:
    $type: textField
ruleField:
  $type: compositeField
  properties:
    list:
      $type: multiValueField
      field:
        $type: textFieldVoter
package traits;
import info.magnolia.personalization.trait.TraitCollector;
import info.magnolia.voting.voters.AbstractBoolVoter;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
public class ListVoter extends AbstractBoolVoter<TraitCollector> {
    private List<String> list = new ArrayList<>();
    @Override
    protected boolean boolVote(final TraitCollector traitCollector) {
        return Optional.ofNullable(traitCollector)
                .map(collector -> collector.getTrait(List.class))
                .map(list::containsAll)
                .orElse(false);
    }
    public void setList(List<String> list) {
        this.list = list;
    }
}Trait detector filter
This example filter detects all cookie names.
package traits;
import info.magnolia.personalization.trait.AbstractTraitDetectorFilter;
import info.magnolia.personalization.trait.TraitCollector;
import info.magnolia.personalization.trait.TraitDetectionException;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import javax.inject.Provider;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class ListDetectorFilter extends AbstractTraitDetectorFilter<List<String>> {
    public ListDetectorFilter(Provider<TraitCollector> traitCollectorProvider) {
        super(traitCollectorProvider);
    }
    @Override
    protected List<String> detect(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws TraitDetectionException {
        return Arrays.stream(httpServletRequest.getCookies())
                .map(Cookie::getName)
                .collect(Collectors.toList());
    }
    @Override
    protected Class getTraitClass() {
        return List.class;
    }
}Creating a trait with a complex field
Definition
#traitClass has to be unique, creating multiple traits with multiField requires using different Map implementation per trait
traitClass: java.util.Map
converterClass: info.magnolia.personalization.preview.ui.app.field.MapPreviewConverter
voterClass: info.magnolia.personalization.preview.ui.app.field.MapTraitVoter
defaultPreviewTrait: true
valueField:
  name: complexFieldTrait
  $type: textField
  placeholder: key=value
ruleField:
  $type: compositeField
  properties:
    key:
      $type: textField
    value:
      $type: textFieldVoter
package traits;
import info.magnolia.personalization.trait.TraitCollector;
import info.magnolia.voting.voters.AbstractBoolVoter;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
public class MapTraitVoter extends AbstractBoolVoter<TraitCollector> {
    private String key;
    private String value;
    @Override
    protected boolean boolVote(TraitCollector traitCollector) {
        return Optional.ofNullable(traitCollector)
                .map(collector -> collector.getTrait(Map.class))
                .map(map -> Objects.equals(map.get(key), value))
                .orElse(false);
    }
    public void setKey(String key) {
        this.key = key;
    }
    public void setValue(String value) {
        this.value = value;
    }
}Trait detector filter
This example filter detects all cookies.
package traits;
import info.magnolia.personalization.trait.AbstractTraitDetectorFilter;
import info.magnolia.personalization.trait.TraitCollector;
import info.magnolia.personalization.trait.TraitDetectionException;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
import javax.inject.Provider;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.StringUtils;
public class MapDetectorFilter extends AbstractTraitDetectorFilter<Map<String, Cookie>> {
    public MapDetectorFilter(Provider<TraitCollector> traitCollectorProvider) {
        super(traitCollectorProvider);
    }
    @Override
    protected Map<String, Cookie> detect(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws TraitDetectionException {
        return Arrays.stream(httpServletRequest.getCookies())
                .collect(Collectors.toMap(Cookie::getName, Function.identity()));
    }
    @Override
    protected Class getTraitClass() {
        return Map.class;
    }
}