judge 0.3.1 → 0.4.0
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/VERSION +1 -1
- data/docs/docco.css +196 -0
- data/docs/judge.html +284 -0
- data/judge.gemspec +4 -4
- data/lib/generators/judge/templates/judge.js +107 -47
- data/lib/judge/form.rb +17 -2
- data/test/dummy/app/models/foo.rb +6 -0
- data/test/dummy/app/views/foos/new.html.erb +21 -0
- data/test/judge_test.rb +84 -9
- metadata +7 -7
- data/test/dummy/log/development.log +0 -310
- data/test/dummy/log/production.log +0 -0
data/judge.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{judge}
|
8
|
-
s.version = "0.
|
8
|
+
s.version = "0.4.0"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Joe Corcoran"]
|
12
|
-
s.date = %q{2011-
|
12
|
+
s.date = %q{2011-08-03}
|
13
13
|
s.description = %q{Validate forms in-place using your model validations}
|
14
14
|
s.email = %q{joe@tribesports.com}
|
15
15
|
s.extra_rdoc_files = [
|
@@ -25,6 +25,8 @@ Gem::Specification.new do |s|
|
|
25
25
|
"README.md",
|
26
26
|
"Rakefile",
|
27
27
|
"VERSION",
|
28
|
+
"docs/docco.css",
|
29
|
+
"docs/judge.html",
|
28
30
|
"judge.gemspec",
|
29
31
|
"lib/generators/judge/judge_generator.rb",
|
30
32
|
"lib/generators/judge/templates/json2.js",
|
@@ -78,8 +80,6 @@ Gem::Specification.new do |s|
|
|
78
80
|
"test/dummy/db/migrate/20110725082530_create_continent_country_and_city_tables.rb",
|
79
81
|
"test/dummy/db/schema.rb",
|
80
82
|
"test/dummy/db/test.sqlite3",
|
81
|
-
"test/dummy/log/development.log",
|
82
|
-
"test/dummy/log/production.log",
|
83
83
|
"test/dummy/log/server.log",
|
84
84
|
"test/dummy/public/404.html",
|
85
85
|
"test/dummy/public/422.html",
|
@@ -1,13 +1,17 @@
|
|
1
|
+
// This is the JavaScript part of Judge, a client-side validation gem for Rails 3.
|
2
|
+
// You can find a guide and some more traditional API documentation at <http://joecorcoran.github.com/judge/>.
|
3
|
+
// Hopefully this page will help you understand what's happening under the hood.
|
4
|
+
|
1
5
|
/*jshint curly: true, evil: true, newcap: true, noarg: true */
|
2
|
-
/*global _: false, JSON: false */
|
6
|
+
/*global _: false, JSON: false */
|
3
7
|
|
4
|
-
//
|
8
|
+
// The judge namespace.
|
5
9
|
var judge = judge || {};
|
6
10
|
|
7
|
-
// Watcher is a DOM element wrapper that judge uses to store validation info and instance methods
|
11
|
+
// A judge.Watcher is a DOM element wrapper that judge uses to store validation info and instance methods.
|
8
12
|
judge.Watcher = function (element) {
|
9
13
|
|
10
|
-
// dependency errors
|
14
|
+
// Throw dependency errors.
|
11
15
|
if (!window.hasOwnProperty('_')) {
|
12
16
|
throw new ReferenceError('[judge] Underscore.js not found');
|
13
17
|
}
|
@@ -15,7 +19,7 @@ judge.Watcher = function (element) {
|
|
15
19
|
throw new ReferenceError('[judge] JSON global object not found');
|
16
20
|
}
|
17
21
|
|
18
|
-
// constructor usage errors
|
22
|
+
// Throw some constructor usage errors.
|
19
23
|
if (_(element).isUndefined()) {
|
20
24
|
throw new ReferenceError('[judge] No DOM element passed to constructor');
|
21
25
|
}
|
@@ -23,22 +27,24 @@ judge.Watcher = function (element) {
|
|
23
27
|
throw new TypeError('[judge] Cannot construct new Watcher for object of this type');
|
24
28
|
}
|
25
29
|
if (element.getAttribute('data-validate') === null) {
|
26
|
-
throw new ReferenceError('[judge] Cannot construct
|
30
|
+
throw new ReferenceError('[judge] Cannot construct Watcher for this element, use judge form builders');
|
27
31
|
}
|
28
32
|
if (element.form.getAttribute('data-error-messages') === null) {
|
29
|
-
throw new ReferenceError('[judge]
|
33
|
+
throw new ReferenceError('[judge] Parent form was not created using judge form helper, please amend');
|
30
34
|
}
|
31
35
|
|
32
36
|
|
33
|
-
//
|
37
|
+
// Convenient access to this Watcher.
|
34
38
|
var instance = this;
|
35
39
|
|
36
|
-
//
|
40
|
+
// Watcher instance properties.
|
37
41
|
this.element = element;
|
38
42
|
this.validators = JSON.parse(this.element.getAttribute('data-validate'));
|
39
43
|
this.defaultMessages = JSON.parse(this.element.form.getAttribute('data-error-messages'));
|
40
44
|
|
41
|
-
// instance
|
45
|
+
// This instance method returns the validity of the watched element,
|
46
|
+
// represented as an array of objects containing validity information
|
47
|
+
// for each validator found on the element.
|
42
48
|
this.validate = function() {
|
43
49
|
instance.errorMessages = [];
|
44
50
|
var validators = instance.validators,
|
@@ -63,11 +69,14 @@ judge.Watcher = function (element) {
|
|
63
69
|
|
64
70
|
};
|
65
71
|
|
66
|
-
//
|
72
|
+
// Watcher prototype methods.
|
67
73
|
judge.Watcher.prototype.validates = function() {
|
68
74
|
var instance = this,
|
69
75
|
extendedMethods = judge.customValidators,
|
70
76
|
methods = {
|
77
|
+
|
78
|
+
// Presence validator ported as closely as possible
|
79
|
+
// from [ActiveModel::Validations::PresenceValidator](http://api.rubyonrails.org/classes/ActiveModel/Validations/PresenceValidator.html).
|
71
80
|
presence: function(options) {
|
72
81
|
if (instance.element.value.length) {
|
73
82
|
return { valid:true };
|
@@ -76,6 +85,8 @@ judge.Watcher.prototype.validates = function() {
|
|
76
85
|
}
|
77
86
|
},
|
78
87
|
|
88
|
+
// Length validator ported as closely as possible
|
89
|
+
// from [ActiveModel::Validations::LengthValidator](http://api.rubyonrails.org/classes/ActiveModel/Validations/LengthValidator.html).
|
79
90
|
length: function(options) {
|
80
91
|
var msgs = [],
|
81
92
|
length = instance.element.value.length,
|
@@ -85,32 +96,45 @@ judge.Watcher.prototype.validates = function() {
|
|
85
96
|
is: { operator: '!=', message: 'wrong_length' }
|
86
97
|
};
|
87
98
|
_(types).each(function(properties, type) {
|
88
|
-
|
99
|
+
var invalid = judge.utils.operate(length, properties.operator, options[type]);
|
100
|
+
if (options.hasOwnProperty(type) && invalid) {
|
89
101
|
var m = options[properties.message] || instance.defaultMessages[properties.message];
|
90
102
|
msgs.push(judge.utils.countMsg(m, options[type]));
|
91
103
|
}
|
92
104
|
});
|
93
105
|
return msgs.length ? { valid:false, messages:msgs } : { valid:true };
|
94
106
|
},
|
95
|
-
|
107
|
+
|
108
|
+
// Exclusion validator ported as closely as possible
|
109
|
+
// from [ActiveModel::Validations::ExclusionValidator](http://api.rubyonrails.org/classes/ActiveModel/Validations/ExclusionValidator.html).
|
96
110
|
exclusion: function(options) {
|
97
111
|
var stringIn = _(options['in']).map(function(o) { return o.toString(); });
|
98
112
|
if (_(stringIn).include(instance.element.value)) {
|
99
|
-
return {
|
113
|
+
return {
|
114
|
+
valid:false,
|
115
|
+
messages:[options.message || instance.defaultMessages.exclusion]
|
116
|
+
};
|
100
117
|
} else {
|
101
118
|
return { valid:true };
|
102
119
|
}
|
103
120
|
},
|
104
|
-
|
121
|
+
|
122
|
+
// Inclusion validator ported as closely as possible
|
123
|
+
// from [ActiveModel::Validations::InclusionValidator](http://api.rubyonrails.org/classes/ActiveModel/Validations/InclusionValidator.html).
|
105
124
|
inclusion: function(options) {
|
106
125
|
var stringIn = _(options['in']).map(function(o) { return o.toString(); });
|
107
126
|
if (!_(stringIn).include(instance.element.value)) {
|
108
|
-
return {
|
127
|
+
return {
|
128
|
+
valid:false,
|
129
|
+
messages:[options.message || instance.defaultMessages.inclusion]
|
130
|
+
};
|
109
131
|
} else {
|
110
132
|
return { valid:true };
|
111
133
|
}
|
112
134
|
},
|
113
|
-
|
135
|
+
|
136
|
+
// Numericality validator ported as closely as possible
|
137
|
+
// from [ActiveModel::Validations::NumericalityValidator](http://api.rubyonrails.org/classes/ActiveModel/Validations/NumericalityValidator.html).
|
114
138
|
numericality: function(options) {
|
115
139
|
var operators = {
|
116
140
|
greater_than: '>',
|
@@ -121,16 +145,23 @@ judge.Watcher.prototype.validates = function() {
|
|
121
145
|
},
|
122
146
|
msgs = [],
|
123
147
|
value = instance.element.value,
|
124
|
-
parsedValue = parseFloat(value);
|
148
|
+
parsedValue = parseFloat(value, 10);
|
125
149
|
|
126
150
|
if (isNaN(Number(value))) {
|
127
151
|
msgs.push(options.message || instance.defaultMessages.not_a_number);
|
128
152
|
} else {
|
129
|
-
if (options.odd && judge.utils.isEven(parsedValue)) {
|
130
|
-
|
131
|
-
|
153
|
+
if (options.odd && judge.utils.isEven(parsedValue)) {
|
154
|
+
msgs.push(instance.defaultMessages.odd);
|
155
|
+
}
|
156
|
+
if (options.even && judge.utils.isOdd(parsedValue)) {
|
157
|
+
msgs.push(instance.defaultMessages.even);
|
158
|
+
}
|
159
|
+
if (options.only_integer && !judge.utils.isInt(parsedValue)) {
|
160
|
+
msgs.push(instance.defaultMessages.not_an_integer);
|
161
|
+
}
|
132
162
|
_(operators).each(function(operator, key) {
|
133
|
-
|
163
|
+
var valid = judge.utils.operate(parsedValue, operators[key], parseFloat(options[key], 10));
|
164
|
+
if (options.hasOwnProperty(key) && !valid) {
|
134
165
|
var m = options.message || instance.defaultMessages[key];
|
135
166
|
msgs.push(judge.utils.countMsg(m, options[key]));
|
136
167
|
}
|
@@ -138,29 +169,42 @@ judge.Watcher.prototype.validates = function() {
|
|
138
169
|
}
|
139
170
|
return msgs.length? { valid:false, messages:msgs } : { valid:true };
|
140
171
|
},
|
141
|
-
|
172
|
+
|
173
|
+
// Format validator ported as closely as possible
|
174
|
+
// from [ActiveModel::Validations::FormatValidator](http://api.rubyonrails.org/classes/ActiveModel/Validations/FormatValidator.html).
|
142
175
|
format: function(options) {
|
143
176
|
var msgs = [],
|
144
177
|
value = instance.element.value;
|
145
178
|
if (options.hasOwnProperty('with')) {
|
146
179
|
var withReg = judge.utils.convertRegExp(options['with']);
|
147
|
-
if (!withReg.test(value)) {
|
180
|
+
if (!withReg.test(value)) {
|
181
|
+
msgs.push(options.message || instance.defaultMessages.invalid);
|
182
|
+
}
|
148
183
|
}
|
149
184
|
if (options.hasOwnProperty('without')) {
|
150
185
|
var withoutReg = judge.utils.convertRegExp(options.without);
|
151
|
-
if (withoutReg.test(value)) {
|
186
|
+
if (withoutReg.test(value)) {
|
187
|
+
msgs.push(options.message || instance.defaultMessages.invalid);
|
188
|
+
}
|
152
189
|
}
|
153
190
|
return msgs.length ? { valid:false, messages:msgs } : { valid:true };
|
154
191
|
},
|
155
|
-
|
192
|
+
|
193
|
+
// Acceptance validator ported as closely as possible
|
194
|
+
// from [ActiveModel::Validations::AcceptanceValidator](http://api.rubyonrails.org/classes/ActiveModel/Validations/AcceptanceValidator.html).
|
156
195
|
acceptance: function(options) {
|
157
196
|
if (instance.element.checked === true) {
|
158
197
|
return { valid:true };
|
159
198
|
} else {
|
160
|
-
return {
|
199
|
+
return {
|
200
|
+
valid:false,
|
201
|
+
messages:[options.message || instance.defaultMessages.accepted]
|
202
|
+
};
|
161
203
|
}
|
162
204
|
},
|
163
|
-
|
205
|
+
|
206
|
+
// Confirmation validator ported as closely as possible
|
207
|
+
// from [ActiveModel::Validations::ConfirmationValidator](http://api.rubyonrails.org/classes/ActiveModel/Validations/ConfirmationValidator.html).
|
164
208
|
confirmation: function(options) {
|
165
209
|
var id = instance.element.getAttribute('id'),
|
166
210
|
confId = id + '_confirmation',
|
@@ -168,25 +212,31 @@ judge.Watcher.prototype.validates = function() {
|
|
168
212
|
if (instance.element.value === confElem.value) {
|
169
213
|
return { valid:true };
|
170
214
|
} else {
|
171
|
-
return {
|
215
|
+
return {
|
216
|
+
valid:false,
|
217
|
+
messages:[options.message || instance.defaultMessages.confirmation]
|
218
|
+
};
|
172
219
|
}
|
173
220
|
}
|
174
221
|
};
|
222
|
+
// Return all validation methods, including those found in judge.customValidators.
|
223
|
+
// If you name a custom validation method the same as a default one, for example
|
224
|
+
// `judge.customValidators.presence = function() {};`
|
225
|
+
// then the custom method _will overwrite_ the default one, so be careful!
|
175
226
|
return _.extend(methods, extendedMethods);
|
176
227
|
};
|
177
228
|
|
178
|
-
//
|
229
|
+
// This object should contain any judge validation methods
|
230
|
+
// that correspond to custom validators used in the model.
|
179
231
|
judge.customValidators = {};
|
180
232
|
|
181
|
-
//
|
233
|
+
// The judge store is now open :)
|
182
234
|
judge.store = (function() {
|
183
|
-
// private
|
184
235
|
var store = {};
|
185
236
|
|
186
|
-
// public methods
|
187
237
|
return {
|
188
238
|
|
189
|
-
//
|
239
|
+
// Stores a Watcher for an element against a user defined key.
|
190
240
|
save: function(key, element) {
|
191
241
|
var elements = judge.utils.isCollection(element) ? element : [element];
|
192
242
|
_(elements).each(function(element) {
|
@@ -200,7 +250,7 @@ judge.store = (function() {
|
|
200
250
|
return store;
|
201
251
|
},
|
202
252
|
|
203
|
-
//
|
253
|
+
// Removes an individual stored Watcher.
|
204
254
|
remove: function(key, element) {
|
205
255
|
if (!store.hasOwnProperty(key)) {
|
206
256
|
return null;
|
@@ -210,13 +260,15 @@ judge.store = (function() {
|
|
210
260
|
return store;
|
211
261
|
},
|
212
262
|
|
213
|
-
//
|
263
|
+
// Returns the entire store object, or an array of Watchers stored against
|
264
|
+
// the given key.
|
214
265
|
get: function(key) {
|
215
266
|
if (_(key).isUndefined()) { return store; }
|
216
267
|
return store.hasOwnProperty(key) ? store[key] : null;
|
217
268
|
},
|
218
269
|
|
219
|
-
//
|
270
|
+
// Returns the entire store object with Watchers converted to elements,
|
271
|
+
// or all DOM elements stored within all Watchers stored against the given key.
|
220
272
|
getDOM: function(key) {
|
221
273
|
if (_(key).isUndefined()) {
|
222
274
|
var convertedStore = {};
|
@@ -228,7 +280,8 @@ judge.store = (function() {
|
|
228
280
|
return store.hasOwnProperty(key) ? _(store[key]).pluck('element') : null;
|
229
281
|
},
|
230
282
|
|
231
|
-
//
|
283
|
+
// Wipes the entire store object, or wipes all Watchers stored against
|
284
|
+
// the given key.
|
232
285
|
clear: function(key) {
|
233
286
|
if (_(key).isUndefined()) {
|
234
287
|
store = {};
|
@@ -242,7 +295,8 @@ judge.store = (function() {
|
|
242
295
|
};
|
243
296
|
}());
|
244
297
|
|
245
|
-
//
|
298
|
+
// Validate all given element(s) without storing. Watcher creation is handled for you,
|
299
|
+
// but the created Watchers will not be returned, so it's less flexible.
|
246
300
|
judge.validate = function(elements) {
|
247
301
|
var results = [];
|
248
302
|
elements = judge.utils.isCollection(elements) ? elements : [elements];
|
@@ -253,9 +307,10 @@ judge.validate = function(elements) {
|
|
253
307
|
return results;
|
254
308
|
};
|
255
309
|
|
256
|
-
//
|
310
|
+
// The obligatory utilities.
|
257
311
|
judge.utils = {
|
258
|
-
|
312
|
+
|
313
|
+
// Determines whether an object is a DOM element of the types that judge can work with.
|
259
314
|
isValidatable: function(object) {
|
260
315
|
var type = judge.utils.getObjectString(object);
|
261
316
|
return (
|
@@ -265,7 +320,7 @@ judge.utils = {
|
|
265
320
|
);
|
266
321
|
},
|
267
322
|
|
268
|
-
//
|
323
|
+
// A way of checking isArray but including NodeList.
|
269
324
|
isCollection: function(object) {
|
270
325
|
var type = judge.utils.getObjectString(object);
|
271
326
|
return (
|
@@ -273,27 +328,32 @@ judge.utils = {
|
|
273
328
|
);
|
274
329
|
},
|
275
330
|
|
276
|
-
//
|
331
|
+
// Returns the object type as represented in `Object.prototype.toString`.
|
277
332
|
getObjectString: function(object) {
|
278
333
|
var string = Object.prototype.toString.call(object);
|
279
334
|
return string.replace(/\[|\]/g, '').split(' ')[1];
|
280
335
|
},
|
281
336
|
|
282
|
-
//
|
337
|
+
// OMG! How can you use eval, you monster! It's totally evil!
|
338
|
+
// It's used for stuff like `(3, '<', 4) => '3 < 4' => true`.
|
283
339
|
operate: function(input, operator, validInput) {
|
284
340
|
return eval(input+' '+operator+' '+validInput);
|
285
341
|
},
|
286
342
|
|
287
|
-
//
|
343
|
+
// Sub the expected value into an error message.
|
288
344
|
countMsg: function(message, count) { return message.replace(/\%\{count\}/, count); },
|
289
345
|
|
290
|
-
// numerical helpers
|
346
|
+
// Some nifty numerical helpers.
|
291
347
|
isInt: function(value) { return value === +value && value === (value|0); },
|
292
348
|
isFloat: function(value) { return value === +value && value !== (value|0); },
|
293
349
|
isEven: function(value) { return (value % 2 === 0) ? true : false; },
|
294
350
|
isOdd: function(value) { return !judge.utils.isEven(value); },
|
295
351
|
|
296
|
-
//
|
352
|
+
// Converts a ruby flag-first formatted regular expression, given as a string, into JavaScript.
|
353
|
+
// This is rudimentary at best, as there are many, many difference between Ruby
|
354
|
+
// and JavaScript when it comes to regexp-fu. Basically, if you validate your records
|
355
|
+
// records using complex regular expressions AND you hope that this will "just work"
|
356
|
+
// on the client-side too… you are going to be pretty disappointed.
|
297
357
|
convertRegExp: function(string) {
|
298
358
|
var p = string.slice(1, -1).split(':'),
|
299
359
|
o = p.shift(),
|
data/lib/judge/form.rb
CHANGED
@@ -4,7 +4,7 @@ module Judge
|
|
4
4
|
|
5
5
|
include ActionView::Helpers::TagHelper
|
6
6
|
|
7
|
-
%w{text_field text_area}.each do |type|
|
7
|
+
%w{text_field text_area password_field}.each do |type|
|
8
8
|
helper = <<-END
|
9
9
|
def validated_#{type}(method, options = {})
|
10
10
|
options = { "data-validate" => Judge::Utils.jsonify_validators(self.object, method) }.merge(options)
|
@@ -19,7 +19,6 @@ module Judge
|
|
19
19
|
@template.radio_button(@object_name, method, tag_value, objectify_options(options))
|
20
20
|
end
|
21
21
|
|
22
|
-
|
23
22
|
def validated_check_box(method, options = {}, checked_value = "1", unchecked_value = "0")
|
24
23
|
options = { "data-validate" => Judge::Utils.jsonify_validators(self.object, method) }.merge(options)
|
25
24
|
@template.check_box(self.object_name, method, objectify_options(options), checked_value, unchecked_value)
|
@@ -39,6 +38,22 @@ module Judge
|
|
39
38
|
html_options = { "data-validate" => Judge::Utils.jsonify_validators(self.object, method) }.merge(html_options)
|
40
39
|
@template.grouped_collection_select(self.object_name, method, collection, group_method, group_label_method, option_key_method, option_value_method, objectify_options(options), @default_options.merge(html_options))
|
41
40
|
end
|
41
|
+
|
42
|
+
%w{date_select datetime_select time_select}.each do |type|
|
43
|
+
helper = <<-END
|
44
|
+
def validated_#{type}(method, options = {}, html_options = {})
|
45
|
+
html_options = { "data-validate" => Judge::Utils.jsonify_validators(self.object, method) }.merge(html_options)
|
46
|
+
@template.#{type}(self.object_name, method, objectify_options(options), html_options)
|
47
|
+
end
|
48
|
+
END
|
49
|
+
class_eval helper, __FILE__, __LINE__
|
50
|
+
end
|
51
|
+
|
52
|
+
def validated_time_zone_select(method, priority_zones = nil, options = {}, html_options = {})
|
53
|
+
html_options = { "data-validate" => Judge::Utils.jsonify_validators(self.object, method) }.merge(html_options)
|
54
|
+
@template.time_zone_select(@object_name, method, priority_zones, objectify_options(options), @default_options.merge(html_options))
|
55
|
+
end
|
56
|
+
|
42
57
|
|
43
58
|
end
|
44
59
|
|
@@ -8,6 +8,12 @@ class Foo < ActiveRecord::Base
|
|
8
8
|
validates :five, :format => { :without => /[A-Za-z]+/ }
|
9
9
|
validates :six, :acceptance => true
|
10
10
|
validates :seven, :confirmation => true
|
11
|
+
validates :eight, :confirmation => true
|
12
|
+
|
13
|
+
validates :nine, :presence => true
|
14
|
+
validates :ten, :presence => true
|
15
|
+
validates :eleven, :presence => true
|
16
|
+
validates :twelve, :presence => true
|
11
17
|
|
12
18
|
end
|
13
19
|
|
@@ -41,6 +41,27 @@
|
|
41
41
|
<%= f.label :seven %><br />
|
42
42
|
<%= f.validated_text_field :seven %>
|
43
43
|
</div>
|
44
|
+
<div class="field">
|
45
|
+
<%= f.label :eight %><br />
|
46
|
+
<%= f.validated_password_field :eight %>
|
47
|
+
</div>
|
48
|
+
<div class="field">
|
49
|
+
<%= f.label :nine %><br />
|
50
|
+
<%= f.validated_time_zone_select :nine, ActiveSupport::TimeZone.us_zones, :include_blank => true %>
|
51
|
+
</div>
|
52
|
+
<div class="field">
|
53
|
+
<%= f.label :ten %><br />
|
54
|
+
<%= f.validated_time_select :ten, :include_blank => true, :ignore_date => true %>
|
55
|
+
</div>
|
56
|
+
<div class="field">
|
57
|
+
<%= f.label :eleven %><br />
|
58
|
+
<%= f.validated_datetime_select :eleven, :include_blank => true %>
|
59
|
+
</div>
|
60
|
+
<div class="field">
|
61
|
+
<%= f.label :twelve %><br />
|
62
|
+
<%= f.validated_date_select :twelve, :include_blank => true %>
|
63
|
+
</div>
|
64
|
+
|
44
65
|
<div class="actions">
|
45
66
|
<%= f.submit %>
|
46
67
|
</div>
|