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-pinIs
formThe 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.
keyThe data-key of the closest element around the button: which row was clicked. Empty when there is none.
connectionThe request, for the httpconnection blocks such as cookies and headers.
paramsThe :name segments of the page's URL.
queryThe page's query string.
languageThe language of the page.

An action has gates of its own

  1. The scopes of the page it was posted to must admit the visitor.
  2. If the action has scopes, those must admit the visitor too.
  3. If it has none, the scopes of every web_page in the blueprint must admit the visitor.
  4. 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.

BlockDoes in the browserIts pins
web_replaceSwaps an element for new HTML. The usual answer to an edit.key or target, html
web_removeRemoves an element after a short fade. The usual answer to a delete.key or target
web_insertAdds HTML at the end or start of an element, or before or after it.key or target, html, position
web_updateGives an element new contents and keeps the element: a cart counter, a total.key or target, html
web_errorsShows messages next to the form's fields.errors, message, target
web_redirectSends the visitor to another page.url, status

An update needs a key or a target

  • key finds [data-key="..."]. This is the way to reach a row.
  • target is a CSS selector, such as #cart-count, used when key is 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 update in its console and carries on, and a web_remove stays 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

positionThe new HTML lands
append (default)Inside the element, as its last child.
prependInside the element, as its first child.
beforeRight before the element, as a sibling.
afterRight 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 an http:// or https:// URL.
  • Refused with an error that stops the flow: javascript: and any other scheme, and //host or /\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, 302 unless the status pin says 301, 303, 307 or 308. This is the one update block that also works in a web_page flow.

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 by key, web_remove(key).
  • Edit: web_action(save), storage update, web_template of the row with the updated document, web_replace(key, html).
  • Add: web_action(add), storage create, web_template of the new row with its guid as key, web_insert(target "#products").
The three flows
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_redirect when 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.