BPM, Workflow, and Case

 View Only

Using React to build a Checklist coach view [Part 2]

By Tamer Mohamed posted Tue March 16, 2021 09:25 PM

  
Introduction

Coach views can be rather simple control widgets or reusable fully functional subforms that can be embedded in coaches in a Client Side Human Service (CSHS).

In this two part blog, we will illustrate 2 possible approaches to use React to  build a coach view.

In part 1, we showed the rather simple and quick approach by directly including React library as an embedded script in the generated coach view in the client side.
In part 2, we demonstrate a more elaborate approach using a build system based on webpack in a manner that mimics using the familiar create-react-app. We will use these techniques to build a checklist control. 

Part 2: Using a module bundler and Dojo style wrapper to build a coach view

Step 1: Setup the development / build environment

Our goal here is not to use React scripts which are a part of create-react-app. Instead, we will create the minimum possible setup that can bundle our React code and wrap it as a custom AMD module which can then be registered and loaded by the Dojo framework as described in the knowledge center.

  1. Install NodeJS.
  2. Create a new folder then create an initial package.json file by typing in the terminal: npm init
  3. Create 3 subfolders: src, build, and deploy
  1. Install the required packages (React) and the development dependencies (webpack, babel, several plugins, loaders and parsers), and optionally you can also install eslint packages if you don’t have them installed globally already:
    npm i react react-dom
    
    npm i --save-dev webpack webpack-cli @babel/core @babel/preset-env @babel/preset-react babel-loader autoprefixer babel-eslint css-loader css-minimizer-webpack-plugin file-loader filemanager-webpack-plugin mini-css-extract-plugin node-sass postcss postcss-loader sass-loader style-loader
    
    npm i --save-dev eslint eslint-config-react-app eslint-plugin-flowtype eslint-plugin-import eslint-plugin-jsx-a11y eslint-plugin-react eslint-plugin-react-hooks
    
  2. In package.json, add key “build” to the "scripts" section with value: "webpack -–mode production"
  3. Optionally create eslint configuration file .eslintrc.json
    {
      "extends": ["react-app"],
      "parser": "babel-eslint"
    }
    
  4. Create the configuration file for webpack; webpack.config.js.  Notice that we are using the FileManagerPlugin to create a zip file from the build result into the deploy folder.
    const FileManagerPlugin = require("filemanager-webpack-plugin");
    const MiniCssExtractPlugin = require("mini-css-extract-plugin");
    const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
    
    module.exports = (webpackEnv, argv) => {
      const isProduction = argv && argv.mode !== "development";
      const isDevelopment = !isProduction;
      
      const srcPath = __dirname + "/src";
      const buildPath = __dirname + "/build";
      const deployPath = __dirname + "/deploy";
      const moduleName = "react_module";
    
      let plugins = [
        new FileManagerPlugin({
          events: {
            onEnd: {
              copy: [{ source: srcPath + "/*Map.js", destination: buildPath }],
              archive: [{ source: buildPath, destination: deployPath + "/" + moduleName + "_bundle.zip" }]
            }
          }
        }),
        new MiniCssExtractPlugin({
          filename: "[name].css",
          chunkFilename: "[id].css" //.[chunkhash:8]
        })
      ];
    
      return [
        {
          entry: {  [moduleName]: srcPath + "/index.jsx" },
          output: { filename: "[name]_bundle.js", path: buildPath },
          resolve: { extensions: [".js", ".jsx", "scss", "css"] },
          target: ["web", "es5"],
          plugins: plugins,
          devtool: isDevelopment && "eval-source-map",
          module: {
            rules: [
              {
                test: /\.(js|jsx)$/,
                exclude: /node_modules/,
                use: {
                  loader: "babel-loader",
                  options: { presets: ["@babel/preset-env", "@babel/preset-react"]}
                }
              },
              {
                test: /\.(sa|sc|c)ss$/,
                use: [ MiniCssExtractPlugin.loader, "css-loader", "postcss-loader", "sass-loader"]
              },
              {
                test: /\.(png|svg|jpg|gif)$/,
                use: [{ loader: "file-loader", options: { name: "../media/[name].[ext]" } }]
              }
            ]
          },
          optimization: {
            minimize: isProduction,
            minimizer: [
              `...`,
              new CssMinimizerPlugin({
                minimizerOptions: { preset: [ "default", { discardComments: { removeAll: true } }] }
              })
            ]
          }
        }
      ];
    };
    

Step 2: Create the required component files and run the build

  1. Now that the environment is setup we will create the code for the checklist component (Checklist.jsx and Checklist.scss). We will also create the AMD wrapper (index.jsx) that will be used within the dojo framework and a package map (packageMap.js).

Note the slight changes in syntax in Checklist.jsx compared to the code for the same component in part 1 using tagged templates.

  1. After the files are created, type in the terminal: npm run build

Code listing for: src/components/Checklist/Checklist.jsx
import React, { useState } from "react";
import "./Checklist.scss";

