Skip to content

Instantly share code, notes, and snippets.

@Legioth
Last active August 28, 2020 15:57
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Legioth/6fde19fe22b3135a9214ecdb055296c7 to your computer and use it in GitHub Desktop.
Save Legioth/6fde19fe22b3135a9214ecdb055296c7 to your computer and use it in GitHub Desktop.
JavaScript Renderer for Flow
package org.vaadin.leif;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import org.slf4j.LoggerFactory;
import com.vaadin.flow.data.provider.CompositeDataGenerator;
import com.vaadin.flow.data.provider.DataGenerator;
import com.vaadin.flow.data.provider.DataKeyMapper;
import com.vaadin.flow.data.renderer.Renderer;
import com.vaadin.flow.data.renderer.RendererUtil;
import com.vaadin.flow.data.renderer.Rendering;
import com.vaadin.flow.dom.Element;
import com.vaadin.flow.function.SerializableBiConsumer;
import com.vaadin.flow.function.SerializableConsumer;
import com.vaadin.flow.function.ValueProvider;
import com.vaadin.flow.internal.JsonSerializer;
import com.vaadin.flow.internal.nodefeature.ReturnChannelMap;
import com.vaadin.flow.internal.nodefeature.ReturnChannelRegistration;
import elemental.json.Json;
import elemental.json.JsonArray;
import elemental.json.impl.JsonUtil;
public class JsRenderer<T> extends Renderer<T> {
private String expression;
private Map<String, SerializableBiConsumer<T, JsonArray>> clientCallables = new HashMap<>();
JsRenderer(String expression) {
this.expression = expression;
}
@Override
public Rendering<T> render(Element container, DataKeyMapper<T> keyMapper, Element contentTemplate) {
throw new UnsupportedOperationException();
}
@Override
public Rendering<T> render(Element container, DataKeyMapper<T> keyMapper) {
ReturnChannelRegistration returnChannel = container.getNode().getFeature(ReturnChannelMap.class)
.registerChannel(arguments -> {
String handlerName = arguments.getString(0);
String itemKey = arguments.getString(1);
JsonArray args;
if (arguments.length() == 3) {
args = arguments.getArray(2);
} else {
args = Json.createArray();
}
SerializableBiConsumer<T, JsonArray> handler = clientCallables.get(handlerName);
if (handler == null) {
throw new IllegalStateException(handlerName);
}
T item = keyMapper.get(itemKey);
if (item != null) {
handler.accept(item, args);
} else {
LoggerFactory.getLogger(RendererUtil.class.getName()).info(
"Received an event for the handler '{}' with item key '{}', but the item is not present in the KeyMapper. Ignoring event.",
handlerName, itemKey);
}
});
StringBuilder builder = new StringBuilder();
builder.append("this.renderer = function(root, column, row) { var item = row.item;\n ");
// TODO Maybe defer this to beforeClientResponse so that we can include
// callables added after the renderer is set
clientCallables.keySet().forEach(handlerName -> {
builder.append("var ").append(handlerName).append(" = function() { $0(").append(JsonUtil.quote(handlerName))
.append(", item.key, arguments[0] instanceof Event ? [] : Array.prototype.slice.call(arguments)) };\n");
});
builder.append(expression);
builder.append("\n}");
container.executeJs(builder.toString(), returnChannel);
return new Rendering<T>() {
@Override
public Optional<DataGenerator<T>> getDataGenerator() {
Map<String, ValueProvider<T, ?>> valueProviders = getValueProviders();
if (valueProviders == null || valueProviders.isEmpty()) {
return Optional.empty();
}
CompositeDataGenerator<T> composite = new CompositeDataGenerator<>();
valueProviders.forEach((key, provider) -> composite.addDataGenerator(
(item, jsonObject) -> jsonObject.put(key, JsonSerializer.toJson(provider.apply(item)))));
return Optional.of(composite);
}
@Override
public Element getTemplateElement() {
return null;
}
};
}
public JsRenderer<T> withProperty(String property, ValueProvider<T, ?> provider) {
setProperty(property, provider);
return this;
}
public JsRenderer<T> withClientCallable(String functionName, SerializableConsumer<T> handler) {
return withClientCallable(functionName, (item, ignore) -> handler.accept(item));
}
public JsRenderer<T> withClientCallable(String functionName, SerializableBiConsumer<T, JsonArray> handler) {
// TODO validate functionName
clientCallables.put(functionName, handler);
return this;
}
public static <T> JsRenderer<T> of(String expression) {
return new JsRenderer<>(expression);
}
}
grid.addColumn(JsRenderer.<String> of("root.textContent = item.foo").withProperty("foo", item -> item)).setHeader("Test");
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment