Actions

Actions is the core component allowing interactivity in Reblocks applications.

Actions are callbacks that are stored in a session and can be called from the browser using AJAX, POST or GET requests.

There are two types of actions:

Application actions

This kind of actions is good when you want to build a scalable application, because amount of memory they are taking does not grow with number of user sessions.

Application action should be a function which only depends on it's argument. They should not closeup any variables because the same function will be called for every application's user.

Here is an example, how to define such action which will create a new user:

(reblocks/app:defapp demo-actions
  :autostart nil)

(reblocks/app-actions:define-action create-user demo-actions (name email)
  (format t "User ~A with email ~A was created.~%"
          name email))

After that, this function can be called by name

(let ((app (make-instance 'demo-actions)))
  (reblocks/actions:eval-action app
                                "create-user"
                                (list "Bob"
                                      "bob@40ants.com"))))
=> "User Bob with email bob@40ants.com was created."

Session actions

Session actions can be a stand-alone functions or closures. This makes it easier to catch some variables and to use them when user interacts with application.

However, such closures are stored in a hash table bound to the each session. This makes a few consequences:

In the next example, we can see how it is easy to catch USER variable into the action's closure and then use it when email will be provided:

(let ((user "Bob"))
  (reblocks-tests/utils:with-session
    (let ((action-id
            (reblocks/actions:make-action
             (lambda (email)
               (format t "Changing email for user ~A to ~A"
                       user email)))))
      (reblocks/actions:eval-action nil
                                    action-id
                                    (list "bob@40ants.com")))))
.. <DEBUG> [12:11:44] reblocks/actions actions.lisp (eval-action) -
..   Calling REBLOCKS/ACTIONS::ACTION: #<FUNCTION (LAMBDA
..                                                    (
..                                                     EMAIL)) {7009B6328B}>
..   with REBLOCKS/ACTIONS::ARGUMENTS: ("bob@40ants.com")
..   and REBLOCKS/ACTIONS::ACTION-NAME: "11626:ca7db51f4b0ffb082dcc560eed1b6121707d9677"
..   
.. Changing email for user Bob to bob@40ants.com
=> NIL

With application-wide action you'll have to render user's id in the hidden field of HTML form and then to retrieve the user object from the database when action get called.

Now lets see how to use actions from the frontend!

Using at the Frontend

At the fronted you might want to call actions as the result of user's click, form submit or some other act, for example, by timer.

Reblocks provides a thin JS layer which includes following functions:

initiateAction(actionCode, options)

This function calls given action using AJAX. You have to pass optional arguments as JS object options:

{
    "method": "POST",
    "args": {},
    "url": undefined,
    "on_success": undefined,
    "on_failure": undefined,
}

Here is an example which uses make-action and renders usual HTML link as a button bound to an anonymous lisp function:

Also, you might generate a link using make-action-url function and use it as HREF argument of any link on a page. Click on such link will trigger the action.

Here I intentionally didn't add a "button" class to show that this is just a link.

Pay attention how this example flickers when you click the link. This is because the whole iframe's content is reloaded when you click the link. In the previous example this is not happening because click leads to an AJAX requst and only a piece of page get updated.

In the next example, we'll pass arguments to our action. To do this, we have to call make-action and render JS code manually:

initiateFormAction(actionCode, form, options)

This version of function accepts a form jquery object as the second argument. Options argument has the same meaning as for initiateAction function, but args attrbute is formed from the serialized form fields and default method is taken from the form's method attribute.

Next example shows how to process a form submit, using a callback, registered with make-js-form-action:

API

Evaluates the action that came with the request.

First it resolves action by trying to find ACTION-NAME inside session or current app's actions. If actions wasn't found, then eval-action calls on-missing-action generic-function. Otherwise it applies arguments to the action callback.

Must be overridden by application to prevent default behaviour - redirect to a root of the application. The new method should determine the behavior in this situation (e.g. redirect, signal an error, etc.).

Accepts a function or an existing action. If the value is a function, adds it to the session actions and returns its unique code as a string. Otherwise, checks if the action already exists. If it does, returns the argument as is. If it does not, signals an error.

Returns JS code which can be inserted into onclick attribute and will execute given Lisp function on click.

It accepts any function as input and produces a string with JavaScript code.

Returns JS code which can be inserted into onsubmit form's attribute.

It accepts any function as input and produces a string with JavaScript code.

On form submit given action will be executed and all input values will be passed as arguments.

function
function-or-action-code &key (keep-query-params t)

Accepts action code and returns a URL that can be used as href attribute of HTML link.

For example:

(reblocks/app:defapp test-app :autostart nil)

(let ((reblocks/request::*request*
        (lack.request:make-request
         (list :path-info "/blah/minor"
               :headers (make-hash-table)))))
  (reblocks/app:with-app (make-instance 'test-app)
    (reblocks/app-actions:define-action test-action test-app ())
    (reblocks/actions:make-action-url "test-action")))
=> "/blah/minor?action=test-action"

If KEEP-QUERY-PARAMS is true (default), then all query arguments are preserved and will be passed as arguments to the action.

A string used to pass actions from a client to the server. See 'reblocks/request:get-request-action'.