Skip to content

Commit

Permalink
Add Element.executeJavaScript
Browse files Browse the repository at this point in the history
Fixes #4538
  • Loading branch information
Legioth committed Aug 23, 2018
1 parent db73c7b commit 470cdaa
Show file tree
Hide file tree
Showing 9 changed files with 95 additions and 45 deletions.
Expand Up @@ -214,6 +214,7 @@ private void addDependency(Dependency dependency) {
ui.getInternals().getDependencyList().add(dependency);
}

// When updating JavaDocs here, keep in sync with Element.executeJavaScript
/**
* Asynchronously runs the given JavaScript expression in the browser. The
* given parameters will be available to the expression as variables named
Expand Down
103 changes: 78 additions & 25 deletions flow-server/src/main/java/com/vaadin/flow/dom/Element.java
Expand Up @@ -37,6 +37,7 @@
import com.vaadin.flow.dom.impl.BasicTextElementStateProvider;
import com.vaadin.flow.dom.impl.CustomAttribute;
import com.vaadin.flow.dom.impl.ThemeListImpl;
import com.vaadin.flow.function.SerializableConsumer;
import com.vaadin.flow.internal.JavaScriptSemantics;
import com.vaadin.flow.internal.JsonCodec;
import com.vaadin.flow.internal.StateNode;
Expand Down Expand Up @@ -466,12 +467,14 @@ public Element removeAttribute(String attribute) {
throw new IllegalArgumentException(ATTRIBUTE_NAME_CANNOT_BE_NULL);
}
String lowerCaseAttribute = attribute.toLowerCase(Locale.ENGLISH);
if(hasAttribute(lowerCaseAttribute)) {
Optional<CustomAttribute> customAttribute = CustomAttribute.get(lowerCaseAttribute);
if (hasAttribute(lowerCaseAttribute)) {
Optional<CustomAttribute> customAttribute = CustomAttribute
.get(lowerCaseAttribute);
if (customAttribute.isPresent()) {
customAttribute.get().removeAttribute(this);
} else {
getStateProvider().removeAttribute(getNode(), lowerCaseAttribute);
getStateProvider().removeAttribute(getNode(),
lowerCaseAttribute);
}
}
return this;
Expand Down Expand Up @@ -1338,12 +1341,13 @@ public Registration addAttachListener(
// This explicit class instantiation is the workaround
// which fixes a JVM optimization+serialization bug.
// Do not convert to lambda
// Detected under Win7_64 /JDK 1.8.0_152, 1.8.0_172
// Detected under Win7_64 /JDK 1.8.0_152, 1.8.0_172
// see ElementAttributeMap#deferRegistration
new Command() {
@Override
public void execute() {
attachListener.onAttach(new ElementAttachEvent(Element.this));
attachListener
.onAttach(new ElementAttachEvent(Element.this));
}
});
}
Expand All @@ -1370,12 +1374,13 @@ public Registration addDetachListener(
// This explicit class instantiation is the workaround
// which fixes a JVM optimization+serialization bug.
// Do not convert to lambda
// Detected under Win7_64 /JDK 1.8.0_152, 1.8.0_172
// Detected under Win7_64 /JDK 1.8.0_152, 1.8.0_172
// see ElementAttributeMap#deferRegistration
new Command() {
@Override
public void execute() {
detachListener.onDetach(new ElementDetachEvent(Element.this));
detachListener
.onDetach(new ElementDetachEvent(Element.this));
}
});
}
Expand Down Expand Up @@ -1451,27 +1456,75 @@ public void callFunction(String functionName, Serializable... arguments) {
assert !functionName
.startsWith(".") : "Function name should not start with a dot";

getNode().runWhenAttached(
ui -> doCallFunction(ui, functionName, arguments));

runBeforeAttachedResponse(ui -> {
// $0.method($1,$2,$3)
String paramPlaceholderString = IntStream
.range(1, arguments.length + 1).mapToObj(i -> "$" + i)
.collect(Collectors.joining(","));
Serializable[] jsParameters = Stream
.concat(Stream.of(this), Stream.of(arguments))
.toArray(Serializable[]::new);

ui.getPage().executeJavaScript(
"$0." + functionName + "(" + paramPlaceholderString + ")",
jsParameters);
});
}

private void doCallFunction(UI ui, String functionName,
Serializable... arguments) {
ui.getInternals().getStateTree().beforeClientResponse(getNode(),
context -> {
// $0.method($1,$2,$3)
String paramPlaceholderString = IntStream
.range(1, arguments.length + 1)
.mapToObj(i -> "$" + i)
.collect(Collectors.joining(","));
Serializable[] jsParameters = Stream
.concat(Stream.of(this), Stream.of(arguments))
.toArray(Serializable[]::new);
// When updating JavaDocs here, keep in sync with Page.executeJavaScript
/**
* Asynchronously runs the given JavaScript expression in the browser in the
* context of this element. This element will be available to the expression
* as <code>this</code>. The given parameters will be available as variables
* named <code>$0</code>, <code>$1</code>, and so on. Supported parameter
* types are:
* <ul>
* <li>{@link String}
* <li>{@link Integer}
* <li>{@link Double}
* <li>{@link Boolean}
* <li>{@link Element} (will be sent as <code>null</code> if the server-side
* element instance is not attached when the invocation is sent to the
* client)
* </ul>
* Note that the parameter variables can only be used in contexts where a
* JavaScript variable can be used. You should for instance do
* <code>'prefix' + $0</code> instead of <code>'prefix$0'</code> and
* <code>value[$0]</code> instead of <code>value.$0</code> since JavaScript
* variables aren't evaluated inside strings or property names.
*
* @param expression
* the JavaScript expression to invoke
* @param parameters
* parameters to pass to the expression
*/
public void executeJavaScript(String expression,
Serializable... parameters) {

ui.getPage().executeJavaScript("$0." + functionName + "("
+ paramPlaceholderString + ")", jsParameters);
});
// Add "this" as the last parameter
Serializable[] wrappedParameters = Stream
.concat(Stream.of(parameters), Stream.of(this))
.toArray(Serializable[]::new);

// Wrap in a function that is applied with last parameter as "this"
String wrappedExpression = "(function() { " + expression + "}).apply($"
+ parameters.length + ")";

runBeforeAttachedResponse(ui -> ui.getPage()
.executeJavaScript(wrappedExpression, wrappedParameters));
}

/**
* Runs the given action right before the next response during which this
* element is attached.
*
* @param action
* the action to run
*/
private void runBeforeAttachedResponse(SerializableConsumer<UI> action) {
getNode().runWhenAttached(
ui -> ui.getInternals().getStateTree().beforeClientResponse(
getNode(), context -> action.accept(context.getUI())));
}

/**
Expand Down
Expand Up @@ -115,6 +115,7 @@ public void publicElementMethodsShouldReturnElement() {
ignore.add("as");
// Possibly returns a remover or a wrapped return value in the future
ignore.add("callFunction");
ignore.add("executeJavaScript");

// ignore shadow root methods
ignore.add("attachShadow");
Expand Down
Expand Up @@ -45,8 +45,8 @@ protected void onShow() {
e -> {
Input input = new Input();
input.addClassName("newInput");
UI.getCurrent().getPage().executeJavaScript("$0.value = $1",
input, "Value from js");
input.getElement().executeJavaScript("this.value=$0",
"Value from js");
add(input);
});
createElementButton.setId("createButton");
Expand Down
Expand Up @@ -19,8 +19,8 @@
import com.vaadin.flow.component.html.Div;
import com.vaadin.flow.component.html.Label;
import com.vaadin.flow.component.html.NativeButton;
import com.vaadin.flow.uitest.servlet.ViewTestLayout;
import com.vaadin.flow.router.Route;
import com.vaadin.flow.uitest.servlet.ViewTestLayout;

@JavaScript("frontend://in-memory-connector.js")
@Route(value = "com.vaadin.flow.uitest.ui.InMemoryChildrenView", layout = ViewTestLayout.class)
Expand All @@ -32,10 +32,8 @@ protected void onShow() {
label.setId("in-memory");
label.setText("In memory element");
getElement().appendVirtualChild(label.getElement());
getElement().getNode()
.runWhenAttached(ui -> ui.getPage().executeJavaScript(
"window.inMemoryConnector.init($0, $1)", getElement(),
label));
getElement().executeJavaScript(
"window.inMemoryConnector.init(this, $0)", label);
Div target = new Div();
target.setId("target");
add(target);
Expand Down
Expand Up @@ -25,11 +25,11 @@
public class TimingInfoReportedView extends Div {

//@formatter:off
private static final String REPORT_TIMINGS = "setTimeout(function() {"
private static final String REPORT_TIMINGS = "var element = this; setTimeout(function() {"
+ "function report(array){ "
+ "var div = document.createElement('div');"
+ "div.className='log';"
+ "$0.appendChild(div); "
+ "element.appendChild(div); "
+ "if (array.length != 5) { "
+ " div.appendChild(document.createTextNode('ERROR: expected 5 values, got '+array.length())); "
+ "}"
Expand All @@ -49,10 +49,10 @@ public class TimingInfoReportedView extends Div {

@Override
protected void onAttach(AttachEvent attachEvent) {
getUI().get().getPage().executeJavaScript(REPORT_TIMINGS, getElement());
getElement().executeJavaScript(REPORT_TIMINGS);
NativeButton button = new NativeButton("test request");
button.addClickListener(event -> getUI().get().getPage()
.executeJavaScript(REPORT_TIMINGS, getElement()));
button.addClickListener(
event -> getElement().executeJavaScript(REPORT_TIMINGS));
add(button);
}

Expand Down
Expand Up @@ -22,20 +22,20 @@
public class UIElementView extends AbstractDivView {

public UIElementView() {
getPage().executeJavaScript(getJs(), getElement());
getElement().executeJavaScript(getJs());

NativeButton attachElement = new NativeButton("Attach Element via JS",
event -> attachElement());
add(attachElement);
}

private void attachElement() {
getPage().executeJavaScript(getJs(), getElement());
getElement().executeJavaScript(getJs());
}

private String getJs() {
return "var newElement = document.createElement('div');"
+ "newElement.className='body-child';"
+ "$0.appendChild(newElement);";
+ "this.appendChild(newElement);";
}
}
Expand Up @@ -23,9 +23,7 @@
public class JsInjectedDiv extends Div {

public JsInjectedDiv() {
getElement().getNode()
.runWhenAttached(ui -> ui.getPage().executeJavaScript(
"window.divConnector.jsFunction($0)", getElement()));
getElement().executeJavaScript("window.divConnector.jsFunction(this)");
}

@ClientCallable
Expand Down
Expand Up @@ -26,8 +26,7 @@
public class JsInjectedElement extends PolymerTemplate<TemplateModel> {

public JsInjectedElement() {
getElement().getNode().runWhenAttached(ui -> ui.getPage()
.executeJavaScript("$0.clientHandler()", getElement()));
getElement().executeJavaScript("this.clientHandler()");
}

@ClientCallable
Expand Down

0 comments on commit 470cdaa

Please sign in to comment.