Actions
Actions are 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-wide.
Session-bound.
Application actions
This kind of action is good when you want to build a scalable application, because the amount of memory they are taking does not grow with the number of user sessions.
An application action should be a function which only depends on its argument. They should not close up any variables because the same function will be called for every application user.
Here is an example of how to define such an 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 stand-alone functions or closures. This makes it easier to catch some variables and to use them when the user interacts with the application.
However, such closures are stored in a hash table bound to each session. This has a few consequences:
it is impossible to use a database as session storage and after application restart all session actions expire.
it makes it harder to scale the application, because now you need to redirect each user to a single backend, using sticky sessions on the balancer.
session actions tend to accumulate over time and eat more memory.
In the next example, we can see how easy it is to catch the USER variable
into the action's closure and then use it when an email is provided:
(let ((user "Bob"))
(reblocks-tests/utils:with-test-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
=> NILWith an application-wide action you'll have to render the user's id in a hidden
field of the HTML form and then retrieve the user object from the database
when the action gets called.
Now let's see how to use actions from the frontend!
Using at the Frontend
At the frontend you might want to call actions as the result of a user's click, form submit or some other act, for example, by timer.
Reblocks provides a thin JS layer which includes the following functions:
initiateAction(actionCode, options)initiateFormAction(actionCode, form, options)
initiateAction(actionCode, options)
This function calls a given action using AJAX. You have to pass optional
arguments as a JS object options:
{
"method": "POST",
"args": {},
"url": undefined,
"on_success": undefined,
"on_failure": undefined,
}Here is an example which uses make-action and renders a usual HTML link as a button
bound to an anonymous Lisp function:
Also, you might generate a link using the make-action-url function and use it as the HREF
argument of any link on a page. Clicking on such a link will trigger the action.
Here I intentionally didn't add a "button" class to show that this is just a link.
Pay attention to 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 the click leads to an AJAX request and only a
piece of the page gets 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 the function accepts a form jQuery object as the second argument.
The options argument has the same meaning as for the initiateAction function,
but the args attribute is formed from the serialized form fields and the default
method is taken from the form's method attribute.
The next example shows how to process a form submit, using a callback
registered with make-js-form-action: