MishkaGervaz.Form.Types.Field.KeyMap (MishkaGervaz v0.0.6)

Copy Markdown View Source

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"]
  ]
end

Or 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"]
  ]
end

Declaring 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:

KeyRequiredMeaning
:nameyesthe key written into the map
:typeno (:text):text, :textarea, :toggle, :checkbox, :number, :select, :json, :password
:labelnoshown above the control; humanized from :name otherwise
:placeholdernoplaceholder text, or the empty option's label on a :select
:optionsfor :selectthe choices, as {label, value} pairs or bare values
:requiredno (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

One declared key, as written in options.

Functions

The declared keys, normalised to maps with :name, :type, :label, :placeholder, :options and :required.

keys/1, with options fn state -> keys end read with the form state.

Types

key()

@type key() :: keyword() | map()

One declared key, as written in options.

Functions

keys(config)

@spec keys(map() | keyword() | nil) :: [map()]

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(config, state)

@spec keys(map() | keyword() | nil, map()) :: [map()]

keys/1, with options fn state -> keys end read with the form state.