Skip to content

Commit f1a96b0

Browse files
authored
fix: Ensure necessary scripts are loaded into page (#210)
In Liferay "Content Page" edit mode, Vaadin portlets are not directly added to the main page, but loaded into an iframe requesting the same page in preview mode, and then added to the main page through javascript, but they are detached from iframe before initialization is completed. This way, scripts needed to register the portlet web component are not added to the main document, so the webcomponent is not correctly rendered. This patch add an additional script to BootstrapHandler response that takes care to add needed script to main page and to postpone element registration until they are loaded. Fixes #202
1 parent b7d0609 commit f1a96b0

File tree

4 files changed

+99
-16
lines changed

4 files changed

+99
-16
lines changed

vaadin-portlet/src/main/java/com/vaadin/flow/portal/PortletBootstrapHandler.java

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -95,23 +95,25 @@ public boolean synchronizedHandleRequest(VaadinSession session,
9595
LicenseChecker.checkLicense(VaadinPortletService.PROJECT_NAME,
9696
VaadinPortletService.getPortletVersion());
9797
}
98-
String initScript = String
99-
.format("<script>window.Vaadin.Flow.Portlets.registerElement('%s','%s','%s','%s','%s');</script>",
100-
tag, namespace, Collections
101-
.list(portlet.getWindowStates("text/html"))
102-
.stream()
103-
.map(state -> "\"" + state.toString()
104-
+ "\"")
105-
.collect(Collectors.joining(",", "[", "]")),
106-
Collections
107-
.list(portlet.getPortletModes("text/html"))
108-
.stream()
109-
.map(mode -> "\"" + mode.toString() + "\"")
110-
.collect(Collectors.joining(",", "[", "]")),
111-
((RenderResponse) resp).createActionURL());
112-
// For liferay send accepted window states, portlet modes and action url to add to client side data.
11398

114-
writer.write(initScript);
99+
String registrationInstruction = String
100+
.format("window.Vaadin.Flow.Portlets.registerElement('%s','%s','%s','%s','%s');",
101+
tag, namespace,
102+
Collections.list(portlet.getWindowStates("text/html"))
103+
.stream()
104+
.map(state -> "\"" + state.toString() + "\"")
105+
.collect(Collectors.joining(",", "[", "]")),
106+
Collections.list(portlet.getPortletModes("text/html"))
107+
.stream().map(mode -> "\"" + mode.toString() + "\"")
108+
.collect(Collectors.joining(",", "[", "]")),
109+
((RenderResponse) resp).createActionURL());
110+
// For liferay send accepted window states, portlet modes and action
111+
// url to add to client side data.
112+
113+
String initScript = portlet.portletElementRegistrationScript(request,
114+
scriptUrl, registrationInstruction);
115+
116+
writer.printf("<script>%s</script>", initScript);
115117
writer.write("<" + tag + " data-portlet-id='" + namespace
116118
+ "' style='width: 100%;'></" + tag + ">");
117119
} catch (Exception exception) {

vaadin-portlet/src/main/java/com/vaadin/flow/portal/VaadinLiferayPortlet.java

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.vaadin.flow.portal;
22

33
import java.io.IOException;
4+
import java.util.Scanner;
45

56
import javax.portlet.HeaderRequest;
67
import javax.portlet.HeaderResponse;
@@ -9,6 +10,7 @@
910
import javax.portlet.RenderResponse;
1011

1112
import com.vaadin.flow.component.Component;
13+
import com.vaadin.flow.server.VaadinRequest;
1214

1315
/**
1416
* VaadinPortlet workarounds for Liferay versions 7.2.x and 7.3.x
@@ -71,4 +73,31 @@ protected String getStaticResourcesPath() {
7173
private boolean checkStaticResourcesConfiguration() {
7274
return getStaticResourcesPath().startsWith("/o/");
7375
}
76+
77+
// For portlets added to Liferay Content Pages we need to be sure that both
78+
// PortletMethod.js and web-component scripts are loaded into the main page,
79+
// and postpone the registration instruction until scripts are effectively
80+
// loaded, otherwise the web-component contents are not rendered at all.
81+
// See https://github.com/vaadin/portlet/issues/202 for details.
82+
@Override
83+
String portletElementRegistrationScript(VaadinRequest request,
84+
String scriptUrl, String registrationInstruction) {
85+
String portletMethodScriptUrl = getPortletScriptTag(
86+
(RenderRequest) ((VaadinPortletRequest) request)
87+
.getPortletRequest(),
88+
"scripts/PortletMethods.js")
89+
.replaceFirst("^.*src=\"([^\"]+)\".*", "$1");
90+
91+
StringBuilder initScript = new StringBuilder();
92+
try (Scanner scanner = new Scanner(getPortletContext()
93+
.getResourceAsStream("scripts/LiferayPortletRegistrationHelper.js"))
94+
.useDelimiter("\\A")) {
95+
initScript.append(scanner.next());
96+
}
97+
initScript.append("(['").append(scriptUrl).append("','")
98+
.append(portletMethodScriptUrl).append("'], function() { ")
99+
.append(registrationInstruction).append(";})");
100+
return initScript.toString();
101+
}
102+
74103
}

vaadin-portlet/src/main/java/com/vaadin/flow/portal/VaadinPortlet.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@
6060
import com.vaadin.flow.server.ServiceException;
6161
import com.vaadin.flow.server.SessionExpiredException;
6262
import com.vaadin.flow.server.VaadinConfigurationException;
63+
import com.vaadin.flow.server.VaadinRequest;
6364
import com.vaadin.flow.server.VaadinService;
6465
import com.vaadin.flow.server.VaadinSession;
6566
import com.vaadin.flow.shared.communication.PushMode;
@@ -659,4 +660,13 @@ static <C extends Component> void initComponent(C component) {
659660
view.onPortletViewContextInit(context);
660661
}
661662
}
663+
664+
// By default, portlet registration instruction can be sent to the client
665+
// as is, but some portlet containers (e.g. Liferay) may need to wrap or
666+
// enhance it with more instructions.
667+
String portletElementRegistrationScript(VaadinRequest request,
668+
String scriptUrl, String registrationInstruction) {
669+
return registrationInstruction;
670+
}
671+
662672
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
(function( scriptUrls, callback ) {
2+
3+
const alreadyLoadedScripts = [...document.querySelectorAll("script")]
4+
.filter(function(el) { return scriptUrls.indexOf(el.src) >= 0; })
5+
.map(function(el) { return el.src; });
6+
7+
const createScript = function(url) {
8+
if (alreadyLoadedScripts.indexOf(url) >= 0) {
9+
// Script already present on page, no need to load it again
10+
return Promise.resolve(url);
11+
}
12+
return new Promise(function(resolve, reject) {
13+
const script = document.createElement("script")
14+
script.type = "text/javascript";
15+
script.onload = function() {
16+
resolve(url);
17+
};
18+
script.src = url;
19+
document.getElementsByTagName( "head" )[0].appendChild( script );
20+
});
21+
};
22+
23+
const isRegistrationScriptInitialized = function() {
24+
return window.Vaadin && window.Vaadin.Flow
25+
&& window.Vaadin.Flow.Portlets
26+
&& window.Vaadin.Flow.Portlets.registerElement;
27+
};
28+
29+
const poller = function (attempt) {
30+
if(isRegistrationScriptInitialized()) {
31+
callback();
32+
} else if (attempt < 100) {
33+
// PortletMethods.js not yet loaded, try again
34+
setTimeout(function() { poller(attempt + 1) }, 50);
35+
} else {
36+
console.log("PortletMethods.js not loaded, element cannot be registered");
37+
}
38+
};
39+
40+
Promise.all(scriptUrls.map(createScript)).then(function() { poller(0); });
41+
42+
})

0 commit comments

Comments
 (0)