TemplateRenderer parsing only some properties

In order to replace as many ComponentRenderers in my Grids as possible with TemplateRenderers, I am trying to make my own “Checkbox TemplateRenderer”. However, some of the properties I defined in the template String are never parsed, even though I provided them with a withProperty.

Here is my attempt

public class GridTemplateRenderer {
	public static <SOURCE> TemplateRenderer<SOURCE> checkbox(ValueProvider<SOURCE, Boolean> valueProvider, ValueProvider<SOURCE, Boolean> disabledProvider){
        return TemplateRenderer.<SOURCE> of("<vaadin-checkbox [[item.checked]
] aria-checked='[[item.ariaChecked]
]' [[item.disabled]
]></vaadin-checkbox>")
                .withProperty("checked", item -> valueProvider.apply(item) ? "checked" : "")
                .withProperty("ariaChecked", item -> valueProvider.apply(item) ? "true" : "false")
                .withProperty("disabled", item -> disabledProvider.apply(item) ? "disabled" : "");
    }
}

and here the resulting HTML:
<vaadin-checkbox [[item.checked] ] [[item.disabled] ] tabindex="0" aria-checked="false" role="checkbox"></vaadin-checkbox>

I can see that the "ariaChecked" property was parsed correctly as false, but "checked" and "disabled" item-properties were not parsed.
Why is that? And how can I fix it?

