Creating Modern Applications on Liferay with ReactJS, Docker, DXP Cloud, and Headless APIs (I)

 The Japan APAC team has been creating a project using DXPCloud, ReactJS, ClayUI and Headless API’s in the middle of a trying time. After four months, a deliverable has finally taken form in a portlet completely written in ReactJS, live on our DXPC-hosted Liferay workspace. And now it’s time to share the fruits of our labor! 

Before we begin, introductions are in order.

My name is Alexa Javellana. I work in the Japan APAC team out in Tokyo. I just started in January as an Associate Consultant, so not many people may know me here. To those who do, hello! How’s life back home in the States? 

Besides frontend engineering, I like knitting, sewing, drawing, fashion and pretty things. In February, I had been tasked to make pretty things with ReactJS for Liferay for a new project.

For this project, the Japan APAC team had been tasked with creating a reservation system using ReactJS and Headless API’s, that would eventually be hosted on DXP Cloud. Theoretically, this type of structure can be used for any kind of system you’d like, from hosting blog posts, creating fully-integrated client facing systems for insurance, hospitality, and much more. 


The React Reservation System


We titled our project the “React Reservation System”. For this project, our client approached us with simple requirements and design plans. 

Thankfully, the design was already created in lieu with pre-existing Liferay design methodology. And luckily, the Liferay team has documented React-friendly components to use in React projects that follow this design thinking, known as ClayUI. That took care of our frontend: we would be using ReactJS and ClayUI to create the user facing portion of our project.

Administrative Control Panel Reservation Portlet 

User Reservation Portlet

For the booking system itself, we wanted to use a cloud-approach to back-end communication, and opted to create our own Headless API’s hosted on DXP Cloud. With API’s, things like listing rooms on the system were easy as invoking GET requests to a link, such as http://localhost:8080/o/headless-reservation/v1.0/rooms.

With our API’s in order, Liferay running and our frontend created separately, we faced the issue of migrating this standalone react application as a Portlet on the workspace. 

This blog post will go through the whole process, separated into three distinct parts:

Creating our standalone ReactJS + ClayUI workspace
Working With API’s and DXPCloud on Docker
Migration from React Application to Liferay Portlet

 If you would like a fully-fledged explanation on how we created our API’s, setting up our dev instance on the Cloud and more, a separate set of blog posts will need to be developed. For now, this series covers a general sense of how to migrate a ReactJS application to Liferay,  front-end usage of API's, and the necessary file/path changes, dependencies and architecture changes from standalone to React-based portlet on Liferay 7.2.
 

Creating Our Standalone ReactJS + ClayUI on a local Liferay Workspace


The easiest way to start development is by separately developing your React Application inside an existing Liferay Workspace.

 In this project, I am using OSX. You can use an installer to instantiate workspace via dmg at this link:  
https://sourceforge.net/projects/lportal/files/Liferay%20IDE/3.8.0/

You can also initialize a repository using blade in an empty directory. That will pull and create all essential files needed to spin up a fresh Liferay Workspace (CE). (blade init)

Now in my new workspace, I want to create a React application to do local development in. It's best to do this so we can test progress we make immediately without having to wait for Liferay to build for every small thing we change. 


To create a ReactJS application in the Liferay workspace, we instantiate the project in the root directory with the create-react-app command. Let's call this application the 'frontend-reservation' application. The correct command would be npx create-react-app frontend-reservation.

At minimum, for Liferay 7.2, please use node lts Dubnium (v10.0+) and openjdk version "1.8.0_232". Use nvm to change node versions on Terminal locally if there are issues with compatibility, as this was a common occurrence during development that still occurs anytime you develop from a fresh Terminal.

Note that to use commands such as npx, one must download it via Homebrew on OSX. 

homebrew install npx 
npx create-react-app frontend-reservation
.
├── Jenkinsfile
├── README-dxpcloud.md
├── README.md
├── build.gradle
├── frontend-reservation
├── gradle
.


In our new ReactJS project directory frontend-reservation, we will have the usual ReactJS files, as below. 

.
├── README.md
├── node_modules
├── package-lock.json
├── package.json         —> Dependency download section
├── public
├── src                  —> Where your Component files will go
└── sync.sh


