Skip to content

Commit

Permalink
WIP new readme
Browse files Browse the repository at this point in the history
  • Loading branch information
patbenatar committed Oct 19, 2013
1 parent 7c1dbc7 commit 66c419d
Showing 1 changed file with 222 additions and 74 deletions.
296 changes: 222 additions & 74 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,150 +1,298 @@
# Forms

WIP Ruby form framework heavily inspired by django.Forms
WIP Ruby form framework. While I'm building it out, please peruse the README
below and let me know if its something you'd like to use. Ideas for improvement?
Shoot!

### Key Components
### Key Concepts & Goals

* __Form__: A collection of fields that can be rendered to screen and used to
parse incoming HTTP params.
* __Field__: Converts strings submitted from HTML forms into more
relevant data types, and vice versa.
* __Editor__: Renders the field to the screen as HTML.
1. __Flexibility:__ Decoupling models from user input allows for painfree
changes to user interfaces and workflows.
1. __Reusability:__ Break forms down into discrete components to be composed in
new and interesting ways.
1. __Implicit parameter sanitization:__ No need for whitelisting or
strong_params when it's clear what fields exist on the form.
1. __Easy UI construction:__ Define template code once (or take advantage of
semantic defaults) and easily reuse throughout your app

#### Component API
### Need to address

All of the above behave as "Components" of a form, and thus possess the
ability to be nested in all sorts of fun ways. Components must implement the
following interface:

* `initialize(namespace, options)` Where namespace is an array in increasing order of specificity
* `value=(value)`
* `value`
* `parse(params)`
* `render`
1. Dynamic setting of options based on context: like showing certain states in
a select box depending on user's country.
1. Avoid double-nesting when using `field` DSL: since it expands into a nested
`embed`, it'll double nest the namespace.
1.

## Form Objects

### Simple Forms

```ruby
class UserForm < Forms::Form
class UserForm < Forms::Component
# Define on the class...
field :name
field :phone_number
field :accept_terms, :checkbox
field :gender, radio: { options: %i[male female] }
field :state, select: { options: [["California", "ca"], ["Oregon", "or"]] }
field :pricing_plan, select: { options: :available_pricing_plans }

private

def setup
# ...or on the instance
field :pricing_plan, select: { options: available_pricing_plans }
end

def available_pricing_plans
if my_model.is_cheap?
["Free Plan", Plan 1", "Plan 2"]
else
["Plan 3", "Plan 4"]
end
end
end
class CompanyForm < Forms::Form
class CompanyForm < Forms::Component
field :name
field :founded, MyCustomInput
end
```
### Nested Forms (future)
```ruby
class EmploymentForm < Forms::Form
field :date_hired
class EmploymentForm < Forms::Component
field :date_hired, :date
field :title
embed UserForm
embed CompanyForm
embed EmailInput
end
```
### Nested Collections (future)
```ruby
class AdminForm < Forms::Form
embed CompanyForm, many: true
class AdminForm < Forms::Component
embed_many CompanyForm
end
```
### Fields
## Rendering
Converts strings submitted from HTML forms into more relevant data
types, and vice versa.
Forms can render themselves to HTML:
```ruby
class UserForm < Forms::Form
# Use a Boolean type field
field :is_admin, :boolean
end
form = MyForm.new
form.render
```
#### Included Fields

