edifice-forms 0.3.0 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +145 -3
- data/app/assets/javascripts/edifice-forms/rails_form.js +20 -7
- data/app/assets/javascripts/edifice-forms/show-errors.js +1 -1
- data/edifice-forms.gemspec +1 -1
- data/lib/edifice-forms.rb +6 -4
- data/lib/edifice-forms/controller.rb +8 -6
- data/lib/edifice-forms/form_model.rb +35 -33
- data/lib/edifice-forms/helper.rb +19 -17
- data/lib/edifice-forms/responder.rb +18 -16
- data/lib/edifice-forms/version.rb +4 -2
- metadata +4 -4
data/README.md
CHANGED
@@ -1,4 +1,146 @@
|
|
1
|
-
Unobtrusive
|
2
|
-
|
1
|
+
Unobtrusive Javascript Form Extensions for Rails 3
|
2
|
+
==================================================
|
3
3
|
|
4
|
-
|
4
|
+
edifice-forms the part of the [edifice project](https://github.com/tmeasday/edifice) which improves your experience with forms inside rails.
|
5
|
+
|
6
|
+
Note that it does not depend on edifice, although it complements it well.
|
7
|
+
|
8
|
+
Extending remote forms to handle errors
|
9
|
+
---------------------------------------
|
10
|
+
|
11
|
+
Rails 3 includes the excellent Unobtrusive JS, which allows us to define remote forms unobtrusively:
|
12
|
+
|
13
|
+
```html
|
14
|
+
<form data-remote="true">
|
15
|
+
```
|
16
|
+
|
17
|
+
This will result in the form submitting via AJAX, however there is no support for errors in the form. This is surprising as the convention for rails form errors is clearly defined; return a HTML status 422 (`:unprocessible_entity`). In fact this is what the `responds_with` responder will do (for non-AJAX requests):
|
18
|
+
|
19
|
+
```ruby
|
20
|
+
# Super-lean Rails controllers do it for me
|
21
|
+
class UsersController
|
22
|
+
def update
|
23
|
+
respond_with @user = User.update(params[:user])
|
24
|
+
end
|
25
|
+
end
|
26
|
+
```
|
27
|
+
|
28
|
+
edifice-forms extends this convention to AJAX requests. Firstly, we augment rails to respond with a 422 on invalid AJAX updates. Secondly, we add a `data-form` attribute:
|
29
|
+
|
30
|
+
```erb
|
31
|
+
<%= form_for @user, :remote => true, :html => {:'data-form' => 'show_errors'} do |f| %>
|
32
|
+
```
|
33
|
+
|
34
|
+
Now, when your users controller returns an error + an updated form with the errors highlighted, we'll automatically replace the form with the 'errored' version. Not a single line of Javascript required for such a common behaviour!
|
35
|
+
|
36
|
+
rails_form.js
|
37
|
+
-------------
|
38
|
+
|
39
|
+
What about if your form returns data in JSON? We've got you covered. In order for remote JSON forms to show errors, we've written a small jQuery plugin: `rails_form.js`. It is written to conform to the conventions laid down by actionpack.
|
40
|
+
|
41
|
+
For instance, if you have, in your view:
|
42
|
+
|
43
|
+
```html
|
44
|
+
<form>
|
45
|
+
<label for="user[name]"></label>
|
46
|
+
<input name="user[name]" value="Tom"></input>
|
47
|
+
</form>
|
48
|
+
```
|
49
|
+
|
50
|
+
You can call:
|
51
|
+
|
52
|
+
```js
|
53
|
+
$('form').rails_form('add_error', 'user[name]', 'needs a surname');
|
54
|
+
```
|
55
|
+
|
56
|
+
Which will result in:
|
57
|
+
|
58
|
+
```html
|
59
|
+
<form>
|
60
|
+
<div class="field_with_errors">
|
61
|
+
<label for="user[name]"></label>
|
62
|
+
</div>
|
63
|
+
<div class="formError">needs a surname</div>
|
64
|
+
<div class="field_with_errors">
|
65
|
+
<input name="user[name]" value="Tom"></input>
|
66
|
+
</div>
|
67
|
+
</form>
|
68
|
+
```
|
69
|
+
|
70
|
+
The error can be removed with:
|
71
|
+
|
72
|
+
```js
|
73
|
+
$('form').rails_form('clear_error', 'user[name]');
|
74
|
+
```
|
75
|
+
|
76
|
+
We've also added a convention that rails seemed to leave out, if you prefer your errors to be co-located:
|
77
|
+
|
78
|
+
```erb
|
79
|
+
<%= render_errors(f) >
|
80
|
+
```
|
81
|
+
|
82
|
+
Which will output something like:
|
83
|
+
|
84
|
+
```html
|
85
|
+
<ul class="errors">
|
86
|
+
<li data-for="name">Name needs a surname</li>
|
87
|
+
</ul>
|
88
|
+
```
|
89
|
+
|
90
|
+
Forms with `show_errors` set will detect such a structure and update it on AJAX errors.
|
91
|
+
|
92
|
+
Use at your discretion.
|
93
|
+
|
94
|
+
FormModel
|
95
|
+
---------
|
96
|
+
|
97
|
+
The final piece of the puzzle is perhaps the most useful. Suppose you have a form on your site which isn't backed by a model. A good example is a feedback form. The feedback 'model' doesn't need to persist, it simply needs to send an email when it successfully saves; but we would still like to have all the ActiveModel goodness (validations, callbacks, etc) of a real ActiveRecord model. Enter the FormModel:
|
98
|
+
|
99
|
+
```ruby
|
100
|
+
class Feedback < Edifice::Forms::FormModel
|
101
|
+
attr_accessor :message
|
102
|
+
attr_accessor :email
|
103
|
+
|
104
|
+
# some simple validators
|
105
|
+
validates :email, :presence => true, :format => {:with => /^.+@.+\..+$/}
|
106
|
+
validates :message, :presence => true
|
107
|
+
|
108
|
+
# if validations pass and we successfully save, go ahead and deliver the
|
109
|
+
# feedback email to us, so we can read it.
|
110
|
+
def save
|
111
|
+
SelfMailer.feedback(self).deliver
|
112
|
+
end
|
113
|
+
end
|
114
|
+
```
|
115
|
+
|
116
|
+
Looks a lot like a ActiveRecord model, doesn't it? We get to write our controllers in the same super skinny way:
|
117
|
+
|
118
|
+
```ruby
|
119
|
+
class FeedbacksController < ApplicationController
|
120
|
+
def new
|
121
|
+
respond_with @feedback = Feedback.new
|
122
|
+
end
|
123
|
+
|
124
|
+
def create
|
125
|
+
respond_with @feedback = Feedback.create params[:feedback]
|
126
|
+
end
|
127
|
+
end
|
128
|
+
```
|
129
|
+
|
130
|
+
Don't worry, we can use the `@feedback` in our views just as we would with a real model:
|
131
|
+
|
132
|
+
```erb
|
133
|
+
<%= form_for @feedback, :remote => true,
|
134
|
+
:html => {:'data-form' => 'show_errors'} do |f| %>
|
135
|
+
<%= f.label :message, 'Your Feedback' %>
|
136
|
+
<%= f.error_message_on :message %>
|
137
|
+
<%= f.text_area :message, :placeholder => 'How can we help?' %>
|
138
|
+
<% end %>
|
139
|
+
```
|
140
|
+
|
141
|
+
Simple, huh?
|
142
|
+
|
143
|
+
License
|
144
|
+
-------
|
145
|
+
|
146
|
+
Edifice is crafted by [Percolate Studio](http://percolatestudio.com) and released under the [MIT license](www.opensource.org/licenses/MIT)
|
@@ -19,6 +19,14 @@ var methods = {
|
|
19
19
|
return this.find('input, textarea, select');
|
20
20
|
},
|
21
21
|
|
22
|
+
field: function(name_or_field) {
|
23
|
+
if (name_or_field instanceof $) {
|
24
|
+
return name_or_field;
|
25
|
+
} else {
|
26
|
+
return this.rails_form('fields').filter('[name*=' + $.escape(name_or_field) + ']');
|
27
|
+
}
|
28
|
+
},
|
29
|
+
|
22
30
|
error_fields: function() {
|
23
31
|
var $form = this;
|
24
32
|
return this.rails_form('fields').filter(function() {
|
@@ -30,15 +38,17 @@ var methods = {
|
|
30
38
|
return this.find('input[type=submit], button[type=submit]');
|
31
39
|
},
|
32
40
|
|
33
|
-
label_for: function(
|
41
|
+
label_for: function(name_or_field) {
|
42
|
+
var $field = this.rails_form('field', name_or_field);
|
34
43
|
return this.find('label[for=' + $field.attr('id') + ']');
|
35
44
|
},
|
36
45
|
|
37
|
-
error_on: function(
|
38
|
-
return this.rails_form('label_for',
|
46
|
+
error_on: function(name_or_field) {
|
47
|
+
return this.rails_form('label_for', name_or_field).parents('.field_with_errors').next('.formError');
|
39
48
|
},
|
40
49
|
|
41
|
-
has_error: function(
|
50
|
+
has_error: function(name_or_field) {
|
51
|
+
var $field = this.rails_form('field', name_or_field);
|
42
52
|
return $field.parent('.field_with_errors').length > 0;
|
43
53
|
},
|
44
54
|
|
@@ -52,7 +62,8 @@ var methods = {
|
|
52
62
|
this.find('.errors').html('');
|
53
63
|
},
|
54
64
|
|
55
|
-
clear_error: function(
|
65
|
+
clear_error: function(name_or_field) {
|
66
|
+
var $field = this.rails_form('field', name_or_field);
|
56
67
|
var id = $field.attr('id');
|
57
68
|
if (this.rails_form('has_error', $field)) { $field.unwrap() }
|
58
69
|
|
@@ -77,8 +88,8 @@ var methods = {
|
|
77
88
|
return this;
|
78
89
|
},
|
79
90
|
|
80
|
-
add_error: function(
|
81
|
-
var $field = this.rails_form('
|
91
|
+
add_error: function(name_or_field, error) {
|
92
|
+
var $field = this.rails_form('field', name_or_field);
|
82
93
|
$field.filter('.field_with_errors > *').unwrap();
|
83
94
|
$field.wrap('<div class="field_with_errors">');
|
84
95
|
|
@@ -101,6 +112,8 @@ var methods = {
|
|
101
112
|
// if there is an .errors list, show the error there
|
102
113
|
var $errors = this.find('ul.errors');
|
103
114
|
if ($errors.length) {
|
115
|
+
// turns bindle[0][name] -> name etc
|
116
|
+
var name = $field.attr('name').replace(/^.*\[(.*)\]$/, '$1');
|
104
117
|
var message = name.charAt(0).toUpperCase() + name.substring(1).replace('_', ' ') +
|
105
118
|
' ' + error;
|
106
119
|
|
@@ -1,5 +1,5 @@
|
|
1
1
|
(function($) {
|
2
|
-
var showErrorSelector = 'form[data-
|
2
|
+
var showErrorSelector = 'form[data-form*=show_errors]';
|
3
3
|
|
4
4
|
$(showErrorSelector).live('ajax:error', function(event, request, status, error) {
|
5
5
|
// CLIENT ERROR -- server-side validation failed. -- FIXME -should this be 422 only?
|
data/edifice-forms.gemspec
CHANGED
data/lib/edifice-forms.rb
CHANGED
@@ -3,10 +3,12 @@ require 'edifice-forms/controller'
|
|
3
3
|
require 'edifice-forms/form_model'
|
4
4
|
require 'edifice-forms/responder'
|
5
5
|
|
6
|
-
module
|
7
|
-
|
6
|
+
module Edifice
|
7
|
+
module Forms
|
8
|
+
class Engine < Rails::Engine
|
9
|
+
end
|
8
10
|
end
|
9
11
|
end
|
10
12
|
|
11
|
-
ActionView::Base.send :include,
|
12
|
-
ActionController::Base.send :include,
|
13
|
+
ActionView::Base.send :include, Edifice::Forms::Helper
|
14
|
+
ActionController::Base.send :include, Edifice::Forms::Controller
|
@@ -1,9 +1,11 @@
|
|
1
|
-
module
|
2
|
-
module
|
3
|
-
|
4
|
-
controller
|
5
|
-
|
6
|
-
|
1
|
+
module Edifice
|
2
|
+
module Forms
|
3
|
+
module Controller
|
4
|
+
def self.included(controller)
|
5
|
+
controller.class_eval do
|
6
|
+
def self.responder
|
7
|
+
Edifice::Forms::Responder
|
8
|
+
end
|
7
9
|
end
|
8
10
|
end
|
9
11
|
end
|
@@ -1,46 +1,48 @@
|
|
1
1
|
# kind of the evolution of the FormStruct
|
2
2
|
|
3
|
-
module
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
3
|
+
module Edifice
|
4
|
+
module Forms
|
5
|
+
class FormModel
|
6
|
+
include ActiveModel::Validations
|
7
|
+
include ActiveModel::Conversion
|
8
|
+
extend ActiveModel::Naming
|
9
|
+
extend ActiveModel::Callbacks
|
10
|
+
define_model_callbacks :save
|
10
11
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
12
|
+
# more or less the same as activerecord's one
|
13
|
+
class RecordInvalid < Exception
|
14
|
+
attr_reader :record
|
15
|
+
def initialize(record)
|
16
|
+
@record = record
|
17
|
+
errors = @record.errors.full_messages.join(", ")
|
18
|
+
super(errors)
|
19
|
+
end
|
18
20
|
end
|
19
|
-
end
|
20
21
|
|
21
|
-
|
22
|
-
|
23
|
-
|
22
|
+
def initialize(attributes = {})
|
23
|
+
attributes.each { |n, v| send("#{n}=", v) if respond_to?("#{n}=") }
|
24
|
+
end
|
24
25
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
26
|
+
# default implementation, override as necessary
|
27
|
+
def save
|
28
|
+
run_callbacks :save do
|
29
|
+
valid?
|
30
|
+
end
|
29
31
|
end
|
30
|
-
end
|
31
32
|
|
32
|
-
|
33
|
-
|
34
|
-
|
33
|
+
def save!
|
34
|
+
save || raise(RecordInvalid.new(self))
|
35
|
+
end
|
35
36
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
37
|
+
def self.create(attributes = {})
|
38
|
+
form = new(attributes)
|
39
|
+
form.save
|
40
|
+
form
|
41
|
+
end
|
41
42
|
|
42
|
-
|
43
|
-
|
43
|
+
def persisted?
|
44
|
+
false
|
45
|
+
end
|
44
46
|
end
|
45
47
|
end
|
46
48
|
end
|
data/lib/edifice-forms/helper.rb
CHANGED
@@ -1,21 +1,23 @@
|
|
1
|
-
module
|
2
|
-
module
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
1
|
+
module Edifice
|
2
|
+
module Forms
|
3
|
+
module Helper
|
4
|
+
# not sure why there isn't something like this in rails
|
5
|
+
#
|
6
|
+
# render the errors on an object in a fairly standard way:
|
7
|
+
#
|
8
|
+
# <ul class="errors">
|
9
|
+
# <li data-for="name">Name cannot be blank</li>
|
10
|
+
# </ul>
|
11
|
+
def render_errors(builder)
|
12
|
+
errors = builder.object.errors
|
13
|
+
messages = errors.full_messages
|
14
|
+
content_tag :ul, :class => :errors do
|
15
|
+
output = ''
|
16
|
+
errors.each_with_index do |error, i|
|
17
|
+
output << content_tag(:li, :'data-for' => "#{builder.object_name}_#{error[0]}") { messages[i] }
|
18
|
+
end
|
19
|
+
output.html_safe
|
17
20
|
end
|
18
|
-
output.html_safe
|
19
21
|
end
|
20
22
|
end
|
21
23
|
end
|
@@ -1,22 +1,24 @@
|
|
1
1
|
# Basically a standard responder, but if
|
2
|
-
module
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
2
|
+
module Edifice
|
3
|
+
module Forms
|
4
|
+
class Responder < ActionController::Responder
|
5
|
+
protected
|
6
|
+
# add the :u_e header to xhr error requests
|
7
|
+
def to_html
|
8
|
+
if controller.request.xhr? && !get? and has_errors? && default_action
|
9
|
+
render :action => default_action, :status => :unprocessable_entity, :layout => nil
|
10
|
+
else
|
11
|
+
super
|
12
|
+
end
|
11
13
|
end
|
12
|
-
end
|
13
14
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
15
|
+
# actually render something on successful updates
|
16
|
+
def to_format
|
17
|
+
unless get? or has_errors? or post?
|
18
|
+
display resource, :status => :ok
|
19
|
+
else
|
20
|
+
super
|
21
|
+
end
|
20
22
|
end
|
21
23
|
end
|
22
24
|
end
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: edifice-forms
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 15
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 0
|
8
|
-
-
|
8
|
+
- 4
|
9
9
|
- 0
|
10
|
-
version: 0.
|
10
|
+
version: 0.4.0
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Tom Coleman
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2012-01-
|
18
|
+
date: 2012-01-18 00:00:00 Z
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|
21
21
|
name: jquery-rails
|