In package.json, we will add the ClayUI dependencies (https://clayui.com/docs/components/index.html). This brings over all of the Clay components we will need to use in our projects, such pre-defined as Buttons, Forms, Radio groups, etc. 


As of now, there is no npm batch install command to download all ClayUI dependencies into a React project. I suggest instead copying the contents of the code in the below and pasting it directly into the package.json devDependencies: 

"@clayui/alert": “^3.3.0",
"@clayui/autocomplete": “^3.1.1",
"@clayui/badge": "^3.0.1",
"@clayui/breadcrumb": "^3.1.1",
"@clayui/button": "^3.3.0",
"@clayui/card": "^3.1.1",
"@clayui/charts": "^3.1.0",
"@clayui/color-picker": "^3.2.1",
"@clayui/css": "^3.8.0",
"@clayui/data-provider": "^3.1.0",
"@clayui/date-picker": "^3.1.1",
"@clayui/drop-down": "^3.4.0",
"@clayui/empty-state": "^3.0.0",
"@clayui/form": "^3.6.0",
"@clayui/icon": "^3.0.4",
"@clayui/label": "^3.1.1",
"@clayui/link": "^3.1.0",
"@clayui/list": "^3.2.1",
"@clayui/loading-indicator": "^3.0.1",
"@clayui/management-toolbar": "^3.1.0",
"@clayui/modal": "^3.4.0",
"@clayui/multi-select": "^3.5.1",
"@clayui/multi-step-nav": "^3.1.1",
"@clayui/nav": "^3.1.0",
"@clayui/navigation-bar": "^3.1.0",
"@clayui/pagination": "^3.1.1",
"@clayui/pagination-bar": "^3.1.1",
"@clayui/panel": "^3.1.0",
"@clayui/popover": "^3.3.0",
"@clayui/progress-bar": "^3.0.4",
"@clayui/shared": "^3.1.0",
"@clayui/slider": "^3.1.0",
"@clayui/sticker": "^3.1.0",
"@clayui/table": "^4.0.0",
"@clayui/tabs": "^3.1.0",
"@clayui/time-picker": "^3.1.0",
"@clayui/tooltip": "^3.2.2",
"@clayui/upper-toolbar": "^3.0.0"

Run npm install in the root of your ReactJS application and the Clay dependencies should install.

If anyone knows people on the Clay team, please suggest them to make a (working) batch install command. Formatting this was... frankly annoying but also I love shortcuts :-)

Now that we have our dependencies, let’s create the architecture of our Component space. Relocate into the src folder, and create a new directory called components. We will be following a relatively standard organizational structure.

./frontend-reservation/src
├── App.css
├── App.js
├── App.test.js
├── components      -> Create this folder
├── index.css
├── index.js        —> The main page of the application
├── logo.svg
├── properties.js
├── serviceWorker.js
├── setupTests.js
└── style

And now inside of the components folder, anytime we want to create a batch of components, we create a folder for it. In our application, we need custom components for things like how we interact with Office data, Booking data, Admin panels, etc. so it ended up looking something like this:

./frontend-reservation/src/components
├── Admin
├── Booking
├── Clay
├── Facility
├── Office
  ├── Office.js
  ├── OfficeCreate.js
  ├── OfficeModal.js
  ├── OfficeSelection.js
  ├── OfficeTable.js
  ├── OfficeTableCell.js
  ├── OfficeTableHeader.js
  ├── OfficeTest.js
  └── style
    └── office.scss
├── Participants
├── Purpose
├── Room
└── svg

Let's focus on the Office component. There are multiple JS files, including a style folder where we keep our scss styles. This architecture is the same in each component folder. Let's take a look into OfficeTest.js.

import React from 'react';
import icons from '../svg/icons.svg';

# Clay Imports 
import ClayTable from '@clayui/table';
import ClayButton, { ClayButtonWithIcon } from '@clayui/button';

export default class OfficeTable extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      rows: 5
    }
    this.createTestRows = this.createTestRows.bind(this);
  }