* `:string`
* `:boolean`
Note that they do not include `<form>` or `<input type="submit">` tags. Where
and how your form is submitted is up to you. In Rails, you might do something
like this:
### Editors
```erb
<%= form_tag employment_path, method: :post do %>
<%= form.render %>
<%= submit_tag %>
<% end %>
```
Renders the actual attribute to the screen as HTML. Upon submission,
extracts its relevant params and returns them to a format the Field
can understand. For instance, a date may be rendered as three separate
inputs but the Field only cares about the combined value of those inputs.
## Validations (future)
```ruby
class UserForm < Forms::Form
# Use a special editor for phone numbers
field :phone_number, string: :phone_number
class UserForm < Forms::Component
embed :name, :text, validate: { presence: true }
field :email, :text, validate: { presence: true, email: true }
# Options for editor:
field :gender, string: { radio: { options: %w[male female] } }
# Validations on this object
validate :name_is_unique
validate :user_can_sign_up
private
def name_is_unique
if name_is_not_unique?
get(:name).errors << Forms::Error.new("Name must be unique")
end
end
def user_can_sign_up
unless cool_enough_to_sign_up?
errors << Forms::Error.new("Sorry you're not cool enough")
end
end
end
```
#### Included Editors
## Granular Rendering (future)
If you need control over how and where your components are rendered but prefer
not to implement custom components with accompanying templates, you can render
them individually:
```erb
<div>
<%= form.get(:name).render %>
<p>Lorem ipsum...</p>
</div>
<ul>
<li><%= form.get(:date_field).get(:day).render %></li>
</ul>
```
## Customizing
Forms are composed of objects that inherit from `Component` and implement
the Component API. As such they possess the ability to be nested in all sorts
of fun ways. Understanding this structure is essential to customizing your
implementations.
### Component API
All components implement the following API:
* `initialize(namespace, options)` where namespace is an array in increasing
order of specificity
* `value=(value)`
* `value`
* `parse(params)`
* `render`
`Component` has default implementations for all of these, see
`lib/forms/component.rb`
### Nesting
Components nest other components and are responsible for rendering their
children as well as setting/getting them. Setting values and parsing params
are passed down the tree to the relevant components. Retrieving data reaches
down the tree to pull cleansed input back up.
### Building Custom Components
* `:text`
* `:checkbox`
Now that we understand the basics of the framework, let's build a custom set of
components to collect date input in three separate fields: one for day, month,
and year.

### Validations (future)
With nested inputs:

```ruby
class UserForm < SuperRadForm
# Validate at field level
field :name, string: { validates: { presence: true } }
class DateInput < Forms::Component
embed :day, :text
embed :month, :text
embed :year, :text
def value=(date)
get(:day).value = date.day
get(:month).value = date.month
get(:year).value = date.year
end
# Validate at form level
validate :name_is_unique
def value
Date.new get(:year).value, get(:month).value, get(:day).value
end
# Validate at editor level
field :name, string: { editor: { text: { validates: { presence: true } } } }
# The default implementations of `initialize`, `parse` and `render` will do
# just fine for this input.
end
```

### Loading (future)
All on one component:

...coming...
```ruby
class DateInput < Forms::Component
# The default implementations of `initialize` will do just fine
#### Nested forms (future)
def value=(date)
@day = date.day
@month = date.month
@year = date.year
end
```ruby
class EmploymentsController < ApplicationController
def new
@employment_form = EmploymentForm.new(
Employment.find(params[:id]),
user_form: current_user,
company_form: current_user.company
)
def value
Date.new(@year, @month, @day)
end
def parse(params)
@year = params[:year]
@month = params[:month]
@day = params[:day]
end
def render
# render some HTML with 3 inputs
end
end
```

### Saving (future)
```ruby
class MyForm < Forms::Component
embed :birthday, DateInput
end
```

...coming...
```erb
form = MyForm.new
form.get(:birthday).render
```

### Rendering
## Built-in Conveniences

Forms can render their fields easily:
### Fields

Pairing a label with an input is a common use case. Forms ships with a default
`Field` component for exactly this purpose.

```ruby
form = MyForm.new
form.render
class UserForm < Forms::Component
# Use a Boolean type field
field :is_admin, :checkbox
# Essentially a shorthand for:
embed :is_admin_field, Forms::Field do
embed :is_admin, Forms::CheckboxInput
end
end
```

#### Detailed Rendering (future)
### Inputs

Forms comes with a grip of standard inputs:

* `Forms::Text`, shorthand: `:text`
* `Forms::Textarea`, shorthand: `:textarea`
* `Forms::Checkbox`, shorthand: `:checkbox`
* `Forms::Radio`, shorthand: `:radio` (future)
* `Forms::Select`, shorthand: `:select` (future)



### Loading & Saving

This part is up to you. You might prefer to implement `load` and `save` methods
on their form objects, or maybe you pass your form object and models to a
service object to handle that. You could even do it in your controller. Forms
has no opinion on this.
















...coming... Render each field or editor individually

## Example Rails Usage

Expand Down

0 comments on commit 66c419d

Please sign in to comment.