Building the UX web application (tutorial)

Build a complete UX Web application with ReactJS and IBM Carbon components by creating models, data sources, views, and applications in Maximo® Real Estate and Facilities, then developing the frontend using create-react-app template with data tables, services, and routing.

In this tutorial, you will be guided to build a UX Web Application with ReactJS and IBM Carbon components.

Before you begin

In your web browser's address bar, enter the following URL address:
https://<workspace_id>.facilities.<mas_domain>
Where <workspace_id> and <mas_domain> are the values for your Maximo Real Estate and Facilities environment.
Note: If you do not have Node.js or npm installed on your computer, then access https://nodejs.org to download and install Node.js (npm is installed as part of the Node.js installation). For previous releases, see https://nodejs.org/en/download/releases/.

To check if you have Node.js installed, run this command in your terminal:

node -v

To confirm that you have npm installed, you can run this command in your terminal:

npm -v

Step 1. Add the Model

From the side navigation menu, select Tools. In the portal, select Model Designer. Click Add. Enter the name, exposed name, and ID of your model. The exposed name should be a browser-friendly string. For our example, we'll enter the following information:

Name
My First App Model
Exposed Name
myFirstApp
ID
My First App Model

Then click Create.

Figure 1. Model Metadata
Example of adding the model

Step 2. Add the Data Source and Fields for Current User

Next, in the Data Sources section of your model, click Add. Enter the following information:

Name
Current User
Exposed Name
currentUser
Data Source Type
CURRENT_USER

Next, in the Fields section of your data source, click Quick Add for two fields. Enter the following information:

Name
(1) triFirstNameTX, (2) triLastNameTX
Exposed Name
(1) firstName, (2) lastName
Field Name
(1) triFirstNameTX, (2) triLastNameTX
Data Type
(1) STRING, (2) STRING

Then click Create. Finally, Save & Close your model.

Figure 2. Data Source Metadata and Fields
Example of adding the data source and fields

Step 3. Add the View

From the side navigation menu, select Tools. In the portal, select Web View Designer. Click Add. Enter the name, exposed name, and ID of your view. For our example, we'll enter the following information:

Name
My First App
Exposed Name
my-first-app
ID
My First App
View Type
WEB_APP
Root Folder
Messages Folder
Index filename

Click Create. Then Save & Close.

Figure 3. Web View Metadata
Example of the Web View Designer form

Step 4. Add the Model-and-View

From the side navigation menu, select Tools. In the portal, select Model and View Designer. Click Add. Enter the name, exposed name, and ID of your model-and-view. For our example, we'll enter the following information:

Name
My First App Model and View
Exposed Name
myFirstApp
ID
My First App Model and View
Model Name
Select
View Name
Select
View Type
Select

Click Create. Then Save & Close.

Figure 4. Model and View Metadata
Example of adding the model-and-view

Step 5. Add the Application

From the side navigation menu, select Tools. In the portal, select Application Designer. Click Add. Enter the name, exposed name, and ID of your application. For our example, we'll enter the following information:

Name
My First App
Exposed Name
myFirstApp
ID
My First App
Label
My first app
App Type
Select
App Name
Select
Instance ID
-1

Click Create. Then Save & Close.

Figure 5. Application Metadata
Example of adding the application

Step 6. Create the Skeleton Application from a Template

A Maximo Real Estate and Facilities template can be used with the NPM-based create-react-app tool to build a skeleton UX Web Application. This is the best way to start building a UX Web Application with ReactJS and IBM Carbon components.

To create a new UX Web Application from this template, run this command in your terminal:

npx create-react-app my-first-app --template @tririga/cra-template --use-npm

Step 7. Run the Application Locally

Now let's run the UX Web Application that you created. In the my-first-app folder that was created, make a copy of the .env.development.local.template file and rename the copied file to .env.development.local. Open the .env.development.local file and set the values for the following variables:

  • REACT_APP_INSTANCE_ID: Instance ID from the application metadata
  • REACT_APP_TRIRIGA_URL: URL of the Maximo Real Estate and Facilities server
  • REACT_APP_CONTEXT_PATH: Maximo Real Estate and Facilities context path . (No longer used)
  • REACT_APP_MODEL_AND_VIEW: Exposed name of the model-and-view
  • REACT_APP_BASE_PATH: Path when running the app on the local development server
  • REACT_APP_EXPOSED_NAME: Exposed name of the application
  • REACT_APP_SSO: If SSO is enabled on the server, then true; otherwise, false

For our example, we'll enter the following information, where REACT_APP_TRIRIGA_URL and REACT_APP_CONTEXT_PATH are the specific values for your Maximo Real Estate and Facilities environment:

