JSON input

v0.0.10

v0.0.10StableMobStatelessApache-2.0
Installation mix mishka.ui.gen.mob json_input
Source View Source

The Mishka Chelekom Mob JSON Input is a multiline text field for JSON that shows an error state when the text does not parse, on iOS and Android. It is the native port of the headless JSON Input and runs on Mob.

Overview of the Mob JSON Input component for iOS and Android

The field holds raw text. Your screen owns that text and receives it on every keystroke. On each render, JSON Input parses the text with Jason.decode/1 and styles the result. Valid and blank text look normal. Invalid text turns the border to the error color and prints the parser's message under the field.

The web component leaves parsing to the server and runs no JavaScript. This port keeps the same division of labor: your Elixir code parses the text with Jason, and the native field only draws the result. The component calls Jason.decode/1, so your app needs Jason.

The field never reformats the text as you type, because that would move the cursor out from under you. Formatting is a deliberate action, such as a button or a blur, and format/1 does it. With lines above 1 the return key inserts a newline and does not submit, as a textarea does.

The screen owns the state, and events arrive in handle_info/2. See the Mob documentation for how screens work.

Generate the JSON input component

Run this from your Mob app's root to add the component to lib/<app>/components/:

bash
mix mishka.ui.gen.mob json_input

The generator registers the tag, so you can use it in ~MOB markup. It also copies the shared Event helper module that JSON Input uses to wire on_change. The examples on this page use the mishka_ prefix of the showcase app (--module-prefix mishka_), so the tag is <MishkaJsonInput>. Without a prefix, the tag is <JsonInput>.

Valid JSON in a JSON input

Parsing is Jason's job, the same division of labor the web version keeps. The field reports each keystroke as {:change, :json, text}, the screen stores the raw text, and validate/1 returns {:ok, term} for valid JSON.

Valid JSON in a JSON input

JSON input example card titled Valid, and validated on the server, with a five row box holding an object with keys name and stars, and the text Parses: true
elixir
<MishkaJsonInput value={@json} lines={5} on_change={:json} id="config" />

# The field never reformats as you type — that would move the cursor out
# from under the user — so the screen just holds the raw text.
def handle_info({:change, :json, text}, socket) do
  {:noreply, Mob.Socket.assign(socket, :json, text)}
end

MishkaJsonInput.validate(@json)
#=> {:ok, %{"name" => "mishka", "stars" => 42}}

Invalid JSON shows the parser message

Invalid text turns the border and the message to the error color, and the parser's own message appears under the field. Both use the :error theme token, so they follow the light and dark themes. A blank field is not an error, because an untouched field is not a mistake.

Invalid JSON shows the parser message

JSON input example card titled Invalid shows the parser's own message, with a red bordered box holding broken JSON and a red error about byte position 9
elixir
<MishkaJsonInput value={@broken} lines={3} on_change={:broken} id="json-broken" />

# The border reddens and the parser's own message appears underneath.
# Both use the :error THEME token, so they follow light and dark.

Disabled JSON input

A disabled field is inert, so you cannot type into it. JSON Input sets enabled={false} on the field and drops the on_change handler.

Disabled JSON input

JSON input example card titled Disabled, with a gray three row box holding an object with keys name and stars, and a note about disabled fields
heex
<MishkaJsonInput value={@json} disabled={true} />

Validate and format JSON in your screen

validate/1 returns {:ok, term}, {:error, message} or :empty. Blank text is :empty and not an error. A bare scalar such as 42 is valid JSON and returns {:ok, 42}. format/1 pretty prints the text, or returns it untouched when it does not parse.

elixir
MishkaJsonInput.validate(~s({"a": 1}))
#=> {:ok, %{"a" => 1}}

MishkaJsonInput.validate("   ")
#=> :empty

MishkaJsonInput.validate("42")
#=> {:ok, 42}

MishkaJsonInput.format(~s({"a":1}))
#=> "{\n  \"a\": 1\n}"

MishkaJsonInput.format("{oops}")
#=> "{oops}"

invalid?/2 decides whether the field shows its error state. An explicit invalid prop wins, and otherwise the parse decides. invalid: true on text that parses means you know something the parser does not, such as a schema that rejected it. In that case the field only tints, unless you also pass an error_text to show.

A page can show several of these fields at once. Give the field an id and error_id(id) returns the test tag of its message.

elixir
MishkaJsonInput.error_id("config")
#=> "config-error"

Props

PropTypeDefaultDescription
valuestring""The raw text. Never reformatted as you type.
linesinteger6Height of the field in rows.
placeholderstring"{ }"Hint shown while the field is empty.
invalidbooleanDerivedForces the error state. Otherwise validate/1 decides.
error_textstringThe parser's messageWhat to show under the field.
show_errorbooleantrueWhether to render the message at all.
disabledbooleanfalseMutes the field and removes its handler.
on_changeevent tagNoneSends {:change, tag, text} on every keystroke.
error_colorcolor token or ARGB int:errorThe invalid border and message.
backgroundcolor:surfaceThe field background.
border_colorcolor:borderThe field border when it is valid.
border_widthnumber1The field border width.
paddingspacing:space_smPadding inside the field.
idstringnilTest tag on the field. The message gets <id>-error.

Events

Your screen receives {:change, tag, text} in handle_info/2 on every keystroke. The field reports nothing without on_change, and a disabled field has no handler at all. Store the raw text and leave it alone.

elixir
def handle_info({:change, :json, text}, socket) do
  {:noreply, Mob.Socket.assign(socket, :json, text)}
end

Differences from the web component

  • name and form are not ported, because they are form plumbing.
  • id is ported as a test handle, and the error message gets <id>-error.
  • Multiline input works on Android only. See the warning in the overview for iOS.