Making a AngularJS based Liferay portlet instanceable

In one of my previous posts I showed how to create a Liferay portlet based on AngularJS. The problem with that concept was, that only a single portlet of that kind could be placed on a portal page.
I will show now how to improve the concept to not only allow multiple AngularJS portlets on a single Liferay page, but to allow the same portlet multiple times on a page.

The key is to manually bootstrap the AngularJS apps on the page, so we can no longer simple use ng-app. Instead of:

  <div ng-app>
     
  </div>

We assign an ID to our root DIV und attach the AngularJS module to it:

<div id="angularjsPortletDemo">
 
</div>

<script type="application/javascript" th:inline="javascript">
  (function() {
    if (typeof(AUI) !== 'undefined') {
      /* We are within Liferay */
      AUI().ready(function() {
        startAngular();
      });
    } else {
      document.addEventListener("DOMContentLoaded", function(event) {
        startAngular();
      });
    }

    function startAngular() {
      var appRootElem = document.getElementById('angularjsPortletDemo');
      angular.bootstrap(appRootElem, ['angularjsPortletDemo']);
    }
  })();
</script>

To make the portlet acutally instanceable we have to do more:

  1. Get rid of the global JavaScript variables in the HEAD (which we used to pass parameters from the backend such as the AJAX URL).
  2. Replace the ID on the root DIV by the actual Liferay portlet ID
  3. Change the Liferay portlet configuration

For the first step we just move the global variables into the IIFE block where the AngularJS bootstrapping happens. And we pass the variables to AngularJS as values to be able to use it in the AngularJS dependency injection system:

function startAngular() {
    var ajaxURL = /*[[${ajaxURL}]]*/ "/testdata/";
    var isStandalone = /*[[${standalone}]]*/ true;
    var authenticatedUser = /*[[${authenticatedUser}]]*/ "anonymous";

    var app = angular.module('angularjsPortletDemo');
    app.value('ajaxUrl', ajaxURL);
    app.value('isStandalone', isStandalone);
    app.value('authenticatedUser', authenticatedUser);

    var appRootElem = document.getElementById('angularjsPortletDemo');
    angular.bootstrap(appRootElem, ['angularjsPortletDemo']);
}

For the second step we use Thymeleaf to inject the portletId into the HTML template:

@RenderMapping
public String view(RenderRequest request, RenderResponse response, ModelMap model) {
  //...
  ThemeDisplay themeDisplay = (ThemeDisplay) request.getAttribute(WebKeys.THEME_DISPLAY);
  PortletDisplay portletDisplay = themeDisplay.getPortletDisplay();
  model.addAttribute("portletId", portletDisplay.getId());
}
<div id="angularjsPortletDemo" th:id="${portletId}" 

</div>

var portletId = /*[[${portletId}]]*/ 'angularjsPortletDemo';

var appRootElem = document.getElementById(portletId);
angular.bootstrap(appRootElem, ['angularjsPortletDemo']);

The last and most simple step to make the portlet instantiable is to set the homonymous attribute in liferay-portlet.xml:

<portlet>
    <portlet-name>liferay-angularjs-portlet</portlet-name>
    <instanceable>true</instanceable>
    <requires-namespaced-parameters>false</requires-namespaced-parameters>
    <ajaxable>true</ajaxable>
    <header-portlet-css>/css/app.css</header-portlet-css>
    <footer-portlet-javascript>/js/vendor.js</footer-portlet-javascript>
    <footer-portlet-javascript>/js/app.js</footer-portlet-javascript>
</portlet>

And the best of all: The AngularJS app can still be launched standalone: Just run grunt server on the console!

The full code can be found on GitHub.

Build Pipeline on Jenkins with incremental Maven builds and automatic versioning

Creating robust build pipelines on Continuous Integration servers such as Jenkins is still quite a challenge. Especially when it comes to versioning and performance (duration of the builds).

I’m going to show here how to use the nonsnapshot-maven-plugin together with Maven 3.2.x to build fully automated build pipelines with automatic version updates and incremental (fast) builds.

The pipeline will have the following features:

  • Update upstream dependencies versions (from a previous build step)
  • Update the version of all modules with code changes or updated (upstream) dependencies
  • Build (compile, test and deploy) of all modules with a new version
  • Commit changed POM files

Here a diagram how the steps of our pipeline shall work:
build_pipeline10

Example

Let’s say we have two projects depending on each other (Project 2 depends on Project 1, therefore the build pipeline consists of two consecutive steps):

build_pipeline5

The build of Project 1 starts and the nonsnapshot-maven-plugin detects code changes in Module 1. So, it updates the version and also the version of Module 2, because it has a dependency to Module 1. The two changed Modules will be compiled, tested and deployed.
At the end the build of Project 2 will be triggered, because it is a downstream project. The nonsnapshot-maven-plugin detects a newer version of Project 1 Module 2 in the Maven repository und updates the version of Module 2 accordingly. Maven will compile, test and deploy only this module. Like this:

build_pipeline6

Maven Configuration

For the incremental build you need at least Maven 3.2.1. Additionally you have configure the nonsnapshot-maven-plugin for each root (aggregator) project of your build pipeline. Below an example configuration for GIT, which considers all Maven dependencies with the groupId at.nonblocking and org.springframework as upstream dependencies (built by previous build steps):

<build>
    <plugins>
        <plugin>
            <groupId>at.nonblocking</groupId>
            <artifactId>nonsnapshot-maven-plugin</artifactId>
            <version>2.0.9</version>
            <configuration>
                <baseVersion>${myproject.base.version}</baseVersion>
                <scmType>GIT</scmType> <!-- GIT or SVN -->
                <!-- <scmUser></scmUser> -->
                <scmPassword>${git.passphrase}</scmPassword>
                <deferPomCommit>true</deferPomCommit>
                <generateIncrementalBuildScripts>true</generateIncrementalBuildScripts>        
                <!-- <generateChangedProjectsPropertyFile>true</generateChangedProjectsPropertyFile> -->
                <upstreamDependencies>
                    <upstreamDependency>at.nonblocking:*:LATEST</upstreamDependency>
                    <upstreamDependency>org.springframework:*:4.0</upstreamDependency>
                </upstreamDependencies>
            </configuration>
        </plugin>
    </plugins>
</build>

<pluginRepositories>
	<pluginRepository>
		<id>jcenter</id>
		<url>http://jcenter.bintray.com</url>
	</pluginRepository>
</pluginRepositories>

The baseVersion defines the version applied to changed modules. The option generateIncrementalBuildScripts enables the generation of a script for an incremental build.
The upstream dependency configuration org.springframework:*:4.0 means: Try to find newer versions of artifacts with the groupId org.springframework, but consider only 4.0.x versions.

On the command line execute a build step as follows:

cd <myproject-root>
mvn nonsnapshot:updateVersions -Dmyproject.base.version=1.2.3
./nonsnapshotBuildIncremental.sh clean install
mvn nonsnapshot:commitVersions

Note, you don’t have to touch any code to update the base version. Just change the property on the command line.

Jenkins Configuration

To create a build step on Jenkins, enable the option generateChangedProjectsPropertyFile in the nonsnapshot-maven-plugin configuration. It will create a property file with the changed modules, which can be passed to the Maven build for an incremental build. Furthermore you’ll need the EnvInject Jenkins plugin.

Configure the Jobs as follows:

build_pipeline1

(The Check-out strategy could also be Always check out a fresh copy)

build_pipeline2

build_pipeline3

build_pipeline4

build_pipeline5

Something similar should be possible on other CI servers.