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 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.
@@ -1,3 +1,3 @@
1
1
  module ErrSupply
2
- VERSION = "0.0.3"
2
+ VERSION = "0.0.4"
3
3
  end
@@ -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(errors) {
12
- if (errors) {
13
- var error;
14
- var content;
15
- var $field;
16
- var position;
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
- 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>";
28
+ $field = $("#" + id);
29
+ if ($field.offset()) {
27
30
 
28
- $field = $("#" + id);
29
- if ($field.offset()) {
30
-
31
- // determine position
32
- if (($field.offset().left + $field.outerWidth() + 300) < $(window).width()) {
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
- // 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
- }
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
- style: {
69
- border: {
70
- radius: 4,
71
- color: "#c00",
72
- },
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
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 an error
110
- form.find(".error:first").focus();
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: 25
4
+ hash: 23
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
8
  - 0
9
- - 3
10
- version: 0.0.3
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-10 00:00:00 -05:00
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.rdoc
66
+ - README.md
67
67
  - Rakefile
68
68
  - err_supply.gemspec
69
69
  - init.rb
data/README.rdoc DELETED
@@ -1,2 +0,0 @@
1
- = Err Supply
2
-