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

Allow to add a dependency dynamically via JS expression #6602

Merged
merged 11 commits into from
Oct 4, 2019
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>
73 changes: 52 additions & 21 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,12 +49,28 @@ 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);
}
};

private static final ResourceLoadListener DYNAMIC_IMPORT_LOAD_LISTENER = new ResourceLoadListener() {
@Override
public void onLoad(ResourceLoadEvent event) {
// Call start for next before calling end for current
endEagerDependencyLoading();
}

@Override
public void onError(ResourceLoadEvent event) {
Console.error(
"\"" + event.getResourceData() + "\" could not be loaded.");
// show must go on
onLoad(event);
}
};

private static final ResourceLoadListener LAZY_RESOURCE_LOAD_LISTENER = new ResourceLoadListener() {
@Override
public void onLoad(ResourceLoadEvent event) {
Expand All @@ -61,7 +79,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 Down Expand Up @@ -98,6 +116,12 @@ private void loadLazyDependency(String dependencyUrl,
loader.accept(dependencyUrl, LAZY_RESOURCE_LOAD_LISTENER);
}

private void loadDynamicImport(String expression,
final BiConsumer<String, ResourceLoadListener> loader) {
startEagerDependencyLoading();
loader.accept(expression, DYNAMIC_IMPORT_LOAD_LISTENER);
}

/**
* Adds a command to be run when all eager dependencies have finished
* loading.
Expand Down Expand Up @@ -188,28 +212,33 @@ 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),
resourceLoader);
break;
case INLINE:
inlineDependency(
dependencyJson.getString(Dependency.KEY_CONTENTS),
if (type == Dependency.Type.DYNAMIC_IMPORT) {
loadDynamicImport(dependencyJson.getString(Dependency.KEY_URL),
resourceLoader);
break;
default:
throw new IllegalArgumentException(
"Unknown load mode = " + loadMode);
} else {
switch (loadMode) {
case EAGER:
loadEagerDependency(getDependencyUrl(dependencyJson),
resourceLoader);
break;
case LAZY:
lazyDependencies.set(getDependencyUrl(dependencyJson),
resourceLoader);
break;
case INLINE:
inlineDependency(
dependencyJson.getString(Dependency.KEY_CONTENTS),
resourceLoader);
break;
default:
throw new IllegalArgumentException(
"Unknown load mode = " + loadMode);
}
}
}
return lazyDependencies;
Expand Down Expand Up @@ -250,6 +279,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