Protoform
This is a more opinionated fork of Superform. Uses protos as base components.
Originally this library was created to experiment with Superform and has become its own distinct flavor.
Usage
gem "protos-protoform", require: "protoform"
Once the gem is installed you can run the generators:
bin/rails g protoform:install
This will:
- Add
phlex-railsto your gemfile if it does not exist - Add
layouts,componentsand other folders to be autoloaded fromapp/views - Add an
ApplicationFormas the base form class to yourapp/views
This gem follows the same conventions as Superform with some key differences:
- All components inherit from
Protos::Component
Forms
In classic Rails you use a form builder object. Protoform is a type of form builder built specifically for Phlex based components. Otherwise, it completely follows the behavior of Rails forms, just with a nicer API.
- Every form takes a model that at least includes
ActiveModel::NamingandActiveModel::Conversion - URLs for the form are defaulted to the current controller's
createorupdatemethods but can be overwritten - Naming for IDs and names follows Rails conventions
- Collections and namespaces have a different API but work the same way
Defining forms
At minimum your form needs to inherit from Protoform::Rails::Form and have a
Field constant inside that defines the field's API.
class ApplicationForm < Protoform::Rails::Form
class Field < Protoform::Field
end
endThen you can use these to subclass your own forms:
module Posts
class Form < ApplicationForm
def view_template
render field(:title).label("Title")
render field(:title).input(required: true)
render field(:body).label("Body")
render field(:body).textarea(rows: 10)
button "Submit"
end
end
end
render Posts::Form.new(@post)Assuming our model is a Post, then the params would be returned under that
key.
{
post: {
title: "The title",
body: "The body"
}
}
We can change that by overriding the #key within our form:
module Posts
class Form < ApplicationForm
def key = "article"
# ...
endNow you would get:
{
article: {
title: "The title",
body: "The body"
}
}
If we wanted to customize the #input method or anything else we could override
it inside the Field constant:
class ApplicationForm < Protoform::Rails::Form
class Field < Protoform::Field
# We can override the default fields with our own
def input(...)
Forms::Input.new(self, ...)
end
end
# Here we make a simple helper to make our syntax shorter. Given a field it
# will also render its label.
def labeled(component)
div class: "form-row" do
render component.field.label
render component
end
end
def submit(text)
button(type: :submit) { text }
end
endWith the helper we created can now write our form more succinctly:
module Posts
class Form < ApplicationForm
def view_template
labeled field(:title).input(required: true)
labeled field(:body).textarea(rows: 10)
submit
end
end
endIn this way you can design your own form DSL that fits your application's needs.
Namespacing and collections
The way collections work is not quite the same as Rails:
class AccountForm < Superform::Rails::Form
def view_template
# Account#owner returns a single object
namespace :owner do |owner|
# Renders input with the name `account[owner][name]`
owner.field(:name).text
# Renders input with the name `account[owner][email]`
owner.field(:email).email
end
# Account#members returns a collection of objects
collection(:members).each do |member|
# Renders input with the name `account[members][0][name]`,
# `account[members][1][name]`, ...
member.field(:name).input
# Renders input with the name `account[members][0][email]`,
# `account[members][1][email]`, ...
member.field(:email).input(type: :email)
# Member#permissions returns an array of values like
# ["read", "write", "delete"].
member.field(:permissions).collection do |permission|
# Renders input with the name `account[members][0][permissions][]`,
# `account[members][1][permissions][]`, ...
render permission.label do
plain permisson.value.humanize
render permission.checkbox
end
end
end
end
end#collection methods require the use of the each method to enumerate over each
item in the collection.
There's three different types of namespaces and collections to consider:
-
Namespace -
namespace(:field_name)is used to map form fields to a single object that's a child of another object. In ActiveRecord, this could be ahas_oneorbelongs_torelationship. -
Collection -
collection(:field_name).eachis used to map a collection of objects to a form. In this case, the members of the account. In ActiveRecord, this could be ahas_manyrelationship. -
Field Collection -
field(:field_name).collection.eachis used when the value of a field is enumerable, like an array of values. In ActiveRecord, this could be an attribute that's an Array type.
Architecture
Forms are a nested hierarchy of Protoform::Namespace and Protoform::Field objects.
form = Protoform::Namespace.new(:post, parent: nil, object: nil) do |f|
f.field(:title, value: "Hello World")
f.collection(:comments) do |comments|
comments.namespace(:comment, object: nil) do |comment|
comment.field(:body, value: "Great post!")
end
end
endDevelopment
After checking out the repo, run bin/setup to install dependencies. Then, run
rake spec to run the tests. You can also run bin/console for an interactive
prompt that will allow you to experiment.
To install this gem onto your local machine, run bundle exec rake install. To
release a new version, update the version number in version.rb, and then run
bundle exec rake release, which will create a git tag for the version, push
git commits and the created tag, and push the .gem file to
rubygems.org.
Contributing
Bug reports and pull requests are welcome on GitHub at https://github.com/rubymonolith/superform. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the code of conduct.
License
The gem is available as open source under the terms of the MIT License.
Code of Conduct
Everyone interacting in the Superform project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the code of conduct.