err_supply 0.0.3 → 0.0.4
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +281 -0
- data/lib/err_supply/version.rb +1 -1
- data/lib/generators/err_supply/install/templates/err_supply.js +88 -78
- metadata +5 -5
- data/README.rdoc +0 -2
data/README.md
ADDED
@@ -0,0 +1,281 @@
|
|
1
|
+
# err_supply
|
2
|
+
|
3
|
+
Err Supply is a Rails 3 view helper that helps produce simple, beautiful error messages.
|
4
|
+
|
5
|
+
The helper unpacks and rekeys the standard Rails error hash to make applying error messages to your views dead simple. Even better, because the extension cures Rails' brain-damaged way of recording errors from nested resources/attributes, it works with both simple and complex forms.
|
6
|
+
|
7
|
+
|
8
|
+
|
9
|
+
***
|
10
|
+
## What Is This?
|
11
|
+
|
12
|
+
Err Supply is designed to make the default Rails error reporting structure more useful for complex
|
13
|
+
interfaces.
|
14
|
+
|
15
|
+
### HTML vs AJAX form submissions
|
16
|
+
|
17
|
+
We started thinking about Err Supply when we worked on a project that required an AJAX form submission.
|
18
|
+
|
19
|
+
For normal HTML submissions, we coded up a custom error handler in Rails to display the messages below the
|
20
|
+
form input. To replicate this for the AJAX submission, we realized we would have to convert the error hash
|
21
|
+
to JSON and then wire up a jQuery event handler to perform the same DOM manipulations Rails was performing
|
22
|
+
internally. Obviously, we weren't super excited about having code in two places and in two languages to
|
23
|
+
provide the same business value.
|
24
|
+
|
25
|
+
|
26
|
+
### Ambiguous error messages for nested attributes
|
27
|
+
|
28
|
+
The problem was compounded a few months later when we worked on a different project that required an AJAX
|
29
|
+
form submission for a nested form.
|
30
|
+
|
31
|
+
Here, even the workaround is problematic. Because Rails reports errors on nested attributes ambiguously,
|
32
|
+
there really wasn't any way to use a javascript workaround without first reconstituting the error hash
|
33
|
+
itself.
|
34
|
+
|
35
|
+
If you don't know what I mean by saying the error messages are ambiguous, here's an example. Say you have
|
36
|
+
these models defined:
|
37
|
+
|
38
|
+
class Father < ActiveRecord::Base
|
39
|
+
attr_accessible :name
|
40
|
+
attr_accessible :age
|
41
|
+
|
42
|
+
has_many :sons
|
43
|
+
|
44
|
+
accepts_nested_attributes_for :sons, :allow_destroy => true
|
45
|
+
|
46
|
+
validates_presence_of :name, :age
|
47
|
+
end
|
48
|
+
|
49
|
+
class Son < ActiveRecord::Base
|
50
|
+
attr_accessible :name
|
51
|
+
attr_accessible :age
|
52
|
+
|
53
|
+
belongs_to :father
|
54
|
+
|
55
|
+
validates_presence_of :name, :age
|
56
|
+
end
|
57
|
+
|
58
|
+
If you pull up the nested edit form for a father with two sons and delete one son's name and the other
|
59
|
+
son's age, Rails will return the following error hash:
|
60
|
+
|
61
|
+
{
|
62
|
+
"sons.name": ["can't be blank"],
|
63
|
+
"sons.age": ["can't be blank"]
|
64
|
+
}
|
65
|
+
|
66
|
+
Umm, thanks, but which son is missing a name and which one is missing an age? Or is it the same son missing
|
67
|
+
both values? Or, do they both have problems?
|
68
|
+
|
69
|
+
|
70
|
+
|
71
|
+
***
|
72
|
+
## Our Solution
|
73
|
+
|
74
|
+
Err Supply converts the Rails error hash from a slightly ambiguous object graph to a flat, unambiguous
|
75
|
+
hash of DOM element ids. It does this by traversing the object graph for you and determining exactly which
|
76
|
+
child resources have errors. It then adds those errors to a new hash object where the key is the DOM id
|
77
|
+
of the corresponding form input.
|
78
|
+
|
79
|
+
Err Supply publishes this newly constituted error hash via a custom jQuery event, allowing the view
|
80
|
+
to handle errors through a single javascript interface. This strategy allows errors from HTML and AJAX
|
81
|
+
form submissions to be run through a single piece of code.
|
82
|
+
|
83
|
+
|
84
|
+
|
85
|
+
***
|
86
|
+
## Installation
|
87
|
+
|
88
|
+
Install me from RubyGems.org by adding a gem dependency to your Gemfile. Bundler does
|
89
|
+
the rest.
|
90
|
+
|
91
|
+
gem "err_supply"
|
92
|
+
|
93
|
+
If you want the default javascript handlers, run the install generator from the command line.
|
94
|
+
|
95
|
+
$ rails g err_supply:install
|
96
|
+
|
97
|
+
This will copy two javascripts files to your project: the latest stable version of the
|
98
|
+
jQuery plugin qtip and a simple $.live() function to modify the view using the information
|
99
|
+
in the err_supply error hash.
|
100
|
+
|
101
|
+
|
102
|
+
|
103
|
+
***
|
104
|
+
## Basic Usage
|
105
|
+
|
106
|
+
The main `err_supply` helper returns an escaped javascript invocation that triggers a custom
|
107
|
+
event named `err_supply:loaded` and supplies the edited error hash as data.
|
108
|
+
|
109
|
+
<script type="text/javascript">
|
110
|
+
<%= err_supply @father %>
|
111
|
+
</script>
|
112
|
+
|
113
|
+
This will evaluate @father.errors and apply an errors to the form. It assumes all form inputs
|
114
|
+
are named in the standard rails way and that all error attribute keys match the form input
|
115
|
+
keys exactly.
|
116
|
+
|
117
|
+
|
118
|
+
### Whitelists/Blacklists
|
119
|
+
|
120
|
+
Attributes can be whitelisted or blacklisted using the standard `:only` and `:except` notation.
|
121
|
+
|
122
|
+
Because `err_supply` will ignore any unmatched attributes, such declarations are not strictly
|
123
|
+
required. They typically only make sense for minor actions against models with many,
|
124
|
+
many attributes.
|
125
|
+
|
126
|
+
|
127
|
+
### Changing Labels
|
128
|
+
|
129
|
+
Say a User class has an attribute `:born_on` to store the user's date of birth. In your form
|
130
|
+
builder you declare the textbox normally like so:
|
131
|
+
|
132
|
+
<%= f.text_field :born_on %>
|
133
|
+
|
134
|
+
A presence\_of error will be formatted as:
|
135
|
+
|
136
|
+
Born on can't be blank.
|
137
|
+
|
138
|
+
To make this nicer, we can change the label for the attribute like this:
|
139
|
+
|
140
|
+
<script type="text/javascript">
|
141
|
+
<%= err_supply @user, :born_on => { :label => "Date of birth" } %>
|
142
|
+
</script>
|
143
|
+
|
144
|
+
This will attach the following presence of error to the :born_on field:
|
145
|
+
|
146
|
+
Date of birth can't be blank.
|
147
|
+
|
148
|
+
|
149
|
+
### Changing Keys
|
150
|
+
|
151
|
+
Say a User class belongs to an Organization class. In your form, you declare a selector
|
152
|
+
for assigning the organization. The selector is named `:ogranization_id`.
|
153
|
+
|
154
|
+
Depending on how your validations are written, you might very well get an error message for
|
155
|
+
this form keyed to `:organization`. Because your selector is keyed to `:organization_id`,
|
156
|
+
the default javascript handler will consider this an unmatched attribute.
|
157
|
+
|
158
|
+
You can solve this problem by changing the key for the attribute like so:
|
159
|
+
|
160
|
+
<script type="text/javascript">
|
161
|
+
<%= err_supply @user, :organization => { :key => :organization_id } %>
|
162
|
+
</script>
|
163
|
+
|
164
|
+
|
165
|
+
### Nested Attributes
|
166
|
+
|
167
|
+
You can apply the same principles to nested attributes by nesting the instructions. To return
|
168
|
+
to our father/son example, you can change the name labels for both entities using the following
|
169
|
+
notation:
|
170
|
+
|
171
|
+
<script type="text/javascript">
|
172
|
+
<%= err_supply @father,
|
173
|
+
:name => { :label => "Father's name" },
|
174
|
+
:sons => {
|
175
|
+
:name => { :label => "Son's name" }
|
176
|
+
}
|
177
|
+
%>
|
178
|
+
</script>
|
179
|
+
|
180
|
+
|
181
|
+
### Combining Instructions (aka Go Crazy)
|
182
|
+
|
183
|
+
Attribute instructions are provided as hashes so that both `key` and `label` changes can be
|
184
|
+
declared on the same attribute.
|
185
|
+
|
186
|
+
Honestly, such instructions are rare in the real world, but error handling can get weird fast,
|
187
|
+
so the library supports it.
|
188
|
+
|
189
|
+
|
190
|
+
***
|
191
|
+
## Advanced Usage
|
192
|
+
|
193
|
+
There are a handful of scenarios that fall outside the area of basic usage worth discussing.
|
194
|
+
|
195
|
+
### 1. AJAX Submissions and Nested Forms
|
196
|
+
|
197
|
+
When Rails submits a nested form via a full page refresh, Rails automatically re-indexes any
|
198
|
+
DOM elements in a nested collection starting at 0.
|
199
|
+
|
200
|
+
If you're submitting a form remotely, it's on you to do this by re-rendering the nested fields
|
201
|
+
with the current collection.
|
202
|
+
|
203
|
+
As long as you do this before the `err_supply` call is made, everything will work normally.
|
204
|
+
|
205
|
+
|
206
|
+
### 2. Other Javascript Libraries
|
207
|
+
|
208
|
+
If you don't use jQuery, you may want to override the `err_supply` helper to format the
|
209
|
+
javascript invocation differently.
|
210
|
+
|
211
|
+
The library is constructed so the main public method named `err_supply` is pretty dumb. Most
|
212
|
+
of the real hash-altering magic occurs in a protected method called `err_supply_hash`. You can
|
213
|
+
override the much simpler method without worrying about damaging the core hash interpretation
|
214
|
+
functionality.
|
215
|
+
|
216
|
+
See the `lib` directory for details.
|
217
|
+
|
218
|
+
|
219
|
+
### 3. Handling Unmatched Errors
|
220
|
+
|
221
|
+
The default javascript handler bundles up unmatched error keys into a new hash and publishes them
|
222
|
+
using the custom jQuery event `err_supply:unmatched`.
|
223
|
+
|
224
|
+
|
225
|
+
|
226
|
+
***
|
227
|
+
## Prerequisites
|
228
|
+
|
229
|
+
* <b>Ruby on Rails:</b> <http://rubyonrails.org>
|
230
|
+
* <b>jQuery:</b> <http://jquery.com>
|
231
|
+
|
232
|
+
|
233
|
+
|
234
|
+
***
|
235
|
+
## Helpful Links
|
236
|
+
|
237
|
+
* <b>Repository:</b> <http://github.com/coroutine/err_supply>
|
238
|
+
* <b>Gem:</b> <http://rubygems.org/gems/err_supply>
|
239
|
+
* <b>Authors:</b> <http://coroutine.com>
|
240
|
+
|
241
|
+
|
242
|
+
|
243
|
+
|
244
|
+
***
|
245
|
+
## Gemroll
|
246
|
+
|
247
|
+
Other gems by Coroutine include:
|
248
|
+
|
249
|
+
* [acts\_as\_current](http://github.com/coroutine/acts_as_current)
|
250
|
+
* [acts\_as\_label](http://github.com/coroutine/acts_as_label)
|
251
|
+
* [acts\_as\_list\_with\_sti\_support](http://github.com/coroutine/acts_as_list_with_sti_support)
|
252
|
+
* [delayed\_form\_observer](http://github.com/coroutine/delayed_form_observer)
|
253
|
+
* [kenny\_dialoggins](http://github.com/coroutine/kenny_dialoggins)
|
254
|
+
* [michael\_hintbuble](http://github.com/coroutine/michael_hintbuble)
|
255
|
+
* [tiny\_navigation](http://github.com/coroutine/tiny_navigation)
|
256
|
+
|
257
|
+
|
258
|
+
|
259
|
+
***
|
260
|
+
## License
|
261
|
+
|
262
|
+
Copyright (c) 2011 [Coroutine LLC](http://coroutine.com).
|
263
|
+
|
264
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
265
|
+
a copy of this software and associated documentation files (the
|
266
|
+
"Software"), to deal in the Software without restriction, including
|
267
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
268
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
269
|
+
permit persons to whom the Software is furnished to do so, subject to
|
270
|
+
the following conditions:
|
271
|
+
|
272
|
+
The above copyright notice and this permission notice shall be
|
273
|
+
included in all copies or substantial portions of the Software.
|
274
|
+
|
275
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
276
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
277
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
278
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
279
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
280
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
281
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/lib/err_supply/version.rb
CHANGED
@@ -2,112 +2,122 @@
|
|
2
2
|
ErrSupply jQuery Adapter
|
3
3
|
Copyright (c) 2011 Coroutine LLC
|
4
4
|
Licensed under the MIT license
|
5
|
-
Version: 0.1.0
|
6
5
|
*/
|
7
6
|
jQuery(function($) {
|
8
7
|
$("form").live("err_supply:loaded", function(event, errors) {
|
9
8
|
|
10
9
|
// define how errors are applied to dom elements
|
11
|
-
var applyFn = function(
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
10
|
+
var applyFn = function(error_hash) {
|
11
|
+
var unmatched = {};
|
12
|
+
var errors = error_hash || {};
|
13
|
+
var error;
|
14
|
+
var content;
|
15
|
+
var $field;
|
16
|
+
var position;
|
17
|
+
|
18
|
+
for (var id in errors) {
|
19
|
+
error = errors[id];
|
20
|
+
if (error && error.messages && error.messages.length) {
|
21
|
+
|
22
|
+
content = "<div class='err_supply'><ul>";
|
23
|
+
for (var i = 0, n = error.messages.length; i < n; i++) {
|
24
|
+
content += "<li>" + error.label + " " + error.messages[i] + "</li>";
|
25
|
+
};
|
26
|
+
content += "</ul></div>";
|
17
27
|
|
18
|
-
|
19
|
-
|
20
|
-
if (error && error.messages && error.messages.length) {
|
21
|
-
|
22
|
-
content = "<div class='err_supply'><ul>";
|
23
|
-
for (var i = 0, n = error.messages.length; i < n; i++) {
|
24
|
-
content += "<li>" + error.label + " " + error.messages[i] + "</li>";
|
25
|
-
};
|
26
|
-
content += "</ul></div>";
|
28
|
+
$field = $("#" + id);
|
29
|
+
if ($field.offset()) {
|
27
30
|
|
28
|
-
|
29
|
-
if ($field.offset()) {
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
position = {
|
34
|
-
field: "rightMiddle",
|
35
|
-
tooltip: "leftMiddle"
|
36
|
-
};
|
37
|
-
}
|
38
|
-
else {
|
39
|
-
position = {
|
40
|
-
field: "leftMiddle",
|
41
|
-
tooltip: "rightMiddle"
|
42
|
-
};
|
31
|
+
// determine position
|
32
|
+
if (($field.offset().left + $field.outerWidth() + 300) < $(window).width()) {
|
33
|
+
position = {
|
34
|
+
field: "rightMiddle",
|
35
|
+
tooltip: "leftMiddle"
|
43
36
|
};
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
37
|
+
}
|
38
|
+
else {
|
39
|
+
position = {
|
40
|
+
field: "leftMiddle",
|
41
|
+
tooltip: "rightMiddle"
|
42
|
+
};
|
43
|
+
};
|
44
|
+
|
45
|
+
// add class
|
46
|
+
$field.addClass("error");
|
47
|
+
|
48
|
+
// add qtip
|
49
|
+
$field.qtip({
|
50
|
+
content: content,
|
51
|
+
show: {
|
52
|
+
delay: 0,
|
53
|
+
when: {
|
54
|
+
event: "focus"
|
55
|
+
}
|
56
|
+
},
|
57
|
+
hide: {
|
58
|
+
when: {
|
59
|
+
event: "blur"
|
60
|
+
}
|
61
|
+
},
|
62
|
+
position: {
|
63
|
+
corner: {
|
64
|
+
target: position.field,
|
65
|
+
tooltip: position.tooltip
|
66
|
+
}
|
67
|
+
},
|
68
|
+
style: {
|
69
|
+
border: {
|
70
|
+
radius: 4,
|
71
|
+
color: "#c00",
|
67
72
|
},
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
corner: position.tooltip,
|
78
|
-
color: "#c00",
|
79
|
-
size: {
|
80
|
-
x: 10,
|
81
|
-
y: 12
|
82
|
-
}
|
73
|
+
background: "#c00",
|
74
|
+
color: "#fff",
|
75
|
+
width: 280,
|
76
|
+
tip: {
|
77
|
+
corner: position.tooltip,
|
78
|
+
color: "#c00",
|
79
|
+
size: {
|
80
|
+
x: 10,
|
81
|
+
y: 12
|
83
82
|
}
|
84
83
|
}
|
85
|
-
}
|
86
|
-
};
|
84
|
+
}
|
85
|
+
});
|
86
|
+
}
|
87
|
+
else {
|
88
|
+
unmatched[id] = error;
|
87
89
|
};
|
88
90
|
};
|
89
|
-
};
|
91
|
+
};
|
92
|
+
|
93
|
+
return unmatched;
|
90
94
|
};
|
91
95
|
|
92
96
|
// get reference to form firing event
|
93
|
-
var form = $(this);
|
97
|
+
var $form = $(this);
|
94
98
|
|
95
99
|
// find all contained elements with a style of error and remove the class.
|
96
100
|
// this is typically more important for ajax submissions. html submissions
|
97
101
|
// tend not to have this problem.
|
98
|
-
form.find(".error").removeClass("error");
|
102
|
+
$form.find(".error").removeClass("error");
|
99
103
|
|
100
104
|
// hide all fields that have been explicitly destroyed. when an html submission
|
101
105
|
// has errors, the _destroy value renders as true rather than 1, which may or may not
|
102
106
|
// causes destroyed sets to be visible after the reload. here, we scan for both
|
103
107
|
// values and hide any containing div.fields elements.
|
104
|
-
form.find("input:hidden[id$=_destroy]").filter("[value=true], [value=1]").closest(".fields").hide();
|
108
|
+
$form.find("input:hidden[id$=_destroy]").filter("[value=true], [value=1]").closest(".fields").hide();
|
105
109
|
|
106
110
|
// apply errors to dom elements
|
107
|
-
applyFn(errors);
|
111
|
+
var unmatched_errors = applyFn(errors) || {};
|
112
|
+
|
113
|
+
// publish unmatched errors, in case view cares about that
|
114
|
+
$form.trigger("err_supply:unmatched", unmatched_errors);
|
108
115
|
|
109
|
-
// move focus to first field with
|
110
|
-
form.find(".error
|
116
|
+
// move focus to first field with error (or first field)
|
117
|
+
var $focus_field = ($form.find(".error").size() > 0) ?
|
118
|
+
$form.find(".error").filter(":first") :
|
119
|
+
$form.find(":not(.filter) :input:visible:enabled:first");
|
120
|
+
$focus_field.focus()
|
111
121
|
|
112
122
|
// cancel event
|
113
123
|
return false;
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: err_supply
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 23
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 0
|
9
|
-
-
|
10
|
-
version: 0.0.
|
9
|
+
- 4
|
10
|
+
version: 0.0.4
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Coroutine
|
@@ -16,7 +16,7 @@ autorequire:
|
|
16
16
|
bindir: bin
|
17
17
|
cert_chain: []
|
18
18
|
|
19
|
-
date: 2011-06-
|
19
|
+
date: 2011-06-13 00:00:00 -05:00
|
20
20
|
default_executable:
|
21
21
|
dependencies:
|
22
22
|
- !ruby/object:Gem::Dependency
|
@@ -63,7 +63,7 @@ extra_rdoc_files: []
|
|
63
63
|
files:
|
64
64
|
- .gitignore
|
65
65
|
- Gemfile
|
66
|
-
- README.
|
66
|
+
- README.md
|
67
67
|
- Rakefile
|
68
68
|
- err_supply.gemspec
|
69
69
|
- init.rb
|
data/README.rdoc
DELETED