function Checkbox(props) {
    return <input type="checkbox" {...props} />;
}
  
export default function ChecklistComponent({title, choices, items, initialData, onChange}) {
    const [data, setData] = useState(initialData || new Array(items.length));
    
    function handleChange(event) {
      const checkboxId = event.target.id.split("_");
      const newData = data.slice();
      newData[parseInt(checkboxId[0])] = event.target.checked ? checkboxId[1] : null;
      setData(newData);
      onChange(newData);
    }
    
    return (
      <table className={"checklist"}>
        <thead>
         <tr>
          <th>{title}</th>
          {choices.map(choice => 
            <th key={choice.value}>{choice.name}</th>
          )}
         </tr>
        </thead>
        <tbody>
          {items.map((item, k) =>
            <tr key={k}>
              <td>{item}</td>
              {choices.map(choice => 
               <td key={`${k}_${choice.value}`}>
                 <Checkbox
                   id={`${k}_${choice.value}`}
                   checked={false || (data[k] && data[k]===choice.value)}
                   onClick={handleChange}
                 />
               </td>
              )}        
            </tr>
          )}
        </tbody>
      </table>
    );
  }
Code listing for: src/components/Checklist/Checklist.scss
.checklist {
  width: 100%;
  font-family: arial, sans-serif;
  border-collapse: collapse;
  
  td:first-child, th:first-child {
    text-align: left;
  }

  th {
    font-weight: bold;
  }

  td, th {
    border: 1px solid #dddddd;
    text-align: center;
    padding: 8px;
  }

  tr:nth-child(even) {
    background-color: #dddddd;
  }
}
Code listing for the wrapper: src/index.jsx
/* eslint-disable import/no-amd */
import r1 from "react";
import r2 from "react-dom";
import c from "./components/Checklist/Checklist";

const React = r1;
const ReactDOM = r2;
const Checklist = c; 

define([], () => {
  return {
    renderChecklist: function(props, domRef) {
      ReactDOM.render(<Checklist {...props}/>, domRef);
    }
  };
});
Code listing for Dojo package map: src/packageMap.js
require({
  packages: [
    {
      name: 'react_module_bundle',
      location: com_ibm_bpm_coach.getManagedAssetUrl('react_module_bundle.zip', com_ibm_bpm_coach.assetType_WEB)
    }
  ]
});


Step 3: Create a new coach view

  1. Create a new process app either from the legacy ProcessCenter or from the newer WorkflowCenter landing page and let's call it "Checklist demo".
  2. Upload the generated zip file from the deploy folder to the project "web file" category.
  3. Create a new “View” from the submenu User Interface. Let’s call it "checklist”.
  4. In the variables tab, we will create 3 configurations options: title (String), choices (List of NameValuePair) and items (List of String). The bound data will be stored with the name “value”.
  5. In the behaviour tab, select the "Included Scripts" node and click the "+" button to add 2 files; the css file and the package map from the uploaded zip file.
  6. Select the AMD Dependencies section and add an alias for our module. The module ID defines the path of the js file within the zip file. Here the full path is react_module_bundle/react_module_bundle. Let’s choose the alias for this module to be “react_module
  7. We will add the remaining initialization code under the “load” event handler. Copy and paste the following code which initializes and renders our component by calling the function that we defined in the AMD wrapper (index.jsx). The function name is “renderChecklist” which takes two inputs; the props and the dom node. Note that this code is almost identical to the initialization code in part 1 of this blog.
    const view = this;
    const titleStr = view.getOption("title");
    const choiceList = view.getOption("choices").items;
    const itemList = view.getOption("items").items;
    var boundData = view.context.binding && view.context.binding.get("value");
    
    function updateOutput(data) {
      if(boundData) {
        for(let i = 0; i < data.length; i++) {
          boundData.items[i] = data[i];
        }
        view.context.binding.set("value", boundData);
      }
    }
    
    var domNode = document.createElement("div");
    view.context.element.appendChild(domNode);
    
    react_module.renderChecklist(
      {
          title: titleStr,
          choices: choiceList,
          items: itemList,
          initialData: boundData? boundData.items : null,
          onChange: updateOutput
      },
      domNode
    );
    ​

Step 4: Create a CSHS coach to test the new coach view
  1. Create a CSHS and let’s call it “test Checklist”.
  2. In the coach editor, search for “checklist” in the template and it should display the view we just created. Drag and drop into the layout, then in the configuration section, let's add some choices and some items.
  3. To test that the data is updated, we will create a variable in the coach variable list of type list of “String” and bind it to the view.

  4. Finally let's preview the CSHS and check the result.
      2 comments
      106 views

      Permalink

      Comments

      Tue September 20, 2022 10:32 AM

      This is a very helpful blog in terms of start with react coaches

      Wed March 17, 2021 10:16 PM

      This is awesome!

      Opens up to endless potential to create awesome UIs for client solutions