Reactive Server Pages: A shopping cart

A simple shopping cart

The application (available online) is composed of a menu with four options:

  • Buy shows a list of available items to buy.
  • Client requests the name and address of the client with a form.
  • Status shows the current items in the cart, and also whether the client has filled out the form.
  • Quit terminates the application.

The whole structure of the application resides in the file init.lua in the cart/ directory shown below. The external file catalog.lua is loaded into the globally accessible CATALOG (line 5). The application also requires auxiliary files to implement each of the menu options (lines 26-28).

(The call to meta.dofile makes the loaded files to follow the semantics of Lua modules, and at the same time, the LuaGravity meta language, where variables starting with underscores behave reactively.)

The pub interface with the client (lines 7-12) defines the table qtt to hold the quantity of each item in cart, the current visible page _page, and the client info _name and _addr. Lines 13-15 set the initial quantities to zero. As the pub table contains unsafe data provided by the client, lines 17-32 deal with validating and defining private safer versions of them: QTT, _NAME, _ADDR, and _page.

The _html variable holds the whole interface of the application (lines 34-53), and only changes after receiving the event quit (lines 55-57). However, _html depends on the variable _page (line 48), which is initially set to buy._html (lines 9 and 30). When the user selects one of the options in the menu (lines 43-45), the _page variable is set to the proper subpage, and _html reacts to this change.

 1:  local meta = require 'luagravity.meta'
 2:
 3:  local _assert, _tonumber, _tostring = L(assert), L(tonumber), L(tostring)
 4:
 5:  CATALOG = dofile 'catalog.lua'
 6:
 7:  pub = meta.new {
 8:      qtt   = meta.new{},
 9:      _page = 'buy',
10:      _name  = '',
11:      _addr  = '',
12:  }
13:  for k, t in pairs(CATALOG) do
14:      pub.qtt['_'..k] = 0
15:  end
16:
17:  QTT = meta.new{}
18:  for k, v in meta.pairs(pub.qtt) do
20:      QTT[k] = _assert(_tonumber(v))
21:  end
22:
23:  _NAME = _assert(_tostring(pub._name))
24:  _ADDR = _assert(_tostring(pub._addr))
25:
26:  buy    = meta.dofile('buy.lua',    _M)
27:  client = meta.dofile('client.lua', _M)
28:  status = meta.dofile('status.lua', _M)
29:
30:  _page = OR( AND(EQ(pub._page, 'buy'), buy._html),
31:              OR( AND(EQ(pub._page, 'client'), client._html),
32:                  OR( AND( EQ(pub._page, 'status'), status._html), 'invalid page' )))
33:
34:  _html = [[
35:  <html>
36:  <body>
37:  <center>
38:  <table width='80%' border='0' cellspacing='0'>
39:  <tr>
40:      <td width='20%' valign='top'>
41:      <ul>
42:          <b>Menu:</b>
43:          <li><a href="nop?_page=buy">   Buy   </a>
44:          <li><a href="nop?_page=client">Client</a>
45:          <li><a href="nop?_page=status">Status</a>
46:          <li><a href="quit">            Quit  </a>
47:      </ul>            </td>
48:      <td width='80%' valign='top'>]].._page..[[</td>
49:  </tr>    </table>
50:  </center>
51:  </body>
52:</html>
53:]]
54:
55:await 'quit'
56:
57:_html = [[Goodbye!]]

In this application, we expect that each subpage provides an _html variable with its contents. This practice and the chosen name for the variable are not enforced by RSP, but eases the modularization of the application. We also follow the Lua module policy that creates an environment to required files where their globals are visible to the rest of the application. However, LuaGravity does not support local reactive variables, so we use two underscores on their names to suggest they are locals.

Status Page

The simplest subpage shows the Status of the current shopping cart, and only sets its _html to depend on data in the other pages:

 1:  __valid = AND( NEQ(_NAME, ''), NEQ(_ADDR, '') )
 2:
 3:  _html = [[
 4:  <ul>
 5:      <li>Personal data: ]]..OR(AND(__valid, 'OK!'), 'PENDING...')..[[
 6:      <li>Shopping Cart: ]]..buy._tot_items..[[ items = $ ]]..buy._tot_cost..[[
 7:  </ul>
 8:  ]]

The boolean variable __valid tells whether the client has filled out the form correctly. In line 5, we output OK! or PENDING... depending on that value.

