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 CHANGED
@@ -1,4 +1,146 @@
1
- Unobtrusive JS Form Extensions for rails 3.1
2
- ======================================================
1
+ Unobtrusive Javascript Form Extensions for Rails 3
2
+ ==================================================
3
3
 
4
- Builds on jquery-ujs to extend form functionality.
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($field) {
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($field) {
38
- return this.rails_form('label_for', $field).parents('.field_with_errors').next('.formError');
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($field) {
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($field) {
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(name, error) {
81
- var $field = this.rails_form('fields').filter('[name*=' + name + ']');
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-show-errors]';
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?
@@ -3,7 +3,7 @@ require "edifice-forms/version"
3
3
 
4
4
  Gem::Specification.new do |s|
5
5
  s.name = 'edifice-forms'
6
- s.version = EdificeForms::VERSION
6
+ s.version = Edifice::Forms::VERSION
7
7
  s.platform = Gem::Platform::RUBY
8
8
  s.author = 'Tom Coleman'
9
9
  s.email = 'tom@percolatestudio.com'
@@ -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 EdificeForms
7
- class Engine < Rails::Engine
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, EdificeForms::Helper
12
- ActionController::Base.send :include, EdificeForms::Controller
13
+ ActionView::Base.send :include, Edifice::Forms::Helper
14
+ ActionController::Base.send :include, Edifice::Forms::Controller
@@ -1,9 +1,11 @@
1
- module EdificeForms
2
- module Controller
3
- def self.included(controller)
4
- controller.class_eval do
5
- def self.responder
6
- EdificeForms::Responder
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 EdificeForms
4
- class FormModel
5
- include ActiveModel::Validations
6
- include ActiveModel::Conversion
7
- extend ActiveModel::Naming
8
- extend ActiveModel::Callbacks
9
- define_model_callbacks :save
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
- # more or less the same as activerecord's one
12
- class RecordInvalid < Exception
13
- attr_reader :record
14
- def initialize(record)
15
- @record = record
16
- errors = @record.errors.full_messages.join(", ")
17
- super(errors)
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
- def initialize(attributes = {})
22
- attributes.each { |n, v| send("#{n}=", v) if respond_to?("#{n}=") }
23
- end
22
+ def initialize(attributes = {})
23
+ attributes.each { |n, v| send("#{n}=", v) if respond_to?("#{n}=") }
24
+ end
24
25
 
25
- # default implementation, override as necessary
26
- def save
27
- run_callbacks :save do
28
- valid?
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
- def save!
33
- save || raise(RecordInvalid.new(self))
34
- end
33
+ def save!
34
+ save || raise(RecordInvalid.new(self))
35
+ end
35
36
 
36
- def self.create(attributes = {})
37
- form = new(attributes)
38
- form.save
39
- form
40
- end
37
+ def self.create(attributes = {})
38
+ form = new(attributes)
39
+ form.save
40
+ form
41
+ end
41
42
 
42
- def persisted?
43
- false
43
+ def persisted?
44
+ false
45
+ end
44
46
  end
45
47
  end
46
48
  end
@@ -1,21 +1,23 @@
1
- module EdificeForms
2
- module Helper
3
- # not sure why there isn't something like this in rails
4
- #
5
- # render the errors on an object in a fairly standard way:
6
- #
7
- # <ul class="errors">
8
- # <li data-for="name">Name cannot be blank</li>
9
- # </ul>
10
- def render_errors(builder)
11
- errors = builder.object.errors
12
- messages = errors.full_messages
13
- content_tag :ul, :class => :errors do
14
- output = ''
15
- errors.each_with_index do |error, i|
16
- output << content_tag(:li, :'data-for' => "#{builder.object_name}_#{error[0]}") { messages[i] }
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 EdificeForms
3
- class Responder < ActionController::Responder
4
- protected
5
- # add the :u_e header to xhr error requests
6
- def to_html
7
- if controller.request.xhr? && !get? and has_errors? && default_action
8
- render :action => default_action, :status => :unprocessable_entity, :layout => nil
9
- else
10
- super
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
- # actually render something on successful updates
15
- def to_format
16
- unless get? or has_errors? or post?
17
- display resource, :status => :ok
18
- else
19
- super
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
@@ -1,3 +1,5 @@
1
- module EdificeForms
2
- VERSION = "0.3.0"
1
+ module Edifice
2
+ module Forms
3
+ VERSION = "0.4.0"
4
+ end
3
5
  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: 19
4
+ hash: 15
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
- - 3
8
+ - 4
9
9
  - 0
10
- version: 0.3.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-17 00:00:00 Z
18
+ date: 2012-01-18 00:00:00 Z
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
21
21
  name: jquery-rails