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-wide.
Session-bound.
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:
it impossible to use database as a session storage and after application restart all session actions are expire.
it makes harder to scale 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 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)
initiateFormAction(actionCode, form, options)
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.
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'.