Formex - a form library for Phoenix inspired by Symfony

Hi everyone,

I’m coming from the Symfony (PHP) framework. I like Phoenix, but it has a one thing that was build much better in the Symfony - forms. Symfony has a form system where I can define forms in separated files, instead of writing code inside templates and controllers.

So I created Formex - a form library for Phoenix inspired by Symfony.

I’m currently working on nested forms.

Do you like it?

12 Likes

Does it require a schema or how does it work? It initially looked like it could work off of a map to pull the information, but it looks like you look up associations, but is that hard-coded? Reason I ask is I almost never send a schema directly, I always have them massaged to only send the necessary data (which might be coming from a dozen tables/schemas), so how would this fit in that style? Also what all does it gain from making more form helpers via phoenix forms?

Looks nice overall though. :slight_smile:

EDIT: I am a bit iffy on the fact it hides the association database calls from the caller though, it looks like those happen in the view, which is very bad for reasoning about data flow, plus it prevents the user from being able to optimize those lookups via a join query or so.

2 Likes

Does it require a schema

Yes, for now it only works with Ecto schemas. I have plans to make a possibility for create forms without Ecto, which is also possible in Symfony.

It hides the association database calls … it prevents the user from being able to optimize those lookups via a join query

Another thing to my todo list :smiley:
BTW. If you talking about my SelectAssoc - you can always use a standard :select.

Also what all does it gain from making more form helpers via phoenix forms?

The main reason why I started developing that lib was the <select>'s. I had to create own queries to get data for <option>'s from database, then pass it to the view, from this view to another view… there was a lot of code which I didn’t had to write in a Symfony.

Another thing is readability - why to write whole form in the view?

And there is one additional validation in Formex for selects - it checks if value of select sent by user exists in a generated form.

1 Like

Well you don’t. ^.^

I have quite a sizable selection of helpers for my views that I’ve built over time, one of which is just a simple generator based on a schema that I pass in, but most of my templates are just things like:

<div class="card">
  <%= alert_error assigns %>
  <%= Auth.Request.form @conn, @callback_url, @provider %>
</div>

Where alert_error is just a normal function with an eex template embedded in it that basically just generates:

def alert_error(assigns, tag \\ :error) do
  case assigns[tag] do
    nil -> nil
    errors ->
      ~e"""
      <div class="alert">
        <input type="checkbox" id="error-message">
        <label for="error-message">Dismiss</label>
        <div><%= format_errors errors %></div>
      </div>
      """
  end
end

And the form just calls the Auth.Request.form function that takes the stuff it is interested in and returns a usual Phoenix.HTML form_for setup. My only really long template is my layout. :slight_smile:

1 Like

Ok, I also used to use helpers like this.

Another benefit from using Formex is that, you have a one place where you define a form structure. You don’t create separately changeset and the view, it’s generated by data from the same source.

1 Like

What would this ‘same source’ be? Because in my ‘Auth.Request’ case it is pulling from 6 different tables across two different databases (one PostgreSQL, one Oracle) and an LDAP server. Almost none of my forms anywhere pull from just a single table, and that is a rare thing on any website I’ve built in decades. I’d love to find a library to help simplify all this. :slight_smile:

EDIT: Oh how much I wish elixir had sum and prod types and that Ecto used them to be able to build up a schema tree… ^.^

Sadly Ecto is built very much with a singular world view, cannot cross schema (in the postgresql sense) join, cannot mix database types in a singular way (Ecto.Multi would be awesome there if so), etc… ^.^

Also wish there were a way to pick values out of Ecto.Multi to inject into queries later in the path without needing to hide it all within Ecto.Multi.run commands, those are about impossible to introspect for testing…

1 Like

I meant a Type file where you declare your form.

In “vanilla” Phoenix to create a form you have to:

  1. create changeset, where you have to put all fields that will be used in form
  2. create template and again list all of fields

When you want to add or remove a field, you need to do it in two places. Doesn’t it breaks the DRY rule?

In Formex you have just one file with the form. Changeset and template (if you used formex_rows) will be automatically generated based on this one file.

Even if you will not use the formex_rows but just formex_row you still have situations when you don’t have to open template file, but you would have to do that in standard Phoenix. For example, when you want to set that some field is required you have to set it in the changeset and then put an asterisk inside the template. In formex you just set the required: true option.

1 Like

Not as of yet? My forms are only defined where they are, just as functions that take arguments, and I pass in the arguments to them to generate the form template. Remember that I rarely use changesets on my front-end, I pull from way too many sources, many of which are not even via Ecto, so no, no changesets. :slight_smile:

For required fields I just add required to the html, I let html5 deal with making it required though I still confirm on the server as well (inside Ecto.Multi, I use a lot of Ecto.Multi). :slight_smile:

I should make an image animation, hmm, here we go, for the above code and the simple controller that accesses complex domain code, I have this, there is no javascript (not even for the dismissing) and this computer is heavily lagged (Windows 10 does not handle being out of memory very well, the very Windows interface is laggy):

