View Source Components Library
This library only comes with building blocks for your components, and you should built your own form components - or use the provided building blocks directly.
Feel free to grab and customize these components to your needs, and treat them as a starting point when building your form builder.
The code below is released under public domain:
defmodule MyAppWeb.Surface.FormComponents do
defmodule Label do
use Surface.Component
prop(form, :struct, required: true)
prop(field, :atom, required: true)
prop(text, :string, required: true)
prop(required, :boolean, required: false, default: false)
def render(assigns) do
~F"""
{#if @required}
<label for={"#{@form.ref}_#{@field}"}>* {@text}</label>
{#else}
<label for={"#{@form.ref}_#{@field}"}>{@text}</label>
{/if}
"""
end
end
defmodule Errors do
use Surface.Component
prop(form, :struct, required: true)
prop(errors, :map, required: true)
prop(field, :atom, required: true)
def render(assigns) do
~F"""
<Spike.Surface.Errors form={@form} errors={@errors} field={@field} :let={field_errors: field_errors}>
<span class="error">
{#for {_key, message} <- field_errors}
{message}<br/>
{/for}
</span>
</Spike.Surface.Errors>
"""
end
end
defmodule Input do
use Surface.Component
alias MyAppWeb.Surface.FormComponents.{Errors, Label}
prop(form, :struct, required: true)
prop(type, :string, required: true)
prop(errors, :map, required: true)
prop(field, :atom, required: true)
prop(label, :string, required: false)
prop(target, :any, required: false, default: nil)
prop(options, :list, required: false, default: [])
prop(checked_value, :string, required: false, default: "1")
prop(unchecked_value, :string, required: false, default: "0")
def render(%{type: type} = assigns) when type in ["text", "password", "email", "textarea"] do
~F"""
<div>
{#if @label}
<Label form={@form} field={@field} text={@label} required={is_required?(@form, @field)} />
{/if}
<Spike.Surface.FormField form={@form} field={@field} target={@target}>
{#if @type == "textarea"}
<textarea id={"#{@form.ref}_#{@field}"} name="value">{ @form |> Map.get(@field) }</textarea>
{#else}
<input id={"#{@form.ref}_#{@field}"} type={@type} name="value" value={@form |> Map.get(@field)} />
{/if}
</Spike.Surface.FormField>
<Errors form={@form} field={@field} errors={@errors} />
</div>
"""
end
def render(%{type: "select", options: _} = assigns) do
~F"""
<div>
{#if @label}
<Label form={@form} field={@field} text={@label} required={is_required?(@form, @field)} />
{/if}
<Spike.Surface.FormField form={@form} field={@field} target={@target}>
<select id={"#{@form.ref}_#{@field}"} name="value">
{#for {value, text} <- @options}
<option value={value || ""} selected={@form |> Map.get(@field) == value}>{ text }</option>
{/for}
</select>
</Spike.Surface.FormField>
<Errors form={@form} field={@field} errors={@errors} />
</div>
"""
end
def render(%{type: "checkbox", options: _} = assigns) do
~F"""
<div>
<Spike.Surface.FormField form={@form} field={@field} target={@target}>
<span class="float-left">
<input id={"#{@form.ref}_#{@field}_unchecked"} name="value" type="hidden" value={@unchecked_value} />
<input id={"#{@form.ref}_#{@field}"} name="value" type="checkbox" value={@checked_value} checked={is_checked?(@form, @field, @checked_value)} />
</span>
<span>
{#if @label}
<Label form={@form} field={@field} text={@label} required={is_required?(@form, @field)} />
{/if}
</span>
</Spike.Surface.FormField>
<Errors form={@form} field={@field} errors={@errors} />
</div>
"""
end
defp is_checked?(form, field, checked_value) do
Map.get(form, field) == checked_value || Map.get(form, field) == true
end
defp is_required?(form, field) do
validations = Vex.Extract.settings(form) |> Map.get(field, [])
{:presence, true} in validations
end
end
end
Also see Spike Example app for more examples.