Skip to content

Commit

Permalink
Allow to add a dependency dynamically via JS expression (#6602)
Browse files Browse the repository at this point in the history
* Add a new dependency type JS_EXPRESSION

* Call endEagerDependencyLoading when JS Expression dependency execution has ended

* Add GWT unit test for checking dependencies loading order and changes
processing.

* Use NativeFunction instead of ExecuteJavaScriptProcessor
Move loading dynamic import to ResourceLoader
Rename ResourceLoadEvent.resourceUrl to ResourceLoadEvent.resourceData

* Add Gwt unit tests and IT test

* Add server side unit tests.

* Hide unnecessary public method.

* Add javadocs to public method one more time.

* Fix javadocs once again

* Remove extra file left after a merge and fix code review comments.

* Use async mode for GWT unit tests
  • Loading branch information
Denis authored and Johannes Eriksson committed Oct 4, 2019
1 parent 9653de2 commit b7000f5
Show file tree
Hide file tree
Showing 12 changed files with 532 additions and 76 deletions.
2 changes: 1 addition & 1 deletion flow-client/eclipse/GWT tests.launch
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,5 @@
<stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="com.vaadin.client.GwtSuite"/>
<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="flow-client"/>
<stringAttribute key="org.eclipse.jdt.launching.SOURCE_PATH_PROVIDER" value="org.eclipse.m2e.launchconfig.sourcepathProvider"/>
<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-Dgwt.args=&quot;-war target/www-test -logLevel INFO -prod -ea -sourceLevel 1.8 -runStyle HtmlUnit:FF38 -style PRETTY&quot;"/>
<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-Dgwt.args=&quot;-war target/www-test -logLevel INFO -prod -ea -sourceLevel 1.8 -runStyle HtmlUnit:FF38 -style PRETTY&quot; -Dvaadin.enableDevServer=false"/>
</launchConfiguration>
67 changes: 36 additions & 31 deletions flow-client/src/main/java/com/vaadin/client/DependencyLoader.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,15 @@
import java.util.function.BiConsumer;

import com.google.gwt.core.client.Scheduler;

import com.vaadin.client.ResourceLoader.ResourceLoadEvent;
import com.vaadin.client.ResourceLoader.ResourceLoadListener;
import com.vaadin.client.flow.collection.JsArray;
import com.vaadin.client.flow.collection.JsCollections;
import com.vaadin.client.flow.collection.JsMap;
import com.vaadin.flow.shared.ui.Dependency;
import com.vaadin.flow.shared.ui.LoadMode;

import elemental.json.JsonArray;
import elemental.json.JsonObject;

Expand All @@ -47,7 +49,8 @@ public void onLoad(ResourceLoadEvent event) {

@Override
public void onError(ResourceLoadEvent event) {
Console.error(event.getResourceUrl() + " could not be loaded.");
Console.error(
"'" + event.getResourceData() + "' could not be loaded.");
// The show must go on
onLoad(event);
}
Expand All @@ -61,7 +64,7 @@ public void onLoad(ResourceLoadEvent event) {

@Override
public void onError(ResourceLoadEvent event) {
Console.error(event.getResourceUrl() + " could not be loaded.");
Console.error(event.getResourceData() + " could not be loaded.");
// The show must go on
onLoad(event);
}
Expand All @@ -81,21 +84,15 @@ public DependencyLoader(Registry registry) {
this.registry = registry;
}

private void inlineDependency(String dependencyContents,
private void loadLazyDependency(String dependencyUrl,
final BiConsumer<String, ResourceLoadListener> loader) {
startEagerDependencyLoading();
loader.accept(dependencyContents, EAGER_RESOURCE_LOAD_LISTENER);
loader.accept(dependencyUrl, LAZY_RESOURCE_LOAD_LISTENER);
}

private void loadEagerDependency(String dependencyUrl,
private void loadDependencyEagerly(String data,
final BiConsumer<String, ResourceLoadListener> loader) {
startEagerDependencyLoading();
loader.accept(dependencyUrl, EAGER_RESOURCE_LOAD_LISTENER);
}

private void loadLazyDependency(String dependencyUrl,
final BiConsumer<String, ResourceLoadListener> loader) {
loader.accept(dependencyUrl, LAZY_RESOURCE_LOAD_LISTENER);
loader.accept(data, EAGER_RESOURCE_LOAD_LISTENER);
}

/**
Expand Down Expand Up @@ -188,28 +185,34 @@ private JsMap<String, BiConsumer<String, ResourceLoadListener>> extractLazyDepen
.map();
for (int i = 0; i < dependencies.length(); i++) {
JsonObject dependencyJson = dependencies.getObject(i);
Dependency.Type type = Dependency.Type
.valueOf(dependencyJson.getString(Dependency.KEY_TYPE));
BiConsumer<String, ResourceLoadListener> resourceLoader = getResourceLoader(
Dependency.Type.valueOf(
dependencyJson.getString(Dependency.KEY_TYPE)),
loadMode);
type, loadMode);

switch (loadMode) {
case EAGER:
loadEagerDependency(getDependencyUrl(dependencyJson),
resourceLoader);
break;
case LAZY:
lazyDependencies.set(getDependencyUrl(dependencyJson),
if (type == Dependency.Type.DYNAMIC_IMPORT) {
loadDependencyEagerly(
dependencyJson.getString(Dependency.KEY_URL),
resourceLoader);
break;
case INLINE:
inlineDependency(
dependencyJson.getString(Dependency.KEY_CONTENTS),
resourceLoader);
break;
default:
throw new IllegalArgumentException(
"Unknown load mode = " + loadMode);
} else {
switch (loadMode) {
case EAGER:
loadDependencyEagerly(getDependencyUrl(dependencyJson),
resourceLoader);
break;
case LAZY:
lazyDependencies.set(getDependencyUrl(dependencyJson),
resourceLoader);
break;
case INLINE:
loadDependencyEagerly(
dependencyJson.getString(Dependency.KEY_CONTENTS),
resourceLoader);
break;
default:
throw new IllegalArgumentException(
"Unknown load mode = " + loadMode);
}
}
}
return lazyDependencies;
Expand Down Expand Up @@ -250,6 +253,8 @@ private BiConsumer<String, ResourceLoadListener> getResourceLoader(
}
return (scriptUrl, resourceLoadListener) -> resourceLoader
.loadJsModule(scriptUrl, resourceLoadListener, false, true);
case DYNAMIC_IMPORT:
return resourceLoader::loadDynamicImport;
default:
throw new IllegalArgumentException(
"Unknown dependency type " + resourceType);
Expand Down
73 changes: 59 additions & 14 deletions flow-client/src/main/java/com/vaadin/client/ResourceLoader.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,20 @@

package com.vaadin.client;

import java.util.function.Supplier;

import com.google.gwt.core.client.Duration;
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.RepeatingCommand;
import com.google.gwt.user.client.Timer;

import com.vaadin.client.flow.collection.JsArray;
import com.vaadin.client.flow.collection.JsCollections;
import com.vaadin.client.flow.collection.JsMap;
import com.vaadin.client.flow.collection.JsSet;
import com.vaadin.client.flow.util.NativeFunction;

import elemental.client.Browser;
import elemental.dom.Document;
import elemental.dom.Element;
Expand Down Expand Up @@ -127,19 +132,20 @@ public void onError(ResourceLoadEvent event) {
*/
public static class ResourceLoadEvent {
private final ResourceLoader loader;
private final String resourceUrl;
private final String resourceData;

/**
* Creates a new event.
*
* @param loader
* the resource loader that has loaded the resource
* @param resourceUrl
* the url of the loaded resource
* @param resourceData
* the url or content of the loaded resource or the JS
* expression that imports the resource
*/
public ResourceLoadEvent(ResourceLoader loader, String resourceUrl) {
public ResourceLoadEvent(ResourceLoader loader, String resourceData) {
this.loader = loader;
this.resourceUrl = resourceUrl;
this.resourceData = resourceData;
}

/**
Expand All @@ -152,12 +158,14 @@ public ResourceLoader getResourceLoader() {
}

/**
* Gets the absolute url of the loaded resource.
* Gets the absolute url or content of the loaded resource or the JS
* expression that imports the resource.
*
* @return the absolute url of the loaded resource
* @return the absolute url or content of the loaded resource or the JS
* expression that imports the resource
*/
public String getResourceUrl() {
return resourceUrl;
public String getResourceData() {
return resourceData;
}

}
Expand Down Expand Up @@ -356,7 +364,7 @@ private void loadScript(String scriptUrl,
* listener to notify when script is loaded
*/
public void inlineScript(String scriptContents,
final ResourceLoadListener resourceLoadListener) {
final ResourceLoadListener resourceLoadListener) {
ResourceLoadEvent event = new ResourceLoadEvent(this, scriptContents);
if (loadedResources.has(scriptContents)) {
if (resourceLoadListener != null) {
Expand Down Expand Up @@ -635,6 +643,25 @@ public void inlineStyleSheet(String styleSheetContents,
}
}

/**
* Loads a dynamic import via the provided JS {@code expression} and reports
* the result via the {@code resourceLoadListener}.
*
* @param expression
* the JS expression which returns a Promise
* @param resourceLoadListener
* a listener to report the Promise result exection
*/
public void loadDynamicImport(String expression,
ResourceLoadListener resourceLoadListener) {

ResourceLoadEvent event = new ResourceLoadEvent(this, expression);
NativeFunction function = new NativeFunction(expression);
runPromiseExpression(expression, () -> function.call(null),
() -> resourceLoadListener.onLoad(event),
() -> resourceLoadListener.onError(event));
}

private void addCssLoadHandler(String styleSheetContents,
ResourceLoadEvent event, StyleElement styleSheetElement) {
if (BrowserInfo.get().isSafariOrIOS() || BrowserInfo.get().isOpera()) {
Expand Down Expand Up @@ -707,8 +734,8 @@ private static boolean addListener(String resourceId,

private void fireError(ResourceLoadEvent event) {
registry.getSystemErrorHandler()
.handleError("Error loading " + event.getResourceUrl());
String resource = event.getResourceUrl();
.handleError("Error loading " + event.getResourceData());
String resource = event.getResourceData();

JsArray<ResourceLoadListener> listeners = loadListeners.get(resource);
loadListeners.delete(resource);
Expand All @@ -723,8 +750,8 @@ private void fireError(ResourceLoadEvent event) {
}

private void fireLoad(ResourceLoadEvent event) {
Console.log("Loaded " + event.getResourceUrl());
String resource = event.getResourceUrl();
Console.log("Loaded " + event.getResourceData());
String resource = event.getResourceData();
JsArray<ResourceLoadListener> listeners = loadListeners.get(resource);
loadedResources.add(resource);
loadListeners.delete(resource);
Expand All @@ -737,4 +764,22 @@ private void fireLoad(ResourceLoadEvent event) {
}
}
}

private static native void runPromiseExpression(String expression,
Supplier<Object> promiseSupplier, Runnable onSuccess,
Runnable onError)
/*-{
try {
var promise = [email protected]::get(*)();
if ( !(promise instanceof Promise )){
throw new Error('The expression "'+expression+'" result is not a Promise.');
}
promise.then( function(result) { [email protected]::run(*)(); } ,
function(error) { [email protected]::run(*)(); } );
}
catch(error) {
[email protected]::run(*)();
}
}-*/;

}
Original file line number Diff line number Diff line change
Expand Up @@ -743,7 +743,7 @@ public void onLoad(ResourceLoadEvent event) {
@Override
public void onError(ResourceLoadEvent event) {
getConnectionStateHandler()
.pushScriptLoadError(event.getResourceUrl());
.pushScriptLoadError(event.getResourceData());

}
};
Expand Down
Loading

0 comments on commit b7000f5

Please sign in to comment.