Skip to content

Set form attribute on inputs when form is given an id #610

Description

@simonmcconnell

When you're creating forms and associating inputs with them by using the form attribute like so

<form id="form-1"></form>
<form id="form-2"></form>

<input type="text" name="form-1-name" form="form-1" />
<input type="text" name="form-2-name" form="form-2" />

... you need to get the form from the Context and set opts={form: form.id}. It would be nice if the input components set the form attribute if the form has an id such that the below could use the IdealFormsComponent. Would you consider a PR that added this? Or should I be asking the question on phoenix_html?

defmodule MyWeb.FormsComponent do
  use Surface.Component
  alias Surface.Components.Context
  alias Surface.Components.Form.{Field, Label, TextInput}

  def render(assigns) do
    ~F"""
    <Context get={form: form}>
      <Field name={:thing}>
        <Label text="Thing" form={form} />
        <TextInput form={form} opts={form: form.id} />
      </Field>
    </Context>
    """
  end
end

defmodule MyWeb.FormsComponentNamedspaceContext do
  use Surface.Component
  alias Surface.Components.Context
  alias Surface.Components.Form.{Field, Label, TextInput}

  def render(assigns) do
    ~F"""
    <Context get={Surface.Components.Form, form: form}>
      <Field name={:thing}>
        <Label text="Thing" />
        <TextInput opts={form: form.id} />
      </Field>
    </Context>
    """
  end
end

defmodule MyWeb.IdealFormsComponent do
  use Surface.Component
  alias Surface.Components.Context
  alias Surface.Components.Form.{Field, Label, TextInput}

  def render(assigns) do
    ~F"""
    <Field name={:thing}>
      <Label text="Thing" />
      <TextInput />
    </Field>
    """
  end
end

defmodule MyWeb.FormsLive do
  use MyWeb, :surface_live_view
  alias MyWeb.FormsComponent
  alias MyWeb.FormsComponentNamedspaceContext
  alias Surface.Components.Context

  @impl true
  def mount(_params, _session, socket) do
    {:ok,
     assign(socket,
       thing1: changeset(%{thing: "thing1"}),
       thing2: changeset(%{thing: "thing2"})
     )}
  end

  defp changeset(data \\ %{}, params) do
    types = %{thing: :string}
    Ecto.Changeset.cast({data, types}, params, Map.keys(types))
  end

  @impl true
  def render(assigns) do
    ~F"""
    <main>
      {form1 =
        form_for(@thing1, "#",
          as: :thing1,
          id: "thing1form",
          phx_change: "change1",
          autocomplete: "off"
        )}
      <#Raw></form></#Raw>

      {form2 =
        form_for(@thing2, "#",
          as: :thing2,
          id: "thing2form",
          phx_change: "change2",
          autocomplete: "off"
        )}
      <#Raw></form></#Raw>

      {!--
      <Context put={Surface.Components.Form, form: form1}>
        <IdealFormsComponent />
      </Context>
      --}

      <Context put={Surface.Components.Form, form: form1}>
        <FormsComponentNamedspaceContext />
      </Context>

      <Context put={form: form2}>
        <FormsComponent />
      </Context>
    </main>
    """
  end

  @impl true
  def handle_event(event, params, socket) do
    IO.inspect([event, params], label: "forms live")
    {:noreply, socket}
  end
end

Activity

  1. msaraiva commented on Jun 2, 2022

    @msaraiva
    Member

    Hi @simonmcconnell!

    Yeah, I think the built-in inputs could do that. Just not sure if it should be the default behavior. Would there be any side effect on always doing that?

    @miguel-s WDYT?

  2. simonmcconnell commented on Jun 3, 2022

    @simonmcconnell
    ContributorAuthor

    You associate an input with a form by either putting it in the form or setting the form attribute. Forms can't be nested but this is how we can get around that, i.e. allowing us to intermingle different parts of forms on a page or place inputs for a form anywhere we like on the page. With this proposed change, the input would have the form attribute set if either the user has nested the input inside a form (usual way of doing things) and given that form an id, or the user has put the form into the Context themselves. Either way, the input will be associated with the form that they expect, I expect.

    MDN on the form attribute of an input:

    A string specifying the <form> element with which the input is associated (that is, its form owner). This string's value, if present, must match the id of a <form> element in the same document. If this attribute isn't specified, the <input> element is associated with the nearest containing form, if any.

    The form attribute lets you place an input anywhere in the document but have it included with a form elsewhere in the document.

    Note: An input can only be associated with one form.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions