Getting started with ReactJS and eXo Platform portlet development

This article is a repost from Grégory Picavet’s own blog. Grégory is the tech lead on collaborative portal projects for a French Ministry, and a web development passionate.
Tech Lead on a collaborative portal at a French Ministry, and web development passionate
ReactJSandeXoPlatformPortlet-_1000x665

Content

Here is the first post of what I hope will be many posts dedicated to web development!

In this article, I would like to share some information about the eXo Platform portal and ReactJS. You’ll learn how to set up a simple ReactJS and node.js development stack, build a standalone app, and package it all to a portlet. If you’re looking to develop React.js applications on the Exo Platform, you should consider hiring Node.js developers with experience in both technologies. Learn how to Create Figma to React with this comprehensive tutorial.

How did we get to React? eXo Platform comes with a portlet framework called Juzu that lets developers build interactive client-side UIs. While many developers in my organization have mastered several frameworks, they were not very comfortable with Juzu. At that time, my office was heavily promoting ReactJS. In addition, the eXo Platform portlet does not require any specific technology to build portlets, and you can even use your own! If you don’t know anything about ReactJS, take a look at the site first.

To efficiently manage JS library dependencies, we will use node.js development services and npm. We’ll then use Maven to build and package the portlet.

Though JavaScript is cool for simple projects, it’s not Java. It’s more painful to build robust JavaScript mainly because it lacks a static, strong system. Fortunately, some standards have emerged from the mess over the last few years! You can now use languages like CoffeeScript, TypeScript, and ES2015, which also come with syntax improvements. However, these languages will not execute in the browser, except for ES2015 which is the next version of javascript but still only partially supported. ReactJS also comes with a syntax called JSX, which mixes JS and HTML tags, so we’ll have to transpile it as well. Babel will be good for that task, and we’ll use this time with ES2015.
It’s time to open your IDE. If you do not have one, try Atom. It is lightweight and open source, and it works well with JS. Don’t forget to install the JSX plugin!
The source code is available here. Please follow the instructions for building and deploying in the portlet section.

1. node.js stack and standalone application

  • As I said before, the stack is based on node.js. First, install it on your system. It will come with npm, which you can use to install the JS dependencies (this works like Maven).
  • Create a directory named “react-portlet” and generate an initial package.json file:
				
					<pre class="lang:default decode:true ">npm init</pre>  
Let the default values for this time be:
  • Next, install the React and Moment libraries to deal with the date format later:
				
					<pre class="lang:default decode:true ">npm install --save react@15.3.1 react-dom@15.3.1 moment</pre>  
Note: React has a core part and a specific lib to render to DOM.
  • Install the Babel transpilers as a dev dependency:
				
					<pre class="lang:default decode:true ">npm install --save-dev babel-loader babel-core babel-preset-es2015 babel-preset-react</pre>  
  • We will need the webpack library to gather all the JS modules into a bundle file and do some “hot rebuilding.” We’ll also install the express HTTP server to quickly test our app in standalone mode.
				
					<pre class="lang:default decode:true ">npm install –save-dev webpack express</pre>  
At this point, package.json has been updated (with the –save argument), and the node dependencies have been installed in the “node_modules” directory. package.json must be added to version control so anyone can build your project.
  • The project structure has to be Maven-compliant (keep in mind that we have to build a portlet!):
				
					<pre class="show-plain-default:true lang:default decode:true ">/
├─src
│  ├─main
│  │  ├─java
│  │  ├─js
│  │  ├─webapp
│  │      ├─css
│  │      ├─META-INF
│  │      ├─WEB-INF
│  ├─static
├─package.json
├─pom.xml</pre>  
  • Now we can create the first React component, “Activities,” to fetch the user’s activities from the server and transform them into HTML. To use the modularity of React, we will also create a child component to render a single activity. First, create the “Activities.jsx” file in /src/main/js:
				
					<pre class="lang:js decode:true ">import React from 'react';
import ReactDOM from 'react-dom';
import {Activity} from './Activity.jsx';

class Activities extends React.Component {

  constructor(props) {
    super(props);
    this.state = {activities:[]};
  }

