Packages
formex
0.5.2
0.6.7
0.6.6
0.6.5
0.6.4
0.6.3
0.6.2
0.6.1
0.6.0
0.5.10
0.5.9
0.5.8
0.5.7
0.5.6
0.5.5
0.5.4
0.5.3
0.5.2
0.5.1
0.5.0
0.4.16
0.4.15
0.4.14
0.4.13
0.4.12
0.4.11
0.4.10
0.4.9
0.4.8
0.4.7
0.4.6
0.4.5
0.4.4
0.4.3
0.4.2
0.4.1
0.4.0
0.3.3
0.3.2
0.3.1
0.3.0
0.2.3
0.2.2
0.2.1
0.2.0
0.1.4
0.1.3
0.1.2
0.1.1
0.1.0
Form library for Phoenix with Ecto support
Current section
Files
Jump to
Current section
Files
lib/formex/view_collection.ex
defmodule Formex.View.Collection do
use Phoenix.HTML
import Formex.View
alias __MODULE__
alias Formex.Form
alias Formex.FormCollection
@moduledoc """
Helper functions for templating collection of forms.
See [Type docs](https://hexdocs.pm/formex/Formex.Type.html#module-collections-of-forms)
for example of use.
"""
defstruct [:form, :item, :template, :template_options, :fun_item]
@type t :: %Collection{}
@doc false
def formex_collection(form, item_name) do
formex_collection(form, item_name, [])
end
@doc false
def formex_collection(form, item_name, options) when is_list(options) do
formex_collection(form, item_name, options, &get_default_fun/1, &get_default_fun_item/1)
end
@doc false
def formex_collection(form, item_name, fun) when is_function(fun) do
formex_collection(form, item_name, [], fun, &get_default_fun_item/1)
end
@doc false
def formex_collection(form, item_name, options, fun) when is_list(options) and is_function(fun) do
formex_collection(form, item_name, options, fun, &get_default_fun_item/1)
end
@doc false
def formex_collection(form, item_name, fun, fun_item) when is_function(fun) do
formex_collection(form, item_name, [], fun, fun_item)
end
@doc """
Generates a HTML for collection of forms
## Examples of use:
* Standard
```
<%= formex_collection f, :user_addresses %>
```
* Set a form template for collection
```
<div class="form-horizontal">
<%= formex_collection f, :user_addresses, template: Formex.Template.BootstrapHorizontal %>
</div>
```
* Use your render function
```
<%= formex_collection f, :user_addresses, [template: Formex.Template.BootstrapHorizontal],
fn collection -> %>
<div class="form-horizontal">
<%= formex_collection_items collection %>
<%= formex_collection_add collection, "Add" %>
</div>
<% end %>
```
* You can also set a render function for collection item
```
<% collection = fn collection -> %>
<div class="form-horizontal">
<%= formex_collection_items collection %>
<%= formex_collection_add collection, "Add" %>
</div>
<% end %>
<% collection_item = fn subform -> %>
<%= formex_collection_remove {:safe, "×"}, "Are you sure you want to remove?" %>
<%= formex_row subform, :street %>
<%= formex_row subform, :city %>
<% end %>
<%= formex_collection f, :user_addresses, [template: Formex.Template.BootstrapHorizontal],
collection, collection_item %>
```
## Generated HTML
The `formex_collection` produces
```html
<div class="formex-collection data-formex-prototype="..."></div>
```
The `formex-prototype` is used by JS to generate new subforms.
Content of `.formex-collection` is a result of a `fun` argument, which by default is:
```
<%= formex_collection_items collection %>
<%= formex_collection_add collection %>
```
The `formex_collection_items` produces
```html
<div class="formex-collection-items"></div>
```
Content of `.formex-collection-items` is a result of a `fun_item` argument, which by default is:
```
<%= formex_collection_remove %>
<%= formex_rows subform %>
```
The final result may look like this:
```html
<div class="formex-collection" data-formex-prototype=" result of `fun_item` ">
<div class="formex-collection-items">
<input name="user[user_addresses][0][id]" type="hidden" value="1">
<div class="formex-collection-item">
<input name="user[user_addresses][0][formex_delete]" type="hidden" value="false">
<a class="formex-collection-item-remove" data-formex-confirm="Are you sure?" href="#">×</a>
subform inputs
</div>
<input name="user[user_addresses][1][id]" type="hidden" value="9">
<div class="formex-collection-item">
<input name="user[user_addresses][1][formex_delete]" type="hidden" value="false">
<a class="formex-collection-item-remove" data-formex-confirm="Are you sure?" href="#">×</a>
subform inputs
</div>
</div>
<button class="formex-collection-add" type="button">Add</button>
</div>
```
"""
@spec formex_collection(Form.t, Atom.t, List.t, fun :: (t -> Phoenix.HTML.unsafe),
fun_item :: (Formex.t -> Phoenix.HTML.unsafe)) :: Phoenix.HTML.safe
def formex_collection(form, item_name, options, fun, fun_item) do
item = Enum.find(form.items, &(&1.name == item_name))
template = Formex.View.get_template(form, options)
template_options = Formex.View.get_template_options(form, options)
if !item do
throw("Key :"<>to_string(item_name)<>" not found in form "<>to_string(form.type))
end
prototype = if !options[:without_prototype] do
generate_collection_prototype(form, item_name, item, fun_item, options)
end
html = fun.(%Collection{
form: form,
item: item,
template: template,
template_options: template_options,
fun_item: fun_item
})
if prototype do
content_tag :div, html,
class: "formex-collection",
data: ["formex-prototype": prototype |> elem(1) |> to_string]
else
html
end
end
defp get_default_fun(collection) do
[
formex_collection_items(collection),
formex_collection_add(collection)
]
end
defp get_default_fun_item(subform) do
[
formex_collection_remove(),
formex_rows(subform)
]
end
@spec formex_collection_items(t) :: Phoenix.HTML.safe
def formex_collection_items(collection) do
form = collection.form
item = collection.item
template = collection.template
template_options = collection.template_options
html = form.phoenix_form
|> Phoenix.HTML.Form.inputs_for(item.name, [default: []], fn f ->
fake_struct = %{id: f.params["id"], formex_id: f.params["formex_id"]}
item
|> FormCollection.get_subform_by_struct(fake_struct)
|> case do
nil -> ""
nested_form ->
subform = nested_form.form
|> Map.put(:phoenix_form, f)
|> Map.put(:template, template)
|> Map.put(:template_options, template_options)
html = collection.fun_item.(subform)
style = if FormCollection.to_be_removed(item, nested_form) do
"display: none;"
else
""
end
if subform.struct.id do
id_field = Phoenix.HTML.Form.hidden_input f, :id
delete_field = Phoenix.HTML.Form.hidden_input f, item.delete_field,
"data-formex-remove": ""
content_tag(:div, [
html,
id_field,
delete_field
], class: "formex-collection-item", style: style)
else
formex_id_field = Phoenix.HTML.Form.hidden_input f, :formex_id, "data-formex-id": ""
content_tag(:div, [
html,
formex_id_field
], class: "formex-collection-item formex-collection-item-new", style: style)
end
end
end)
content_tag(:div, html, class: "formex-collection-items")
end
@spec formex_collection_add(t, String.t, String.t) :: Phoenix.HTML.safe
def formex_collection_add(form_collection, label \\ "Add", class \\ "") do
button = Formex.Button.create_button(:button, :add, label: label, phoenix_opts: [
class: "formex-collection-add "<>class
])
template_options = form_collection.template_options
template = form_collection.template
form = form_collection.form
template.generate_row(form, button, template_options)
end
@spec formex_collection_remove(String.t, String.t) :: Phoenix.HTML.safe
def formex_collection_remove(label \\ {:safe, "×"}, confirm \\ "Are you sure?") do
content_tag(:a, [
label
], href: "#", class: "formex-collection-item-remove", "data-formex-confirm": confirm)
end
defp generate_collection_prototype(form, item_name, item, fun_item, options) do
substruct = item.struct_module
|> struct
struct = form.struct_module
|> struct
|> Map.put(item_name, [substruct])
prot_form = Formex.Builder.create_form(form.type, struct, %{}, form.opts)
options = Keyword.put(options, :without_prototype, true)
{:safe, prot_html} = formex_form_for(prot_form, "", [as: form.phoenix_form.name], fn f ->
formex_collection(f, item_name, options, fn collection ->
formex_collection_items(collection)
end, fun_item)
end)
{:safe, Enum.at(prot_html, 1)}
end
end