Build in-place editable forms comprised of fields.
An in-place editable form is a form representing an existing entity where each individual field is in "Displaying" state
initially, but can be switched to "Editing" state by clicking on it.
The updated entity is usually being validated and returned onBlur
.
If you're looking for a classic form which can represent a non-existing entity (like a sign up form) and which is
comprised of editable fields, take a look at Uniform.Classic
module.
Unsurprisingly, a Form
is a collection of fields
which produces an output
when filled with values
.
Of course, this does nothing by itself. It needs to have some fields
!
type alias User =
{ name : String
, age : Int
}
type alias Values =
{ name : String
, nameState : Uniform.FieldState
, age : String
, ageState : Uniform.FieldState
}
type alias Fields =
{ name : Uniform.Field String String Values
, age : Uniform.Field String Int Values
}
type alias UserForm =
Uniform.Form Values Fields User
succeed : fields -> output -> Form values fields output
Make a empty form with no fields that always successfully produces the given output.
Useful as a starting point to which you can append
fields.
A value that knows how to fill a specific field with an appropriate value from values
.
You shouldn't care about this type at all, it's exported so that Uniform.Classic.field
can be implemented.
field : (value -> Basics.Bool) -> FieldConfig value output values -> FormField value output values
Define an actual field. It is not tied to a specific form yet, go and append
it!
First argument is a function telling if the given value is empty.
Useful for custom field types, but if you find yourself working mostly with text fields, take a look at textField
.
Second argument is where you describe how to get and set the value, and how to parse it into the output
.
ageField =
Uniform.field
String.isEmpty
{ parser = Result.fromMaybe "Not an integer" String.toInt
, value = .age
, updateValue = \values age -> { values | age = age }
, state = Uniform.Displaying
, updateState = \values state -> { values | ageState = state }
}
{ parser : value -> Result String output
, value : values -> ( value
, FieldState )
, update : values -> ( value
, FieldState ) -> values
}
Describe how to get, set, parse the field's value and state.
parser
specifies how to validate the value.
It needs a function that processes the value of the field and produces a Result
of either:String
describing an error,output
.value
specifies how to get the value and the state from values
.
Most of the time it will be in a form of .fieldName
.update
specified how to update values
with a new value and/or a new state.
Most of the time it will look like \values value -> { values | fieldName = value }
append : FormField value fieldOutput values -> Form values (Field value fieldOutput values -> fields) (fieldOutput -> output) -> Form values fields output
Take a field
and append it to the form.
userForm =
Uniform.succeed User Fields
|> Uniform.append nameField
|> Uniform.append ageField
Field String output values
Lots of fields, however, are just plain strings with custom validation. This alias is not mandatory to use, but can help to shorten your fields definition a bit.
It is intentional that there are no other similar aliases in the library. I encourage you to define your own aliases for your custom value types.
textField : FieldConfig String output values -> FormField String output values
Define a text field. Not mandatory to use, but can help to shorten your fields definition a bit.
All it does is calling field String.isEmpty
.
It is intentional that there are no other similar helpers in the library. I encourage you to implement similar helper functions for your custom value types.
optional : FormField value output values -> FormField value (Maybe output) values
All fields are required by default, but you can make them optional
.
Uniform.succeed SomeOutput SomeFields
|> Uniform.append field1
|> Uniform.append (optional field2)
The corresponding field in your output model must be Maybe output
instead of just output
.
dynamicField : (values -> FormField value output values) -> FormField value output values
This function allows fields to peek into other fields values.
You can use it for plenty different use cases:
values
should only represent form values?
Nothing stops you from defining an extra field that you can inspect later).fieldWithComplexValidation =
Uniform.dynamicField
\values ->
Uniform.textField
{ parser = complexParser values
, ...
}
fill : Form values fields output -> values -> { fields : fields, output : Maybe output }
Once you have your form defined and fields appended, you can now fill
it with values
.
If all values are properly parsed into fields, the returned output
field will contain Just output
, otherwise Nothing
.
You can inspect individual fields from the returned fields
, each one will have a type of Field
.
However, I would advise not to implement your view
for the form by inspecting the returned fields
directly.
It is error-prone: it is easy to forget to branch on the state, or to call wrong update functions.
viewField
function is a type-safe way to view fields, it should cover all your needs.
{ value : value
, updateValue : value -> values
, state : FieldState
, updateState : values
, output : Result Error output
}
Represents a field filled with values
.
value
contains the "raw" value that can be displayed in a view,updateValue
is a function that is usually used in an onInput
event,state
contains the current state
of the field,updateState
is a function that is usually used in onClick
/onBlur
events,output
contains either a successfully parsed output
or an error
.Each field in an in-place editable form can be either Editing
or Displaying
.
I hope these names are obvious :) If not, submit a PR!
This represents validation errors that may happen during fill
ing a form.
Again, naming should be obvious. If not, submit a PR!
mapOutput : (a -> b) -> Form values fields a -> Form values fields b
Transform the output of the form.
This function can help you to keep forms decoupled from specific view messages:
Uniform.mapOutput SignUp userForm
The form definition is completely decoupled from its view. You are responsible for implementing views for your form; as there are no two identical forms, I don't even try to provide a "universal, configurable" solution to this problem.
However, I can provide some helpers so that it's easy to display the form's fields properly.
If these helpers don't work for you, opt-out and inspect the filled form's fields
directly.
viewField : ViewConfig value values element -> Field value output values -> element
A type-safe way to view a single field from the filled form's fields
.
viewNameField =
Uniform.viewField
{ viewWhenDisplaying = ...
, viewWhenEditing = ...
}
viewAgeField =
Uniform.viewField
{ viewWhenDisplaying = ...
, viewWhenEditing = ...
}
viewForm model =
let
filled =
Uniform.fill userForm model.formValues
in
Html.div
[]
[ viewNameField
, viewAgeField
]
{ viewWhenDisplaying : { value : value
, makeEditable : values } -> element
, viewWhenEditing : { value : value
, updateValue : value -> values
, finishEditing : values } -> element
}
Describe how to view a form's field in a proper state.
viewWhenDisplaying
is a function that is called when the field is in Displaying
state.
Accepts the value
to display and the makeEditable
function that should be called in onClick
handler like this:
viewWhenDisplaying { value, makeEditable } =
Html.div
[ Html.Events.onClick (FormUpdated makeEditable) ]
[ Html.text value ]
viewWhenEditing
is a function that is called when the field is in Editing
state.
Accepts the value
to display, the updateValue
function that should be called in onInput
handler,
and the finishEditing
function that should be called in onBlur
handler like this:
viewWhenEditing { value, updateValue, finishEditing } =
Html.input
[ Html.Events.onBlur (FormUpdated finishEditing)
, Html.Events.onInput (FormUpdated << updateValue)
, Html.Attributes.value value
, Html.Attributes.type\_ "text"
[]