Aurora.Uix.Templates.Basic.Renderers.ManyToMany (Aurora UIX v0.1.5)

Copy Markdown

Renders many-to-many association fields as a list of checkboxes over the related records.

Membership is a set of existing records, not a child the parent owns, so neither of the other association renderers fits: there is no local foreign key for ManyToOne's single select to bind to, and OneToMany's "create a new child" flow is the wrong verb — the user picks from records that already exist. A checkbox list expresses exactly the available operations: the checked set is the membership, so adding and removing are the same gesture.

Checkboxes rather than a <select multiple>: multi-select needs a modifier-key gesture that is undiscoverable and unusable on touch, conveys state only through a background colour, and has nowhere to host bulk controls. The wire format is identical — every box shares the parent[field][] name, so only checked values are submitted, exactly as only selected options are — which is why the sentinel below and the host's persistence contract are unchanged.

Reads come from the preloaded association (filter_preloads/1 puts the field in parsed_opts.preload), and the candidate list comes from the related resource's own list_function. No join-aware query is involved: both backends resolve the join table during preload.

Key Features

  • :form renders one checkbox per candidate record; the checked boxes are the current membership.
  • Submits in the same POST as the parent, under parent[field][].
  • Emits a hidden empty-value sentinel so that de-selecting everything still submits the key, which is what makes clearing the last membership possible at all.
  • Ships :default_toggle_all, a tri-state checkbox beside the label: checked when every candidate is a member, unchecked when none is, and a dash when only some are. Clicking a checked toggle clears the membership; clicking it in either other state selects everything.
  • Registers three action groups through Aurora.Uix.Actionlabel (holding the toggle), header and footer, the latter two empty — so a host adds, replaces or removes controls in any of the three strips from the layout DSL field options.
  • Honours the option_label: field option through the shared Aurora.Uix.Templates.Basic.Helpers.get_select_options/1; when the host declares none, the label falls back to a conventional display column (:name, :title, …) of the related resource's :index layout, instead of the record's raw primary key.
  • :show renders only the current membership as a plain, read-only list — no checkboxes, no candidates that aren't members — with the same dt("No items to show") empty-state message one_to_many uses for its own read-only list.
  • Renders nothing when the related schema is not a registered Aurora UIX resource.

Key Constraints

  • The library is transport-only for writes: it renders the input name and forwards the submitted list of primary keys untouched, and never builds a changeset. Persisting membership is the host's responsibility — put_assoc/4 in an Ecto changeset, or argument + change manage_relationship(..., type: :append_and_remove) in an Ash action.
  • Because of the sentinel, the submitted list always carries one blank entry. The host must reject it. On Ash the argument also needs constraints: [nil_items?: true], since Ash casts a blank to nil and otherwise rejects the list with "no nil values" before any change runs.
  • An Ecto host must declare on_replace: :delete on the association, or put_assoc/4 raises. That option is also what deletes the join rows — removing a member must never delete the related record itself.
  • html_type stays :select: the field is a set of options, only the widget changed, and get_select_options/1 dispatches on it.
  • The toggle rides the parent form's phx-change="validate", identified by _target, and costs one extra list_function call per click. It must not use phx-click: a click on a checkbox fires click and then change, and the trailing change would re-validate with the pre-click membership and undo the toggle. Nor can it be resolved client-side — the result has to reach auix.form.params or the next phx-change would discard it.
  • Requires the related resource to be registered, and the parent to be preloaded for :show and for editing a persisted record.

Summary

Functions

Renders a many-to-many association field.

Functions

render(assigns)

@spec render(map()) :: Phoenix.LiveView.Rendered.t()

Renders a many-to-many association field.

Parameters

  • assigns (map()) - LiveView assigns containing:
    • :field (map()) - Field definition with association details in :data.
    • :auix (map()) - Aurora UIX context with form, entity and layout configuration.

Returns

Phoenix.LiveView.Rendered.t() - Rendered many-to-many association component.