package xapi.components.impl;
import elemental.client.Browser;
import elemental.dom.Element;
import elemental.html.DivElement;
import xapi.collect.X_Collect;
import xapi.collect.api.Fifo;
import xapi.components.api.OnWebComponentAttributeChanged;
import xapi.components.api.ShadowDomPlugin;
import xapi.fu.In1Out1;
import xapi.fu.In2Out1;
import xapi.util.X_String;
import static xapi.components.impl.JsFunctionSupport.wrapConsumerOfThis;
import static xapi.components.impl.JsFunctionSupport.wrapRunnable;
import static xapi.components.impl.JsFunctionSupport.wrapWebComponentChangeHandler;
import static xapi.components.impl.JsSupport.copy;
import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.core.client.UnsafeNativeLong;
import com.google.gwt.core.client.js.JsProperty;
import com.google.gwt.core.client.js.JsType;
import java.util.function.Consumer;
import java.util.function.IntConsumer;
import java.util.function.IntSupplier;
import java.util.function.LongConsumer;
import java.util.function.LongSupplier;
import java.util.function.Supplier;
public class WebComponentBuilder {
@JsType
interface WebComponentPrototype {
@JsProperty
void setAttachedCallback(JavaScriptObject callback);
@JsProperty
JavaScriptObject getAttachedCallback();
@JsProperty
void setCreatedCallback(JavaScriptObject callback);
@JsProperty
JavaScriptObject getCreatedCallback();
@JsProperty
void setDetachedCallback(JavaScriptObject callback);
@JsProperty
JavaScriptObject getDetachedCallback();
@JsProperty
void setAttributeChangedCallback(JavaScriptObject callback);
@JsProperty
JavaScriptObject getAttributeChangedCallback();
}
public static WebComponentBuilder create() {
return new WebComponentBuilder(htmlElementPrototype());
}
public static WebComponentBuilder create(final JavaScriptObject proto) {
return new WebComponentBuilder(proto);
}
public static native JavaScriptObject htmlElementPrototype()
/*-{
return HTMLElement.prototype;
}-*/;
private final WebComponentPrototype prototype;
private String superTag;
private Fifo<ShadowDomPlugin> plugins;
public WebComponentBuilder(JavaScriptObject prototype) {
plugins = X_Collect.newFifo(); // uses linked list in jvm, but array in js
if (prototype == null) {
prototype = htmlElementPrototype();
}
this.prototype = (WebComponentPrototype) prototype;
}
public WebComponentBuilder attachedCallback(final Runnable function) {
return attachedCallback(wrapRunnable(function));
}
public <E extends Element> WebComponentBuilder attachedCallback(
final Consumer<E> function) {
return attachedCallback(wrapConsumerOfThis(function));
}
public WebComponentBuilder attachedCallback(final JavaScriptObject function) {
if (prototype.getAttachedCallback() == null) {
prototype.setAttachedCallback(function);
} else {
// append the functions together
prototype.setAttachedCallback(JsFunctionSupport.merge(prototype
.getAttachedCallback(), function));
}
return this;
}
public <E extends Element> WebComponentBuilder attributeChangedCallback(
final OnWebComponentAttributeChanged function) {
return attachedCallback(wrapWebComponentChangeHandler(function));
}
public WebComponentBuilder attributeChangedCallback(final JavaScriptObject function) {
if (prototype.getAttributeChangedCallback() == null) {
prototype.setAttributeChangedCallback(function);
} else {
// append the functions together
prototype.setAttributeChangedCallback(JsFunctionSupport.merge(prototype
.getAttributeChangedCallback(), function));
}
return this;
}
public WebComponentBuilder createdCallback(final Runnable function) {
return createdCallback(wrapRunnable(function));
}
private static final DivElement templateHost = Browser.getDocument().createDivElement();
public void addShadowDomPlugin(ShadowDomPlugin plugin) {
plugins.give(plugin);
}
public WebComponentBuilder addShadowRoot(String html, ShadowDomPlugin ... plugins) {
final In1Out1<Element, Element> initializer;
if (html.contains("<template")) {
templateHost.setInnerHTML(html);
Element template = templateHost.getFirstElementChild();
String id = template.getId();
if (X_String.isEmpty(id)) {
id = JsSupport.newId();
template.setId(id);
}
templateHost.setInnerHTML("");
initializer = In2Out1.with2(this::setShadowRootTemplate, template);
} else {
initializer = In2Out1.with2(this::setShadowRoot, html);
}
// Optimize for (common) cases of not having any plugins...
if (plugins.length == 0 && this.plugins.isEmpty()) {
return createdCallback(initializer.ignoreOutput().toConsumer());
}
return createdCallback(element->{
Element root = initializer.io(element);
for (ShadowDomPlugin plugin : plugins) {
root = plugin.transform(root);
}
for (ShadowDomPlugin plugin : this.plugins.forEach()) {
root = plugin.transform(root);
}
});
}
private native Element setShadowRootTemplate(Element element, Element template)
/*-{
var root = element.createShadowRoot();
var clone = document.importNode(template.content, true);
root.appendChild(clone);
return root;
}-*/;
private native Element setShadowRoot(Element element, String html)
/*-{
var root = element.createShadowRoot();
root.innerHTML = html;
return root;
}-*/;
public <E extends Element> WebComponentBuilder createdCallback(
final Consumer<E> function) {
return createdCallback(wrapConsumerOfThis(function));
}
public WebComponentBuilder createdCallback(JavaScriptObject function) {
function = reapplyThis(function);
if (prototype.getCreatedCallback() == null) {
prototype.setCreatedCallback(function);
} else {
// append the functions together
prototype.setCreatedCallback(JsFunctionSupport.merge(
function,
prototype.getCreatedCallback()
));
}
return this;
}
private native JavaScriptObject reapplyThis(JavaScriptObject f)
/*-{
return function() {
return f.apply(this, [this].concat(Array.prototype.slice.apply(arguments)));
};
}-*/;
public WebComponentBuilder detachedCallback(final Runnable function) {
return detachedCallback(wrapRunnable(function));
}
public <E extends Element> WebComponentBuilder detachedCallback(
final Consumer<E> function) {
return detachedCallback(wrapConsumerOfThis(function));
}
public WebComponentBuilder detachedCallback(final JavaScriptObject function) {
if (prototype.getDetachedCallback() == null) {
prototype.setDetachedCallback(function);
} else {
// append the functions together
prototype.setDetachedCallback(JsFunctionSupport.merge(prototype
.getDetachedCallback(), function));
}
return this;
}
public native JavaScriptObject build()
/*-{
var p = {
prototype : this.@xapi.components.impl.WebComponentBuilder::prototype
};
if (this.@xapi.components.impl.WebComponentBuilder::superTag != null) {
p['extends'] = this.@xapi.components.impl.WebComponentBuilder::superTag;
}
return p;
}-*/;
public WebComponentBuilder extend() {
return new WebComponentBuilder(copy(prototype));
}
public WebComponentBuilder setExtends(final String tagName) {
this.superTag = tagName;
return this;
}
public WebComponentBuilder addValue(final String name, final JavaScriptObject value) {
return addValue(name, value, false, true, true);
}
public WebComponentBuilder addValueReadOnly(final String name,
final JavaScriptObject value) {
return addValue(name, value, false, true, false);
}
public native WebComponentBuilder addValue(String name,
JavaScriptObject value, boolean enumerable, boolean configurable,
boolean writeable)
/*-{
Object
.defineProperty(
this.@xapi.components.impl.WebComponentBuilder::prototype,
name, {
value : value,
enumerable : enumerable,
configurable : configurable,
writeable : writeable
});
return this;
}-*/;
public <T> WebComponentBuilder addProperty(final String name, final Supplier<T> get,
final Consumer<T> set) {
return addProperty(name, get, set, true, false);
}
public <T> WebComponentBuilder addPropertyReadOnly(final String name,
final Supplier<T> get) {
return addProperty(name, get, null, true, false);
}
public <T> WebComponentBuilder addPropertyWriteOnly(final String name,
final Consumer<T> set) {
return addProperty(name, null, set, true, false);
}
public native <T> WebComponentBuilder addProperty(String name,
Supplier<T> get, Consumer<T> set, boolean enumerable, boolean configurable)
/*-{
var proto = {
enumerable : enumerable,
configurable : configurable,
};
if (get) {
proto.get = function() {
get.__caller__ = this;
return get.@java.util.function.Supplier::get()()
};
}
if (set) {
proto.set = function(i) {
set.__caller__ = this;
set.@java.util.function.Consumer::accept(Ljava/lang/Object;)(i)
};
}
Object
.defineProperty(
this.@xapi.components.impl.WebComponentBuilder::prototype,
name, proto);
return this;
}-*/;
public WebComponentBuilder addPropertyInt(final String name, final IntSupplier get,
final IntConsumer set) {
return addPropertyInt(name, get, set, true, false);
}
public WebComponentBuilder addPropertyIntReadOnly(final String name, final IntSupplier get) {
return addPropertyInt(name, get, null, true, false);
}
public WebComponentBuilder addPropertyIntWriteOnly(final String name,
final IntConsumer set) {
return addPropertyInt(name, null, set, true, false);
}
public native WebComponentBuilder addPropertyInt(String name,
IntSupplier get, IntConsumer set, boolean enumerable, boolean configurable)
/*-{
var proto = {
enumerable : enumerable,
configurable : configurable,
};
if (get) {
proto.get = function() {
get.__caller__ = this;
var i = get.@java.util.function.IntSupplier::getAsInt()();
return @xapi.components.impl.JsSupport::unboxInteger(Lcom/google/gwt/core/client/JavaScriptObject;)(i)
};
}
if (set) {
proto.set = function(i) {
set.__caller__ = this;
var i = @xapi.components.impl.JsSupport::unboxInteger(Lcom/google/gwt/core/client/JavaScriptObject;)(i);
set.@java.util.function.IntConsumer::accept(I)(i)
};
}
Object
.defineProperty(
this.@xapi.components.impl.WebComponentBuilder::prototype,
name, proto);
return this;
}-*/;
public WebComponentBuilder addPropertyLong(final String name, final LongSupplier get,
final LongConsumer set) {
return addPropertyLong(name, get, set, true, false);
}
public WebComponentBuilder addPropertyLongReadOnly(final String name,
final LongSupplier get) {
return addPropertyLong(name, get, null, true, false);
}
public WebComponentBuilder addPropertyLongWriteOnly(final String name,
final LongConsumer set) {
return addPropertyLong(name, null, set, true, false);
}
@UnsafeNativeLong
public native WebComponentBuilder addPropertyLong(String name,
LongSupplier get, LongConsumer set, boolean enumerable,
boolean configurable)
/*-{
var proto = {
enumerable : enumerable,
configurable : configurable,
};
if (get) {
proto.get = function() {
get.__caller__ = this;
var i = get.@java.util.function.LongSupplier::getAsLong()();
return @xapi.components.impl.JsSupport::unboxLong(Lcom/google/gwt/core/client/JavaScriptObject;)(i)
};
}
if (set) {
proto.set = function(i) {
set.__caller__ = this;
var i = @xapi.components.impl.JsSupport::unboxLong(Lcom/google/gwt/core/client/JavaScriptObject;)(i);
set.@java.util.function.LongConsumer::accept(J)(i)
};
}
Object
.defineProperty(
this.@xapi.components.impl.WebComponentBuilder::prototype,
name, proto);
return this;
}-*/;
public WebComponentBuilder addPropertyLongNativeUnbox(final String name,
final LongSupplier get, final LongConsumer set) {
return addPropertyLongNativeUnbox(name, get, set, true, false);
}
public WebComponentBuilder addPropertyLongNativeUnboxReadOnly(final String name,
final LongSupplier get) {
return addPropertyLongNativeUnbox(name, get, null, true, false);
}
public WebComponentBuilder addPropertyLongNativeUnboxWriteOnly(final String name,
final LongConsumer set) {
return addPropertyLongNativeUnbox(name, null, set, true, false);
}
@UnsafeNativeLong
public native WebComponentBuilder addPropertyLongNativeUnbox(String name,
LongSupplier get, LongConsumer set, boolean enumerable,
boolean configurable)
/*-{
var proto = {
enumerable : enumerable,
configurable : configurable,
};
if (get) {
proto.get = function() {
get.__caller__ = this;
var i = get.@java.util.function.LongSupplier::getAsLong()();
return @xapi.components.impl.JsSupport::unboxLongNative(Lcom/google/gwt/core/client/JavaScriptObject;)(i);
};
}
if (set) {
proto.set = function(i) {
set.__caller__ = this;
var i = @xapi.components.impl.JsSupport::unboxLong(Lcom/google/gwt/core/client/JavaScriptObject;)(i);
set.@java.util.function.LongConsumer::accept(J)(i)
};
}
Object
.defineProperty(
this.@xapi.components.impl.WebComponentBuilder::prototype,
name, proto);
return this;
}-*/;
}