  componentDidMount() {
    fetch("/rest/v1/social/activities?limit="+this.props.limit+"&amp;expand=identity",
      {credentials: 'include'})
    .then((res) =&gt; {
        return res.json();
      })
    .then((json) =&gt; {
       this.setState(json);
    })
  }

  render() {
    var list = this.state.activities.map( (act) =&gt; {
      return &lt;Activity key={act.id} {...act} /&gt;
    });
    return &lt;ul&gt;{list}&lt;/ul&gt;;
  }

}

ReactDOM.render(&lt;Activities limit="10"/&gt;, document.getElementById('app'));</pre>  
The “Activity.jsx” file in /src/main/js will be:
				
					<pre class="lang:js decode:true ">import React from 'react';
var moment = require('moment');

export class Activity extends React.Component {

  constructor(props) {
    super(props);
  }

  render() {
      var titleHtml = {__html:this.props.title};
      return (
          &lt;li&gt;
              &lt;img src={this.props.identity.profile.avatar===null ? "/eXoSkin/skin/images/system/UserAvtDefault.png":this.props.identity.profile.avatar} /&gt;
              &lt;div className="block"&gt;
                &lt;div className="header"&gt;
                  &lt;strong&gt;{this.props.identity.profile.fullname}&lt;/strong&gt;
                  &lt;br/&gt;
                  Posted : &lt;span&gt;{moment(new Date(this.props.createDate)).fromNow()}&lt;/span&gt;
                &lt;/div&gt;
                &lt;div dangerouslySetInnerHTML={titleHtml}/&gt;
              &lt;/div&gt;
             &lt;/li&gt;);
  }

}</pre>  
We are using the ES6/ES2015 syntax with class inheritance. In short, the React lifecycle will initiate the state, mount the component to the DOM, fetch data from the API, and render it. There’s a method for each lifecycle step. For example, using fetch for componentDidMount is the safest way due to the asynchronous updates.
Whenever the state is modified, the render method is called, and it checks which part of the DOM has to be updated. In this example, we won’t take any actions, so the state will not be modified. A good React pattern to use whenever possible is to manage the state at the highest-level component (which is Activities here) and pass the data to the child components (which is Activity here) via the “props.” Thus, we could replace the Activity class with the react stateless function pattern to get rid of the useless lifecycle.
Did you notice the weird attribute “dangerouslySetInnerHTML”? React is XSS-proof, but sometimes you have to inject preformed HTML code. You should do this it the HTML has been sanitized on the server side.
  • Now we create index.html in /src/static and declare the React mount tag as a simple div:
				
					<pre class="lang:xhtml decode:true ">&lt;!doctype html&gt;
&lt;html&gt;
  &lt;head&gt;
    &lt;meta charset="UTF-8"&gt;
    &lt;link rel="stylesheet" href="/css/main.css"&gt;
  &lt;/head&gt;
  &lt;body&gt;
    &lt;div id="app" class="container"&gt;&lt;/div&gt;

    &lt;script src="js/bundle.js"&gt;&lt;/script&gt;
  &lt;/body&gt;
&lt;/html&gt;</pre>  
Note: We include the “bundle.js” script that will be generated with webpack.
  • To generate the bundle, we create a “/src/main/js/index.js” file that will import the root component of our app (which is Activities here):
				
					<pre class="lang:js decode:true ">import Activities from './Activities.jsx';</pre>  
Note: Webpack will use this file as an entry point to pack all the imports.
				
					<pre class="lang:js decode:true ">var path = require('path');
var webpack = require('webpack');

