Map field type whose keys are declared, drawn as one control per key.
Use it for a constrained-map column whose shape is fixed and small — a Phoenix attribute's
opts, a set of feature switches, a pair of coordinates. Use :json instead when the keys are
not known ahead of time.
Each declared key is rendered through the form's UI adapter in the control its :type asks for,
and the field hands back a plain map with string keys.
Example
As a field of its own, over a :map column:
field :settings, :key_map do
options [
[name: :theme, type: :select, options: ~w(light dark), label: "Theme"],
[name: :compact, type: :toggle, label: "Compact"]
]
endOr as a sub-field of a nested row, which is where a Phoenix attribute's opts lives:
nested_field :opts, :key_map do
options [
[name: :required, type: :toggle, label: "Required"],
[name: :default, type: :text, label: "Default", placeholder: "e.g. medium"],
[name: :doc, type: :text, label: "What it is for"]
]
endDeclaring keys
Keys are read from options, the same place a :select keeps the list it offers. Each key is a
keyword list or a map:
| Key | Required | Meaning |
|---|---|---|
:name | yes | the key written into the map |
:type | no (:text) | :text, :textarea, :toggle, :checkbox, :number, :select, :json, :password |
:label | no | shown above the control; humanized from :name otherwise |
:placeholder | no | placeholder text, or the empty option's label on a :select |
:options | for :select | the choices, as {label, value} pairs or bare values |
:required | no (false) | marks the key's label as a required field's label is marked |
Keys from the form state
A field of its own may take options fn state -> keys end in place of a list. The form reads it
with its state each time the field is drawn and each time its params are read, so one form can
draw different keys for different openings. A page that opens one create form per source passes
the source in defaults, and each source supplies its keys as data:
field :settings, :key_map do
options fn state -> MyApp.Sources.keys(state.defaults[:source]) end
end
send_update(MishkaGervaz.Form.Web.Live,
id: "connection-form",
defaults: %{source: "wordpress"},
reset: true
)A key the function no longer names is dropped from what the form saves. A sub-field of a nested row takes a list only.
A password key
:password draws the key through the UI adapter's password_input, with autocomplete="off".
What the key holds is never written back into the page: the control starts empty each time the
form opens, the browser keeps what is typed into it while the form is open, and a key left empty
is dropped like any blank key, so a form that edits a record asks for it again.
A key that may hold a map or a list
:json is a text control for a key whose value is usually text but may be a map, a list, a number
or a boolean — a Phoenix attribute's default, which is "medium" on one attribute and %{} on
another. A text control cannot draw a map at all, and one drawn as text would be saved back as
text, so the attribute that wanted %{} would get the string "%{}". A :json key is shown and
read as the :json field type shows and reads its value (MishkaGervaz.Form.Types.Field.Json): a
map or a list is shown as JSON, and what is typed is read as JSON when it is JSON ({}, [1],
5, true) and kept as text when it is not (medium).
Blank keys are dropped
A key left blank is omitted from the stored map rather than written as "", and a toggle left
off is omitted rather than written as false, so "not set" stays distinct from "set to nothing".
Values are cast to their declared type before they reach the changeset; see
MishkaGervaz.Form.Types.Field.Nested.
See MishkaGervaz.Form.Behaviours.FieldType, MishkaGervaz.Form.Types.Field, and
MishkaGervaz.Form.Types.Field.KeyList for the repeating version.
Summary
Types
Functions
The declared keys, normalised to maps with :name, :type, :label, :placeholder,
:options and :required.
Reads :keys if the config carries one, :options otherwise. Keys with no :name are dropped,
and so is fn state -> keys end, which only keys/2 can read.
iex> MishkaGervaz.Form.Types.Field.KeyMap.keys(%{options: [[name: :doc]]})
[%{name: :doc, type: :text, label: nil, placeholder: nil, options: [], required: false}]
keys/1, with options fn state -> keys end read with the form state.