Building JS Portlets Part 1

Introduction

In Liferay 7 CE / Liferay DXP, there are new facilities in place to help us create JS portlets. In this blog series I'm going to present a new project to demonstrate how to build Vue.js portlets.

Vue.js is a lightweight JS framework similar to React or Angular or ... I'm actually picking Vue.js for this series not so much because I think it is better than the other frameworks, but mostly because I want to focus on building JS portlets and I don't want to get hung up on perfect React or Angular implementation. I figured that by picking a newer framework I could present topics that affect all implementations and avoid the framework debates.

And who knows, maybe this will start a big trend of adopting Vue.js in Liferay. We'll just see how it goes.

Prerequisites

So I'm going to lay down some prerequisites, but they are not requirements per se, they're just things that I want to have being a regular portlet developer.

So prereq #1 is that the portlet has to fit into my Liferay Workspace. I mean, I'm building all kinds of modules in there: JSP fragment bundles, Service Builder modules, Liferay MVC portlet modules, etc. I don't want to maintain two separate repositories for normal stuff and JS stuff. So the JS portlets must fit into the Gradle-based Liferay Workspace for the general build process. I'm okay with the module leveraging other tools (gulp, npm, etc.) the portlets might need, but the Gradle build must rule them all.

Prereq #2 is that I need open and unfettered access to the internet. I know a lot of developers sit behind proxies and that's okay, as long as they can get to the web for plugins and projects from GitHub, open maven repositories, NPM repositories, etc. If you find yourself in a secured environment that needs approval for all external tools, libraries, etc., you might want to stop now and rethink following along here. All of the JS-based portlets are going to leverage a lot of new stuff from NPM, new build tools such as Gulp, new build plugins from Liferay GitHub repositories, etc.  There's going to be a long list of external sites to pull from, and if you need to get permissions for each one there is going to be a ton of red tape in your future. You might be better served just sticking with Liferay MVC portlet implementations and rely on the built-in SennaJS support to provide the ajaxy-sort of responsiveness we all expect now.

For the record, there really is nothing wrong with sticking with Liferay MVC and SennaJS. When you do a Liferay DXP trial walkthru and use the portlets on a page, you'll see that there are few full page refreshes, and when there are they are usually a result of page navigation within the portal. The portlets themselves are still using the regular Portlet Lifecycle, they're just getting invoked via AJAX and the browser is going to be doing partial DOM updates in the page. So you can get most of the benefits from the new whiz-bang JS frameworks without retooling yourself or your team.

Prereq #3 concerns deployment; I'd like to be able and build and deploy my portlet independently without requiring supporting theme work. As of right now I'm not 100% confident that will work or even that it is the best path, but it is something that I'd like to shoot for. To me, the more things that make a portlet deployment difficult, the more things there are that can go wrong during deployment.

Prereq #4 is that I am really only targeting JS portlets. I have no plan on co-running my Vue.js apps as both portlets and straight-up web apps, so I have no plans on testing, styling or running these guys outside of the portal.

Prereq #5 is that since they are running within the portal, I expect the UI to be consistent with the rest of the portal; buttons should look the same, fonts, etc. I don't want to have a portlet that stands out just because it is from some other framework type.

And finally, prereq #6 is that they must take advantage of the Liferay environment. I expect them to support portlet preferences via the Configuration panel. I expect them to respect the Liferay permissioning framework. I expect them to support localization through standard Liferay techniques. After all, I don't want to be doing things one way for standard Liferay stuff and some other way for JS portlets.

So let's get started...

Starting The Project

As per usual I'm going to be sticking with the Blade CLI for everything to highlight that you don't need an IDE to get these things started.

blade init liferay-vuejs

This will give me the new Liferay Workspace to build everything in. If you have an existing workspace, you're all good.

So this is really all you need to do from a workspace level.  Everything else goes into the individual modules.

I am planning on demonstrating remote services in the JS portlet, so eventually our modules folder will contain some Service Builder modules as well as a REST module, but we'll worry about creating those later.

In our modules folder we want to start the JS portlet itself.  Navigate to liferay-vuejs/modules to create the new module:

blade create -t mvc-portlet -p com.dnebinger.vue vue-portlet-1

So you might be wondering why we're using the Liferay MVC portlet template since we're building a JS portlet.

We use the Liferay MVC portlet as a template because we need to be able to kick off the JS inside of our portlet frame, we need to be able to declare and pull in resources, etc. The Liferay MVC portlet template will give that to us, plus a lot more.

Creating The Portlet Instance Preferences

Since we have a new Liferay MVC portlet, let's take a moment to create a configuration page.

What? We're not starting with the JS directly?

Well, no. Like I said in the prereqs, one of my goals is to include portlet prefs in order to be a proper Liferay portlet. To support that, we'll create a simple JSP configuration page for our portlet and worry about wiring it into JS later on...

Our configuration is going to be pretty simple. We're just going to have a couple of checkboxes to capture two flag values. Here's the full src/main/resources/META-INF/resources/configuration.jsp file:

<%@ include file="/init.jsp" %>

<%
  portletInstanceConfig = ConfigurationProviderUtil.getConfiguration(
      VuePortlet1PortletInstanceConfiguration.class,
      new ParameterMapSettingsLocator(request.getParameterMap(),
          new PortletInstanceSettingsLocator(themeDisplay.getLayout(), 
              portletDisplay.getPortletResource())));
%>
<liferay-portlet:actionURL portletConfiguration="<%= true %>" var="configurationActionURL" />

<liferay-portlet:renderURL portletConfiguration="<%= true %>" var="configurationRenderURL" />

<aui:form action="<%= configurationActionURL %>" method="post" name="fm">
  <aui:input name="<%= Constants.CMD %>" type="hidden" value="<%= Constants.UPDATE %>" />
  <aui:input name="redirect" type="hidden" value="<%= configurationRenderURL %>" />

  <div class="portlet-configuration-body-content">
    <div class="container-fluid-1280">
      <aui:fieldset-group markupView="lexicon">
        <aui:fieldset>
          <aui:input label="config.flag.one" name="preferences--flagOne--" 
              type="toggle-switch" value="<%= portletInstanceConfig.flagOne() %>" />
          <aui:input label="config.flag.two" name="preferences--flagTwo--" 
              type="toggle-switch" value="<%= portletInstanceConfig.flagTwo() %>" />
        </aui:fieldset>
      </aui:fieldset-group>
    </div>
  </div>

  <aui:button-row>
    <aui:button cssClass="btn-lg" type="submit" />
  </aui:button-row>
</aui:form>

This is going to give us two boolean portlet preferences leveraging the new Liferay Config Admin services. They will be instance parameters so, if we choose to create an instanceable portlet, each one will have its own preferences.

We will use our view.jsp page to show the values:

<%@ include file="/init.jsp" %>

<p>
  <b><liferay-ui:message key="vue-portlet-1.caption"/></b>
</p>
<p><liferay-ui:message key="caption.flag.one"/> <%= 
  String.valueOf(portletInstanceConfig.flagOne()) %></p>
<p><liferay-ui:message key="caption.flag.two"/> <%= 
  String.valueOf(portletInstanceConfig.flagTwo()) %></p>

Since we're showing the JSP, here's the init.jsp:

<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib uri="http://java.sun.com/portlet_2_0" prefix="portlet" %>
<%@ taglib uri="http://liferay.com/tld/aui" prefix="aui" %>
<%@ taglib uri="http://liferay.com/tld/portlet" prefix="liferay-portlet" %>
<%@ taglib uri="http://liferay.com/tld/theme" prefix="liferay-theme" %>
<%@ taglib uri="http://liferay.com/tld/ui" prefix="liferay-ui" %>
<%@ taglib uri="http://liferay.com/tld/frontend" prefix="liferay-frontend" %>

<%@ page import="com.dnebinger.vue.portlet.configuration.VuePortlet1PortletInstanceConfiguration" %>
<%@ page import="com.liferay.portal.kernel.module.configuration.ConfigurationProviderUtil" %>
<%@ page import="com.liferay.portal.kernel.settings.PortletInstanceSettingsLocator" %>
<%@ page import="com.liferay.portal.kernel.util.Constants" %>
<%@ page import="com.liferay.portal.kernel.settings.ParameterMapSettingsLocator" %>

<liferay-frontend:defineObjects />

<liferay-theme:defineObjects />

<portlet:defineObjects />

<%
  VuePortlet1PortletInstanceConfiguration portletInstanceConfig =
    ConfigurationProviderUtil.getConfiguration(VuePortlet1PortletInstanceConfiguration.class,
      new PortletInstanceSettingsLocator(themeDisplay.getLayout(), portletDisplay.getId()));
%>

Okay, so we now have basically a simple Liferay MVC portlet project that has portlet preferences and initial support for the language bundle (as seen in the view.jsp file).

If we build and deploy the portlet, this is what we currently will see (after changing one of the toggles on the configuration panel):

Conclusion

Hey, wait a minute, there's no Javascript frameworks in here! I don't see any Vue.js stuff, no node, in fact this looks like a simple Liferay MVC portlet! What's going on here?

Well, I should have said that this is actually going to be a blog series. In this first post, we're basically going to stop here since our portlet is ready to start overlaying the key parts for building out our Javascript portlet.

You can find the sample project checked in here: https://github.com/dnebing/liferay-vuejs.

See you in Part 2!

博客
Thanks David ! I'll stay tuned for the others parts. Can you please cover the best way to manage JS dependencies when we have different business portlets using different JS libraries as all will be merged in a single HTML page at the end.