Skip to content

Form button with an Ajax handler

Version v1

Initialising viewer…

You can apply this model as a recipe (Drupal 10.3 or later) to your own Drupal site:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
## Import recipe
composer require drupal-eca-recipe/eca_lib_0042

# Apply recipe with Drush (requires version 13 or later):
drush recipe ../recipes/eca_lib_0042

# Apply recipe without Drush:
cd web && php core/scripts/drupal recipe ../recipes/eca_lib_0042

# Rebuilding caches is optional, sometimes required:
drush cr

Purpose

This model adds a "Topic" text field and a "Generate draft" button to the Article node form. Selecting the button sends an Ajax request that fills the title, the body summary and the body text in one go, without reloading the page and without saving the node.

It is the counterpart to Field widget actions with ECA. A field widget action can only ever write back to the one field its button belongs to. A form button with an Ajax handler has no such limit, so this is the pattern to reach for when one interaction has to populate several fields at once.

The field, the button and everything that happens when it is selected come from the model alone. No custom code, and no form display configuration. The values are calculated with plain tokens, so the example is deterministic and uses no AI.

How it works

The "Build form" event fires whenever an Article node form is built. Three actions run in order: "Form: add text field" adds the eca_topic field, "Form: add submit button" adds the eca_generate button, and "Form: add Ajax handler" attaches Ajax behavior to that button.

The Ajax handler is configured to validate only eca_topic. That matters on a fresh node form, because without it the empty required Title would raise a validation error and the button would never get through. It also leaves the topic readable as [current_form:values:eca_topic] while the rest of the form stays unvalidated.

Selecting the button submits the form over Ajax, which makes the "Submit form" event fire. A "Form: compare triggered submission" condition checks that the trigger name is eca_generate, so an ordinary Save is not affected. Three "Form field: set value" actions then write the title, the body summary and the body text. Finally the form is rebuilt and the whole form is returned in the Ajax response, so all three fields come back filled.

Why the topic field is not marked required

The topic is needed for the button to produce anything useful, so marking the field required looks like the obvious answer. It is the wrong one, and the reason is worth knowing.

"Form: add text field" sets a plain #required on a form element that becomes part of the node form. Drupal validates required elements on every submission, and the errors are only narrowed afterwards, for the button that carries a limited validation scope. The "Generate draft" button carries one, so it would behave correctly. The ordinary Save button does not, so nothing would narrow the error there: an author who never touches the button could no longer save an Article until they filled in a field that exists only to feed this sample.

Instead the field stays optional and the requirement is enforced only for the button. The "Validate form" event fires on the Ajax submission, a "Form: compare triggered submission" condition limits the branch to eca_generate, a "Chain action for AND condition" adds the second condition, and "Form field: compare submitted value" checks whether the topic is empty. If it is, "Form field: set validation error" puts the message on the topic field. The author sees exactly what a required field would have shown, at exactly the moment it matters, and saving an Article is never affected.

Why the node is not saved

"Form: add submit button" gives its button its own submit handler instead of the one the node form uses, and "Form: add Ajax handler" appends a handler that flags the form for rebuild. The node form's own save handler is never part of that chain, so the button computes and redisplays values without ever writing an entity.

Points worth copying

  • Field names use dot notation, for example body.0.value rather than body[0][value]. Square brackets collide with token replacement, and dot notation is what "Form field: set value" expects for nested form elements.
  • The values are written into the submitted input, which is what a rebuilt form reads from. This is why the action works after an Ajax request while "Form field: set default value" would not.
  • Leaving the Ajax target empty refreshes the whole form. A target can be set to refresh a smaller part, but then only fields inside that part are redisplayed.
  • The button and the text field are added on every form build, including the rebuild, so both survive the Ajax round trip.
  • The body is filled with HTML, so "Strip tags" is off for that action. "Filter XSS" stays on, which matters because the value interpolates whatever the author typed into the topic. Note that the XSS filter keeps a fixed, fairly short list of tags: a, em, strong, cite, blockquote, code, ul, ol, li, dl, dt and dd. Anything else, including p, br and the heading tags, is removed even though the text format would have allowed it. The markup here stays inside that list.
  • The body summary is left as plain text. It is a plain textarea with no text format of its own, so markup there would show up as literal characters rather than being rendered.
  • No text format is assigned. The widget's own format selector already defaults to Basic HTML on a standard site, which permits every tag used here, and it keeps whatever the author chose. Writing a format into the model would override that choice and would break on a site that does not have that format.

Requirements

This recipe installs the eca, eca_base, eca_form and modeler_api modules. It needs no extra field beyond the title and body that come with the Article content type.

It also applies core's article_content_type recipe, which provides that content type together with its body field. From Drupal 11.4 the Standard profile no longer creates it, and all three events here are restricted to the article bundle, so without this step nothing would fire at all and the form would look untouched.

Applying a recipe from within another one is not a pure no-op on an existing site. Configuration that is already present is never overwritten, but the included recipe also runs its own config actions, and those reset the components of the Article form and view displays to core's defaults. On a site whose Article displays have been customized, apply this recipe to a copy first.

To use the model on another content type, change the bundle on all three events. To use it on a form that is not a node form, clear the entity type and bundle restrictions and set a form ID instead.

Dependencies

  • config
    • field.field.node.article.body
    • field.field.node.page.body
    • field.storage.node.body
    • node.type.article
    • node.type.page
  • module
    • eca_base
    • eca_form
    • modeler_api
    • node
    • text

Used plugins

Events

Conditions

Actions