jdumont

jdumont

"Best" way to augment forms in primary server-rendered app

OK, I’ll start with a disclaimer. The term “best” is going to be subjective, and based on varying needs, preferences, experience, etc.

The situation I’m currently faced with is an app that has a need of quite intricate & interactive forms. The form spans multiple schemas in the backend of the app. Choices and inputs in later parts of the form are dependent on earlier selections.

At present I’m managing this with a temporary structure that uses an Ecto embedded_schema and an Agent to store the current state of the structure. I’ve assigned a route for each stage in the form, and the data from each Phoenix form is submitted to this temporary structure. Upon submission of the final form the temporary structure is transformed into all the required nested schemas and saved with a simple Repo.insert.

I like this method, as I have various temporary structures with different requirements and validation logic that all get saved in the same set of DB tables, which don’t need to enforce the same rules.

I’d like to improve the user experience throughout the forms. Things like searching for records to add a list (a la Lunr.js), reordering them with a simple drag and drop interface. This will help the user understand and recognise the structures they are populating.

The web is just going to be one client (another being a native iOS app). For that reason I wanted to leave as much logic as possible server-side to save rewriting it in both (and each future) clients.

However, I can’t decide on the best JS approach. So far I’ve tried:

  • Straight up ES6 JS using lots of document.getElementById
  • StimulusJS
  • Elm
  • Vue
  • Drab

Each have had their drawbacks, the main one being a less-than-perfect approach to progressive enhancement.

I don’t want or need to use an SPA for this problem, and would like to avoid client-side rendering if possible (mainly because of the repeated logic it requires).

That said, I tried both an Elm and a Vue component which takes over the page and manages the entire flow of the form having been given the required data up front. At the end of the form it could just submit the required information as JSON, or populate it to a form (might be a way for progressive enhancement that way). The big downside here is that it doesn’t utilise the server-side logic in the temporary structures, and requires a fair bit of duplicated logic.

Vanilla and Stimulus allow for this better than the others, but are lacking in state management. Storing state of this complexity in the DOM doesn’t really work well, especially compared to the more declarative options. I’d probably end up communicating over a Phoenix channel and having the server send back HTML fragments to place at certain points, which whilst acceptable can get out of hand quite quickly.

This was the approach I took with Drab, and it sort of ended being a server-dependent MUV (TEA) structure which worked quite well. That said I found Drab biased to broadcasting server-side events, rather than updating server-side state based on client-side events. It didn’t buy me much over straight JS, and it’s unclear how well I could reuse that in the iOS app.

At the moment I’ve got a preference for Stimulus and Turbolinks because they would allow for a hybrid approach in the iOS app, again reducing maintenance overhead, which given I’m a one-man team would be very helpful in getting this app out the door and on tickover.

At the moment I feel either the Vue/Elm or Stimulus/Channel approach is viable, it just depends on whether I want to lean on server-rendered HTML, or client-rendered HTML, the latter requiring more duplicated logic.

What’s your preference when you’ve got fiddly forms? How do you provide a good experience to the user without adding too much unnecessary complexity to your app?

Most Liked

jdumont

jdumont

Just thought I should say that I’ve revisited Drab and am now using Drab.Live pretty extensively throughout the UI. I couldn’t quite make it work until I found the ability to save PIDs into Drab.Sessions from the controller (Plug.Session), which allowed me to updated my Agent with the temp structure in realtime.

The knockoff is that the UI now has the ability to drown my DB in queries, so am going to need to implement some form of caching, or change where values are stored throughout the form filling process.

Wanted to say a big thank-you to @grych, Drab is brilliant and the more I dig into it the more cool things I find!

OvermindDL1

OvermindDL1

Hmm? Drab was first and foremost a callback system to respond to client events, do some work, and return (potentially multiple times). What are you referencing?

Don’t forget Unpoly.js too.

The server state is the authorative state, the client state is used for input to the server state, as it should be as the database is on the server.

Cachex! I love it! So many features and it’s great on performance, and it’s trivial to send a command out to nodes to clear even down to specific data to force it to re-cache. I kind of use it like a ‘materialized view’ in a database that times out for updates over time, it’s awesome!

jdumont

jdumont

