Components advanced topics

Making attributes optional

To make an attribute of an element component optional, you may add @OptionalBinding annotation to the corresponding setter method. For example:

    @BindAttribute(name = "name")
    @OptionalBinding
    public void setNameSupplier(Supplier<String> nameSupplier) {
        this.nameSupplier = nameSupplier;
    }

Exposing variables to the body

A component may compute a value and make it available to its body (i.e. a fragment bound to @BindContent). You should use @BindAttribute for this purpose. Unlike parameters, which are represented by setter methods with one parameter, variable methods must take zero parameters and return the actual value of a variable. For example, look at the std:with implementation:

    @BindAttribute(name = "var")
    public T getVariable() {
        return variable;
    }

    @BindAttribute(name = "value")
    public void setValue(Supplier<T> value) {
        this.value = value;
    }

    @Override
    public void render() {
        if (contentRenderer == null) {
            contentRenderer = content.create();
            getSlot().append(contentRenderer.getSlot());
        }
        variable = value.get();
        contentRenderer.render();
    }

Inner components

Sometimes there is a component that can’t function alone. For example, std:choose is implemented as one parent component and a child component for each clause. It’s possible to create inner components by applying @BindElement to methods. See this excerpt from the std:choose implementation:

    @BindElement(name = "option")
    public void setClauses(List<ChooseClause> clauses) {
        this.clauses = clauses;
    }

    @BindElement(name = "otherwise")
    @OptionalBinding
    public void setOtherwiseClause(OtherwiseClause otherwiseClause) {
        this.otherwiseClause = otherwiseClause;
    }

Components with multiple names

It’s possible to bind more than one name to a component. First, note that both @BindAttributeComponent and @BindElement accept arrays in their name attribute. Second, names can be finished by * character, which means that given element or attribute name may be followed by any character sequence.

To get the actual name of the DOM element or attribute bound to the component, create a method which takes single String parameter and mark it with @BindElementName. Example:

@BindElement(name = { "foo", "bar", "baz-*" })
class MyComponent extends AbstractComponent {
    private String actualName;
    
    @BindElementName
    public void setActualName(String actualName) {
        this.actualName = actualName;
    }
}

Passing multiple parameters to an attribute component

A Component bound to an attribute is limited to only one parameter. However, it’s sometimes necessary to pass multiple parameters. In this case you need an intermediate class that holds values of these parameters. This class must be marked with @SettingsObject and define a setter for each parameter.

To use such a component, you should use JSON-like syntax to pass parameter values.

See this example:

@BindAttributeComponent(name = "my-component")
public class MyComponent implements Renderable {
    private ModifierTarget target;
    private Supplier<MyOptions> optionsSupplier;

    public MyComponent(ModifierTarget target) {
        this.target = target;
    }

    @BindContent
    public void setOptionsSupplier(Supplier<String> optionsSupplier) {
        this.optionsSupplier = optionsSupplier;
    }

    @Override
    public void render() {
        target.getElement().setAttribute("class", 
                optionsSupplier.get().getFirst() + optionsSupplier.get().getSecond());
    }

    @Override
    public void destroy() {
    }
}
@SettingsObject
public class MyOptions {
    private String first;
    private String second;
    
    public String getFirst() {
        return first;
    }
    
    public void setFirst(String first) {
        this.first = first;
    }
    
    public String getSecond() {
        return second;
    }
    
    public void setSecond(String second) {
        this.second = second;
    }
}

which can be used as follows:

<div prefix:my-component="{ first: 'foo', second: 'bar' }">Nothing useful here</div>

Bidirectional binding

You can emulate bidirectional binding by mapping the same attribute or attribute content to two different component properties. Due to expression shortcuts (i.e. param -> foo = param and () -> foo both have a shorter form foo), Flavour will handle this case properly. Example:

@BindAttributeComponent(name = "bidir-binding")
public class BidirExampleComponent implements Renderable {
    private ModifierTarget target;
    private Supplier<String> fooSupplier;
    private Consumer<String> fooConsumer;

    public BidirExampleComponent(ModifierTarget target) {
        this.target = target;
    }

    @BindContent
    public void setFooSupplier(Supplier<String> fooSupplier) {
        this.fooSupplier = fooSupplier;
    }
    
    @BindContent
    public void setFooConsumer(Consumer<String> fooConsumer) {
        this.fooConsumer = fooConsumer;
    }

    @Override
    public void render() {
        // Use fooSupplier and fooConsumer here
    }

    @Override
    public void destroy() {
    }
}
<div prefix:bidir-binding="myProperty">Nothing useful here</div>


Improve this page