Web Actions
A button with data-action="delete" posts to the page's own URL, the blueprint runs its web_action named delete, and that flow answers with update blocks: replace a row, remove it, insert one, update a counter or redirect. Only those parts of the page change.
The action block receives what the button sent
A web_action is reached only by a POST to the URL of a web_page in the same blueprint, with the field _action set to its name. The script in the page sends that for you. Give each action a name of its own within its blueprint. The name is the block's event setting, set in the code view like a page's URL: flow delete_row = web_action with {event: "delete"} {.
| Out-pin | Is |
|---|---|
form | The fields the form or button sent. A repeated name, or a name ending in [], is a list. _action and _key are taken out. See Forms. |
key | The data-key of the closest element around the button: which row was clicked. Empty when there is none. |
connection | The request, for the httpconnection blocks such as cookies and headers. |
params | The :name segments of the page's URL. |
query | The page's query string. |
language | The language of the page. |
An action has gates of its own
- The scopes of the page it was posted to must admit the visitor.
- If the action has
scopes, those must admit the visitor too. - If it has none, the scopes of every
web_pagein the blueprint must admit the visitor. - A request from another website is refused, whatever the scopes say.
The third rule means a public page is never the way in to an action written for the admin page next to it. It also means that a public action in a blueprint that also has an admin page needs scopes: ["*public"] of its own, or the public cannot use it. Escaping and Security says what the visitor sees when refused.
Six blocks answer the browser
They apply in the order they ran. Anywhere inside the action's flow counts, including a function the action calls.
| Block | Does in the browser | Its pins |
|---|---|---|
web_replace | Swaps an element for new HTML. The usual answer to an edit. | key or target, html |
web_remove | Removes an element after a short fade. The usual answer to a delete. | key or target |
web_insert | Adds HTML at the end or start of an element, or before or after it. | key or target, html, position |
web_update | Gives an element new contents and keeps the element: a cart counter, a total. | key or target, html |
web_errors | Shows messages next to the form's fields. | errors, message, target |
web_redirect | Sends the visitor to another page. | url, status |
An update needs a key or a target
keyfinds[data-key="..."]. This is the way to reach a row.targetis a CSS selector, such as#cart-count, used whenkeyis empty.- Neither one is an error that stops the flow. The action fails, and the answer tells a blueprint developer why.
- An element that is not on the page is not an error. The browser logs
no element for updatein its console and carries on, and aweb_removestays silent. - These blocks work only in a flow started by
web_action, and fail anywhere else. In a simulation there is no browser, so they do nothing.
web_replace swaps the element itself
The new markup is the element, outer tag included. When the old element has a data-key and the new markup is a single element without one, the browser keeps the old key on it. A row stays reachable after an edit, even if its template does not mention the key.
web_update keeps the element and its attributes
The contents become html. It is HTML, also when you meant plain text: pass user input through a web_template first, so it gets escaped. A number from a stored document is fine as it is.
web_insert has four positions
position | The new HTML lands |
|---|---|
append (default) | Inside the element, as its last child. |
prepend | Inside the element, as its first child. |
before | Right before the element, as a sibling. |
after | Right after the element, as a sibling. |
Any other position is an error. Give an inserted row a data-key through the key pin of web_template, so later actions can reach it.
web_errors writes next to the fields
errors is an object of field name to message, and message is for the form as a whole. Forms and Validation has the details.
web_redirect accepts paths and http(s) URLs
- Allowed: a path on this site (
/thanks,?page=2,#top) or anhttp://orhttps://URL. - Refused with an error that stops the flow:
javascript:and any other scheme, and//hostor/\host, which are another host dressed as a path. - In an action the browser navigates there. Without JavaScript it is a
303. - On a page request the answer is a real redirect,
302unless thestatuspin says 301, 303, 307 or 308. This is the one update block that also works in aweb_pageflow.
Render a row again with web_template
To answer an edit, render the same row template with web_template, hand it the updated document and its key, and pass its html to web_replace. For a stored row, the template is just {{> product-row}}.
- Delete:
web_action(delete), storage remove bykey,web_remove(key). - Edit:
web_action(save), storage update,web_templateof the row with the updated document,web_replace(key, html). - Add:
web_action(add), storage create,web_templateof the new row with its guid as key,web_insert(target "#products").
blueprint "Products admin"
flow page = web_page with {event: "admin/products", scopes: ["*loggedin"]} {
web_render(page.connection, title = "Products", template = "{{> products-page}}")
}
flow delete_row = web_action with {event: "delete"} {
remove@products(guid: delete_row.key)
web_remove(key: delete_row.key)
}
flow save_row = web_action with {event: "save"} {
let {name, price} = save_row.form
updated = update@products(guid: save_row.key, mutations: mutations{name: name, number price: price})
web_replace(
key: save_row.key,
html: web_template(data: updated.object, key: save_row.key, template = "{{> product-row}}")
)
}
flow add_row = web_action with {event: "add"} {
let {name, price} = add_row.form
created = create@products(mutations{name: name, number price: price})
web_insert(
target = "#products",
html: web_template(data: created.object, key: created.guid, template = "{{> product-row}}")
)
}web_template is a data block with no flow pins. With key, the fragment's first element gets that data-key, and one that already has its own keeps it. Plain text is wrapped in a <div> so it can still be reached. A template error gives an empty html, success false and the message on error.
A web_render in an action answers with a whole page
For an order confirmation, or anything where most of the page changes, end the action in a web_render.
- With JavaScript the browser takes the page's title and body, and scrolls to the top. The
<head>stays as it was, and scripts in the new body do not run. - Without JavaScript it is simply the page the visitor gets.
- Update blocks that ran before it apply first, and then the page replaces the body.
- Choose this over a redirect when the page shows a result you do not want to store under a URL. Choose
web_redirectwhen a reload should not send the form again.
An action with no updates leaves the page alone
It answers an empty list, and the page stays as it is. That is the right answer for something like logging a click or a load action that found nothing. Without JavaScript the visitor is sent back to the page with a 303.
A failed action tells the visitor little and you everything
A flow error answers a short message and logs the reason. The browser writes it to the console, brings back a row that data-optimistic hid, and fires a rual:web-error event. If the form has a [data-error-for="_form"] element, the message appears there: your form's data-msg-failed text if it has one. For a blueprint developer the answer also holds the full reason. If the visitor's session has run out, the answer is a 401 and the page reloads, which leads to the login.
Limits
- HTML an action returns is inserted as markup only. Its scripts do not run. Put behaviour in data attributes, or in a script the page already loads.
- Actions take form fields and JSON. Upload files through the upload endpoints and pass the result to the action.
- An action belongs to the page's blueprint. A page cannot run another blueprint's actions directly. Call a shared function from the action instead: update blocks inside it still answer the same request.
- A blueprint with pages of different scopes needs scopes on each action, as an action without them requires what every page requires.
Next steps
Forms and Validation What the browser checks, and how server errors reach the right field. Markup Reference Triggers, confirmations, loading text and optimistic removal. Common Blueprint Patterns Flows you will combine with web pages: validation, branching, calling functions.Frequently asked
How do I update one row of a page without reloading it in RUAL?
Give the button data-action="name" and add a web_action block with that name to the page's blueprint. The action runs only for visitors its page (and its own scopes) allow, receives the row's data-key and answers with update blocks: web_remove removes the row, web_replace swaps it for a row rendered again with web_template, web_insert adds a new one. Only that piece of HTML is sent back.
What can a RUAL web action answer with?
Updates, applied in order: web_replace swaps an element, web_remove removes one, web_insert adds HTML, web_update changes an element's contents, web_errors shows messages on a form and web_redirect sends the visitor elsewhere. A web_render in an action answers with a whole new page, and an action with no updates leaves the page as it is.