module.exports = {
  devtool : 'cheap-module-source-map',
  entry: './src/main/js/index.js',
  output: { path: path.join(__dirname, 'target/react-portlet/js'),
	    filename: 'bundle.js'},

  module: {
    loaders: [
      {
        test: /.jsx?$/,
        loader: 'babel-loader',
        exclude: /node_modules/,
        query: {
          presets: ['es2015', 'react']
        }
      }
    ]
  },

  plugins:[
  new webpack.DefinePlugin({
    'process.env':{
      'NODE_ENV': JSON.stringify(process.env.NODE_ENV)
    }
  })]
};</pre>  
Here we use the “index.js” file that we just created. Then we define the output folder for bundle.js. This folder corresponds to the Maven target directory (it will be useful later). Then we define the Babel loader, which takes over transpilation. The define plugin just transmits the NODE_ENV variable to the loader parser to prune the dev code in the React lib at build time.
  • Before we run the app, our static files need to be copied to the target directory as well. This can be done with scripts in “/package.json”:
				
					<pre class="lang:js decode:true ">"scripts": {
  "copy": "cp -R src/static/* target/static &amp; cp -R src/main/webapp/css target/static"
}</pre>  
Note: In a real project, you should consider using a library like Gulp to externalize complex build tasks and become OS independent!
Call the script:
				
					<pre class="lang:default decode:true ">npm run copy</pre>  
  • Next, we create the express HTTP server in the /server.js file.
  • The express server will manage our standalone mode and serve static files (CSS, JS, HTML) and proxy API requests to static files.
				
					<pre class="lang:js decode:true ">var express    = require('express');        // call express
var app        = express();                 // define our app using express
var port       = 3000;  			      // set our port

// REGISTER STATIC FILES -------------------------------
app.use(express.static(__dirname+"/target/static/"));
app.use(express.static(__dirname+"/target/react-portlet/"));

// ROUTES FOR OUR API
// =============================================================================
var router = express.Router();              // get an instance of the express Router

router.get('/v1/social/activities', function(req, res) {
    res.set('content-type','application/json; charset=utf8')
    res.sendFile(__dirname+"/target/static/api/activities.json");
});

router.get('/avatar.png', function(req, res) {
    res.sendFile(__dirname+"/target/static/avatar.png");
});

// REGISTER OUR ROUTES -------------------------------
app.use('/rest/api', router);

// START THE SERVER
// =============================================================================
app.listen(port);</pre>  
Note: You have to record real API responses in static files before (look at the source code for an example).
  • Before starting the server, we also want to start webpack in “watch mode” to be able to build again on any change. Add a “watch” script with the following commands in /package.json:
				
					<pre class="lang:js decode:true ">"scripts": {
  ...
  "watch": "npm run copy &amp;&amp; node node_modules/webpack/bin/webpack.js --progress --colors --watch -d"
}</pre>  
Now just type:
				
					<pre class="lang:default decode:true ">npm run watch</pre>  
You should see something like this:
				
					<pre class="show-plain-default:true lang:default decode:true ">Hash: 0aed17830ca00bbed3fd
Version: webpack 1.13.2
Time: 4640ms
        Asset     Size  Chunks             Chunk Names
    bundle.js  1.22 MB       0  [emitted]  main
bundle.js.map  1.43 MB       0  [emitted]  main
    + 278 hidden modules</pre>  
Look at the size. Don’t worry though because it is not optimized yet!
The map file will map source lines from the generated bundle code to the original ES2015 file. It will be downloaded by the browser only when you open the debugger. Note: Static files are not watched; you have to restart the server. We could improve that by writing a simple Gulp script and adding it to the start script.
  • Now start the server:
				
					<pre class="lang:default decode:true ">npm start</pre>  
and enjoy the results at http://localhost:3000. It should look like this:
  • When you’re ready for release, you can add the following script in /package.json:
				
					<pre class="lang:js decode:true ">"scripts": {
  ...
  "release": "export NODE_ENV=production &amp;&amp; webpack -p"
}</pre>  
We set NODE_ENV equal to production to disable the React dev mode (it helps a lot since it’s a lot slower) and started webpack with the optimizers.
After optimization, the bundle will be three times smaller:
				
					<pre class="show-plain-default:true lang:default decode:true ">Hash: 958d20ab7b77c17c6474
Version: webpack 1.13.2
Time: 8955ms
        Asset       Size  Chunks             Chunk Names
    bundle.js     418 kB       0  [emitted]  main
bundle.js.map  219 bytes       0  [emitted]  main
    + 278 hidden modules</pre>  
Note: Did you notice a few warnings from the optimizer? They occur because the library is not always cleaned.
Important: The optimized bundle should not be too big (< 1 Mo), so for large apps, you can look at webpack’s code-splitting feature.
Note: You can still debug the original files in production since the map file is also updated.
Now you’re done with this part! You could use this app outside eXo Platform, but you’ll have to adapt the proxy routes in server.js to the eXo backend (which is easy!) and deal with SSO authentication (which is actually the hard part!).
FREE WHITE PAPER
Benefits of Open Source Software
for the Enterprise
Open source en entreprise
The term open source refers to any solution that has its source code widely accessible to the public for modification and sharing.
FREE WHITE PAPER
Benefits of Open Source Software
for the Enterprise
The term open source refers to any solution that has its source code widely accessible to the public for modification and sharing.

2. Portlet Integration

  • To get started, you can pick some resources from the sample available at https://github.com/exo-samples/docs-samples/tree/4.3.x/portlet/js. It’s a simple javax.portlet that forwards to an index.jsp (the view part of the portlet).
  • Modify index.jsp and only declare an HTML fragment with the “app” mount point:
				
					<pre class="lang:xhtml decode:true ">&lt;div class='react-portlet'&gt;
  &lt;div id="app" class="container"&gt;&lt;/div&gt;
&lt;/div&gt;</pre>  
  • Open webapp/WEB-INF/gatein-resources.xml to declare both the bunderle.js, as a JS module, and the main.css stylesheet:
				
					<pre class="lang:xhtml decode:true ">&lt;portlet&gt;
  &lt;name&gt;reactsample&lt;/name&gt;
  &lt;module&gt;
    &lt;script&gt;
      &lt;path&gt;/js/bundle.js&lt;/path&gt;
    &lt;/script&gt;
  &lt;/module&gt;
&lt;/portlet&gt;

&lt;portlet-skin&gt;
  &lt;application-name&gt;react-portlet&lt;/application-name&gt;
  &lt;portlet-name&gt;reactsample&lt;/portlet-name&gt;
  &lt;skin-name&gt;Default&lt;/skin-name&gt;
  &lt;css-path&gt;/css/main.css&lt;/css-path&gt;
&lt;/portlet-skin&gt;</pre>  
Note: These modules will be automatically loaded by eXo Platform when you load the portlet.

There are two main module styles in JavaScript: AMD and CommonJS. When transpiling ES2015 to ES5, Babel replaces imports with the CommonJS style. Because Exo uses AMD modules, it will automatically adapt them. However, some libraries require manual adaptation. This would be the case with React if we had to load it separately from bundle.js. When you need to hire ReactJS developers, ensuring they understand module styles and library adaptations is crucial.

  • Now we get to the the build part. When we build the portlet, it would be interesting to 1) install JS dependencies and 2) do a webpack release. The Maven exec plugin will do the job. In “/pom.xml”:
				
					<pre class="lang:xhtml decode:true ">&lt;plugins&gt;
  &lt;plugin&gt;
    &lt;groupId&gt;org.codehaus.mojo&lt;/groupId&gt;
    &lt;artifactId&gt;exec-maven-plugin&lt;/artifactId&gt;
    &lt;version&gt;1.5.0&lt;/version&gt;
    &lt;executions&gt;
      &lt;execution&gt;
        &lt;id&gt;npm install&lt;/id&gt;
        &lt;phase&gt;generate-resources&lt;/phase&gt;
        &lt;goals&gt;
          &lt;goal&gt;exec&lt;/goal&gt;
        &lt;/goals&gt;
        &lt;configuration&gt;
          &lt;executable&gt;npm&lt;/executable&gt;
          &lt;arguments&gt;
            &lt;argument&gt;install&lt;/argument&gt;
          &lt;/arguments&gt;
        &lt;/configuration&gt;
      &lt;/execution&gt;
      &lt;execution&gt;
      &lt;id&gt;npm release&lt;/id&gt;
      &lt;phase&gt;generate-resources&lt;/phase&gt;
      &lt;goals&gt;
        &lt;goal&gt;exec&lt;/goal&gt;
      &lt;/goals&gt;
      &lt;configuration&gt;
      &lt;executable&gt;npm&lt;/executable&gt;
        &lt;arguments&gt;
          &lt;argument&gt;run&lt;/argument&gt;
          &lt;argument&gt;${webpack.release}&lt;/argument&gt;
        &lt;/arguments&gt;
      &lt;/configuration&gt;
      &lt;/execution&gt;
    &lt;/executions&gt;
  &lt;/plugin&gt;