I had totally missed Drab.Live, which meant that I wasn’t getting the updated state from the server back in updated assigns. Now that I’ve worked out to read the documentation thoroughly before writing something off… how to go about it, I’ve got a pretty comfortable back & forth between server and client.

I’ll take a look at that.

Main criteria is loading a list of structs into it, and then querying that list by two of it’s string-based fields. Not your usual caching case I’d imagine, but probably not too complex.

My next challenge is reordering a list with drag & drop using Drab. This could be interesting! :wink:

yurko

yurko

Not sure what will be the best for you, so I’ll share our experiences:

  • Vanilla JS and server rendered responses, what Drab does but there was no Drab as we did it - for real-timey looking pieces of interface unless there are many of them works quite well and is dead simple

  • Vue - allows for a reasonably simple setup where you can have a “hybrid” approach, render most of the stuff on the server but encapsulate some pars in components that can communicate with API, updates themselves etc. Good for bigger Apps that are not SPA but have dynamic parts

  • React and React Native - that was an SPA and not just a form, it would also make sense to move all your rendering to frontend and provide an API on the server side (which can use WebSockets). Big plus here is that you can port features to the App relativlely simply and will also be able to provide one for Android

axelson

axelson

Scenic Core Team

Have you looked at Intercooler or Unpoly? They lean a bit more to server-side rendering than Stimulus appears to (I don’t have any direct experience with Stimulus). Using a library like this is great for progressive enhancement since they just enhance specific parts of your pages.

Where Next?

Popular in Questions Top

itssasanka
Hi all, Trying to get some more clarity over utc_datetime and naive_datetime for Ecto: https://hexdocs.pm/ecto/Ecto.Schema.html#module-...
New
_russellb
I want to try my hand at web scraping. What tools/libraries do I need to use. I’m hoping to turn this into something professional so don’...
New
New
lastday4you
I wanted to check elixir version in phoenix because i found that my elixir is 1.5 but when i use Enum.chunk_by it said the function is un...
New
rms.mrcs
Hi, I need to transform a list of numbers into a map where the keys are the indexes and the values are the original values of the list....
New
alice
Hey, Just curious what are the main benefits of Elixir compared to Clojure? When is Elixir more useful than Clojure and vice versa? Th...
New
johnnyicon
Hi all, I've just started learning Elixir and Phoenix Framework, so please pardon my n00bness at this stage. I'm trying to use Postg...
New
baxterw3b
Hi guys, i’m new in the Elixir world, and i have to say, that i love it! i’m having some problem to understand anonymous functions with ...
New
chrisalley
ExUnit now has describe blocks which is a welcome addition coming from RSpec. In the docs, it states that nested hierarchies of describe ...
New
jay1
Why is it that the mnesia database isn’t the most preferred database for use in Elixir/Phoenix?
New

Other popular topics Top

JDanielMartinez
Hi! May someone helps me, please! I have two apps into an umbrella project: the first one is Database, which manages queries, and the se...
New
yurko
Here are few pieces of (common) Linux knowledge that we use for reasonably small one server apps. We use Ubuntu but this should work for ...
New
Tee
can someone please explain to me how Enum.reduce works with maps
New
_russellb
I want to try my hand at web scraping. What tools/libraries do I need to use. I’m hoping to turn this into something professional so don’...
New
nsuchy
Hi. I’ve noticed that Windows Powershell has it’s own IEX command and you cannot access Elixir’s IEX due to the conflict. This isn’t a cr...
New
aadeshere1
I have a another noob question about loop. Since elixir is immutable, while loop is not directly possible. total = 10 while total != 0 ...
New
fayddelight
I tried installing elixir 1.11.2 erlang 23.3.4 via asdf in my zsh shell. Enabled the versions locally and globally. When I list them ...
New
msaraiva
Surface is an experimental library built on top of Phoenix LiveView and its new LiveComponent API that aims to provide a more declarative...
564 42633 214
New
AstonJ
by Lance Halvorsen Elixir and Phoenix are generating tremendous excitement as an unbeatable platform for building modern web application...
460 27162 124
New
magnetic
Hey :wave:t3: Elixir community, I’ve been learning Elixir, and working on some side projects. My editor of choice is VSCode, and althoug...
New

We're in Beta

About us Mission Statement