Deploying Clarity's Ticket List Custom Element

Clarity needs an application that allows distributors to view, filter, and expand their open and in-progress tickets. Clarity’s development team is familiar with the React framework and prefers to use it in their frontend implementations. With Liferay’s plug-and-play capabilities, they can continue using React without compromising Liferay’s updates or upgrades.

In this exercise, you’ll explore and deploy a React application developed by Clarity’s team as a custom element client extension to retrieve, filter, and display ticket data.

  1. Open a file explorer and navigate to the exercises/module-1/ folder in your course workspace.

  2. Rename the react-app/ folder to clarity-ticketing-ui.
    This is our application folder, which we will transform into a client extension.

  3. Open the clarity-ticketing-ui/webpack.config.js file and paste the following code.

    /**
     * SPDX-FileCopyrightText: (c) 2000 Liferay, Inc. https://liferay.com
     * SPDX-License-Identifier: LGPL-2.1-or-later OR LicenseRef-Liferay-DXP-EULA-2.0.0-2023-06
     */
    
    const path = require('path');
    const webpack = require('webpack');
    
    const DEVELOPMENT = process.env.NODE_ENV === 'development';
    const WEBPACK_SERVE = !!process.env.WEBPACK_SERVE;
    
    module.exports = {
        devServer: {
            headers: {
                'Access-Control-Allow-Origin': '*',
            },
            port: 3000,
        },
        devtool: DEVELOPMENT ? 'source-map' : false,
        entry: {
            index: './index.js',
        },
        externals: {
            'react': 'react',
            'react-dom': 'react-dom',
        },
        experiments: {
            outputModule: true,
        },
        mode: DEVELOPMENT ? 'development' : 'production',
        module: {
            rules: [
                {
                    test: /\.(js|jsx)$/, // Process .js and .jsx files
                    exclude: /node_modules/, // Exclude dependencies
                    use: {
                        loader: 'babel-loader',
                        options: {
                            presets: ['@babel/preset-env', '@babel/preset-react'],
                        },
                    },
                },
                {
                    test: /\.css$/, // Process .css files
                    use: ['style-loader', 'css-loader'],
                },
            ],
        },
        resolve: {
            extensions: ['.js', '.jsx'], // Add .jsx to extensions
        },
        optimization: {
            minimize: !DEVELOPMENT,
        },
        output: {
            clean: true,
            environment: {
                dynamicImport: true,
                module: true,
            },
            filename: WEBPACK_SERVE ? '[name].js' : '[name].[contenthash].js',
    //Here we set the library format, which specifies how the output bundle should be exposed
            library: {
                type: 'module',
            },
            path: path.resolve('build', 'static'),
        },
        plugins: [
            new webpack.optimize.LimitChunkCountPlugin({
                maxChunks: 1,
            }),
        ],
    };
    

    Note that we’ve added the library format, which specifies how the output bundle should be exposed.

  4. Save the file.

  5. From the clarity-ticketing-ui/public folder, open the index.html file in a text editor or IDE.

  6. Replace the <root> and the <tickets-root> tags with a <clarity-ticketing-ui> tag, the name of our custom element.
    Your final result should look like this:

    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Clarity Ticketing UI</title>
      </head>
      <body>
        <clarity-ticketing-ui></clarity-ticketing-ui>
        <script type="module" src="build/static/index.js"></script>
      </body>
    </html>
    
  7. Save the file.

  8. Paste the following code into the clarity-ticketing-ui/index.js file.

    import React, { useState, useEffect } from 'react';
    import ReactDOM, { render } from 'react-dom';
    import './assets/style.css';
    import TicketsList from './assets/components/TicketsList';
    import { HashRouter, Route, Routes } from "react-router-dom";
    import App from './App';
    
    // Custom Element class
    class CustomElement extends HTMLElement {
        connectedCallback() {
            // Ensure the React component is rendered only once
            if (!this.rendered) {
                // Create a container if it doesn't exist
                const container = document.createElement('div');
                container.id = 'tickets-root';
                this.appendChild(container);
    
                // Render the React component into the container
                ReactDOM.render(<TicketsList />, container);
                this.rendered = true;
            }
        }
    
        disconnectedCallback() {
            // Clean up the React component when the element is removed
            const container = this.querySelector('#tickets-root');
            if (container) {
                ReactDOM.unmountComponentAtNode(container);
            }
            this.rendered = false;
        }
    }
    
    // Define the custom element
    const ELEMENT_NAME = 'clarity-ticketing-ui';
    
    if (!customElements.get(ELEMENT_NAME)) {
        customElements.define(ELEMENT_NAME, CustomElement);
    }
    
    // Automatically add the custom element to the page if not already present
    document.addEventListener('DOMContentLoaded', () => {
        if (!document.querySelector(ELEMENT_NAME)) {
            const customElement = document.createElement(ELEMENT_NAME);
            document.body.appendChild(customElement);
        }
    });
    

    This replaces the default use of render() on the ticket-root div, leveraging a Web Component to define the React app as a reusable and self-contained custom element.

  9. Save the file.

  10. Create a new file named client-extension.yaml in the clarity-ticketing-ui/ folder and add the following code.

    assemble:
        - from: build/static
          into: static
    clarity-ticketing-ui:
       friendlyURLMapping: clarity-ticketing-ui
       htmlElementName: clarity-ticketing-ui
       instanceable: false
       name: Clarity Ticketing UI
       portletCategoryName: category.client-extensions
       type: customElement
       urls:
          - index.*.js
       useESM: true
    
  11. Move the clarity-ticketing-ui/ folder into the client-extensions/ folder within your course workspace.

  12. Open a terminal and navigate to the client-extensions/clarity-ticketing-ui/ folder.

  13. Run this command to build and deploy the custom element client extension:

    blade gw clean deploy 
    
  14. Verify that the client extension deploys successfully:

    2025-01-28 11:50:59.076 INFO  [fileinstall-directory-watcher][BundleStartStopLogger:68] STARTED clarityticketingui_7.4.13 [1462]
    

    Now that you've deployed the custom element client extension, examine the ticketing app UI.

  15. In your Liferay instance, sign in as the Clarity Admin user.

    • Username: admin@clarityvisionsolutions.com

    • Password: learn

  16. Open the Site Menu (icon-product-menu.png), click Page Tree, and select the Tickets page.

  17. Click Edit (icon-edit.png) to start editing the page.

  18. In the Fragments and Widgets search bar, search for Clarity Ticketing UI.

  19. Drag and drop the Clarity Ticketing UI widget to the page.
    Drag and drop the Clarity Ticketing UI widget to the page.

  20. Click Publish.

You've now learned how to deploy custom element client extensions, giving you more control over your application.

Conclusion

Great! You’ve successfully deployed a custom element client extension for retrieving and displaying Clarity’s ticket data. Next, you’ll learn about a different use case for custom elements as remote applications.

Loading Knowledge

Capabilities

Product

Education

Contact Us

Connect

Powered by Liferay
© 2024 Liferay Inc. All Rights Reserved • Privacy Policy