REACT_APP_INSTANCE_ID=-1
REACT_APP_TRIRIGA_URL=https://www.tririga-dev.com/dev
REACT_APP_CONTEXT_PATH=/dev
REACT_APP_MODEL_AND_VIEW=myFirstApp
REACT_APP_BASE_PATH=/
REACT_APP_EXPOSED_NAME=myFirstApp
REACT_APP_SSO=false

After you've entered the information, change directory (cd) and run this command in your terminal:

cd my-first-app
npm start

This command automatically opens your app home page in a new tab of your default browser. By default, the URL of an app that runs locally is: https://localhost:3000. To check if your app displays some information about your user, click the Current User button.

Congratulations! You've built your first Maximo Real Estate and Facilities UX Web Application with ReactJS components. Create a new page to display all of the buildings.

Figure 6. UX Web Application > Current User
Example of running the application locally

Step 8. Add the Data Source and Fields for All Buildings

First, let's add a data source to query all of the buildings in Maximo Real Estate and Facilities.

From the side navigation menu, select Tools. In the portal, select Model Designer. Select the My First App Model.

Next, in the Data Sources section of your model, click Add. Enter the following information:

Name
All Buildings
Exposed Name
allBuildings
Data Source Type
QUERY
Multiple Records
Yes
Module
Location
Business Object
Building (triBuilding)
Query Name
triBuilding - Building Details

Next, in the Fields section of your data source, click Quick Add for two fields. Enter the following information:

Name
(1) Building, (2) Parent
Exposed Name
(1) building, (2) parentProperty
Field Name
(1) Building, (2) Parent
Data Type
(1) STRING, (2) STRING

Then click Create. Finally, Save & Close your model.

Figure 7. Data Source Metadata and Fields
Example of adding the data source and fields

Step 9. Add the Method to Interact with the Data Source

Open the my-first-app folder by using the integrated development environment (IDE) of your choice. For our example, the following screenshots are taken from Microsoft Visual Studio Code. The code that interacts with the data sources are placed under: /src/model/datasources. Under that folder, create a new file that is named BuildingsDS.js.

Figure 8. JS File > BuildingsDS.js
Example of adding the method

Open the BuildingsDS.js file and add the following code:

import { getAppModel } from "../AppModel";
import { DatasourceNames } from "../../utils";

export async function getAllBuildings() {
  const response = await getAppModel().getRecord(
    DatasourceNames.BUILDINGS_DS_NAME
  );
  return response.data;
}

Basically, this code gets the model object and then calls the getRecord method to get the data from the BUILDINGS_DS_NAME data source.

Next, let's create the BUILDINGS_DS_NAME constant and set its value to allBuildings, the exposed name of the All Buildings data source. Under the /src/utils/constants folder, open the DatasourceNames.js file and add the new const (constant) as follows:

export const CURRENT_USER_DS_NAME = "currentUser";
export const BUILDINGS_DS_NAME = "allBuildings";

Export the BuildingsDS from the model index.js file. Under the /src/model folder, open the index.js file and add the export of the BuildingsDS as follows:

import { createAppModel, getAppModel } from "./AppModel";
import * as CurrentUserDS from "./datasources/CurrentUserDS";
import * as BuildingsDS from "./datasources/BuildingsDS";

export { createAppModel, getAppModel, CurrentUserDS, BuildingsDS };

Step 10. Add the Service to Get All Buildings

A service encapsulates the logic to run the application actions. These actions are usually triggered by a user or event. The services are placed under: /src/services. Under that folder, create a new file that is named BuildingsServices.js.

Figure 9. JS File > BuildingsServices.js
Example of adding the service

Open the BuildingsServices.js file and add the following code:

import { LoadingServices } from ".";
import { BuildingsDS } from "../model";

export async function getAllBuildings() {
  let buildings = [];
  try {
    LoadingServices.setLoading("getAllBuildings", true);
    buildings = await BuildingsDS.getAllBuildings();
  } finally {
    LoadingServices.setLoading("getAllBuildings", false);
  }
  return buildings;
}

This code is a simple action that toggles the loading while calling the BuildingsDS to get all buildings.

Export the BuildingsServices from the services index.js file. Under the /src/services folder, open the index.js file and add the export of the BuildingsServices as follows:

export * as LoadingServices from "./LoadingServices";
export * as CurrentUserServices from "./CurrentUserServices";
export * as MessageServices from "./MessageServices";
export * as BuildingsServices from "./BuildingsServices";

Step 11. Add the Page to Display All Buildings

Next, let's create a new page where we will use an IBM Carbon component that is named DataTable to display all buildings. For more information about the DataTable component, including an example, see:

https://react.carbondesignsystem.com/?path=/docs/components-datatable-sorting--default

