TIL: How to add a form in a table in Phoenix LiveView?

AbulAsar S.AbulAsar S.
2 min read

Suppose, we want to add an inline edit feature on a table row. It can be a little tricky to implement. I had a hard time implementing it because I wasn't about one of the concepts of HTML that we cannot add a form tag inside a table. Something like this

<table>
  .....
  .....
  .....
  <tbody>
    <tr>
      <form>
        <td>
          <input type="text" name="count">
        </td>
        <td>
          <input type="text" name="rate">
        </td>
        <td>
          <input type="text" name="paid">
        </td>
      </form>
    </tr>
  </tbody>
</table>

This wasn't working because placing a form as a child element of a table, tbody, or tr is not allowed. If we add it the browser typically relocates the form to appear after the table, leaving its contents, such as table rows, cells, inputs, etc., in their original positions within the table. Hence, when we try to submit the form it won't work.

How can we fix this problem?

To solve this problem we can use the form attribute. For this, we will place an empty form somewhere within the page and outside of the table tag. We need to provide the form an id attribute. This id will be an identifier for the form fields. Something like this

<.simple_form for={@distribution_record_form} id="distribution_record" phx-submit="save" phx-update="ignore" class="flex">  
</.simple_form>

And later inside the table for each field i.e. cell we can add the form attribute to tell the fields to which form it belongs. It should be the same as the id of the form .

<td>
   <.input field={@distribution_record_form[:rate]} form="distribution_record" />
</td>

The form attribute will tell the input field that it is part of distribution_record form. So, when the user tries to submit the form whatever data the user will add in this input box will be part of the distribution_record form. Our requirement is that each row i.e. tr should appear as a form in which the last column will have a submit button. So below is the code of how each row will appear.

<tr>
     <td>
         <.input field={@distribution_record_form[:count]} form="distribution_record" /> 
     </td>
     <td>
         <.input field={@distribution_record_form[:rate]} type="text" form="distribution_record" />
     </td>
     <td><.input field={@distribution_record_form[:paid]} type="text" form="distribution_record" />
     </td>
     <td>
        <.button phx-disable-with="Saving..." form="distribution_record">
            Update
        </.button>
     </td>
</tr>

The above code will map to the form one that we created earlier. The below diagram will give you an idea of how this mapping is going to work.

I hope this diagram will make sense to you. I know this is quite a trivial thing but I wasn't aware until I came across this problem and I noted it in my TIL list and shared it in this blog. I hope you like this TIL blog. If you have any questions please comment below. Thanks for reading ๐Ÿ˜Š.

1
Subscribe to my newsletter

Read articles from AbulAsar S. directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

AbulAsar S.
AbulAsar S.

I am a Software Engineer from Mumbai, India. In love with Functional programming ( precisely Elixir). Love to share the knowledge that I learn while developing things.