(The expression OR(AND(__valid,'OK'), 'PENDING...') is the reactive version for `__valid and 'OK' or 'PENDING...', as these operators cannot be redefined in Lua.)

The variables buy._tot_items and buy._tot_cost are defined in the module buy, and keep how many items and total cost are in the cart.

The module sets the _html variable once, and no bookkeeping is needed, as a change in any of its dependencies makes it react to update its value.

Client Page

The subpage with the Client form defines four reactive variables to be used by other pages: As usual, the variable _html holds the contents of the page with the form. The variables _NAME and _ADDR hold the validated information supplied by the client in the form.

Form validation is a recurrent pattern in Web pages, and not always easy to handle. In this example, we use a technique possible with reactive programming to cope with that. In the end of the _html definition (line 11), we concatenate the reactive variable __html_errors (which is initially blank, line 1). Whenever we set this variable with a string representing an error, the _html is updated and shows this error to the client.

In order to validate the form input, we spawn a concurrent reactor (lines 15-26) that waits for the event 'client_submit'. This event is triggered whenever the form is submitted (line 9), awaking the spawned reactor (line 17). The form is then validated and sets __html_errors accordingly. The loop inside the reactor is restarted, making it wait for the next form submission.

 1:  __html_errors = ''
 2:
 3:  _html = [[
 4:  <p>Please, enter your personal data:
 5:  <p><form action='client_submit'>
 6:  <table>
 7:  <tr><td>Name:    <td><input name='_name' type='text' value=']].._NAME..[['/></tr>
 8:  <tr><td>Address: <td><input name='_addr' type='text' value=']].._ADDR..[['/></tr>
 9:  <tr><th colspan='2'><input value='Ok!' type='submit'/></tr>
10:  </table>
11:  <p><font color='red'>]]..__html_errors..[[</font>
12:  </form>
13:  ]]
14
15:  spawn(function()
16:      while true do
17:          await 'client_submit'
18:          __html_errors = ''
19:          if _NAME() == '' then
20:              __html_errors = __html_errors() .. '<br>Field name is mandatory!'
21:          end
22:          if _ADDR() == '' then
23:              __html_errors = __html_errors() .. '<br>Field address is mandatory.'
24:          end
25:      end
26:  end)

Buy Page

This page shows a table with the available items in the CATALOG defined in the main file. The module exports three variables to the application: _html, _tot_items, and _tot_cost.

This page's _html uses the variable __items (line 23), which is built in the end of the file (lines 31-46) by iterating over the catalog and appending each item to that. Note that we could not just do __items = __items .. [[...]] as that would create a dependency cycle, hence the use of src in lines 35-37.

Each item in the catalog has its associated quantity reactive variable in the table pub.qtt (defined in the main file), which can be changed by the user in the form (line 42, note that pub is omitted). Furthermore, the main file also defines the validated table QTT, which is accessed in line 34, and both _tot_items and _tot_cost depend on (lines 35-36). Also remember that the status._html depends on _tot_items and _tot_cost. Hence, a change in any _qtt is propagated up to status._html, and possibly the main _html.

 1:  local meta = require 'luagravity.meta'
 2:
 3:  _tot_items = 0
 4:  _tot_cost  = 0
 5:
 6:  __items = ''
 7:
 8:  _html = [[
 9:  <form>
10:  <table width='80%' border=1 cellspacing=0>
11:      <tr>
12:          <th>Item
13:          <th>Description
14:          <th>Price
15:          <th>#
16:          <th>Total
17:      </tr>
18:  ]] .. __items .. [[
19:      <tr>
20:          <td><br>
21:          <td><br>
22:          <td><br>
23:          <td align='right'>]].._tot_items..[[
24:          <td align='right'>]].._tot_cost..[[
25:      </tr>
26:      <tr><th colspan=5><input type='submit' value='ok'/></tr>
27:  </table>
28:  </form>
29:  ]]
30:
31:  for k, t in pairs(CATALOG)
32:  do
33:      k = '_'..k
34:      local _qtt = QTT[k]
35:      _tot_items = _tot_items.src + _qtt
36:      _tot_cost = _tot_cost.src + _qtt*t.price
37:      __items = __items.src .. [[
38:      <tr>
39:          <td>]]..t.title..[[
40:          <td>]]..t.description..[[
41:          <td align='right'>]]..t.price..[[
42:          <td align='right'><input size='2' name='qtt.]] ..k.. [[' value=']] .._qtt.. [['>
43:          <td align='right'>]].._qtt*t.price..[[
44:      </tr>
45:      ]]
46:  end