formex v0.4.14 Formex.Type behaviour

In order to create a form, you need to create the Type file. It’s similar to Symfony’s way of creating forms.

Example:

defmodule App.ArticleType do
  use Formex.Type
  alias Formex.CustomField.SelectAssoc

  def build_form(form) do
    form
    |> add(:title, :text_input, label: "Title")
    |> add(:content, :textarea, label: "Content", phoenix_opts: [
      rows: 4
    ])
    |> add(:hidden, :checkbox, label: "Is hidden", required: false)
    |> add(:category_id, SelectAssoc, label: "Category", phoenix_opts: [
      prompt: "Choose category"
    ])
    |> add(:save, :submit, label: if(form.struct.id, do: "Edit", else: "Add"), phoenix_opts: [
      class: "btn-primary"
    ])
  end

  # optional
  def changeset_after_create_callback(changeset) do
    # do an extra validation
    changeset
  end
end

Nested forms

We have models User, and UserInfo. The UserInfo contains extra information that we don’t want, for some reason, store in the User model.

schema "users" do
  field       :first_name, :string
  field       :last_name, :string
  belongs_to  :user_info, App.UserInfo
end
schema "user_infos" do
  field   :section, :string
  field   :organisation_cell, :string
  has_one :user, App.User
end

Note: belongs_to can also be placed in UserInfo and so on, it doesn’t matter in this example.

Our form will consist of two modules:

user_type.ex

defmodule App.UserType do
  use Formex.Type

  def build_form(form) do
    form
    |> add(:first_name, :text_input)
    |> add(:last_name, :text_input)
    |> add(:user_info, App.UserInfoType)
  end
end

user_info_type.ex

defmodule App.UserInfoType do
  use Formex.Type

  def build_form( form ) do
    form
    |> add(:section, :text_input)
    |> add(:organisation_cell, :text_input)
  end
end

Our form can be displayed in various ways:

  • Print whole form, with nested form, at once

    <%= formex_rows f %>
  • Standard

    <%= formex_row f, :first_name %>
    <%= formex_row f, :last_name %>
    <%= formex_nested f, :user_info %>
  • Set a form template for nested form

    <%= formex_row f, :first_name %>
    <%= formex_row f, :last_name %>
    <div class="form-horizontal">
      <%= formex_nested f, :user_info, template: Formex.Template.BootstrapHorizontal %>
    </div>
  • Use your render function

    <%= formex_row f, :first_name %>
    <%= formex_row f, :last_name %>
    <%= formex_nested f, :user_info, fn subform -> %>
      <%= formex_row subform, :section %>
      <%= formex_row subform, :organisation_cell %>
    <% end %>

Collections of forms

Installation

To start using this functionality you have to include the JS library.

package.json

"dependencies": {
  "formex": "file:deps/formex",
}

JavaScript

import {Collection} from 'formex'

Collection.run(function() {
  // optional callback on collection change
})

Model

We have models User, and UserAddress.

schema "users" do
  field     :first_name, :string
  field     :last_name, :string
  has_many  :user_addresses, App.UserAddress
end
schema "user_addresses" do
  field       :street, :string
  field       :postal_code, :string
  field       :city, :string
  belongs_to  :user, App.User

  formex_collection_child() # important!
end

Form Type

Our form will consist of two modules:

user_type.ex

defmodule App.UserType do
  use Formex.Type

  def build_form(form) do
    form
    |> add(:first_name, :text_input)
    |> add(:last_name, :text_input)
    |> add(:user_addresses, App.UserAddressType)
  end
end

user_address_type.ex

defmodule App.UserAddressType do
  use Formex.Type

  def build_form( form ) do
    form
    |> add(:street, :text_input)
    |> add(:city, :text_input)
  end
end

Template

Our collection can be displayed in various ways:

  • Print whole form, with collection, at once

    <%= formex_rows f %>
  • Standard

    <%= formex_row f, :first_name %>
    <%= formex_row f, :last_name %>
    <%= formex_collection f, :user_addresses %>
  • Set a form template for collection

    <%= formex_row f, :first_name %>
    <%= formex_row f, :last_name %>
    <div class="form-horizontal">
      <%= formex_collection f, :user_addresses, template: Formex.Template.BootstrapHorizontal %>
    </div>
  • Use your render function

    <%= formex_row f, :first_name %>
    <%= formex_row f, :last_name %>
    <%= 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

    <%= formex_row f, :first_name %>
    <%= formex_row f, :last_name %>
    
    <% 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, "&times;"}, "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 %>

For more info about rendering see Formex.View.Collection.formex_collection/5

Result with some additional HTML and CSS:

Summary

Functions

Adds an form item to the form. May be a field, custom field, button, or subform

Callbacks

In this callback you have to add fields to the form

Callback that will be called after changeset creation. In this function you can for example add an extra validation to your changeset

Functions

add(form, name)
add(form, name, opts)
add(form, name, type_or_module, opts)
add(form :: Form.t, name :: Atom.t, type_or_module :: Atom.t, opts :: Map.t) :: Form.t

Adds an form item to the form. May be a field, custom field, button, or subform.

type_or_module is :text_input by default.

Behaviour depends on type_or_module argument:

Callbacks

build_form(form)
build_form(form :: Formex.Form.t) :: Formex.Form.t

In this callback you have to add fields to the form.

changeset_after_create_callback(changeset, form)
changeset_after_create_callback(changeset :: Ecto.Changeset.t, form :: Formex.Form.t) :: Ecto.Changeset.t

Callback that will be called after changeset creation. In this function you can for example add an extra validation to your changeset.