liveshowy / webauthn_components

WebauthnComponents allows Phoenix developers to quickly add passwordless authentication to LiveView applications.
MIT License
178 stars 10 forks source link

Build Form Fields from Changeset #19

Closed type1fool closed 1 year ago

type1fool commented 2 years ago

User Story

As a user of this component, I would like to provide my own changeset from my custom LiveView and see the form render fields based on the changeset's required fields.

Acceptance Criteria

type1fool commented 2 years ago

Example

The webauthn_live_component_demo app includes a change_user/2 function in Demo.Accounts.User. The changeset struct includes some helpful keys which may allow for dynamic form field generation.

iex(11)> changeset = Demo.Accounts.change_user(%Demo.Accounts.User{})
#Ecto.Changeset<
  action: nil,
  changes: %{},
  errors: [username: {"can't be blank", [validation: :required]}],
  data: #Demo.Accounts.User<>,
  valid?: false
>
iex(12)> changeset.                                                  
__struct__      action          changes         constraints     
data            empty_values    errors          filters         
params          prepare         repo            repo_opts       
required        types           valid?          validations     

iex(12)> changeset.__struct__
Ecto.Changeset
iex(13)> changeset.data      
%Demo.Accounts.User{
  __meta__: #Ecto.Schema.Metadata<:built, "users">,
  id: nil,
  inserted_at: nil,
  keys: #Ecto.Association.NotLoaded<association :keys is not loaded>,
  tokens: #Ecto.Association.NotLoaded<association :tokens is not loaded>,
  updated_at: nil,
  username: nil
}
iex(14)> changeset.params
%{}
iex(15)> changeset.required
[:username]
iex(16)> changeset.empty_values
[""]
iex(17)> changeset.prepare     
[]
iex(18)> changeset.types  
%{
  id: :binary_id,
  inserted_at: :naive_datetime,
  keys: {:assoc,
   %Ecto.Association.Has{
     cardinality: :many,
     defaults: [],
     field: :keys,
     on_cast: &Demo.Authentication.UserKey.new_changeset/2,
     on_delete: :nothing,
     on_replace: :raise,
     ordered: false,
     owner: Demo.Accounts.User,
     owner_key: :id,
     preload_order: [],
     queryable: Demo.Authentication.UserKey,
     related: Demo.Authentication.UserKey,
     related_key: :user_id,
     relationship: :child,
     unique: true,
     where: []
   }},
  tokens: {:assoc,
   %Ecto.Association.Has{
     cardinality: :many,
     defaults: [],
     field: :tokens,
     on_cast: #Function<19.50136996/2 in Ecto.Changeset.on_cast_default/2>,
     on_delete: :nothing,
     on_replace: :raise,
     ordered: false,
     owner: Demo.Accounts.User,
     owner_key: :id,
     preload_order: [],
     queryable: Demo.Authentication.UserToken,
     related: Demo.Authentication.UserToken,
     related_key: :user_id,
     relationship: :child,
     unique: true,
     where: []
   }},
  updated_at: :naive_datetime,
  username: :string
}
iex(19)> changeset.changes
%{}
iex(20)> changeset.errors 
[username: {"can't be blank", [validation: :required]}]
iex(21)> changeset.repo  
nil
iex(22)> changeset.valid?
false
iex(23)> changeset.constraints
[
  %{
    constraint: "users_username_index",
    error_message: "has already been taken",
    error_type: :unique,
    field: :username,
    match: :exact,
    type: :unique
  }
]
iex(24)> changeset.filters    
%{}
iex(25)> changeset.repo_opts
[]
iex(26)> changeset.valid    
valid?         validations    
iex(26)> changeset.validations
[]
nicolasblanco commented 2 years ago

Hello!

Thanks for your work on this LV component.

Instead of trying to implement and/or build some HTML/UI in the component, why not just "ask" the end-developer to implement the render with the HTML, buttons and classes he wants and the component would just take care of everything else?

type1fool commented 2 years ago

Hey @nicolasblanco! Thank you for checking out the repo.

I have been thinking about moving in this direction, allowing user-supplied forms. Phx 0.18 will make this a bit easier to implement. That should be better than trying to guess what controls to use based on changeset types - select vs radio vs checkbox for example.