The pages are placed under: /src/pages. Under that folder, create a new folder that is named AllBuildingsPage. Under this new AllBuildingsPage folder, create two new files as follows:

  • _AllBuildingsPage.scss: Partial Sass file that contains the styles that are used by the page
  • AllBuildingsPage.js: JavaScript file that contains the component that renders the page
Figure 10. SCSS File and JS File > _AllBuildingsPage.scss and AllBuildingsPage.js
Example of adding the page

Open the _AllBuildingsPage.scss file and add the following code:

@use "@carbon/react/scss/config" as *;
@use "../../styles/page" as *; 

.allBuildingsPage {
  @include page;

  &__content {
    display: flex;
    flex-direction: column;
  }

  & .#{$prefix}--data-table-container {
    height: 100%;
    display: flex;
    flex-direction: column;
  }
}

Open the AllBuildingsPage.js file and add the following code:

import React from "react";
import {
  DataTable,
  TableContainer,
  Table,
  TableHead,
  TableRow,
  TableHeader,
  TableBody,
  TableCell,
} from "carbon-components-react";
import { Routes, AppMsg } from "../../utils";
import { FooterButtons } from "../../components";
import { BuildingsServices } from "../../services";

const cssBase = "allBuildingsPage";

export default class AllBuildingsPage extends React.PureComponent {
  state = {
    buildings: [],
  };

  async loadBuildings() {
    const buildings = await BuildingsServices.getAllBuildings();
    this.setState({ buildings });
  }

  componentDidMount() {
    this.loadBuildings();
  }

  render() {
    const { buildings } = this.state;
    buildings.forEach((item) => {
      item.id = item._id;
    });
    const headers = [
      {
        key: "building",
        header: AppMsg.getMessage(AppMsg.MESSAGES.NAME),
      },
      {
        key: "parentProperty",
        header: AppMsg.getMessage(AppMsg.MESSAGES.PARENT_PROPERTY),
      },
    ];
    return (
      <div className={cssBase}>
        <div className={`${cssBase}__content`}>
          <DataTable rows={buildings} headers={headers}>
            {({
              rows,
              headers,
              getHeaderProps,
              getRowProps,
              getTableProps,
              getTableContainerProps,
            }) => (
              <TableContainer
                title={AppMsg.getMessage(AppMsg.MESSAGES.BUILDINGS)}
                description={AppMsg.getMessage(
                  AppMsg.MESSAGES.ALL_BUILDINGS_DESCRIPTION
                )}
                {...getTableContainerProps()}
              >
                <Table {...getTableProps()} isSortable>
                  <TableHead>
                    <TableRow>
                      {headers.map((header) => (
                        <TableHeader
                          key={header.key}
                          {...getHeaderProps({ header })}
                          isSortable
                        >
                          {header.header}
                        </TableHeader>
                      ))}
                    </TableRow>
                  </TableHead>
                  <TableBody>
                    {rows.map((row) => (
                      <TableRow key={row.id} {...getRowProps({ row })}>
                        {row.cells.map((cell) => (
                          <TableCell key={cell.id}>{cell.value}</TableCell>
                        ))}
                      </TableRow>
                    ))}
                  </TableBody>
                </Table>
              </TableContainer>
            )}
          </DataTable>
        </div>
        <FooterButtons
          secondaryLabel={AppMsg.getMessage(AppMsg.BUTTONS.HOME)}
          secondaryRoute={Paths.HOME}
        />
      </div>
    );
  }
}

Basically, this code loads all buildings by calling the BuildingsServices.getAllBuildings method when the component is mounted, and then saves the list of buildings to the component state. The render method uses the IBM Carbon DataTable component to render a table with all buildings. Meanwhile, all of the labels that are displayed by the page are retrieved by using the AppMsg.getMessage method.

Import the _AllBuildingsPage.scss file into the _Pages.scss file. Under the /src/pages folder, open the _Pages.scss file and add the import of the _AllBuildingsPage.scss file as follows:

@forward "./HomePage/HomePage";
@forward "./CurrentUserPage/CurrentUserPage";
@forward "./UnauthorizedPage/AuthorizedPage";
@forward "./AllBuildingsPage/AllBuildingsPage"; // add this line

Next, let's export the AllBuildingsPage from the pages index.js file. Under the /src/pages folder, open the index.js file and add the export of the AllBuildingsPage as follows:

import HomePage from "./HomePage/HomePage";
import CurrentUserPage from "./CurrentUserPage/CurrentUserPage";
import UnauthorizedPage from "./UnauthorizedPage/UnauthorizedPage";
import AllBuildingsPage from "./AllBuildingsPage/AllBuildingsPage";

export { HomePage, CurrentUserPage, UnauthorizedPage, AllBuildingsPage };

When you use an IBM Carbon component, you must also import the styles that are used by the component. Under the /src folder, open the index.scss file and add the import of the data-table.scss file as follows:

//Carbon components styles
@use "@carbon/react/scss/components/data-table";
@use '@carbon/react/scss/components/data-table/sort';
@use "@carbon/react/scss/components/button";
@use "@carbon/react/scss/components/loading";
@use "@carbon/react/scss/components/list";
@use "@carbon/react/scss/components/modal";
@use "@carbon/react/scss/components/notification";

Step 12. Add the Labels for the Page

All of the labels that are used by the application are defined in the JSON files that are inside the Messages Folder. Under the /src/utils/messages folder, open the messages.json file and add the following labels:

  • BUILDINGS: Buildings
  • ALL_BUILDINGS_DESCRIPTION: A list of all buildings in Maximo Real Estate and Facilities
  • NAME: Name
  • PARENT_PROPERTY: Parent Property
Figure 11. JSON File > messages.json
{
  "HOME_HEADER": "Welcome to the UX Web Application Home Page",
  "CURRENT_HEADER": "Current user details",
  "UNAUTHORIZED_TITLE": "You do not have permission to access this page.",
  "UNAUTHORIZED_DESCRIPTION": "Due to either a session timeout or unauthorized access, 
you do not have permission to access this page.",
  "BUILDINGS": "Buildings",
  "ALL_BUILDINGS_DESCRIPTION": "A list of all buildings in TRIRIGA",
  "NAME": "Name",
  "PARENT_PROPERTY": "Parent Property"
}

Next, let's create the label constants. Under the /src/utils/messages folder, open the ApplicationMessages.js file and add the following label constants:

  • BUILDINGS
  • ALL_BUILDINGS_DESCRIPTION
  • NAME
  • PARENT_PROPERTY
Figure 12. JS File > ApplicationMessages.js
export const MESSAGES = {
  HOME_HEADER: "HOME_HEADER",
  CURRENT_HEADER: "CURRENT_HEADER",
  UNAUTHORIZED_TITLE: "UNAUTHORIZED_TITLE",
  UNAUTHORIZED_DESCRIPTION: "UNAUTHORIZED_DESCRIPTION",
  BUILDINGS: "BUILDINGS",
  ALL_BUILDINGS_DESCRIPTION: "ALL_BUILDINGS_DESCRIPTION",
  NAME: "NAME",
  PARENT_PROPERTY: "PARENT_PROPERTY",
};

Step 13. Add the Route to the Page

Add a route to the new page that we created. Under the /src/utils/constants folder, open the Paths.js file and add the path to the BUILDINGS route as follows:

export const HOME = "/";
export const CURRENT_USER = "/user";
export const BUILDINGS = "/buildings";

Next, add the AllBuildingsPage to the main application component. Under the /src/app folder, open the TririgaUXWebApp.js file, add the import of the AllBuildingsPage, and declare the AllBuildingsPage component inside a Route element as follows:

import { HomePage, CurrentUserPage, AllBuildingsPage } from "../pages"; // update this line
…
render() {
  const { loading, message } = this.state;
  return (
    <div className={cssBase}>
      <Routes>
        <Route path={Paths.CURRENT_USER} element={<CurrentUserPage />} />
        <Route path={Paths.HOME} element={<HomePage />} />
        <Route path={Paths.BUILDINGS} element={<AllBuildingsPage />} /> // add this line
      </Routes>
      <ShowAppMessages message={message} clearMessage={this.clearMessage} />
      {createPortal(<Loading active={loading} withOverlay />, document.body)}
    </div>
  );
}

Next, we must add a new Buildings button on the home page that allows the user to navigate to the AllBuildingsPage. Under the /src/pages/HomePage folder, open the HomePage.js file and configure the primary button of the FooterButtons component as follows:

export default class HomePage extends React.PureComponent {
  render() {
    return (
      <div className={cssBase}>
        <div className={`${cssBase}__header`}>
          {AppMsg.getMessage(AppMsg.MESSAGES.HOME_HEADER)}
        </div>
        <div className={`${cssBase}__content`} />
        <FooterButtons
          secondaryLabel={AppMsg.getMessage(AppMsg.BUTTONS.CURRENT_USER)}
          secondaryRoute={Paths.CURRENT_USER}
          primaryLabel={AppMsg.getMessage(AppMsg.MESSAGES.BUILDINGS)} // add this line
          primaryRoute={Paths.BUILDINGS} // add this line
        />
      </div>
    );
  }
}

Finally, return to your browser to check the new page that you created.

  • If you closed the browser tab with your page, open a new tab with your local URL: http://localhost:3000.
  • If your application is not running, verify that your local server is running (Step 7).
  • If necessary, check your browser console for errors.

Congratulations! You've built your first Maximo Real Estate and Facilities UX Web Application with ReactJS and IBM Carbon components.

Figure 13. UX Web Application > Buildings
Example of adding the route to the page