&lt;/plugins&gt;</pre>Then simply type:<pre class="lang:default decode:true ">mvn clean install</pre>  
This will build in dev mode (webpack.release=release-debug)
Just add a profile to build in production mode (set webpack.release=release), so:
				
					<pre class="lang:default decode:true ">mvn clean install -Pproduction</pre>  

Incompatibility with the GateIn minifier

  • You’ll quickly notice that the non-minified version of React can’t deal with the GateIn minifier (uses the Google Closure minifier)!
  • Actually, the only way to disable the GateIn minifier is to run eXo in dev mode, which is not great. But there’s weird thing: When you minify your bundle with “webpack -p,” the GateIn minifier works! So the simplest solution is to use the minified version of our bundle.js with eXo’s normal mode and use the non-minified version in eXo’s dev mode! There is some bad news though: You will lose the source mapping because of the double minification.
  • Another solution is to disable the minifier on some libs and build and supply the minified and map files. Actually, you can override the UIPortalApplication.gtmpl script in the portal module, filter the JS paths, and remove the “-min” when you need to but it’s tricky. It would be great if eXo and GateIn could come up with a parameter for module definition!
  • I’ve heard about webjars, and they’re probably a more elegant way to do all this. I’ll have to look at them in the future.

Deploying

Those who are used to eXo can skip this part.
  • You must have an exo account and JDK 8 installed.
  • Then download the latest community edition of eXo (4.3+), unzip it, and launch the start_eXo script.
  • Simply copy the target/react-portlet.war in the webapps directory and wait for deployment.
  • Log into eXo and create a “test” site.
  • Go to the site, edit the page layout, and add the portlet.
  • You should see something very similar to standalone mode, but it should be dynamic (for this, you must have created some activities before).

A note on the docker

  • Sorry—it doesn’t work in an eXo container unless you start it in debug mode. I’ll have to look into this.

Sharing common modules

  • When you’re developing several portlets, it’s okay to reuse some libs (like React). You may already know that GateIn allows you to share modules.
  • Before we edit webpack.config.js, we have to tell webpack to gather the React, ReactDOM, and moment libs in another bundle, which we’ll call the “vendor” bundle:
				
					<pre class="lang:js decode:true ">entry: {
  bundle:'./src/main/js/index.js',
  'vendor-bundle': [
    'react',
    'react-dom',
    'moment'
  ]
}
...
plugins:[
 ...
  new webpack.optimize.CommonsChunkPlugin({
    name: 'vendor-bundle',
    filename: 'vendor-bundle.js',
    minChunks: Infinity
  })
]</pre>  
Note: Take a look at chunking. It’s a powerful way to optimize web apps when they first load!
  • After rebuilding, you’ll see the new size of your bundle!
				
					<pre class="show-plain-default:true lang:default decode:true ">Hash: 837db454da54d24ded16
Version: webpack 1.13.2
Time: 6187ms
               Asset     Size  Chunks             Chunk Names
           bundle.js  7.13 kB       0  [emitted]  bundle
    vendor-bundle.js  1.21 MB       1  [emitted]  vendor-bundle
       bundle.js.map  3.69 kB       0  [emitted]  bundle
vendor-bundle.js.map  1.44 MB       1  [emitted]  vendor-bundle
   [0] multi vendor-bundle 52 bytes {1} [built]
    + 278 hidden modules</pre>  
  • Set vendor-bundle.js as a shared module. To keep things simple, we set it in our portlet. Otherwise, you could package it into another war:
				
					<pre class="lang:xhtml decode:true ">&lt;module&gt;
    &lt;name&gt;vendor&lt;/name&gt;
    &lt;script&gt;
      &lt;path&gt;/js/vendor-bundle.js&lt;/path&gt;
    &lt;/script&gt;
