< Previous Article Next Article >

Stateful Apps

Stateful applications are most often used for database-driven business applications.

You will typically use them to build transactional full-stack apps, where both the frontend and the backend have to be implemented.

A stateful application can display multiple screens, but the browser page is never reloaded during a session. Since it's a Single Page App, a stateful application will generally perform very well.

What makes a stateful app different?

The architecture of a stateful app is different from an app built with a framework like Vue or React because the application flow is controlled by server-side code rather than frontend code.

Since the application logic is automatically connected to a backend, writing full-stack applications becomes easier and takes less lines of code. For instance, you will rarely need to facilitate AJAX requests from the client to the server because the server is already in control.

A stateful app also establishes user sessions and tracks them for you, which reduces the amount of work you have to do.

How to write stateful apps

Each stateful app starts with a Node.js module that has the following format:

function app() {
  // your business logic goes here
}
exports.default = app;

Adding a front-end

While the interface for a stateful app can be built with raw HTML or EJS markup code, it is usually easier to get started by using Rich Displays, which require no markup.

Rich Displays support the concept of binding, which connects the data on the screen directly to your stateful code. To build a Rich Display quickly, simply use NodeRun’s Drag-and-Drop Visual Designer and save it as a json file.

Consider the following Rich Display called app.json:

Screenshot

To use this Rich Display, write the following Node.js code:

function app() {
  pjs.defineDisplay("display", "app.json");
  display.myScreen.execute({ message: "Welcome to NodeRun!" });
}
exports.default = app;

When you run the application, you will see:

Screenshot

The pjs.defineDisplay() statement above creates the display object for us based on app.json. The display object provides a property for each screen that we built in the Visual Designer.

By using these display object properties, we are able to interact with the display directly from our Node.js code.

Loading grids

Now, let’s do something a bit more involved. We’ll add a grid to the Rich Display and make sure the widgets in the grid columns are bound to field names that match our customers database table.

Screenshot

The grid is named myGrid and it is on a screen named myScreen.

Now, let’s write this code:

function app() {
  pjs.defineDisplay("display", "app.json");
  let records = pjs.query("SELECT * FROM customers");
  display.myGrid.replaceRecords(records);
  display.myScreen.execute();
}
exports.default = app;

When we run the application, we see the customer list:

Screenshot

pjs.query() runs an SQL statement and returns a customer array that is placed into records.

The grid’s replaceRecords() method take the records array and places it in the grid.

It only takes 2 lines of code to load data from a database into a grid on the display.

Go ahead and explore this simple app using the NodeRun embed below:

Triggering a screen response

You may be wondering what the execute() method truly does.

We know it displays a screen, but because this is a top-down stateful application, it also pauses code execution until the user takes some action. The user can either:

  1. Terminate the session by closing the browser tab or by simply navigating away from the page
  2. Respond to the screen and pass control back to the Node.js code

The second action is the one of most interest to us.

There are several ways a response from the user can be triggered. The most common way is to place buttons on the screen and bind the buttons’ response properties.

Screenshot

For example, in a CRUD application, we may add buttons such as Add, Remove, or Edit, and bind them to corresponding Boolean fields named btnAdd, btnRemove, btnEdit.

Then, if the user presses the Add button, control is passed to the Node.js code and the btnAdd Boolean field will be set to true.

We can test for btnAdd in our code:

if (btnAdd) {
  // Add button was pressed!
  // Let’s call a function to add the record
  addRecord();
}

Reading input from the screen

You’ve now seen pjs.defineDisplay() and the execute() method in action. You learned that passing a JavaScript object to execute() will send data to the screen. That same object can also receive data back if the screen has input elements or response fields.

However, building the object is sometimes cumbersome, and a simpler method exists.

That is, you can refer to bound fields directly as long as they are in the same scope as your pjs.defineDisplay() statement.

For example, the statement display.myScreen.execute({ message: "Welcome to NodeRun!" }) can be split up into the following 2 lines:

message = 'Welcome to NodeRun';
display.myScreen.execute();

The same applies to any input data that myScreen may have.

Imagine a screen that has textboxes bound to fields num1 and num2, as well as an Add button with a response bound to a Boolean field named btnAdd.

Your code can process this input directly after the execute() as follows:

display.myScreen.execute();
if (btnAdd) result = num1 + num2;

