I have found, that especially when it comes to building user-interfaces with a modern user-experience, portlet developers can easily make a paradigm-shift and start migrating towards another architecture with relatively little effort.
This post aims at helping You to integrate Vue.js into your legacy system based on IBM WebSphere Portal Server 8.5, but conceptually it should also work on any other portal framework. You can use it seamlessly in parallel to your existing code for serving Single Page Applications. From there you can move forward.
1. The container principle
Let's jump right into ourframework.jsp
, which is being rendered on doView
:This is the framework for our reactive portlet. Essential functionality is provided by the Java Server Page (JSP) fragment<%@page session="false" contentType="text/html" pageEncoding="UTF-8"%> <%@page import="de.myportal.pa_myportlet.PA_MyPortlet"%> <%@include file="react.jspf"%> <%@taglib uri="http://java.sun.com/portlet_2_0" prefix="portlet"%><portlet:defineObjects> <div id="<portlet:namespace />myportlet"> <jsp:include page="<%=PA_MyPortlet.Apps.MY_APP_JSP %>"></jsp:include> </div>
<style> #<portlet:namespace />myportlet { box-sizing: border-box; font-family: "Consolas", Arial, sans-serif; } #<portlet:namespace />myportlet * { box-sizing: inherit; } </style>
react.jspf
, which we will discuss next. For now, just note that we establish access to portlet objects by using <portlet:defineObjects>
. With this we generate the portlet namespace on the server-side and use the DIV #<portlet:namespace />myportlet
as a reusable container for which a basic CSS rule-set should hold.We need to define a starting-point, otherwise the Portlet won't do much: Our component
MY_APP_JSP
is being included as a JSP SPA on the server-side.2. Adding reactive functionality
Before we delve deeper, let's have a look at thereact.jspf
file that was statically included into our framework:This fragment includes the Vue.js and Axios library as well as a few JavaScript functions. They are used for injecting asynchronously served JSON or other server-side rendered JSP SPAs. Axios is used by the asynchronous GET and POST methods and returns a<%@taglib uri="http://java.sun.com/portlet_2_0" prefix="portlet"%><portlet:defineObjects /> <script src='<%=renderResponse.encodeURL(renderRequest.getContextPath() + "/js/vue.js")%>' ></script> <script src='<%=renderResponse.encodeURL(renderRequest.getContextPath() + "/js/axios.min.js")%>'></script>
<script type="text/javascript"> function getAsync(url) { return axios({ method: 'GET', url }).then((response) => response.data) } function postAsync(url, data) { return axios({ method: 'POST', url, data }).then((response) => response.data) } function includeMarkup(targetID, markup) { var target = document.getElementById(targetID); target.innerHTML = markup; reloadScriptTags(target); } function reloadScriptTags(target) { [].map.call(target.getElementsByTagName('script'), function(oldscript) { var newscript = document.createElement('script'); newscript.type = 'text/javascript'; newscript.innerHTML = oldscript.innerHTML; if (oldscript.src) { newscript.src = oldscript.src; } oldscript.parentElement.removeChild(oldscript); target.appendChild(newscript); }); } </script>
Promise
itself. We will see how this works in the example app below. You can use the includeMarkup
method to inject the markup to a target. Please note, that every script tag in the markup needs to be included by using the HTML DOM appendChild
method to the target, otherwise the scripts won't work.3. Creating reactive Single Page Applications
Now let's see how our component looks like. First we look at the frontend inmyApp.jsp
:Now we can use our asynchronous functions from 2. to<%@page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@page import="de.myportal.pa_myportlet.PA_MyPortlet"%> <%@ taglib uri="http://java.sun.com/portlet_2_0" prefix="portlet"%><portlet:defineObjects /> <portlet:resourceURL id="<%=PA_MyPortlet.class.getSimpleName() %>" var="dataURL" /> <portlet:resourceURL id="<%=PA_MyPortlet.class.getSimpleName() %>" var="jspURL"> <portlet:param name="jsp" value="<%=PA_MyPortlet.Apps.ANOTHER_APP_JSP%>" /> </portlet:resourceURL> <div id="<portlet:namespace />myApp"> <p>Hello, <span v-if="name">{{ name }}</span><span v-else>world</span>!</p> <label for="name">Name: </label><input id="name" v-model="name" type="text"><br /> <input type="button" value="Get age" @click="send"> <input type="button" value="Load another app" @click="loadAnotherApp"> <p v-if="age">You are {{ age }} years old.</p> </div>
<script> var myApp = new Vue({ data: function() { return { name : null, age : null } }, methods: { send: function() { postAsync('<%=dataURL.toString() %>', myApp.$data).then((json) => this.processResponse(json)); }, loadAnotherApp: function() { getAsync('<%=jspURL.toString()%>', myApp.$data).then((markup) => includeMarkup('<portlet:namespace />myportlet',markup)); }, processResponse: function(json) { if (json.age) { this.age = json.age; } } }, el: '#<portlet:namespace />myApp' }) </script>
</style> /* custom styles for the app go here */ </style>
POST
requests with myApp
's JSON data to the backend and inject the received data back into into our Vue.js component. We can also include markup of other JSPs to our framework container <portlet:namespace />myportlet
. No more page reloads, no more blocking requests. Do you feel the flow already?4.Building the backend
The working of this famework is expecially based on the JSR-286serverResource
method of our PA_MyPortlet.java
class. Let's have a look at how this works.package de.myportal.pa_myportlet;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.Enumeration;
import javax.portlet.GenericPortlet;
import javax.portlet.PortletException;
import javax.portlet.RenderRequest;
import javax.portlet.RenderResponse;
import javax.portlet.ResourceRequest;
import javax.portlet.ResourceResponse;
import javax.ws.rs.HttpMethod;
import javax.ws.rs.core.MediaType;
import com.ibm.json.java.JSONObject;
public class PA_MyPortlet
extends GenericPortlet {
private static final String PATH_TO_JSPS = "/WEB-INF/jsp/";
private static final String PATH_TO_APPS = PATH_TO_JSPS + "apps/";
private static final String ERROR_JSP = PATH_TO_JSPS + "error.jsp";
private static final String FRAMEWORK_JSP = PATH_TO_JSPS + "framework.jsp";
public class Apps {
public static final String MY_APP_JSP = PATH_TO_APPS + "myApp.jsp";
public static final String ANOTHER_APP_JSP = PATH_TO_APPS + "anotherApp.jsp";
}
public void init()
throws PortletException {
super.init();
}
public void doView(RenderRequest request, RenderResponse response)
throws PortletException, IOException {
getPortletContext().getRequestDispatcher(FRAMEWORK_JSP).include(request, response);
}
public void serveResource(ResourceRequest request, ResourceResponse response)
throws PortletException, IOException {
try {
if (!(this.getClass().getSimpleName()).equals(request.getResourceID()))
throw new Exception("The resourceID must fit the Portlet name.");
final JSONObject json = poop(dope(request, response));
final String jsp = request.getParameter("jsp");
if (jsp != null) {
response.setContentType(MediaType.TEXT_HTML);
// request.setAttribute(JSONObject.class.getSimpleName(), json);
getPortletContext().getRequestDispatcher(jsp).include(request, response);
} else {
response.setContentType(MediaType.APPLICATION_JSON);
response.getWriter().println(json.toString());
}
} catch (Exception e) {
// e.printStackTrace();
request.setAttribute(Exception.class.getSimpleName(), e);
getPortletContext().getRequestDispatcher(ERROR_JSP).include(request, response);
}
}
private JSONObject dope(ResourceRequest resourceRequest, ResourceResponse resourceResponse)
throws UnsupportedEncodingException, IOException {
JSONObject json = new JSONObject();
if (resourceRequest.getMethod().equals(HttpMethod.POST)) {
String data = resourceRequest.getReader().readLine();
json = JSONObject.parse(data);
}
final Enumeration<String> params = resourceRequest.getParameterNames();
while (params.hasMoreElements()) {
final String param = params.nextElement();
json.put(param, resourceRequest.getParameter(param));
}
return json;
}
private JSONObject poop(JSONObject json) {
json.put("age", 34);
return json;
}
}
Most frontend components will need at least some backend counter-part for serving user-data. In our portlet environment we have the two cases of serving data or serving JSPs. In the dope
function we collect the params together with the POST-data from our request and pass it to the poop
function afterwards. After processing the requested data we send it back to the client as a JSON String. If the request has a "jsp
" param, we serve the compiled JSP fragment instead. So, that's all. If you are not dependent on portlet-security and the like, you could also use a servlet. Then implement authentication and authorization with JSON Web Token (JWT) and deliver data from a NoSQL database via GraphQL requests, but that's just one possible outlook.
No comments:
Post a Comment
Please stick to the Netiquette Guidelines and consider asking questions the smart way ..