err_supply 0.0.3 → 0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
-