&lt;/module&gt;

&lt;portlet&gt;
  &lt;name&gt;reactsample&lt;/name&gt;
  &lt;module&gt;
    &lt;script&gt;
      &lt;path&gt;/js/bundle.js&lt;/path&gt;
    &lt;/script&gt;
    &lt;depends&gt;
      &lt;module&gt;vendor&lt;/module&gt;
  &lt;/depends&gt;
  &lt;/module&gt;
&lt;/portlet&gt;</pre>  
  • Now when you look at the reactsample.js resource downloaded by the portlet, it will depend on the shared module:
				
					<pre class="lang:js decode:true ">define('PORTLET/react-portlet/reactsample', ["SHARED/vendor"], function(vendor) {
  ...</pre>  

Conclusion

  • We learned how to set up a standalone JS app based on React and built with a node.js/npm/ES2015/Babel/webpack stack. There’s a lot of choices here, and you could replace some of elements of the stack, e.g., bower instead of npm, typescript instead of ES2015, browserify instead of webpack. Each has some pros and cons that you should be aware of when you are choosing.
  • We learned how to simply integrate npm and Maven to build a portlet on top of a standalone app.
  • Unfortunately, the eXo GateIn minifier hates your React code. Even if there’s a workaround, GateIn should really permit lib exclusions from the minifier.
  • Last words: On a real project, you’ll have to deal with unit testing. For the record, we’re currently using Mocha to write tests, Phantomjs as a runtime platform, and Istanbul as a coverage tool. To manage complex build tasks, you should use a lib like Gulp or Grunt.
Thanks again to Grégory Picavet for this great post. Make sure to check his original article and his blog for new articles.
eXo Platform 6 Free Datasheet​​
Download the eXo Platform 6 Datasheet and
discover all the features and benefits
5/5 - (1 vote)
Related posts
  • All
  • eXo
  • Digital workplace
  • Open source
  • Internal communication
  • Collaboration
  • News
  • intranet
  • Future of work
  • workplace
  • Knowledge management
  • Employee engagement
  • Employee experience
  • Employee productivity
  • onboarding
  • Employee recognition
  • Change management
  • Cartoon
  • Digital transformation
  • Infographic
  • Remote work
  • Tips & Tricks
  • Tutorial
  • Uncategorized
Leave a Reply

( Your e-mail address will not be published)

guest
7 Comments
Commentaires en ligne
Afficher tous les commentaires
Bruno
Bruno
11 June 2021 9 h 15 min

There’s definately a lot to learn about this topic.

I really like all of the points you’ve made.

Alonzo
Alonzo
16 June 2021 20 h 52 min

Hi there! This is my first visit to your blog!

We are a team of volunteers and starting a new initiative in a community
in the same niche. Your blog provided us useful information to work on. You have done a
extraordinary job!

Feel free to visit my web page – porn

Reggie
Reggie
16 June 2021 23 h 21 min

Greate article. Keep writing such kind of info
on your page. Im really impressed by your blog.
Hi there, You’ve done an incredible job. I’ll definitely digg
it and personally suggest to my friends. I’m sure they’ll be benefited from this website.

Jacquie
Jacquie
17 June 2021 7 h 06 min

Aw, this was an exceptionally nice post. Spending some time and actual effort to generate
a top notch article… but what can I say… I hesitate a lot and never seem to get nearly anything done.

Brittney
Brittney
18 June 2021 19 h 18 min

It’s actually very difficult in this active life to listen news on TV, therefore I only use world wide web for
that reason, and take the hottest news.

Jamison
Jamison
24 November 2021 3 h 48 min

Nice post. I was checking constantly this weblog and I am inspired! Extremely helpful information specifically the ultimate part 🙂
I take care of such information a lot. I used to be seeking this certain info for a long time.
Thank you and best of luck.

Antoinette
Antoinette
10 January 2022 4 h 06 min

We are a group of volunteers and opening a new scheme in our community.
Your site provided us with valuable information to work on. You have done a formidable job and our whole community will be thankful to you.