golgi

Developing With Golgi: A Comparison With React.js

This tutorial is based around one of the well-known, documented React Examples: Thinking in React.

In this first Chapter of the tutorial, we’ll demonstrate how to use Golgi’s basic functionality to implement the same example, and in doing so, you can compare this with how you’d have used React instead.

You’ll see some key similarities between the technologies:

However, there are some key differences:

So let’s see this in action, and let’s see how to think in Golgi’s terms when building an application.

Start With the Mock

Here’s the mock that is used in the Thinking in React example, and that we’ll also use in this tutorial:

We’ll also use the same JSON data structure to populate our UI:

Step 1: Break The UI Into A Component Hierarchy

The first step is just the same as when using React, and we can simply follow the guidance provided in the React example

So we’ll use the same breakdown of components to represent the mock:

with each number representing the following logical units:

  1. filterable-product-table (orange): contains the entirety of the example
  2. searchbar (blue): receives all user input
  3. product-table (green): displays and filters the data collection based on user input
  4. product-category-row (turquoise): displays a heading for each category
  5. product-row (red): displays a row for each product

The hierarchical structure of these units is:

So far, the analysis is identical to React, but Unlike in React, in Golgi, we need to distinguish between:

So we can conclude from the hierarchy above that we’ll need four Golgi Components:

and one Golgi Assembly:

Additionally we can conclude that the product-table Component will need to dynamically populate its rows and columns from a PRODUCT array.

Step 2: Create our Project Structure

There are no hard and fast rules to this step in Golgi, and you can adapt how I’d do it to your taste.

A key feature of Golgi is that there is no build/bundling step, and no need for any build tools or build chain.

What you do need, however, is access to a working Web Server which will fetch your Golgi Components into your browser. Golgi Components and Assemblies are packaged as ES6 Modules, which means they can be handled completely dynamically.

You can use any Web Server with Golgi, eg you could use NGINX or Apache, or you could use something like Express or Fastify within a Node.js system - it’s entirely up to you. Whatever Web Server you use, you need to create a folder/directory on your Web Server’s system and map its physical path to an appropriate Web Server alias path: how you do this will depend on the Web Server.

I’m going to assume you’ve set up a physical directory named example, eg C:\example or /opt/nginx/www/example and that the URL path you’ll use in the Web Server to fetch resources from this directory is /example.

Within this example folder, I’ll create the following sub-directory structure:

    example
      |
      |- js
      |   |
      |   |- assemblies
      |   |
      |   |- components
      |   |       |
      |   |       |- productui
      |   |       |       |
      |   |

Install the Golgi Module

You need to get a copy of the Golgi module and place it in the /example/js folder.

During development, you may wish to use this full source version, but for production, I’d recommend using the much smaller but functionally identical minified version.

Create an app.js Module

The next step is to create the basic Golgi Application module that configures and kick-starts everything.

    (async () => {

      const {golgi} = await import('./golgi.js');

      let context = {
        componentPaths: {
          productui: './components/productui/'
        },
        assemblyPath: './assemblies/'
      };

      await golgi.renderAssembly('filterable-product-table', 'body', context);
      
    })();

Save this as /example/js/app.js

Note: we’ll create the filterable-product-table Assembly Module file that this refers to in a later step.

Create an index.html File.

Next, create a minimal “bare-bones” HTML page that will start your example application by loading the app.js module into the browser:

    <!DOCTYPE html>
    <html lang="en">
      <head>
        <title>Product UI Example</title>
      </head>
      <body>
        <script type="module" src="/example/js/app.js"></script>
      </body>
    </html>

Save this as /example/index.html

So you should now have the following structure for your project:

    example
      |
      |- index.html
      |
      |- js
      |   |
      |   |- assemblies
      |   |
      |   |- components
      |   |       |
      |   |       |- productui
      |   |       |       |
      |   |
      |   |
      |   |- app.js
      |   |
      |   |- golgi.js

Step 3: Build Out the Golgi Components

The next step is to build an initial basic version of each of the four Golgi Components that we need for the product user interface.

We won’t add all their required functionality just yet, but essentially enough to correctly render their HTML markup.

We’ll create each of these within the /example/js/components/productUI folder

Golgi Components must have a hyphenated prefix in their name, and their names must be all lower-case. This is because they are WebComponents, and also to allow Golgi to locate them. If you look at this part of the app.js module you created earlier:

      let context = {
        componentPaths: {
          productui: './components/productui/'
          ...

This is telling Golgi that any WebComponent with a prefix of productui- will be found in and must be imported from the /components/productui subfolder.

So, for our product user interface example, all our Golgi Components are going to have a prefix of productui-.

A Golgi Component is essentially a stripped-down WebComponent, wrapped as an ES6 module. You’ll see below that you don’t need to worry about any of the standard WebComponent lifecycle methods: Golgi augments each of your Components automatically to provide all the equivalent lifecycle functionality, and a lot more besides.

So, shown below are initial, basic versions of each of the Golgi Components you’ll need for the Product UI example application:

productui-searchbar Component

Copy and save the following to a file named /example/js/components/productui/productui-searchbar.js

    export function load() {

      let componentName = 'productui-searchbar';
      
      customElements.define(componentName, class productui_searchbar extends HTMLElement {
        constructor() {
          super();
          const html = `
    <input type="text" placeholder="Search..." />
    <p>
      <input type="checkbox" />
      &nbsp;Only show products in stock
    </p>
          `;
          this.html = `${html}`;
        }
      });
    };

Note that this “bare-bones” version doesn’t yet include any dynamic behaviour: we’ll show how to do that later.

productui-table Component

Copy and save the following to a file named /example/js/components/productui/productui-table.js

    export function load() {

      let componentName = 'productui-table';
      
      customElements.define(componentName, class productui_table extends HTMLElement {
        constructor() {
          super();
          const html = `
    <table>
      <thead>
        <tr>
          <th>Name</th>
          <th>Price</th>
        </tr>
      </thead>
      <tbody golgi:prop="tbody"></tbody>
    </table>
          `;
          this.html = `${html}`;
        }

        async populate(PRODUCTS) {
          let categoryFound = {};

          for (const [index, product] of PRODUCTS.entries()) {
            if (!categoryFound[product.category]) {
              categoryFound[product.category] = true;
              let crow = await this.renderComponent('productui-category-row', this.tbody, this.context);
              crow.setState(product);
            }
            let row = await this.renderComponent('productui-row', this.tbody, this.context);
            row.setState(product);
          }
        }

      });
    };

Note that this Component includes a custom method - populate() - which will be used to populate the table.

productui-category-row Component

Copy and save the following to a file named /example/js/components/productui/productui-category-row.js

    export function load() {
        
      let componentName = 'productui-category-row';
        
      customElements.define(componentName, class productui_category_row extends HTMLElement {
        constructor() {
          super();
          this.attachShadow({ mode: 'open' });
          const html = `
    <style>
    .td {
      display: table-cell;
      font-weight: bold;
    }
    .colspan {
      max-width: 1px;
      overflow: visible;
      white-space: nowrap;
    }
    :host {
      display: table-row;
    }
    </style>
    <div class="td colspan"></div>
          `;
          this.shadowRoot.innerHTML = `${html}`;
        }

        setState(state) {
          if (state.category) {
            this.rootElement.textContent = state.category;
          }
        }
       
      });
    };

Notes:

productui-row Component

Copy and save the following to a file named /example/js/components/productui/productui-row.js

    export function load() {

      let componentName = 'productui-row';
      
      customElements.define(componentName, class productui_row extends HTMLElement {
        constructor() {
          super();
          this.attachShadow({ mode: 'open' });
          const html = `
    <style>
    .td {
      display: table-cell;
    }
    :host {
      display: table-row;
    }
    </style>
    <div class="td" golgi:prop="name"></div>
    <div class="td" golgi:prop="price"></div>
          `;
          this.shadowRoot.innerHTML = `${html}`;
        }

        setState(state) {
          if (state.name) {
            this.name.textContent = state.name;
          }
          if (state.price) {
            this.price.textContent = state.price;
          }
          if (state.stocked) {
            this.name.style = 'color: black;';
          }
          else {
            this.name.style = 'color: red;';
          }
        }

      });
    };

Notes:

Step 3: Build Out the Golgi Assembly

The next step is to build the top-level Golgi Assembly Module:

filterable-product-table

Copy and save the following to a file named /example/js/assemblies/filterable-product-table.js

    export function load() {

      let gx=`
    <productui-searchbar />
    <productui-table golgi:hook="initialise" />
      `;

      let hooks = {
        'productui-table': {
          initialise: function() {
            let PRODUCTS = [
              {category: 'Sporting Goods', price: '$49.99', stocked: true, name: 'Football'},
              {category: 'Sporting Goods', price: '$9.99', stocked: true, name: 'Baseball'},
              {category: 'Sporting Goods', price: '$29.99', stocked: false, name: 'Basketball'},
              {category: 'Electronics', price: '$99.99', stocked: true, name: 'iPod Touch'},
              {category: 'Electronics', price: '$399.99', stocked: false, name: 'iPhone 5'},
              {category: 'Electronics', price: '$199.99', stocked: true, name: 'Nexus 7'}
            ];
            this.populate(PRODUCTS);
          }
        }
      };

      return {gx, hooks};
    };

Notes:

Run the Application

We should now be ready to run the application in the browser. All we need to do is load the index.html page into the browser, eg:

    http://localhost:3000/example/

or:

    http://localhost:3000/example/index.html

if you need to explicitly specify the page name.

You should see the expected results:

How and Why Did the Golgi Application Work?

Let’s step through what just happened:

And, as a result, the desired output is generated!

Adding the Application’s Interactive Behaviour

So far we’ve just implemented the basic display-only functionality of the Example Application.

Now read Chapter 2 to find out how to add the Example Application’s intereactive filtering behaviour.