Reading input from a grid

A Rich Display grid is an object that represents an array of records. Each record is in turn a JavaScript object with keys matching the bound field names.

Therefore, when working with grids, you can utilize most of the standard JavaScript array methods. For example:

// Retrieve grid records where a checkbox is checked
let selectedRecords = display.myGrid.filter(record => record.checked);

Here, checked can be a field bound to a checkbox widget in the Rich Display.

Grid methods can be used to both read or modify the grid.

In addition to filter(), other common methods include clear(), forEach(), push(), insert(), reduce(), map(), and slice().

You can also use the getRecords() method to retrieve all grid records as a standard JavaScript array:

let allRecords = display.myGrid.getRecirds();

Keep displaying that screen

So, what happens when the user responds to a screen and your app() function exits?

In a stateful environment, it means your session has ended.

However, in many cases, we want the user to keep interacting with the screen. Therefore, we must use execute() again and again until the user is truly ready to exit the application. This is typically done with a simple while loop in JavaScript.

while (!btnLogout) {
  display.myScreen.execute();
}

The code above assumes there is a Log Out button (or a similar widget) on the screen that will end the session.

Screenshot

However, having a button like that is optional. Let’s adjust our code as follows:

while (true) {
  display.myScreen.execute();
}

What you are seeing is a very common pattern in stateful application development.

At first glance, it may seem like we created an infinite loop. But remember, execute() always pauses and waits for a user to respond. The user can terminate the session at any time simply by closing the browser or by navigating away, so a while (true) loop is perfectly normal.

Here is an example of a simple calculator application that uses this pattern:

Database-driven Widgets

The great thing about stateful apps is that they are inherently connected to your application server! Your server, in turn, is configured to work with a database.

  • Note, the default database that ships with every new NodeRun space is MariaDB. However, Rich Displays and the NodeRun applications you build can be configured to work with any popular database with little to no code changes.

This intrinsic database connection found in stateful apps allows you to connect Rich Display widgets directly to a database table. This process is very simple because it is done through point-and-click configuration in the Visual Designer, rather than by writing code.

Let’s start with the most basic example – creating a database-driven dropdown.

In the Visual Designer, add the Dropdown Box widget. Then, set properties for choices database table and choice options field.

Screenshot

When you run a stateful app, you will see the dropdown choices loaded automatically:

Screenshot

You can apply this technique to grids, charts, texboxes with auto-complete, and other similar widgets where it makes sense to retrieve data directly from a database.

Here is a more advanced example where a database-driven dashboard is implemented using only the screen's execute() method:

Screenshot

Try it yourself here: https://noderun.com/profound-logic/responsive-sales-dashboard/

Calling other stateful Node.js modules

Multiple stateful modules can call each other using the pjs.call() API. When a call is made to another module, that module can both receive parameters and return values.

Let’s look at a simple example. The following module displays a grid of customers and allows the user to select one.

// File: customerSelection.js
function customerSelection() {
  pjs.defineDisplay("display", "customerSelection.json");

  // Load customers and display a grid
  let records = pjs.query("SELECT * FROM customers");
  display.custGrid.replaceRecords(records);  
  display.custList.execute();

  // Retrieve selected customer
  let custNum = null;
  let selectedRecords = display.custGrid.filter(records => records.selected);
  if (selectedRecords.length > 0) {
    custNum = selectedRecords[0].customerNumber;
  }

  // Return customer number
  return custNum;  
}
exports.default = customerSelection;

Another module can then call the customerSelection.js module to enable the user to select a customer from a list.

// File: app.js
function app() {
  pjs.defineDisplay("display", "app.json");

  while (true) {
    display.appScreen.execute();
    if (btnFindCustomer) {
      // Show Customer Selection and populate selected customer into a bound field
      custNum = pjs.call("customerSelection.js");
    }
  }
}
exports.default = app;

The result looks as follows:

Screenshot

Try it yourself: https://noderun.com/run/profound-logic/docs-customer-selection/

Want more?

We briefly introduced the basic features of stateful applications. For finer details, you can explore the Rich Displays documentation section, our Guides, and the Profound.js API.

Or check out more community spaces on NodeRun.com.

Questions?

Have questions about this topic? Ask for help on our NodeRun Discussion Forum.

< Previous Article Next Article >