There is no changeset to pull the username/password from, no data, etc… How would this be done (and elsewhere I have very complex forms as well)?

1 Like

So this package isn’t for you. Someday I will add possibility of using it without changesets.

3 Likes

I have added the ability to create a collection of forms. Here is an example of use:

Docs

3 Likes

I believe that a form tool for Phoenix would be best if it did not assume the availability of a library like Ecto, because requiring Ecto creates a coupling between the view and the storage layer, which would then make a couple of decoupling refactors much more difficult in the future.

6 Likes

Now is possible to use embedded_schema instead of schema, it means, there is no database requirement (but still Ecto is required).

I know that using embedded schemas in that way is kinda hack, so I’m not proud of it. But it was very easy to implement.

Example:

defmodule App.Registration do
  use App.Web, :model

  embedded_schema do # instead of `schema "table_name"`
    field :email
    field :password
  end
end
defmodule App.RegistrationType do
  use Formex.Type

  def build_form(form) do
    form
    |> add(:email, :text_input, label: "E-mail")
    |> add(:password, :password_input, label: "Password")
    |> add(:save, :submit, label: "Register")
  end
end

Controller

def register(conn, %{"registration" => registration_params}) do
  RegistrationType
  |> create_form(%Registration{}, registration_params)
  |> handle_form # new function
  |> case do
    {:ok, registration} ->
      # do something with the `registration`
    {:error, form} ->
      # display errors
      render(conn, "index.html", form: form)
  end
end
3 Likes

I have published a new version of formex.

Ecto related code is now extracted to a new library - formex_ecto.
Main library doesn’t have ecto dependency and can be used with standard structs.

Also, now is possible to use external validation libraries. For now there is support for Vex and Ecto.Changeset.validate_* (for those who wants to easily migrate old code which used Changeset’s validation :smiley:) . Current list of supported libraries

UPGRADE.md

6 Likes

I don’t like templates and mixing elixir + html either. I want a separate ui, backend should not have knowledge about the client, ui should be usable with another backend (not elixir / phoenix I mean). I want simple maintenance of forms. So I looked for an opensource drag & drop formbuilder, persisting screendefinitions in json. Of course the forms should be rendered runtime (eventually in development mode) from that same json. The idea is not quite new.
I found something usable and reworked the javascript to work with websockets. So it communicates via phoenix channels with the backend now. Form defs are sent in json to the client, form input is sent to the server via the same channel.
More about how I implemented client-side and serverside validations (with lots of advises against, hahaha ) : Code.eval_something .

1 Like

You seem to be getting mostly criticism in this thread for some reason, so let me say thank you. I appreciate the power that comes from a tool like this.

13 Likes

JSON + Websockets would be so 2017 :smile:

I could create ability to return a form created in formex in JSON that would be sent to a client via Websocket.

This is btw a demo of the formbuilder I’m using https://codepen.io/travist/full/xVyMjo/ (it has a link to github).

1 Like

I also come from the PHP/ Symfony world and I really appreciate your port of the form component. I will give it a try in my next project. :slight_smile:

2 Likes

As @brightball said, people seem to be treating this library as a major software architecture statement and criticising it as such (not configured, not enough decoupling, single data source, etc). This is not supposed to be usable for everyone. This is a labor saving device for the common case, and should be treated as such. If one wants to save typing, of course it’s going to be limited, and coupled to the database and all those evil things. But in some cases, that’s exactly what the user wants :slight_smile:

Personally I think this is very useful, and I’d like a complete CRUD layer, like Django Admin, or it’s Flask cousins (FlaskAdmin and FlaskAppBuilder), which which I’m most familiar (all python libraries).

To expand a little on this point, in FlaskAdmin once you’ve defined your User and Post models (approximately the same as Ecto schemas), you can do this:

from flask_admin.contrib.sqla import ModelView

# Initialize your app

admin = Admin(app, name='...', template_mode='bootstrap3')
admin.add_view(ModelView(User, db.session))
admin.add_view(ModelView(Post, db.session))

And get this for free: http://examples.flask-admin.org/sqla/simple/admin/user/

Yes, it’s extremely coupled to your database. Yes, it’s not very easy to customize. Yes, it supports only a single data source. Yes, it requires an ORM where it’s not easy to predict when the DB is queried (not that much harder than Ecto if you know what you’re doing; if you don’t it might be bad). Yes, you don’t have websockets because Python. Yes, route matching in Flask is very hard to predict (really; the author explains the problem with another framework but all that applies to Flask). Yes black magic happens behind the scenes. But with about THREE lines of code I get a functional admin backend.

Formex is not supposed to help you write Facebook (you should use PHP for that, of course), it’s supposed to help you write FlaskAdmin.

7 Likes

If you’re willing to render the forms on the client, maybe VueJS would be a good option. More, if you’re willing to compile the form templates on the client wuth VueJS’s compiler, this could be done. I’m not sure it’s worth it though. For simple forms, you can do most work on the server, and for complex forms you might want to design the whole thing in Javascript.

1 Like