├── test ├── test_helper.exs └── ex_form_test.exs ├── .gitignore ├── mix.lock ├── README.md ├── mix.exs ├── config └── config.exs └── lib └── ex_form.ex /test/test_helper.exs: -------------------------------------------------------------------------------- 1 | Pavlov.start 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /_build 2 | /deps 3 | erl_crash.dump 4 | *.ez 5 | -------------------------------------------------------------------------------- /mix.lock: -------------------------------------------------------------------------------- 1 | %{"meck": {:hex, :meck, "0.8.2"}, 2 | "pavlov": {:hex, :pavlov, "0.1.2"}} 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ExForm 2 | ====== 3 | 4 | This is a prototype that will be replaced. It will not be supported 5 | -------------------------------------------------------------------------------- /mix.exs: -------------------------------------------------------------------------------- 1 | defmodule ExForm.Mixfile do 2 | use Mix.Project 3 | 4 | def project do 5 | [app: :ex_form, 6 | version: "0.0.1", 7 | elixir: "~> 1.0", 8 | deps: deps] 9 | end 10 | 11 | # Configuration for the OTP application 12 | # 13 | # Type `mix help compile.app` for more information 14 | def application do 15 | [applications: [:logger]] 16 | end 17 | 18 | # Dependencies can be Hex packages: 19 | # 20 | # {:mydep, "~> 0.3.0"} 21 | # 22 | # Or git/path repositories: 23 | # 24 | # {:mydep, git: "https://github.com/elixir-lang/mydep.git", tag: "0.1.0"} 25 | # 26 | # Type `mix help deps` for more examples and options 27 | defp deps do 28 | [ 29 | {:xain, github: "smpallen99/xain"}, 30 | {:pavlov, "~> 0.1.2", only: :test}, 31 | ] 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /config/config.exs: -------------------------------------------------------------------------------- 1 | # This file is responsible for configuring your application 2 | # and its dependencies with the aid of the Mix.Config module. 3 | use Mix.Config 4 | 5 | # This configuration is loaded before any dependency and is restricted 6 | # to this project. If another project depends on this project, this 7 | # file won't be loaded nor affect the parent project. For this reason, 8 | # if you want to provide default values for your application for third- 9 | # party users, it should be done in your mix.exs file. 10 | 11 | # Sample configuration: 12 | # 13 | # config :logger, :console, 14 | # level: :info, 15 | # format: "$date $time [$level] $metadata$message\n", 16 | # metadata: [:user_id] 17 | 18 | # It is also possible to import configuration files, relative to this 19 | # directory. For example, you can emulate configuration per environment 20 | # by uncommenting the line below and defining dev.exs, test.exs and such. 21 | # Configuration from the imported file will override the ones defined 22 | # here (which is why it is important to import them last). 23 | # 24 | # import_config "#{Mix.env}.exs" 25 | -------------------------------------------------------------------------------- /test/ex_form_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ExFormTest do 2 | require Logger 3 | use Pavlov.Case 4 | import Pavlov.Syntax.Expect 5 | use Xain 6 | 7 | defmodule SampleStruct do 8 | defstruct name: "", id: 1, private: "" 9 | end 10 | 11 | def to_eq_s(left, right) do 12 | expect Regex.replace(~r/(^\s*)|(\s+$)|(\n)/m, left, "") |> 13 | to_eq Regex.replace(~r/(^\s*)|(\s+$)|(\n)/m, right, "") 14 | end 15 | 16 | def to_include_list(left, right) do 17 | for item <- right do 18 | expect left |> to_include item 19 | end 20 | end 21 | 22 | it "underscores the module" do 23 | 24 | expect ExForm.underscore(%SampleStruct{}.__struct__) |> to_eq "sample_struct" 25 | end 26 | 27 | it "handles hidden_field" do 28 | sample = %SampleStruct{name: "sample", private: "secret"} 29 | 30 | result = ExForm.form_for(:sample_struct, [url: "/"], fn(f) -> 31 | f |> ExForm.hidden_field(:private, sample.private) 32 | end) 33 | 34 | expect result |> to_include_list ["id=\"new_sample_struct\"", "method=\"post\"", "action=\"/\"", 35 | "type=\"hidden\"", "id=\"sample_struct_private\"", 36 | "name=\"sample_struct[private]\"", "value=\"secret\""] 37 | end 38 | 39 | it "handles hidden_field with model" do 40 | sample = %SampleStruct{name: "sample", private: "secret"} 41 | 42 | result = ExForm.form_for(sample, [url: "/"], fn(f) -> 43 | f |> ExForm.hidden_field(:private) 44 | end) 45 | 46 | expect result |> to_include_list [" 55 | ExForm.input_field(f, :name) 56 | ExForm.submit_field f 57 | end) 58 | 59 | expect result 60 | |> to_include_list [" 70 | ExForm.inputs(f, "Section 1", fn -> 71 | ExForm.input_field(f, :name) 72 | end) 73 | ExForm.inputs(f, fn -> 74 | f |> ExForm.submit_field 75 | end) 76 | end) 77 | expect result 78 | |> to_include_list ["", "Section 1", 80 | " 15 | f 16 | |> input_field(:name, my_model.name) 17 | |> submit("save") 18 | end 19 | 20 | 2. pass model struct and use input 21 | 22 | form_for my_model, [url: "/"], fn(f) -> 23 | f 24 | |> input(:name) 25 | |> submit("save") 26 | end 27 | """ 28 | def form_for(name_or_mode, opts \\ [], fun \\ nil) 29 | def form_for(%{__struct__: model_name} = model, opts, fun) do 30 | _form_for(model, underscore(model_name), opts, fun) 31 | end 32 | def form_for(model_name, opts, fun) do 33 | _form_for(nil, model_name, opts, fun) 34 | end 35 | defp _form_for(model, model_name, opts, fun) do 36 | method = Keyword.get(opts, :method, "post") 37 | url = Keyword.get(opts, :url, "/") 38 | charset = Keyword.get(opts, :charset, "UTF-8") 39 | csrf = Keyword.get(opts, :csrf, nil) 40 | class = case Keyword.get(opts, :class, nil) do 41 | nil -> "" 42 | other -> ".#{other}" 43 | end 44 | {put_method, method} = if method == :put, do: {true, :post}, else: {false, method} 45 | 46 | form_data = %{model: model, model_name: model_name, data: [], type: :form} 47 | case fun do 48 | nil -> form_data 49 | other -> 50 | # markup do 51 | form("#new_#{model_name}#{class}", method: method, 52 | "accept-charset": charset, action: url) do 53 | get_put_input(put_method) 54 | get_csrf_input(csrf) 55 | 56 | other.(form_data) 57 | end 58 | # end 59 | #|> Phoenix.HTML.safe 60 | end 61 | end 62 | 63 | def get_csrf_input(nil), do: nil 64 | def get_csrf_input(csrf) do 65 | input type: :hidden, name: "_csrf_token", value: csrf 66 | end 67 | 68 | def get_put_input(true) do 69 | input(name: "_method", value: "put", type: "hidden") 70 | end 71 | def get_put_input(_), do: nil 72 | 73 | 74 | @doc """ 75 | Generate an input field 76 | 77 | Generates an input field for the provided form data that includes a the given 78 | model data. 79 | 80 | ## Syntax 81 | 82 | input(form_data, :name, class: "one two", style: "some style") 83 | 84 | """ 85 | def input_field(form_data, name, type \\ :text, value \\ "", opts \\ []) 86 | def input_field(%{model: model, data: data} = form_data, name, type, _, opts) when model != nil do 87 | _input_field(form_data, name, type, Map.get(model, name, nil), opts) 88 | end 89 | def input_field(%{} = form_data, name, type, value, opts) do 90 | _input_field(form_data, name, type, value, opts) 91 | end 92 | 93 | defp _input_field(%{data: data, model_name: model_name} = form_data, name, type, value, _opts) do 94 | input("##{model_name}_#{name}", type: type, name: "#{model_name}[#{name}]", value: value) 95 | form_data 96 | end 97 | 98 | @doc """ 99 | Generate a hidden field for form_data that does not include a model 100 | 101 | ## Syntax 102 | 103 | hidden_field(form_data, :group_id, my_model.group_id) 104 | """ 105 | def hidden_field(form_data, name, value \\ nil, opts \\ []), do: input_field(form_data, name, "hidden", value, opts) 106 | 107 | @doc """ 108 | Generate a submit button 109 | """ 110 | def submit_field(%{data: data} = form_data, value \\ "submit", _opts \\ []) do 111 | input type: :submit, name: :commit, value: value 112 | form_data 113 | end 114 | 115 | @doc """ 116 | Generate a field set 117 | """ 118 | def inputs(form_data, name \\ "", opts \\ [], fun) do 119 | fieldset do 120 | if name != "" do 121 | legend do 122 | span(name) 123 | end 124 | end 125 | fun.() 126 | end 127 | form_data 128 | end 129 | 130 | @doc """ 131 | Generate the underscored model name, given the model Atom 132 | 133 | ## Syntax 134 | 135 | iex> underscore(MyProject.MyModel) 136 | :my_model 137 | """ 138 | def underscore(module) do 139 | module 140 | |> Atom.to_string 141 | |> String.split(".") 142 | |> List.last 143 | |> Mix.Utils.underscore 144 | end 145 | 146 | end 147 | --------------------------------------------------------------------------------