createTestRows(noRows) {
  const rows = []
  for(var i = 0; i < this.state.rows; i++) {
    rows.push(
      <ClayTable.Row>
        <ClayTable.Cell align={'left'}>Test</ClayTable.Cell>
        <ClayTable.Cell align={'left'}>Test</ClayTable.Cell>
        <ClayTable.Cell align={'right'}>
          <ClayButtonWithIcon
            symbol="trash"
            spritemap={icons}
            displayType={'unstyled'}
            style={{padding: '0', margin: '0', height: '1rem'}}/>
        </ClayTable.Cell>
      </ClayTable.Row>
    )
  }
  return rows;
}


  render() {
  const spritemap = icons;
    return (
      <div className="room body table">
      <ClayTable>
      <ClayTable.Head>
        <ClayTable.Row>
          <ClayTable.Cell headingCell headingTitle>
            {"オフィス名"}
          </ClayTable.Cell>
          <ClayTable.Cell headingCell headingTitle>
            {"オフィス住所"}
          </ClayTable.Cell>
          <ClayTable.Cell headingCell headingTitle>
            {""}
          </ClayTable.Cell>
        </ClayTable.Row>
      </ClayTable.Head>
      <ClayTable.Body>
        {this.createTestRows(this.state.rows)}
      </ClayTable.Body>
      </ClayTable>
      </div>
    )
  }
}

This is a simple component for rendering a test table with pre-rendered data cells using ClayUI Table components. Styles are imported via relative path, and ClayUI components that are used are also imported inline from the node_module package name. (i.e. ClayTable from @clayui/table, ClayButton from @clayui/button).

The icons svg file is a unique spritemap that is used to display icons in ClayUI. For a local ReactJS, it is best to save the svg from this link into a directory of the root application, like an assets or images folder, and reference it by importing it via relative path each time a class file uses the ClayIcon component:  https://clayui.com/docs/components/icon.html#clay-sidebar for details.

For now to view any kind of component created locally, in this case OfficeTable  (that imports OfficeTableCell), you will have to directly change the src/index.js file. 

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';

import * as serviceWorker from './serviceWorker';

import OfficeTest from './components/Office/OfficeTest';

const office = (
    <OfficeTest />
)

ReactDOM.render(office, document.getElementById('root'));

serviceWorker.unregister();

Import the necessary component via relative path. This will be via './components/ComponentName_Folder/ComponentName_Component'. Then, you will directly render the component into the DOM into an element on the tree. 

A great solution to test Components separately might be by using Storybook. If any of you guys ever try to integrate that into your React + Liferay projects, let me know! 

The default dom node in any ReactJS project is root. If you want to include inner Portlet navigation into your project you will need to have atleast two DOM nodes to work with. You can add as many as you wish in the root/public/index.html body portion of the file:

<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
  <div id="root"></div>
  <div id="content"></div>
</body>
</html>

For now, this static set-up is fine since we are testing locally. If we want to integrate DXP Cloud and migrate the application to Portlet, we will have to do some extra steps, so keep this in mind. 

Now that we have a node defined on the DOM, anything we render to that node will appear at localhost:3000. If you go to the root directory at frontend-reservation and launch the npm start command, at localhost:3000 you should see a ClayUI styled table with Test entry data (the below image has data from the API displayed. More on that later):

Now we have a simple working component in a local ReactJS application!


Overview
 

Let's recap what we went over.

  1. Creating a new Liferay Workspace
  2. Create a ReactJS application within the root of our project
  3. Importing ClayUI in files and locally 
  4. Structuring our ReactJS project 
  5. Creating a test component with styling 
  6. Manipulating the DOM node to render our test components 
  7. Viewing our test components on our local instances 

Not too much, but definitely nothing to disregard!  

Anyways this is only the beginning of the process. More to come in the next parts of our 3-part series. For now, let's take a quick break. Maybe read an article, grab a drink. I've been loving these coconut amazake muffins from a cafe nearby the Tokyo Liferay Office + mango/orange juices I grab from 711.  What's your favorite snack? Comment down below! And, if you have any questions, feel free to ask away.

Catch you on the next part of our series!

Up Next...
Working With Cloud Hosted API’s in ReactJS Liferay Applications