Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Element.executeJavaScript #4539

Merged
merged 1 commit into from
Aug 31, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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() {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MAJOR Make this anonymous inner class a lambda rule

@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() {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MAJOR Make this anonymous inner class a lambda rule

@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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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);";
}
}
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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