The template syntax always needs an attribute name before the [[something] ] binding when inside a tag (i.e. between < and >. A standalone binding can only be used as the text content (i.e. between > and <).

In your case, it should maybe be <vaadin-checkbox checked='[[item.checked] ]' aria-checked='[[item.ariaChecked] ]' disabled='[[item.disabled] ]'>.

Thank you Leif. I was not awware there’s a difference between inside a tag and outside of it. And because in normal HTML the “checked” attribute will be read as true as long as the attribute-name “checked” exists. That’s why I tried it like that. I have since learned through the expert chat that the vaadin component vaadin-checkbox has the “checked” attribute mapped itself, so this would indeed work.

Additionally, I removed the quotes as well, as the values are of type boolean, not String.

The final and working syntax is therefore:

public static <SOURCE> TemplateRenderer<SOURCE> checkbox(ValueProvider<SOURCE, Boolean> valueProvider, ValueProvider<SOURCE, Boolean> disabledProvider){
	return TemplateRenderer.<SOURCE> of("<vaadin-checkbox checked=[[item.checked]
] disabled=[[item.disabled]
]></vaadin-checkbox>")
                .withProperty("checked", valueProvider)
                .withProperty("disabled", disabledProvider);
}

Thank you Leif and Markus!

PS: defining the aria-checked attribute was unnecessary as well.

Actually, this doesn’t work yet. Please help me find what’s wrong, I am clueless at this point!

The checked and disabled properties are sometimes correct and sometimes not. It’s completely unreliable.

Reloading the page will give different results every time.

Here is my full TestView definition. To see how I expect the checkboxes to behave, I also added the very same grid but using ComponentRenderers for each column.

@Route(value = "Test", layout = MainView.class)
public class TestView extends VerticalLayout {
	public TestView(){
        add(new Button("Reload", click -> UI.getCurrent().getPage().executeJs("location.reload()")));

        Grid<String> templateRendererGrid = new Grid<>(String.class, false);

        templateRendererGrid.addColumn(item -> item).setHeader("actual string");

        templateRendererGrid.addColumn(templateRendererCheckbox(item -> true, item -> true)).setHeader("All True & Disabled");
        templateRendererGrid.addColumn(templateRendererCheckbox(item -> false, item -> true)).setHeader("All False & Disabled");
        templateRendererGrid.addColumn(templateRendererCheckbox(item -> true, item -> false)).setHeader("All True & Enabled");
        templateRendererGrid.addColumn(templateRendererCheckbox(item -> false, item -> false)).setHeader("All False & Enabled");

        templateRendererGrid.addColumn(templateRendererCheckbox(item -> item.contains("a"), item -> item.contains("a"))).setHeader("A True & Disabled");
        templateRendererGrid.addColumn(templateRendererCheckbox(item -> item.contains("b"), item -> item.contains("b"))).setHeader("B True & Disabled");
        templateRendererGrid.addColumn(templateRendererCheckbox(item -> item.contains("c"), item -> item.contains("c"))).setHeader("C True & Enabled");

        templateRendererGrid.setItems("a", "b", "c");
        templateRendererGrid.setHeightByRows(true);
        add(new Label("Grid with TemplateRendered checkboxes"));
        add(templateRendererGrid);

        // comparison grid with actual components
        Grid<String> componentRendererGrid = new Grid<>(String.class, false);
        componentRendererGrid.addColumn(item -> item).setHeader("actual string");
        componentRendererGrid.addColumn(componentRendererCheckbox(item -> true, item -> true)).setHeader("All True & Disabled");
        componentRendererGrid.addColumn(componentRendererCheckbox(item -> false, item -> true)).setHeader("All False & Disabled");
        componentRendererGrid.addColumn(componentRendererCheckbox(item -> true, item -> false)).setHeader("All True & Enabled");
        componentRendererGrid.addColumn(componentRendererCheckbox(item -> false, item -> false)).setHeader("All False & Enabled");

        componentRendererGrid.addColumn(componentRendererCheckbox(item -> item.contains("a"), item -> item.contains("a"))).setHeader("A True & Disabled");
        componentRendererGrid.addColumn(componentRendererCheckbox(item -> item.contains("b"), item -> item.contains("b"))).setHeader("B True & Disabled");
        componentRendererGrid.addColumn(componentRendererCheckbox(item -> item.contains("c"), item -> item.contains("c"))).setHeader("C True & Enabled");

        componentRendererGrid.setItems("a", "b", "c");
        componentRendererGrid.setHeightByRows(true);

        add(new Label("Grid with ComponentRendered checkboxes"));
        add(componentRendererGrid);
	}

    private <SOURCE> TemplateRenderer<SOURCE> templateRendererCheckbox(ValueProvider<SOURCE, Boolean> valueProvider, ValueProvider<SOURCE, Boolean> disabledProvider) {
        return TemplateRenderer.<SOURCE> of("<vaadin-checkbox checked=[[item.checked]
] disabled=[[item.disabled]
]></vaadin-checkbox>")
                .withProperty("checked", valueProvider)
                .withProperty("disabled", disabledProvider);
    }

    private <SOURCE> ComponentRenderer<Checkbox, SOURCE> componentRendererCheckbox(ValueProvider<SOURCE, Boolean> valueProvider, ValueProvider<SOURCE, Boolean> disabledProvider) {
        return new ComponentRenderer<Checkbox, SOURCE>(item -> {
            Checkbox checkbox = new Checkbox();
            checkbox.setEnabled(!disabledProvider.apply(item));
            checkbox.setValue(valueProvider.apply(item));
            return checkbox;
        });
    }
}

and here is a screencapture gif of the very strange outcome. I expected both grids to behave exactly the same:
![templateRenderer vs ComponentRenderer]
(https://i.imgur.com/sTvUimx.gif)

Edit: oops the last column header should read C True & Disabled in both grids. not important enough to record a new gif…

This happens because withProperty names are scoped for the whole grid rather than per column / renderer. The randomness happens because the logic for which generator to use when there are duplicates isn’t deterministic, but instead seems to be based on iteration order over a HashSet. Scoping names by column isn’t practical because of the architecture, but I guess it would be helpful if the situation would be detected so that a warning could be logged.

You can fix this by using unique property names for each renderer, e.g. like this:

private <SOURCE> TemplateRenderer<SOURCE> templateRendererCheckbox(
        ValueProvider<SOURCE, Boolean> valueProvider,
        ValueProvider<SOURCE, Boolean> disabledProvider) {
    String id = UUID.randomUUID().toString();
    return TemplateRenderer.<SOURCE> of(
            "<vaadin-checkbox checked=[[item.checked_"+id+"]
] disabled=[[item.disabled_"+id+"]
]></vaadin-checkbox>")
            .withProperty("checked_" + id, valueProvider)
            .withProperty("disabled_" + id, disabledProvider);
}

You are right. That solved my issue now completely.
Thank you!

related github issue: https://github.com/vaadin/flow/issues/8629

PS: for .withEventHandler this uuid workaround is not needed. The grid can handle multiple eventhandlers with the same name. In fact it gave me errors when I applied this unique naming workaround there.