valibot 0.2.5
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +20 -0
- data/README.md +250 -0
- data/js/valibot.js +503 -0
- data/lib/valibot.rb +117 -0
- metadata +90 -0
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2011 Kenichi Nakamura
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,250 @@
|
|
1
|
+
Valibot!
|
2
|
+
========
|
3
|
+
|
4
|
+
Automatic field validation for forms backed by DataMapper models through Sinatra.
|
5
|
+
|
6
|
+
What it requires:
|
7
|
+
-----------------
|
8
|
+
|
9
|
+
* [Datamapper](http://datamapper.org/)
|
10
|
+
* [Sinatra](http://sinatrarb.com/)
|
11
|
+
* [Rack](http://rack.rubyforge.org/)
|
12
|
+
* [jQuery](http://jquery.com/)
|
13
|
+
|
14
|
+
What it provides:
|
15
|
+
-----------------
|
16
|
+
|
17
|
+
* full automatic field validation on default bind events
|
18
|
+
* field value matching confirmation
|
19
|
+
* extensible through callbacks used to build, place, remove, or transform error tags
|
20
|
+
* Valibot#all method to do the whole thing at once.
|
21
|
+
* automatic 'onsubmit' binding
|
22
|
+
|
23
|
+
To use (brief):
|
24
|
+
-------
|
25
|
+
|
26
|
+
* add this gem to your Gemfile, bundle install
|
27
|
+
* add the javascript to your site
|
28
|
+
* add the app to your config.ru (defaults to '/_valibot')
|
29
|
+
* create Valibots for the models on forms where you need them
|
30
|
+
|
31
|
+
To use (details):
|
32
|
+
-----------------
|
33
|
+
|
34
|
+
####to add the javascript to your site, there are two methods:
|
35
|
+
|
36
|
+
Valibot has a route at '/valibot.js' that will deliver the source. This is the *preferred* method:
|
37
|
+
|
38
|
+
<script src="/_valibot/valibot.js"></script>
|
39
|
+
|
40
|
+
Valibot *can* add a helper to Sinatra::Base called 'valibot_js'. call this in a script template like so:
|
41
|
+
|
42
|
+
<%= valibot_js %>
|
43
|
+
|
44
|
+
They deliver the exact same content, but the route adds Cache-Control headers so layers like Varnish or Rack::Cache can do their work. In order to use the helper, you need to register it in your app:
|
45
|
+
|
46
|
+
class App < Sinatra::Base
|
47
|
+
register Valibot::Helpers
|
48
|
+
get('/'){ erb "<script><%= valibot_js %></script>" }
|
49
|
+
end
|
50
|
+
|
51
|
+
####the sinatra app needs a place to run at in your Rack config. something like the following should suffice:
|
52
|
+
|
53
|
+
map '/_valibot' do
|
54
|
+
run Valibot::App
|
55
|
+
end
|
56
|
+
|
57
|
+
note that you can change the path it runs at, but you must pass that path as a value to the Valibot javascript constructor using the key `pathPrefix` (see below).
|
58
|
+
|
59
|
+
####to create the Valibots, call the constructor inside $(document).ready():
|
60
|
+
|
61
|
+
$(document).ready(function(){ new Valibot({ modelName: 'user' }); });
|
62
|
+
|
63
|
+
Constructor options:
|
64
|
+
--------------------
|
65
|
+
|
66
|
+
####modelName : string *required if no `modelNames`*
|
67
|
+
name of the model to validate against, in snake_case. in modern Rack-based webapps, the convention for form field names is to follow a `model_name[field_name]` pattern. Rack translates these names into the params hash and it is very common for the controller to pass this hash to a model constructor like `ModelName.new params['model_name']`. your form should follow this convention for Valibot to work, as it will use jQuery to select all `input`, `textarea`, and `select` tags from the DOM that have names that start with this value. ex:
|
68
|
+
|
69
|
+
<input type="text" name="user[first_name]"/>
|
70
|
+
<input type="text" name="user[email]"/>
|
71
|
+
...
|
72
|
+
new Valibot({ modelName: 'user' });
|
73
|
+
|
74
|
+
---
|
75
|
+
|
76
|
+
####modelNames : array of strings *required if no `modelName`*
|
77
|
+
names of the models to validate against, in snake_case. this behaves exactly like `modelName` but allows multiple models to be validated in the same form. ex:
|
78
|
+
|
79
|
+
<input type="text" name="user[first_name]"/>
|
80
|
+
<input type="text" name="profile[bio]"/>
|
81
|
+
...
|
82
|
+
new Valibot({ modelNames: ['user', 'profile'] });
|
83
|
+
|
84
|
+
note that the first invalid field of the _first model specified_ will be `focus()`ed if the form fails validation on submit.
|
85
|
+
|
86
|
+
---
|
87
|
+
|
88
|
+
####pathPrefix : string
|
89
|
+
path you have Valibot racked up at. ex:
|
90
|
+
|
91
|
+
new Valibot({ modelName: 'user', pathPrefix: 'dogs_and_cats' });
|
92
|
+
|
93
|
+
---
|
94
|
+
|
95
|
+
####errorTagBuilder : function(text, self, minion)
|
96
|
+
a callback for building the tag you want to put in the DOM and show the user with the error message from DataMapper in it. the default builder will create an `<em>` element, set its class to 'error', innerHTML `text` in, and return it.
|
97
|
+
######parameters:
|
98
|
+
* `text` - error message from DataMapper
|
99
|
+
* `self` - reference to the Valibot object
|
100
|
+
* `minion` - reference to the Valitron or Confirm-o-mat object
|
101
|
+
######returns:
|
102
|
+
* an element with the `text` in it suitable for placing in to the DOM.
|
103
|
+
|
104
|
+
---
|
105
|
+
|
106
|
+
####errorTagPlacer : function(tag, errorTag, self, minion)
|
107
|
+
a callback for placing the element returned from the builder into the DOM.
|
108
|
+
######parameters:
|
109
|
+
* `tag` - the field element whose value is being validated
|
110
|
+
* `errorTag` - the element built and returned by the `errorTagBuilder` function of this Valibot
|
111
|
+
* `self` - reference to the Valibot object
|
112
|
+
* `minion` - reference to the Valitron or Confirm-o-mat object
|
113
|
+
|
114
|
+
---
|
115
|
+
|
116
|
+
####errorTagRemover : function(errorTag, self, minion)
|
117
|
+
a callback for removing the element from the DOM in the event of no error found.
|
118
|
+
######parameters:
|
119
|
+
* `errorTag` - the element in the DOM that holds the error message for this field
|
120
|
+
* `self` - reference to the Valibot object
|
121
|
+
* `minion` - reference to the Valitron or Confirm-o-mat object
|
122
|
+
|
123
|
+
---
|
124
|
+
|
125
|
+
####errorTagTransformer : function(text, errorTag, self, minion)
|
126
|
+
a callback for transforming the element in the DOM in the event of a new error message replacing an old one.
|
127
|
+
######parameters:
|
128
|
+
* `text` - error message from DataMapper
|
129
|
+
* `errorTag` - the element in the DOM that holds the error message for this field
|
130
|
+
* `self` - reference to the Valibot object
|
131
|
+
* `minion` - reference to the Valitron or Confirm-o-mat object
|
132
|
+
|
133
|
+
---
|
134
|
+
|
135
|
+
####include : array or map { modelName : array }
|
136
|
+
by default, Valibot will attach Valitrons to elements whose name attribute matches a specific regular expression, after jQuery selection. you can force fields whose name attributes don't match the regex to be checked by Valibot by putting the field name in this array. i'm not actually sure when this would be needed. :)
|
137
|
+
|
138
|
+
---
|
139
|
+
|
140
|
+
####exclude : array or map { modelName : array }
|
141
|
+
you can keep Valibot from attaching Valitrons to certain elements by putting their field names in this
|
142
|
+
array. ex:
|
143
|
+
|
144
|
+
new Valibot({ modelName: 'user', exclude: ['middle_name'] });
|
145
|
+
|
146
|
+
new Valibot({ modelNames: ['user', 'profile'], exclude: {'user': ['middle_name'], 'profile': ['icon']});
|
147
|
+
|
148
|
+
---
|
149
|
+
|
150
|
+
####bindEvents : map { fieldName : event }
|
151
|
+
Valibot has a fairly intuitive default bind event for each input type: `change` for checkboxes, radio buttons, and select drop-downs; `blur` for everything else. you can override these defaults on a per-field basis by passing them in this hash. ex:
|
152
|
+
|
153
|
+
new Valibot({ modelName: 'user', bindEvents: {'middle_name':'keyup'} });
|
154
|
+
|
155
|
+
---
|
156
|
+
|
157
|
+
####context : string
|
158
|
+
DataMapper has contextual validations built-in. if you need to validate against a certain context, pass it in here. ex:
|
159
|
+
|
160
|
+
new Valibot({ modelName: 'user', context: 'signup' });
|
161
|
+
|
162
|
+
---
|
163
|
+
|
164
|
+
####confirm : array of maps { id : things }
|
165
|
+
often on a form, one will have an 'extra' field that is not part of the data model, but needs to match another field in value. usually, these are passwords or email addresses. in this case, if you have given the actual model field element an id of 'password', the confirmation field id should be 'confirm_password'. then you can tell Valibot about this by passing this array of hashes and it will attach a Confirm-o-mat to each. `things` is the word that the Confirm-o-mat will use in its error message about the things not matching. ex:
|
166
|
+
|
167
|
+
new Valibot({ modelName: 'user', confirm: [{'password', 'Passwords'}] });
|
168
|
+
|
169
|
+
this would attach a Confirm-o-mat to `$('#confirm_password')` that checks its value against `$('#password').val()` and will build an errorTag with the text, "Passwords do not match."
|
170
|
+
|
171
|
+
---
|
172
|
+
|
173
|
+
####agreements : array of maps { id : things }
|
174
|
+
often on a form, one will have a checkbox that is not part of the data model, but needs to checked as a legally binding agreement. usually, these are rules or term and conditions, etc. in this case, simply give the input a unique id. then you can tell Valibot about it by passing this array of hashes and it will attach an Agree-Pee-Oh to each. `things` is the word that the Agree-Pee-Oh will use in its error message about the things not being agreed to. ex:
|
175
|
+
|
176
|
+
new Valibot({ modelName: 'user', agreements: [{'rules', 'Rules'}] });
|
177
|
+
|
178
|
+
this would attach an Agree-Pee-Oh to `$('#rules')` that verified it's checked and will build an errorTag with the text, "You must agree to the Rules." if it is not.
|
179
|
+
|
180
|
+
---
|
181
|
+
|
182
|
+
####fancy : boolean
|
183
|
+
Valibot has two built-in types of errorTag(Placer|Remover|Transformer) functions. the default is very bland and simply inserts, replaces, or removes the errorTag element. the fancy option uses jQuery animation to fadeIn and fadeOut the errorTag element. if this is `true`, and you also feed it your own errorTag callbacks, those will be used instead. ex:
|
184
|
+
|
185
|
+
new Valibot({ modelName: 'user', fancy: true, errorTagPlacer: myETPlacer });
|
186
|
+
|
187
|
+
this would utilize the "fancy" Remover and Transformer built-in functions, but call `myETPlacer()` for placement. note that the `_fancyErrorTagTransformer` function actually calls whatever Placer function is set in the containing Valibot; in this case, `myETPlacer`.
|
188
|
+
|
189
|
+
---
|
190
|
+
|
191
|
+
####bindOnSubmit : boolean
|
192
|
+
Valibot automatically binds it's own `onSubmit()` function to the first form that it finds model fields in. this function will run through any unchecked or invalid 'trons or 'mats and call their `checkValue()` functions. once that is complete, if all of them are valid, it submits the form; otherwise, error messages are built/placed like normal, and the first invalid field is focus()ed. this flag allows you to turn off this functionality, for cases where you need to do your own onsubmit dance. note that the Valibot's onsubmit can be called in this situation anyway; but, be careful because it will submit the form if everything is valid. ex:
|
193
|
+
|
194
|
+
var valibot = new Valibot({ modelName: 'user', bindOnSubmit: false });
|
195
|
+
$(valibot.form).bind('submit', function(event) {
|
196
|
+
event.preventDefault();
|
197
|
+
|
198
|
+
// do something clever
|
199
|
+
|
200
|
+
valibot.onSubmit();
|
201
|
+
});
|
202
|
+
|
203
|
+
---
|
204
|
+
|
205
|
+
####submitForm : boolean
|
206
|
+
sometimes, you just want to submit the form yourself. most of the time, you want to do it all AJAX fancypants. fine, just pass this in as `false` and Valibot won't try to submit the form. it will still call `onSubmitCallback` so you probably want to put your XHRs in there.
|
207
|
+
|
208
|
+
---
|
209
|
+
|
210
|
+
####onSubmitCallback : function(valibot)
|
211
|
+
set this to a function you want to run when the form would be submitted. if you set `submitForm` to false, this function will be called, but the form WILL NOT be submitted. use these two together to get Valibot validation on forms you want to submit XHR-style. ex:
|
212
|
+
|
213
|
+
var valibot = new Valibot({ modelName: 'foo', submitForm: false, onSubmitCallback: function(valibot) {
|
214
|
+
$.post(valibot.form.action, $(valibot.form).serialize(), function(response){ /* ... /* });
|
215
|
+
}});
|
216
|
+
|
217
|
+
---
|
218
|
+
|
219
|
+
####appUrl : string
|
220
|
+
perhaps you want to run Valibot at a different URL completely from the one that the form your validating loaded from. fine, just tell Valibot about it with this. note that this puts Valibot into JSONP mode and all validation requests turn from POSTs into GETs. ex:
|
221
|
+
|
222
|
+
new Valibot({ modelName: 'user', appUrl: 'http://some.other.domain/'});
|
223
|
+
|
224
|
+
this would cause all validation requests to go to 'http://some.other.domain/_valibot/:model/...'
|
225
|
+
|
226
|
+
---
|
227
|
+
|
228
|
+
####dependents : map of arrays { field : [ otherField, ... ] }
|
229
|
+
sometimes you need to validate a field based off of the value of another field. you can tell Valibot about this with this option, and it will automatically include the value(s) of the other field(s) in the validation. ex:
|
230
|
+
|
231
|
+
new Valibot({ modelName: 'user', dependents: {'last_name': ['first_name']} });
|
232
|
+
|
233
|
+
this would cause Valibot to POST the value of the field named 'user[first_name]' with the check request for 'last_name'. your model should be configured to validate `last_name` based off of the value of `first_name`:
|
234
|
+
|
235
|
+
validates_with_block :last_name do
|
236
|
+
if self.first_name == 'a' and self.last_name == 'b'
|
237
|
+
[false, "can't have b with a."]
|
238
|
+
else
|
239
|
+
true
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
243
|
+
note that in the above example, `first_name` would still be validated on it's own.
|
244
|
+
|
245
|
+
Next Steps (TODOS):
|
246
|
+
-------------------
|
247
|
+
|
248
|
+
* automated testing (!!)
|
249
|
+
* array parameters
|
250
|
+
* profit!
|
data/js/valibot.js
ADDED
@@ -0,0 +1,503 @@
|
|
1
|
+
/* valibot.js (c) 2011 kenichi nakamura (kenichi.nakamura@gmail.com)
|
2
|
+
*
|
3
|
+
* javascript class for easy automatic form field validation against datamapper
|
4
|
+
* models behind a sinatra app.
|
5
|
+
*
|
6
|
+
* see ____ for details. TODO
|
7
|
+
*/
|
8
|
+
|
9
|
+
// regex to make sure we only grab fields whose 'name' attrs match
|
10
|
+
var _valibotFieldRegExp = new RegExp('^\\w+\\[[^\\]]+\\]$');
|
11
|
+
|
12
|
+
if (Array.prototype.indexOf == null) {
|
13
|
+
Array.prototype.indexOf = function(obj) {
|
14
|
+
for (var i = 0; i < this.length; i++) {
|
15
|
+
if (this[i] == obj) {
|
16
|
+
return i;
|
17
|
+
}
|
18
|
+
}
|
19
|
+
return -1;
|
20
|
+
};
|
21
|
+
}
|
22
|
+
Array.prototype.contains = function(obj){ return this.indexOf(obj) != -1; };
|
23
|
+
|
24
|
+
/* the Valibot class. create an object of this class and feed it a model name. oh,
|
25
|
+
* and options too if you want. the object, during construction, will attach
|
26
|
+
* Valitrons (see below) to each field it finds that matches the modelName specified.
|
27
|
+
* it will also attach Confirmomats (see below) to any field specified as a
|
28
|
+
* confirmation field.
|
29
|
+
*/
|
30
|
+
function Valibot(opts) {
|
31
|
+
|
32
|
+
this.pathPrefix = '_valibot';
|
33
|
+
this.modelNames = [];
|
34
|
+
this.errorTagPlacer = this._defaultErrorTagPlacer;
|
35
|
+
this.errorTagBuilder = this._defaultErrorTagBuilder;
|
36
|
+
this.errorTagRemover = this._defaultErrorTagRemover;
|
37
|
+
this.errorTagTransformer = this._defaultErrorTagTransformer;
|
38
|
+
this.include = {};
|
39
|
+
this.exclude = {};
|
40
|
+
this.bindEvents = {};
|
41
|
+
this.context = null;
|
42
|
+
this.confirm = [];
|
43
|
+
this.confirmomats = [];
|
44
|
+
this.valitrons = [];
|
45
|
+
this.valids = [];
|
46
|
+
this.invalids = [];
|
47
|
+
this.bindOnSubmit = true;
|
48
|
+
this.submitForm = true;
|
49
|
+
this.onSubmitCallback = null;
|
50
|
+
this.agreements = [];
|
51
|
+
this.agreepeeohs = [];
|
52
|
+
this.formId = '';
|
53
|
+
this.appUrl = null;
|
54
|
+
this.dependents = {};
|
55
|
+
|
56
|
+
if (opts != null) {
|
57
|
+
if (opts.fancy != null && opts.fancy) {
|
58
|
+
this.errorTagPlacer = this._fancyErrorTagPlacer;
|
59
|
+
this.errorTagRemover = this._fancyErrorTagRemover;
|
60
|
+
this.errorTagTransformer = this._fancyErrorTagTransformer;
|
61
|
+
}
|
62
|
+
if (opts.modelName != null) this.modelNames = [opts.modelName];
|
63
|
+
if (opts.modelNames != null) this.modelNames = opts.modelNames;
|
64
|
+
if (opts.pathPrefix != null) this.pathPrefix = opts.pathPrefix;
|
65
|
+
if (opts.errorTagPlacer != null) this.errorTagPlacer = opts.errorTagPlacer;
|
66
|
+
if (opts.errorTagBuilder != null) this.errorTagBuilder = opts.errorTagBuilder;
|
67
|
+
if (opts.errorTagRemover != null) this.errorTagRemover = opts.errorTagRemover;
|
68
|
+
if (opts.errorTagTransformer != null) this.errorTagTransformer = opts.errorTagTransformer;
|
69
|
+
if (opts.bindEvents != null) this.bindEvents = opts.bindEvents;
|
70
|
+
if (opts.context != null) this.context = opts.context;
|
71
|
+
if (opts.confirm != null) this.confirm = opts.confirm;
|
72
|
+
if (opts.bindOnSubmit != null) this.bindOnSubmit = opts.bindOnSubmit;
|
73
|
+
if (opts.submitForm != null) this.submitForm = opts.submitForm;
|
74
|
+
if (opts.onSubmitCallback != null) this.onSubmitCallback = opts.onSubmitCallback;
|
75
|
+
if (opts.agreements != null) this.agreements = opts.agreements;
|
76
|
+
if (opts.formId != null) this.formId = '#' + opts.formId + ' ';
|
77
|
+
if (opts.appUrl != null) this.appUrl = opts.appUrl;
|
78
|
+
if (opts.dependents != null) this.dependents = opts.dependents;
|
79
|
+
|
80
|
+
if (opts.include != null) {
|
81
|
+
if (opts.include instanceof Array) {
|
82
|
+
this.include[this.modelNames[0]] = opts.include;
|
83
|
+
} else {
|
84
|
+
this.include = opts.include;
|
85
|
+
}
|
86
|
+
}
|
87
|
+
if (opts.exclude != null) {
|
88
|
+
if (opts.exclude instanceof Array) {
|
89
|
+
this.exclude[this.modelNames[0]] = opts.exclude;
|
90
|
+
} else {
|
91
|
+
this.exclude = opts.exclude;
|
92
|
+
}
|
93
|
+
}
|
94
|
+
|
95
|
+
}
|
96
|
+
|
97
|
+
var _this = this;
|
98
|
+
$(this.modelNames).each(function(i, modelName) {
|
99
|
+
$(_this.formId + 'input[name^=' + modelName + '], ' +
|
100
|
+
_this.formId + 'select[name^=' + modelName + '], ' +
|
101
|
+
_this.formId + 'textarea[name^=' + modelName + ']').each(function(i,f) {
|
102
|
+
|
103
|
+
var _name = $(f).attr('name');
|
104
|
+
var field = _name.substring(_name.indexOf('[') + 1, _name.length - 1);
|
105
|
+
|
106
|
+
var _type = f.type.toLowerCase();
|
107
|
+
var _defaultBindEvent = 'blur';
|
108
|
+
if (_type == 'checkbox' || _type == 'radio' || _type == 'select-one' || _type == 'select-multiple')
|
109
|
+
_defaultBindEvent = 'change';
|
110
|
+
|
111
|
+
var bindEvent = (_this.bindEvents[field] != null) ? _this.bindEvents[field] : _defaultBindEvent;
|
112
|
+
|
113
|
+
if (_this.include[modelName] instanceof Array && _this.include[modelName].indexOf(field) != -1) {
|
114
|
+
_this.valitrons.push(new Valitron(f, modelName, field, bindEvent, _this));
|
115
|
+
} else if (_this.exclude[modelName] instanceof Array && _this.exclude[modelName].indexOf(field) != -1) {
|
116
|
+
// NOOP
|
117
|
+
} else {
|
118
|
+
if (_valibotFieldRegExp.test(f.name))
|
119
|
+
_this.valitrons.push(new Valitron(f, modelName, field, bindEvent, _this));
|
120
|
+
}
|
121
|
+
|
122
|
+
});
|
123
|
+
});
|
124
|
+
|
125
|
+
if (this.confirm != null && this.confirm.length > 0) {
|
126
|
+
$(this.confirm).each(function(i,c) {
|
127
|
+
$.each(c, function(id, things) { _this.confirmomats.push(new Confirmomat(id, things, _this)); });
|
128
|
+
});
|
129
|
+
}
|
130
|
+
|
131
|
+
if (this.agreements != null && this.agreements.length > 0) {
|
132
|
+
$(this.agreements).each(function(i,c) {
|
133
|
+
$.each(c, function(id, things) { _this.agreepeeohs.push(new AgreePeeOh(id, things, _this)); });
|
134
|
+
});
|
135
|
+
}
|
136
|
+
|
137
|
+
this.form = this.valitrons.concat(this.confirmomats).concat(this.agreepeeohs)[0].tag.form;
|
138
|
+
if (this.bindOnSubmit)
|
139
|
+
$(this.form).submit(function(e){ e.preventDefault(); _this.onSubmit(); });
|
140
|
+
|
141
|
+
}
|
142
|
+
|
143
|
+
Valibot.prototype = {
|
144
|
+
|
145
|
+
addValid:
|
146
|
+
function(good) {
|
147
|
+
if (!this.valids.contains(good)) this.valids.push(good);
|
148
|
+
if (this.invalids.contains(good)) this.invalids.splice(this.invalids.indexOf(good), 1);
|
149
|
+
},
|
150
|
+
|
151
|
+
removeValid:
|
152
|
+
function(remove) {
|
153
|
+
if (this.valids.contains(remove)) this.valids.splice(this.valids.indexOf(remove), 1);
|
154
|
+
},
|
155
|
+
|
156
|
+
addInvalid:
|
157
|
+
function(bad) {
|
158
|
+
if (!this.invalids.contains(bad)) this.invalids.push(bad);
|
159
|
+
this.removeValid(bad);
|
160
|
+
},
|
161
|
+
|
162
|
+
unchecked:
|
163
|
+
function() {
|
164
|
+
var uc = [];
|
165
|
+
var _this = this;
|
166
|
+
$(this.valitrons).each(function(i,tron){ if (!_this.valids.contains(tron) && !_this.invalids.contains(tron)) uc.push(tron); });
|
167
|
+
$(this.confirmomats).each(function(i,mat){ if (!_this.valids.contains(mat) && !_this.invalids.contains(mat)) uc.push(mat); });
|
168
|
+
$(this.agreepeeohs).each(function(i,oh){ if (!_this.valids.contains(oh) && !_this.invalids.contains(oh)) uc.push(oh); });
|
169
|
+
return uc;
|
170
|
+
},
|
171
|
+
|
172
|
+
allValid:
|
173
|
+
function() {
|
174
|
+
return this.invalids.length == 0 &&
|
175
|
+
this.valids.length == this.valitrons.length + this.confirmomats.length + this.agreepeeohs.length;
|
176
|
+
},
|
177
|
+
|
178
|
+
onSubmit:
|
179
|
+
function() {
|
180
|
+
if (!this.allValid()) {
|
181
|
+
this.checking = this.invalids.concat(this.unchecked());
|
182
|
+
var _this = this;
|
183
|
+
$(this.invalids).each(function(i, bad){ bad.checkValue(null, function(valid) { _this.onSubmitCheckCallback(bad); }); });
|
184
|
+
$(this.unchecked()).each(function(i, uc){ uc.checkValue(null, function(valid) { _this.onSubmitCheckCallback(uc); }); });
|
185
|
+
} else {
|
186
|
+
if (this.submitForm) {
|
187
|
+
this.form.submit();
|
188
|
+
} else if (typeof this.onSubmitCallback === 'function') {
|
189
|
+
this.onSubmitCallback(this);
|
190
|
+
}
|
191
|
+
}
|
192
|
+
},
|
193
|
+
|
194
|
+
onSubmitCheckCallback:
|
195
|
+
function(tronOrMat, valid) {
|
196
|
+
this.checking.splice(this.checking.indexOf(tronOrMat), 1);
|
197
|
+
if (this.checking.length == 0) {
|
198
|
+
if (!this.allValid()) {
|
199
|
+
this.invalids[0].tag.focus();
|
200
|
+
} else {
|
201
|
+
if (this.submitForm) {
|
202
|
+
this.form.submit();
|
203
|
+
} else if (typeof this.onSubmitCallback === 'function') {
|
204
|
+
this.onSubmitCallback(this);
|
205
|
+
}
|
206
|
+
}
|
207
|
+
}
|
208
|
+
},
|
209
|
+
|
210
|
+
// never called by Valibot or its minions, but left here for utility.
|
211
|
+
all:
|
212
|
+
function(callback) {
|
213
|
+
var params = {};
|
214
|
+
$(this.valitrons).each(function(i, tron){ params[tron.tag.name] = tron.tag.value; });
|
215
|
+
var path = (this.appUrl != null ? this.appUrl : '') +
|
216
|
+
'/' + this.pathPrefix +
|
217
|
+
'/' + this.modelName +
|
218
|
+
(this.context != null ? ('/in/' + this.context) : '');
|
219
|
+
if (this.context != null)
|
220
|
+
path = path + '/in/' + this.context;
|
221
|
+
$.post(path, params, function(res){ callback(res) },
|
222
|
+
this.valibot.appUrl != null ? 'jsonp' : 'json');
|
223
|
+
},
|
224
|
+
|
225
|
+
_defaultErrorTagPlacer:
|
226
|
+
function(tag, errorTag, self, minion) {
|
227
|
+
$(errorTag).insertAfter($(tag));
|
228
|
+
},
|
229
|
+
|
230
|
+
_defaultErrorTagBuilder:
|
231
|
+
function(text, self, minion) {
|
232
|
+
var em = document.createElement('em');
|
233
|
+
em.className = 'error';
|
234
|
+
em.innerHTML = text;
|
235
|
+
return em;
|
236
|
+
},
|
237
|
+
|
238
|
+
_defaultErrorTagRemover:
|
239
|
+
function(errorTag, self, callback, minion) {
|
240
|
+
var ret = $(errorTag).remove();
|
241
|
+
if (callback != null) callback(ret);
|
242
|
+
},
|
243
|
+
|
244
|
+
_defaultErrorTagTransformer:
|
245
|
+
function(text, errorTag, self, minion) {
|
246
|
+
var em = this.errorTagBuilder(text, self, minion);
|
247
|
+
$(errorTag).replaceWith(em);
|
248
|
+
return em;
|
249
|
+
},
|
250
|
+
|
251
|
+
_fancyErrorTagPlacer:
|
252
|
+
function(tag, errorTag, self, minion) {
|
253
|
+
$(errorTag).css('display', 'none');
|
254
|
+
$(errorTag).insertAfter($(tag));
|
255
|
+
$(errorTag).fadeIn();
|
256
|
+
},
|
257
|
+
|
258
|
+
_fancyErrorTagRemover:
|
259
|
+
function(errorTag, self, minion) {
|
260
|
+
$(errorTag).fadeOut(null, null, function(){ $(errorTag).remove(); });
|
261
|
+
},
|
262
|
+
|
263
|
+
_fancyErrorTagTransformer:
|
264
|
+
function(text, errorTag, self, minion) {
|
265
|
+
if (text == errorTag.innerHTML) {
|
266
|
+
return errorTag;
|
267
|
+
} else {
|
268
|
+
var newErrorTag = self.errorTagBuilder(text, self, minion);
|
269
|
+
$(errorTag).fadeOut(null, null, function() {
|
270
|
+
var tag = $(errorTag).prev();
|
271
|
+
$(errorTag).remove();
|
272
|
+
self.errorTagPlacer(tag, newErrorTag);
|
273
|
+
});
|
274
|
+
return newErrorTag;
|
275
|
+
}
|
276
|
+
}
|
277
|
+
|
278
|
+
};
|
279
|
+
|
280
|
+
// ---
|
281
|
+
|
282
|
+
/* the Valitron class. instantiated on each model form field by an overseeing
|
283
|
+
* Valibot. handles the actual XHR between the JS and the valibot sinatra app.
|
284
|
+
*/
|
285
|
+
function Valitron(tag, model, field, bindEvent, valibot) {
|
286
|
+
this.valid = false;
|
287
|
+
this.tag = tag;
|
288
|
+
this.model = model;
|
289
|
+
this.field = field;
|
290
|
+
this.valibot = valibot;
|
291
|
+
this.errorTag = null;
|
292
|
+
var _this = this;
|
293
|
+
$(this.tag).bind(bindEvent, function(event){ _this.checkValue(event); });
|
294
|
+
$(this.tag).bind('keyup', function(event){ _this.reset(); });
|
295
|
+
}
|
296
|
+
|
297
|
+
Valitron.prototype = {
|
298
|
+
|
299
|
+
checkValue:
|
300
|
+
function(event, callback) {
|
301
|
+
var _this = this;
|
302
|
+
var path = (this.valibot.appUrl != null ? this.valibot.appUrl : '') +
|
303
|
+
'/' + this.valibot.pathPrefix +
|
304
|
+
'/' + this.model +
|
305
|
+
'/' + this.field +
|
306
|
+
(this.valibot.context != null ? ('/in/' + this.valibot.context) : '');
|
307
|
+
var val = this.tag.value;
|
308
|
+
if (this.tag.type.toLowerCase() == 'checkbox') val = this.tag.checked;
|
309
|
+
var params = {value: val};
|
310
|
+
if (this.valibot.dependents[this.field]) {
|
311
|
+
$(this.valibot.dependents[this.field]).each(function(i,dep) {
|
312
|
+
params[_this.model + '[' + dep + ']'] =
|
313
|
+
$('input[name="' + _this.model + '[' + dep + ']"], ' +
|
314
|
+
'select[name="' + _this.model + '[' + dep + ']"], ' +
|
315
|
+
'textarea[name="' + _this.model + '[' + dep + ']"]').val();
|
316
|
+
});
|
317
|
+
}
|
318
|
+
$.post(path, params, function(res){ _this.handleResponse(res, callback); },
|
319
|
+
this.valibot.appUrl != null ? 'jsonp' : 'json');
|
320
|
+
},
|
321
|
+
|
322
|
+
handleResponse:
|
323
|
+
function(res, callback) {
|
324
|
+
|
325
|
+
// bad state -> good state
|
326
|
+
if (res == null && this.errorTag != null) {
|
327
|
+
var _this = this;
|
328
|
+
this.valibot.errorTagRemover(this.errorTag, this.valibot, function(ret){ _this.errorTag = null; }, this);
|
329
|
+
this.valibot.addValid(this);
|
330
|
+
if (callback != null) callback(true);
|
331
|
+
this.valid = true;
|
332
|
+
|
333
|
+
// null state -> good state
|
334
|
+
} else if (res == null && this.errorTag == null) {
|
335
|
+
this.valibot.addValid(this);
|
336
|
+
if (callback != null) callback(true);
|
337
|
+
this.valid = true;
|
338
|
+
|
339
|
+
// bad state -> bad state
|
340
|
+
} else if (res != null && res.error != null && this.errorTag != null) {
|
341
|
+
this.errorTag = this.valibot.errorTagTransformer(this.parseErrorStrings(res.error), this.errorTag, this.valibot, this);
|
342
|
+
this.valibot.addInvalid(this);
|
343
|
+
if (callback != null) callback(false);
|
344
|
+
this.valid = false;
|
345
|
+
|
346
|
+
// null state -> bad state
|
347
|
+
} else if (res != null && res.error != null && this.errorTag == null) {
|
348
|
+
this.errorTag = this.valibot.errorTagBuilder(this.parseErrorStrings(res.error), this.valibot, this);
|
349
|
+
this.valibot.errorTagPlacer(this.tag, this.errorTag, this.valibot, this);
|
350
|
+
this.valibot.addInvalid(this);
|
351
|
+
if (callback != null) callback(false);
|
352
|
+
this.valid = false;
|
353
|
+
}
|
354
|
+
},
|
355
|
+
|
356
|
+
parseErrorStrings:
|
357
|
+
function(error) {
|
358
|
+
var es = '';
|
359
|
+
$(error).each(function(i, e) { es += e + '<br/>' });
|
360
|
+
return es.substring(0, es.length - 5); // remove last <br/>
|
361
|
+
},
|
362
|
+
|
363
|
+
reset:
|
364
|
+
function() {
|
365
|
+
this.valibot.removeValid(this);
|
366
|
+
this.valid = false;
|
367
|
+
}
|
368
|
+
|
369
|
+
};
|
370
|
+
|
371
|
+
// ---
|
372
|
+
|
373
|
+
/* the Confirm-o-mat class. instantiated on form fields that are specified as
|
374
|
+
* needing to match another field by an overseeing Valibot. handles the test
|
375
|
+
* for equal values between fields.
|
376
|
+
*/
|
377
|
+
function Confirmomat(id, things, valibot) {
|
378
|
+
if ($('#' + id).length == 1 && $('#confirm_' + id).length == 1) {
|
379
|
+
this.valid = false;
|
380
|
+
this.tag = $('#confirm_' + id)[0];
|
381
|
+
this.id = id;
|
382
|
+
this.things = things;
|
383
|
+
this.valibot = valibot;
|
384
|
+
this.errorTag = null;
|
385
|
+
var _this = this;
|
386
|
+
$(this.tag).bind('blur', function(event){ _this.checkValue(event); });
|
387
|
+
$(this.tag).bind('keyup', function(event){ _this.reset(); });
|
388
|
+
$('#' + this.id).bind('keyup', function(event){ _this.reset(); });
|
389
|
+
} else {
|
390
|
+
// console.log('ERROR: matching fields not found or too many found.');
|
391
|
+
return null;
|
392
|
+
}
|
393
|
+
}
|
394
|
+
|
395
|
+
Confirmomat.prototype = {
|
396
|
+
|
397
|
+
checkValue:
|
398
|
+
function(event, callback) {
|
399
|
+
if ($('#' + this.id).val() == $(this.tag).val()) {
|
400
|
+
|
401
|
+
// bad state -> good state
|
402
|
+
if (this.errorTag != null) {
|
403
|
+
var _this = this;
|
404
|
+
this.valibot.errorTagRemover(this.errorTag, this.valibot, function(ret){ _this.errorTag = null; }, this);
|
405
|
+
}
|
406
|
+
|
407
|
+
// * state -> good state
|
408
|
+
this.valibot.addValid(this);
|
409
|
+
if (callback != null) callback(true);
|
410
|
+
this.valid = true;
|
411
|
+
|
412
|
+
} else {
|
413
|
+
|
414
|
+
// bad state -> bad state
|
415
|
+
if (this.errorTag != null) {
|
416
|
+
this.valibot.errorTagTransformer(this.things + " do not match.", this.errorTag, this.valibot, this);
|
417
|
+
|
418
|
+
// null state -> bad state
|
419
|
+
} else {
|
420
|
+
this.errorTag = this.valibot.errorTagBuilder(this.things + " do not match.", this.valibot, this);
|
421
|
+
this.valibot.errorTagPlacer(this.tag, this.errorTag, this.valibot, this);
|
422
|
+
}
|
423
|
+
|
424
|
+
// * state -> bad state
|
425
|
+
this.valibot.addInvalid(this);
|
426
|
+
if (callback != null) callback(false);
|
427
|
+
this.valid = false;
|
428
|
+
|
429
|
+
}
|
430
|
+
},
|
431
|
+
|
432
|
+
reset:
|
433
|
+
function() {
|
434
|
+
// console.log('confirmomat resetting.');
|
435
|
+
this.valibot.removeValid(this);
|
436
|
+
this.valid = false;
|
437
|
+
}
|
438
|
+
|
439
|
+
};
|
440
|
+
|
441
|
+
// ---
|
442
|
+
|
443
|
+
/* the AgreePeeOh class. instantiated on checkboxes that are specified as
|
444
|
+
* needing to be checked for form submission to be allowed.
|
445
|
+
*/
|
446
|
+
function AgreePeeOh(id, things, valibot) {
|
447
|
+
if ($('#' + id).length == 1) {
|
448
|
+
this.valid = false;
|
449
|
+
this.id = id;
|
450
|
+
this.tag = $('#' + id)[0];
|
451
|
+
this.things = things;
|
452
|
+
this.valibot = valibot;
|
453
|
+
this.errorTag = null;
|
454
|
+
var _this = this;
|
455
|
+
$(this.tag).bind('change', function(event){ _this.checkValue(event); });
|
456
|
+
} else {
|
457
|
+
return null;
|
458
|
+
}
|
459
|
+
}
|
460
|
+
|
461
|
+
AgreePeeOh.prototype = {
|
462
|
+
|
463
|
+
checkValue:
|
464
|
+
function(event, callback) {
|
465
|
+
if ($('#' + this.id).attr('checked')) {
|
466
|
+
|
467
|
+
// bad state -> good state
|
468
|
+
if (this.errorTag != null) {
|
469
|
+
var _this = this;
|
470
|
+
this.valibot.errorTagRemover(this.errorTag, this.valibot, function(ret){ _this.errorTag = null; }, this);
|
471
|
+
}
|
472
|
+
|
473
|
+
// * state -> good state
|
474
|
+
this.valibot.addValid(this);
|
475
|
+
if (callback != null) callback(true);
|
476
|
+
this.valid = true;
|
477
|
+
|
478
|
+
} else {
|
479
|
+
|
480
|
+
// bad state -> bad state
|
481
|
+
if (this.errorTag != null) {
|
482
|
+
this.valibot.errorTagTransformer("You must agree to the " + this.things + ".", this.errorTag, this.valibot, this);
|
483
|
+
|
484
|
+
// null state -> bad state
|
485
|
+
} else {
|
486
|
+
this.errorTag = this.valibot.errorTagBuilder("You must agree to the " + this.things + ".", this.valibot, this);
|
487
|
+
this.valibot.errorTagPlacer(this.tag, this.errorTag, this.valibot, this);
|
488
|
+
}
|
489
|
+
|
490
|
+
// * state -> bad state
|
491
|
+
this.valibot.addInvalid(this);
|
492
|
+
if (callback != null) callback(false);
|
493
|
+
this.valid = false;
|
494
|
+
|
495
|
+
}
|
496
|
+
},
|
497
|
+
|
498
|
+
reset:
|
499
|
+
function() {
|
500
|
+
this.valibot.removeValid(this);
|
501
|
+
this.valid = false;
|
502
|
+
}
|
503
|
+
};
|
data/lib/valibot.rb
ADDED
@@ -0,0 +1,117 @@
|
|
1
|
+
# valibot.rb (c) 2011 kenichi nakamura (kenichi.nakamura@gmail.com)
|
2
|
+
#
|
3
|
+
# easy automatic form field validation against datamapper models behind a sinatra app.
|
4
|
+
# this is the helper module and Sinatra app class.
|
5
|
+
#
|
6
|
+
# see ____ for details. TODO
|
7
|
+
|
8
|
+
module Valibot
|
9
|
+
|
10
|
+
JAVASCRIPT = File.join File.dirname(__FILE__), '..', 'js', 'valibot.js'
|
11
|
+
|
12
|
+
module Helpers
|
13
|
+
|
14
|
+
def self.registered app; app.helpers Valibot::Helpers; end
|
15
|
+
def valibot_js; File.read Valibot::JAVASCRIPT; end
|
16
|
+
|
17
|
+
end
|
18
|
+
|
19
|
+
class App < Sinatra::Base
|
20
|
+
|
21
|
+
before do
|
22
|
+
set_content_type
|
23
|
+
end
|
24
|
+
|
25
|
+
get '/valibot.js' do
|
26
|
+
content_type :js
|
27
|
+
cache_control :public, :max_age => 10 * 60
|
28
|
+
File.read Valibot::JAVASCRIPT
|
29
|
+
end
|
30
|
+
|
31
|
+
post '/:model/:field/in/:context' do
|
32
|
+
validate_field_value *parse(params)
|
33
|
+
end
|
34
|
+
|
35
|
+
post '/:model/in/:context' do
|
36
|
+
validate_model *parse(params)
|
37
|
+
end
|
38
|
+
|
39
|
+
post '/:model/:field' do
|
40
|
+
validate_field_value *parse(params)
|
41
|
+
end
|
42
|
+
|
43
|
+
post '/:model' do
|
44
|
+
validate_model *parse(params)
|
45
|
+
end
|
46
|
+
|
47
|
+
get '/:model/:field/in/:context' do
|
48
|
+
jsonp validate_field_value *parse(params)
|
49
|
+
end
|
50
|
+
|
51
|
+
get '/:model/in/:context' do
|
52
|
+
jsonp validate_model *parse(params)
|
53
|
+
end
|
54
|
+
|
55
|
+
get '/:model/:field' do
|
56
|
+
jsonp validate_field_value *parse(params)
|
57
|
+
end
|
58
|
+
|
59
|
+
get '/:model' do
|
60
|
+
jsonp validate_model *parse(params)
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
def parse params = {}
|
66
|
+
model_class_name = params[:model].camel_case
|
67
|
+
model_class = begin
|
68
|
+
Kernel.const_get(model_class_name)
|
69
|
+
rescue NameError => e
|
70
|
+
halt(Yajl.dump :error => e.message)
|
71
|
+
end
|
72
|
+
field_sym = params[:field] ? params[:field].to_sym : nil
|
73
|
+
context = params[:context] ? params[:context].to_sym : :default
|
74
|
+
dependents = params[params[:model]]
|
75
|
+
[model_class, field_sym, context, dependents].compact
|
76
|
+
end
|
77
|
+
|
78
|
+
def validate_field_value model = nil, field = nil, context = :default, dependents = {}
|
79
|
+
if model && model.included_modules.include?(DataMapper::Resource)
|
80
|
+
begin
|
81
|
+
if model.properties.map(&:name).include?(field) or model.new.__send__(field).kind_of?(DataMapper::Collection)
|
82
|
+
obj = model.new dependents.merge field => params['value']
|
83
|
+
Yajl.dump :error => obj.errors[field] if !obj.valid?(context) && obj.errors.keys.include?(field)
|
84
|
+
else
|
85
|
+
Yajl.dump :error => "Invalid field: #{params[:field]}"
|
86
|
+
end
|
87
|
+
rescue NoMethodError => e
|
88
|
+
Yajl.dump :error => "Invalid field: #{params[:field]}"
|
89
|
+
end
|
90
|
+
else
|
91
|
+
Yajl.dump :error => "Invalid model: #{params[:model].camel_case}"
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def validate_model model = nil, context = :default
|
96
|
+
if model && model.included_modules.include?(DataMapper::Resource)
|
97
|
+
obj = model.new params[params[:model]]
|
98
|
+
unless obj.valid?(context)
|
99
|
+
Yajl.dump :error => obj.errors.to_hash
|
100
|
+
end
|
101
|
+
else
|
102
|
+
Yajl.dump :error => "Invalid model: #{params[:model].camel_case}"
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def set_content_type
|
107
|
+
content_type (params['callback'] ? :js : :json)
|
108
|
+
end
|
109
|
+
|
110
|
+
def jsonp json = ''
|
111
|
+
return json unless params['callback']
|
112
|
+
params['callback'] + "(#{json})"
|
113
|
+
end
|
114
|
+
|
115
|
+
end
|
116
|
+
|
117
|
+
end
|
metadata
ADDED
@@ -0,0 +1,90 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: valibot
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease:
|
5
|
+
version: 0.2.5
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Kenichi Nakamura
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
|
13
|
+
date: 2011-08-02 00:00:00 Z
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: dm-core
|
17
|
+
prerelease: false
|
18
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
19
|
+
none: false
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 1.0.0
|
24
|
+
type: :runtime
|
25
|
+
version_requirements: *id001
|
26
|
+
- !ruby/object:Gem::Dependency
|
27
|
+
name: dm-validations
|
28
|
+
prerelease: false
|
29
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
30
|
+
none: false
|
31
|
+
requirements:
|
32
|
+
- - ">="
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: 1.0.0
|
35
|
+
type: :runtime
|
36
|
+
version_requirements: *id002
|
37
|
+
- !ruby/object:Gem::Dependency
|
38
|
+
name: sinatra
|
39
|
+
prerelease: false
|
40
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ">="
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: 1.1.0
|
46
|
+
type: :runtime
|
47
|
+
version_requirements: *id003
|
48
|
+
description:
|
49
|
+
email:
|
50
|
+
- kenichi.nakamura@gmail.com
|
51
|
+
executables: []
|
52
|
+
|
53
|
+
extensions: []
|
54
|
+
|
55
|
+
extra_rdoc_files: []
|
56
|
+
|
57
|
+
files:
|
58
|
+
- lib/valibot.rb
|
59
|
+
- js/valibot.js
|
60
|
+
- LICENSE
|
61
|
+
- README.md
|
62
|
+
homepage: https://github.com/kenichi/valibot
|
63
|
+
licenses: []
|
64
|
+
|
65
|
+
post_install_message:
|
66
|
+
rdoc_options: []
|
67
|
+
|
68
|
+
require_paths:
|
69
|
+
- lib
|
70
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
71
|
+
none: false
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: "0"
|
76
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
77
|
+
none: false
|
78
|
+
requirements:
|
79
|
+
- - ">="
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: 1.3.4
|
82
|
+
requirements: []
|
83
|
+
|
84
|
+
rubyforge_project: valibot
|
85
|
+
rubygems_version: 1.8.6
|
86
|
+
signing_key:
|
87
|
+
specification_version: 3
|
88
|
+
summary: Automatic field validation for forms backed by DataMapper models through Sinatra.
|
89
|
+
test_files: []
|
90
|
+
|