campo 0.3.4 → 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +3 -0
- data/.rspec +2 -0
- data/.travis.yml +7 -0
- data/{CHANGES → CHANGES.markdown} +62 -0
- data/Gemfile +15 -0
- data/README.markdown +305 -199
- data/Rakefile +11 -0
- data/campo.gemspec +5 -4
- data/lib/campo.rb +7 -524
- data/lib/campo/campo.rb +740 -0
- data/lib/campo/plugins.rb +41 -0
- data/lib/campo/plugins/aria.rb +92 -0
- data/lib/campo/plugins/jqueryvalidation.rb +155 -0
- data/lib/campo/plugins/partial.rb +49 -0
- data/lib/campo/version.rb +1 -1
- data/spec/aria_spec.rb +67 -0
- data/spec/campo_spec.rb +475 -243
- data/spec/jqueryvalidation_spec.rb +174 -0
- data/spec/partial_spec.rb +77 -0
- data/spec/plugins_spec.rb +66 -0
- data/spec/support/matchers/items.rb +8 -0
- metadata +64 -54
- data.tar.gz.sig +0 -2
- metadata.gz.sig +0 -0
data/.gitignore
CHANGED
data/.rspec
ADDED
data/.travis.yml
ADDED
@@ -1,3 +1,65 @@
|
|
1
|
+
## v0.9.0 16th of June 2012 ##
|
2
|
+
|
3
|
+
* Updated the README with info on using plugins, and how to contribute.
|
4
|
+
* `describe` in the Aria plugin no longer needs a tuple to create a list, it can take an array of single valued arrays as the missing part of the tuple will default to an empty hash.
|
5
|
+
* Added testing for the develop branch to Travis.
|
6
|
+
* Moved most of the code to a separate file that is then required back in by lib/campo.rb, as specs were affected by the order they were run in. Now, each spec only `require` the files it needs and clear the plugins before running, which means they can be run in any order and not be affected.
|
7
|
+
|
8
|
+
----
|
9
|
+
|
10
|
+
## v0.8.3b 14th of May 2012 ##
|
11
|
+
|
12
|
+
* `with_default` can now have the 'disabled' attribute turned off.
|
13
|
+
* Added a lot of docs.
|
14
|
+
|
15
|
+
----
|
16
|
+
|
17
|
+
v0.8.2b Literals can have attributes passed. Aria plugin's `describe` can take a list of messages and create an unordered list.
|
18
|
+
|
19
|
+
v0.8.1b Added a Rakefile with a task to generate the docs, for convenience. Fixed a couple of typos in the docs.
|
20
|
+
|
21
|
+
v0.8.0b The Aria plugin's `describe` now takes options for adding attributes.
|
22
|
+
|
23
|
+
v0.7.1b Removed debugging statement that was being output.
|
24
|
+
|
25
|
+
v0.7.0b Use of [] in input names now allowed, for grouping inputs like an array.
|
26
|
+
|
27
|
+
v0.6.14b Lowercased aria.rb for bundler.
|
28
|
+
|
29
|
+
v0.6.12b Another mistake, an accidental move of a file - this will teach me to run the specs even for trivial changes!
|
30
|
+
|
31
|
+
v0.6.11b My mistake this time, not using lowercase for the file names was causing a problem deploying to Heroku.
|
32
|
+
|
33
|
+
v0.6.10b Removed ENV vars from gemspec as deployment to Heroku with Bundler is problematic.
|
34
|
+
|
35
|
+
v0.6.7b Added `describe` method, via the Aria plugin. It will help to keep the forms accessible.
|
36
|
+
|
37
|
+
v0.6.6b Validation rules are now built. Validations for required, digits and maxlength added.
|
38
|
+
|
39
|
+
v0.6.5b Labels get the "required" class too when using JQueryValidation, to make all our styling wishes come true!
|
40
|
+
|
41
|
+
v0.6.4b Default option for select tags now has its id attribute appended with "_default" to stop the id clashing with the select tag.
|
42
|
+
|
43
|
+
v0.6.3b Convenience method `submit` is now more convenient as it takes the value as part of the hash args without a pesky unused parameter getting in the way.
|
44
|
+
|
45
|
+
v0.6.2b Added easier labelling for textareas.
|
46
|
+
|
47
|
+
v0.6.1b The function for the JQuery validation wasn't right, fixed it now, and updated the specs a little for it.
|
48
|
+
|
49
|
+
v0.6.0b All form fields get an id. Added "hidden" input convenience method.
|
50
|
+
|
51
|
+
v0.5.2b Turns out that you can learn a lot if you run the specs, such as inners didn't need changing to default_proc, just atts.
|
52
|
+
|
53
|
+
v0.5.1b Hash defaults for form should work better now as moved to using Hash#default_proc which updates the key the way required.
|
54
|
+
|
55
|
+
v0.5.0b Added a JQuery validation plugin.
|
56
|
+
|
57
|
+
v0.4.0b Added an outputter class, currently hidden under old API. Improved argument passing, added before and after hooks for output. Extra args to Campo.output are now passed as options, and partials are done this way too instead of the clumsy prepended argument.
|
58
|
+
|
59
|
+
v0.3.6b removed need for block variables.
|
60
|
+
|
61
|
+
v0.3.5b Changed tabindex to use an instance variable, so now forms should be able to be nested and the tabindex update properly.
|
62
|
+
|
1
63
|
v0.3.4 Added `password` convenience method.
|
2
64
|
|
3
65
|
v0.3.2 Blocks are now passed on for .literal and .bit_of_ruby
|
data/Gemfile
ADDED
data/README.markdown
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
[![Build Status for development branch](https://secure.travis-ci.org/yb66/Campo.png?branch=develop)](http://travis-ci.org/yb66/Campo)
|
2
|
+
|
1
3
|
# Campo #
|
2
4
|
|
3
5
|
A static dynamic form builder into haml. Yep, static _and_ dynamic. Use it to statically create a form into haml, but you may notice it's taken advantage of haml's "add a hash to the front of the attributes and it'll get merged" property. [See Haml docs for more](http://haml-lang.com/docs/yardoc/file.HAML_REFERENCE.html#attribute_methods). More on that below.
|
@@ -6,8 +8,15 @@ Btw, I'll be using this with Sinatra, if you're using Rails you'll need to work
|
|
6
8
|
|
7
9
|
## Note! ##
|
8
10
|
|
9
|
-
As always, keep in mind this is an open source project (licence below) and you can contribute! If you find a problem or would like a feature changed or added, let me know, or even better, fork the project and send me a pull request.
|
11
|
+
As always, keep in mind this is an open source project (licence below) and you can contribute! If you find a problem or would like a feature changed or added, let me know, or even better, fork the project and send me a pull request. See the "Contributing" section for some notes on how to do that.
|
12
|
+
|
13
|
+
## Double note! ##
|
14
|
+
|
15
|
+
I use Campo myself, and I'm trying to improve it. As I don't want to push new stuff out before I've had a chance to give it a whirl and see if it makes sense and works (through experience, specs aren't everything) I'll have several versions of this up here, some unreleased. I tend to append a 'b' to the end of an unreleased version. Please make sure you're reading the documentation for the version you're using!
|
10
16
|
|
17
|
+
## Version numbers ##
|
18
|
+
|
19
|
+
You'll notice this library is well past version 0.0.1. Some people take this to mean something like "it works brilliantly", but in fact, I'm attempting to use the [semver standard](http://semver.org/). In essence, it tells you about changes to the API, not about code quality - that's what the specs/tests are for. It's worth a read.
|
11
20
|
|
12
21
|
## Why write this? ##
|
13
22
|
|
@@ -22,124 +31,79 @@ Here's an example form:
|
|
22
31
|
|
23
32
|
# Now starts the real action #
|
24
33
|
|
25
|
-
form = Campo.form "
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
f.text "phone_landline", "Phone landline : ", size: 20
|
44
|
-
f.text "phone_mobile", "Phone mobile : ", size: 20
|
45
|
-
|
46
|
-
f.submit "Save"
|
47
|
-
|
34
|
+
form = Campo.form "personal_details", action: %Q!"uri("/my/personal_details/update/")! do
|
35
|
+
fieldset "Your details" do
|
36
|
+
text "full_name", "Full name: ", size: 60
|
37
|
+
text "dob", "Date of birth: ", size: 10 #TODO change this
|
38
|
+
fieldset "Gender: " do
|
39
|
+
radio "gender", "Male", value: 1
|
40
|
+
radio "gender", "Female", value: 2
|
41
|
+
end
|
42
|
+
select("teas").with_default.option("ceylon").option("breakfast").option("earl grey").labelled("Favourite tea:")
|
43
|
+
text "occupation", "Occupation: ", size: 60
|
44
|
+
text "phone_landline", "Phone (landline): ", size: 20
|
45
|
+
text "phone_mobile", "Phone (mobile): ", size: 20
|
46
|
+
fieldset "May we contact you..." do
|
47
|
+
checkbox "contactable", "In the day?", value: "day"
|
48
|
+
checkbox "contactable", "In the evening?", value: "evening"
|
49
|
+
end
|
50
|
+
submit "Save"
|
51
|
+
end
|
48
52
|
end
|
53
|
+
|
54
|
+
|
55
|
+
puts Campo.output( form )
|
49
56
|
|
50
57
|
and the output:
|
51
58
|
|
52
|
-
|
53
|
-
puts Campo.output( form )
|
54
|
-
|
55
59
|
- atts = {} if atts.nil?
|
56
|
-
- atts.
|
60
|
+
- atts.default_proc = proc {|hash, key| hash[key] = {} } if atts.default_proc.nil?
|
61
|
+
- inners = {} if inners.nil?
|
57
62
|
- inners.default = "" if inners.default.nil?
|
58
|
-
-
|
59
|
-
|
60
|
-
%form{ atts[:myform], method: "POST", action: "/my/form/update/", name: "myform", }
|
63
|
+
- @campo_tabindex ||= 0 # for tabindex
|
64
|
+
%form{ atts[:personal_details], id: "personal_details", method: "POST", action: uri("/my/personal_details/update/"), name: "personal_details", }
|
61
65
|
%fieldset{ }
|
62
66
|
%legend{ }Your details
|
63
67
|
%label{ for: "full_name", }
|
64
68
|
Full name:
|
65
|
-
%input{ atts[:full_name], tabindex: "#{
|
69
|
+
%input{ atts[:full_name], tabindex: "#{@campo_tabindex += 1}", id: "full_name", type: "text", size: "60", name: "full_name", }
|
66
70
|
%label{ for: "dob", }
|
67
71
|
Date of birth:
|
68
|
-
%input{ atts[:dob], tabindex: "#{
|
69
|
-
%
|
70
|
-
Gender:
|
71
|
-
%
|
72
|
-
|
73
|
-
%
|
74
|
-
|
72
|
+
%input{ atts[:dob], tabindex: "#{@campo_tabindex += 1}", id: "dob", type: "text", size: "10", name: "dob", }
|
73
|
+
%fieldset{ }
|
74
|
+
%legend{ }Gender:
|
75
|
+
%label{ for: "gender_1", }
|
76
|
+
Male
|
77
|
+
%input{ atts[:gender_1], tabindex: "#{@campo_tabindex += 1}", id: "gender_1", type: "radio", value: "1", name: "gender", }
|
78
|
+
%label{ for: "gender_2", }
|
79
|
+
Female
|
80
|
+
%input{ atts[:gender_2], tabindex: "#{@campo_tabindex += 1}", id: "gender_2", type: "radio", value: "2", name: "gender", }
|
75
81
|
%label{ for: "teas", }
|
76
82
|
Favourite tea:
|
77
|
-
%select{ atts[:teas], tabindex: "#{
|
78
|
-
%option{
|
79
|
-
%option{ atts[:teas_ceylon],
|
80
|
-
%option{ atts[:teas_breakfast],
|
81
|
-
%option{ atts[:teas_earl_grey],
|
82
|
-
%option{ atts[:teas_oolong], value: "oolong", id: "teas_oolong", name: "teas", }Oolong
|
83
|
-
%option{ atts[:teas_sencha], value: "sencha", id: "teas_sencha", name: "teas", }Sencha
|
83
|
+
%select{ atts[:teas], tabindex: "#{@campo_tabindex += 1}", id: "teas", name: "teas", }
|
84
|
+
%option{ atts[:teas], id: "teas", value: "", disabled: "disabled", name: "teas", }Choose one:
|
85
|
+
%option{ atts[:teas_ceylon], id: "teas_ceylon", value: "ceylon", name: "teas", }Ceylon
|
86
|
+
%option{ atts[:teas_breakfast], id: "teas_breakfast", value: "breakfast", name: "teas", }Breakfast
|
87
|
+
%option{ atts[:teas_earl_grey], id: "teas_earl_grey", value: "earl grey", name: "teas", }Earl grey
|
84
88
|
%label{ for: "occupation", }
|
85
89
|
Occupation:
|
86
|
-
%input{ atts[:occupation], tabindex: "#{
|
90
|
+
%input{ atts[:occupation], tabindex: "#{@campo_tabindex += 1}", id: "occupation", type: "text", size: "60", name: "occupation", }
|
87
91
|
%label{ for: "phone_landline", }
|
88
92
|
Phone (landline):
|
89
|
-
%input{ atts[:phone_landline], tabindex: "#{
|
93
|
+
%input{ atts[:phone_landline], tabindex: "#{@campo_tabindex += 1}", id: "phone_landline", type: "text", size: "20", name: "phone_landline", }
|
90
94
|
%label{ for: "phone_mobile", }
|
91
95
|
Phone (mobile):
|
92
|
-
%input{ atts[:phone_mobile], tabindex: "#{
|
93
|
-
%
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
Full name:
|
104
|
-
<input id='full_name' name='full_name' size='60' tabindex='1' type='text' />
|
105
|
-
</label>
|
106
|
-
Date of birth:
|
107
|
-
<input id='dob' name='dob' size='8' tabindex='2' type='text' />
|
108
|
-
</label>
|
109
|
-
<label for='gender_id'>
|
110
|
-
Gender:
|
111
|
-
<select name='gender_id' tabindex='3'>
|
112
|
-
<option disabled='disabled' name='gender_id' value=''>Choose one:</option>
|
113
|
-
<option id='gender_id_1' name='gender_id' value='1'>Male</option>
|
114
|
-
<option id='gender_id_2' name='gender_id' value='2'>Female</option>
|
115
|
-
</select>
|
116
|
-
</label>
|
117
|
-
<label for='teas'>
|
118
|
-
Favourite tea:
|
119
|
-
<select name='teas' tabindex='4'>
|
120
|
-
<option disabled='disabled' name='teas' value=''>Choose one:</option>
|
121
|
-
<option id='teas_ceylon' name='teas' value='ceylon'>Ceylon</option>
|
122
|
-
<option id='teas_breakfast' name='teas' value='breakfast'>Breakfast</option>
|
123
|
-
<option id='teas_earl_grey' name='teas' value='earl grey'>Earl grey</option>
|
124
|
-
<option id='teas_oolong' name='teas' value='oolong'>Oolong</option>
|
125
|
-
<option id='teas_sencha' name='teas' value='sencha'>Sencha</option>
|
126
|
-
</select>
|
127
|
-
</label>
|
128
|
-
<label for='occupation'>
|
129
|
-
Occupation:
|
130
|
-
<input id='occupation' name='occupation' size='60' tabindex='5' type='text' />
|
131
|
-
</label>
|
132
|
-
<label for='phone_landline'>
|
133
|
-
Phone (landline):
|
134
|
-
<input id='phone_landline' name='phone_landline' size='20' tabindex='6' type='text' />
|
135
|
-
</label>
|
136
|
-
<label for='phone_mobile'>
|
137
|
-
Phone (mobile):
|
138
|
-
<input id='phone_mobile' name='phone_mobile' size='20' tabindex='7' type='text' />
|
139
|
-
</label>
|
140
|
-
<input id='Save_Save' tabindex='8' type='submit' value='Save' />
|
141
|
-
</fieldset>
|
142
|
-
</form>
|
96
|
+
%input{ atts[:phone_mobile], tabindex: "#{@campo_tabindex += 1}", id: "phone_mobile", type: "text", size: "20", name: "phone_mobile", }
|
97
|
+
%fieldset{ }
|
98
|
+
%legend{ }May we contact you...
|
99
|
+
%label{ for: "contactable_day", }
|
100
|
+
In the day?
|
101
|
+
%input{ atts[:contactable_day], tabindex: "#{@campo_tabindex += 1}", id: "contactable_day", type: "checkbox", value: "day", name: "contactable", }
|
102
|
+
%label{ for: "contactable_evening", }
|
103
|
+
In the evening?
|
104
|
+
%input{ atts[:contactable_evening], tabindex: "#{@campo_tabindex += 1}", id: "contactable_evening", type: "checkbox", value: "evening", name: "contactable", }
|
105
|
+
%input{ atts[:Save], tabindex: "#{@campo_tabindex += 1}", id: "Save", type: "submit", value: "Save", }
|
106
|
+
|
143
107
|
|
144
108
|
## Haml attributes ##
|
145
109
|
|
@@ -151,65 +115,82 @@ These get added to the top when calling `Campo.output`, to provide sane defaults
|
|
151
115
|
- atts.default = {} if atts.default.nil?
|
152
116
|
- inners = {} if inners.nil?
|
153
117
|
- inners.default = "" if inners.default.nil?
|
118
|
+
- @campo_tabindex ||= 0 # for tabindex
|
154
119
|
|
155
120
|
Note: if you don't want these added, you can do:
|
156
121
|
|
157
|
-
Campo.output :partial
|
122
|
+
Campo.output name-of-tag-here, :partial => true
|
158
123
|
|
124
|
+
e.g
|
159
125
|
|
160
|
-
|
126
|
+
Campo.output form, :partial => true
|
127
|
+
|
161
128
|
|
162
129
|
Here's some Campo code for a select tag with options:
|
163
130
|
|
164
|
-
form = Campo.form "best_bands", action: "/best/bands/" do
|
165
|
-
|
131
|
+
form = Campo.form "best_bands", action: "/best/bands/" do
|
132
|
+
select("bands").option("Suede").option("Blur").option("Oasis").option("Echobelly").option("Pulp").option("Supergrass").with_default.labelled("Favourite band:")
|
166
133
|
end
|
167
134
|
|
168
135
|
or
|
169
136
|
|
170
|
-
form = Campo.form "best_bands", action: "/best/bands/"
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
137
|
+
form = Campo.form "best_bands", action: "/best/bands/" do
|
138
|
+
select "bands" do
|
139
|
+
with_default
|
140
|
+
option "Suede"
|
141
|
+
option "Blur"
|
142
|
+
option "Oasis"
|
143
|
+
option "Echobelly"
|
144
|
+
option "Pulp"
|
145
|
+
option "Supergrass"
|
146
|
+
end.labelled("Favourite band:")
|
147
|
+
end
|
180
148
|
|
181
149
|
or an array of arrays:
|
182
150
|
|
183
|
-
form = Campo.form "best_bands", action: "/best/bands/"
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
151
|
+
form = Campo.form "best_bands", action: "/best/bands/" do
|
152
|
+
select( "bands", opts: [
|
153
|
+
["Suede"],
|
154
|
+
["Blur"],
|
155
|
+
["Oasis"],
|
156
|
+
["Echobelly"],
|
157
|
+
["Pulp"],
|
158
|
+
["Supergrass"],
|
159
|
+
] ).with_default.labelled("Favourite band:")
|
160
|
+
end
|
161
|
+
|
162
|
+
or mix and match blocks, .new, arrays and hashes as you see fit.
|
194
163
|
|
195
164
|
|
196
165
|
Generate the haml:
|
197
166
|
|
198
|
-
Campo.output form
|
167
|
+
Campo.output form
|
199
168
|
|
200
169
|
And the Haml generated:
|
201
170
|
|
202
|
-
|
171
|
+
- atts = {} if atts.nil?
|
172
|
+
- atts.default_proc = proc {|hash, key| hash[key] = {} } if atts.default_proc.nil?
|
173
|
+
- inners = {} if inners.nil?
|
174
|
+
- inners.default = "" if inners.default.nil?
|
175
|
+
- @campo_tabindex ||= 0 # for tabindex
|
176
|
+
%form{ atts[:best_bands], id: "best_bands", method: "POST", action: "/best/bands/", name: "best_bands", }
|
203
177
|
%label{ for: "bands", }
|
204
178
|
Favourite band:
|
205
|
-
%select{ atts[:bands], tabindex: "#{
|
206
|
-
%option{
|
207
|
-
%option{ atts[:bands_suede],
|
208
|
-
%option{ atts[:bands_blur],
|
209
|
-
%option{ atts[:bands_oasis],
|
210
|
-
%option{ atts[:bands_echobelly],
|
211
|
-
%option{ atts[:bands_pulp],
|
212
|
-
%option{ atts[:bands_supergrass],
|
179
|
+
%select{ atts[:bands], tabindex: "#{@campo_tabindex += 1}", id: "bands", name: "bands", }
|
180
|
+
%option{ atts[:bands], id: "bands", value: "", disabled: "disabled", name: "bands", }Choose one:
|
181
|
+
%option{ atts[:bands_suede], id: "bands_suede", value: "Suede", name: "bands", }Suede
|
182
|
+
%option{ atts[:bands_blur], id: "bands_blur", value: "Blur", name: "bands", }Blur
|
183
|
+
%option{ atts[:bands_oasis], id: "bands_oasis", value: "Oasis", name: "bands", }Oasis
|
184
|
+
%option{ atts[:bands_echobelly], id: "bands_echobelly", value: "Echobelly", name: "bands", }Echobelly
|
185
|
+
%option{ atts[:bands_pulp], id: "bands_pulp", value: "Pulp", name: "bands", }Pulp
|
186
|
+
%option{ atts[:bands_supergrass], id: "bands_supergrass", value: "Supergrass", name: "bands", }Supergrass
|
187
|
+
|
188
|
+
|
189
|
+
In the examples above, notice how the output for each tag gets a local variable added to the front.
|
190
|
+
|
191
|
+
> %option{ **atts[:bands_supergrass]**, id: "bands_supergrass", value: "Supergrass", name: "bands", }Supergrass
|
192
|
+
|
193
|
+
You can either fill that variable with a hash pair, or an empty hash gets passed and nothing happens. Read the Haml docs link at the top of the readme for more info.
|
213
194
|
|
214
195
|
|
215
196
|
If you wanted to select "Blur" dynamically (and you should, but I'd accept Suede) you might do:
|
@@ -229,6 +210,11 @@ You can do this with any kind of attribute you wish to add. For example:
|
|
229
210
|
|
230
211
|
atts[:bands_blur] = {not_worth_listening_to: "selected"}
|
231
212
|
|
213
|
+
#=> <option not_worth_listening_to='selected' id='bands_blur' name='bands' value='Blur'>Blur</option>
|
214
|
+
|
215
|
+
But I doubt your name is Noel Gallagher, which makes this a spurious example.
|
216
|
+
|
217
|
+
|
232
218
|
## Be selective ##
|
233
219
|
|
234
220
|
opts = [
|
@@ -245,16 +231,15 @@ You can do this with any kind of attribute you wish to add. For example:
|
|
245
231
|
Output:
|
246
232
|
|
247
233
|
- atts = {} if atts.nil?
|
248
|
-
- atts.
|
234
|
+
- atts.default_proc = proc {|hash, key| hash[key] = {} } if atts.default_proc.nil?
|
249
235
|
- inners = {} if inners.nil?
|
250
236
|
- inners.default = "" if inners.default.nil?
|
251
|
-
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
%option{ atts[:
|
256
|
-
%option{ atts[:
|
257
|
-
%option{ atts[:teas_earl_grey], value: "earl_grey", id: "teas_earl_grey", name: "teas", }Earl grey
|
237
|
+
- @campo_tabindex ||= 0 # for tabindex
|
238
|
+
%form{ atts[:selective_example], id: "selective_example", method: "POST", name: "selective_example", }
|
239
|
+
%select{ atts[:teas], tabindex: "#{@campo_tabindex += 1}", id: "teas", name: "teas", }
|
240
|
+
%option{ atts[:teas_ceylon], id: "teas_ceylon", value: "ceylon", name: "teas", }Ceylon
|
241
|
+
%option{ atts[:teas_english_breakfast], id: "teas_english_breakfast", value: "english_breakfast", selected: "selected", name: "teas", }English breakfast
|
242
|
+
%option{ atts[:teas_earl_grey], id: "teas_earl_grey", value: "earl_grey", name: "teas", }Earl grey
|
258
243
|
|
259
244
|
## Pass a hash ##
|
260
245
|
|
@@ -268,15 +253,15 @@ Output:
|
|
268
253
|
form = Campo.form "simple_hash_example"
|
269
254
|
form.select "teas", opts: opts
|
270
255
|
|
271
|
-
Campo.output
|
256
|
+
Campo.output form, partial: true
|
272
257
|
|
273
258
|
Output:
|
274
259
|
|
275
|
-
%form{ atts[:simple_hash_example], method: "POST", name: "simple_hash_example", }
|
276
|
-
%select{ atts[:teas], tabindex: "#{
|
277
|
-
%option{ atts[:teas_ceylon],
|
278
|
-
%option{ atts[:teas_english_breakfast],
|
279
|
-
%option{ atts[:teas_earl_grey],
|
260
|
+
%form{ atts[:simple_hash_example], id: "simple_hash_example", method: "POST", name: "simple_hash_example", }
|
261
|
+
%select{ atts[:teas], tabindex: "#{@campo_tabindex += 1}", id: "teas", name: "teas", }
|
262
|
+
%option{ atts[:teas_ceylon], id: "teas_ceylon", value: "ceylon", name: "teas", }Ceylon
|
263
|
+
%option{ atts[:teas_english_breakfast], id: "teas_english_breakfast", value: "english_breakfast", name: "teas", }English Breakfast
|
264
|
+
%option{ atts[:teas_earl_grey], id: "teas_earl_grey", value: "earl_grey", name: "teas", }Earl Grey
|
280
265
|
|
281
266
|
With an array for the value:
|
282
267
|
|
@@ -289,15 +274,15 @@ With an array for the value:
|
|
289
274
|
form = Campo.form "hash_with_array_example"
|
290
275
|
form.select "teas", opts: opts
|
291
276
|
|
292
|
-
Campo.output :partial
|
277
|
+
Campo.output form, :partial => true
|
293
278
|
|
294
279
|
Output:
|
295
280
|
|
296
|
-
%form{ atts[:hash_with_array_example], method: "POST", name: "hash_with_array_example", }
|
297
|
-
%select{ atts[:teas], tabindex: "#{
|
298
|
-
%option{ atts[:teas_ceylon],
|
299
|
-
%option{ atts[:teas_english_breakfast],
|
300
|
-
%option{ atts[:teas_earl_grey],
|
281
|
+
%form{ atts[:hash_with_array_example], id: "hash_with_array_example", method: "POST", name: "hash_with_array_example", }
|
282
|
+
%select{ atts[:teas], tabindex: "#{@campo_tabindex += 1}", id: "teas", name: "teas", }
|
283
|
+
%option{ atts[:teas_ceylon], id: "teas_ceylon", value: "ceylon", name: "teas", }Ceylon
|
284
|
+
%option{ atts[:teas_english_breakfast], id: "teas_english_breakfast", value: "english_breakfast", selected: "selected", name: "teas", }English Breakfast
|
285
|
+
%option{ atts[:teas_earl_grey], id: "teas_earl_grey", value: "earl_grey", name: "teas", }Earl Grey
|
301
286
|
|
302
287
|
## Adding in helpers ##
|
303
288
|
|
@@ -322,40 +307,30 @@ Although, if you forget the "=" sign it will add it for you.
|
|
322
307
|
|
323
308
|
## And literals ##
|
324
309
|
|
325
|
-
|
310
|
+
`bit_of_ruby` is really just a literal with a shortcut. Here are some examples using literals:
|
326
311
|
|
327
312
|
|
328
|
-
form = Campo.form "favourite_teas", action: %Q!"uri("/fav/teas/")! do
|
329
|
-
|
330
|
-
|
313
|
+
form = Campo.form "favourite_teas", action: %Q!"uri("/fav/teas/")! do
|
314
|
+
select("teas").with_default.option("ceylon").option("breakfast").option("earl grey").labelled("Favourite tea:")
|
315
|
+
literal %Q<%p= "I like tea!">
|
331
316
|
end
|
332
|
-
|
317
|
+
|
318
|
+
puts Campo.output form
|
333
319
|
|
334
|
-
|
335
|
-
|
320
|
+
- atts = {} if atts.nil?
|
321
|
+
- atts.default_proc = proc {|hash, key| hash[key] = {} } if atts.default_proc.nil?
|
322
|
+
- inners = {} if inners.nil?
|
323
|
+
- inners.default = "" if inners.default.nil?
|
324
|
+
- @campo_tabindex ||= 0 # for tabindex
|
325
|
+
%form{ atts[:favourite_teas], id: "favourite_teas", method: "POST", action: uri("/fav/teas/"), name: "favourite_teas", }
|
336
326
|
%label{ for: "teas", }
|
337
327
|
Favourite tea:
|
338
|
-
%select{ atts[:teas], tabindex: "#{
|
339
|
-
%option{
|
340
|
-
%option{ atts[:teas_ceylon],
|
341
|
-
%option{ atts[:teas_breakfast],
|
342
|
-
%option{ atts[:teas_earl_grey],
|
328
|
+
%select{ atts[:teas], tabindex: "#{@campo_tabindex += 1}", id: "teas", name: "teas", }
|
329
|
+
%option{ atts[:teas], id: "teas", value: "", disabled: "disabled", name: "teas", }Choose one:
|
330
|
+
%option{ atts[:teas_ceylon], id: "teas_ceylon", value: "ceylon", name: "teas", }Ceylon
|
331
|
+
%option{ atts[:teas_breakfast], id: "teas_breakfast", value: "breakfast", name: "teas", }Breakfast
|
332
|
+
%option{ atts[:teas_earl_grey], id: "teas_earl_grey", value: "earl grey", name: "teas", }Earl grey
|
343
333
|
%p= "I like tea!"
|
344
|
-
|
345
|
-
puts Haml::Engine.new( Campo.output form ).render
|
346
|
-
|
347
|
-
<form action='/fav/teas/' method='POST' name='favourite_teas'>
|
348
|
-
<label for='teas'>
|
349
|
-
Favourite tea:
|
350
|
-
<select name='teas' tabindex='1'>
|
351
|
-
<option disabled='disabled' name='teas' value=''>Choose one:</option>
|
352
|
-
<option id='teas_ceylon' name='teas' value='ceylon'>Ceylon</option>
|
353
|
-
<option id='teas_breakfast' name='teas' value='breakfast'>Breakfast</option>
|
354
|
-
<option id='teas_earl_grey' name='teas' value='earl grey'>Earl grey</option>
|
355
|
-
</select>
|
356
|
-
</label>
|
357
|
-
<p>I like tea!</p>
|
358
|
-
</form>
|
359
334
|
|
360
335
|
You can use literals to wrap forms in divs too:
|
361
336
|
|
@@ -363,46 +338,177 @@ You can use literals to wrap forms in divs too:
|
|
363
338
|
wrapper << form # the form defined already above
|
364
339
|
end
|
365
340
|
|
366
|
-
puts Campo.output doc
|
367
|
-
|
341
|
+
puts Campo.output doc, partial: true
|
342
|
+
|
368
343
|
.centred.form
|
369
|
-
%form{ atts[:favourite_teas], method: "POST", action: uri("/fav/teas/"), name: "favourite_teas", }
|
344
|
+
%form{ atts[:favourite_teas], id: "favourite_teas", method: "POST", action: uri("/fav/teas/"), name: "favourite_teas", }
|
370
345
|
%label{ for: "teas", }
|
371
346
|
Favourite tea:
|
372
|
-
%select{ atts[:teas], tabindex: "#{
|
373
|
-
%option{
|
374
|
-
%option{ atts[:teas_ceylon],
|
375
|
-
%option{ atts[:teas_breakfast],
|
376
|
-
%option{ atts[:teas_earl_grey],
|
347
|
+
%select{ atts[:teas], tabindex: "#{@campo_tabindex += 1}", id: "teas", name: "teas", }
|
348
|
+
%option{ atts[:teas], id: "teas", value: "", disabled: "disabled", name: "teas", }Choose one:
|
349
|
+
%option{ atts[:teas_ceylon], id: "teas_ceylon", value: "ceylon", name: "teas", }Ceylon
|
350
|
+
%option{ atts[:teas_breakfast], id: "teas_breakfast", value: "breakfast", name: "teas", }Breakfast
|
351
|
+
%option{ atts[:teas_earl_grey], id: "teas_earl_grey", value: "earl grey", name: "teas", }Earl grey
|
377
352
|
%p= "I like tea!"
|
353
|
+
|
378
354
|
|
379
355
|
## tabindex ##
|
380
356
|
|
381
|
-
Each field gets
|
357
|
+
Each field gets `@campo_tabindex += 1` added to its attributes. This will generate a tabindex easily for you.
|
382
358
|
|
383
|
-
|
359
|
+
Since it's an instance variable it can be passed through easily to nested partials and the count will still be right.
|
384
360
|
|
385
361
|
## Blocks ##
|
386
362
|
|
387
|
-
Most fields will accept a block, so you can nest whatever you like. Generally I just use this for forms, fieldsets and selects (and those have specs) but if you want to try something new, do it! Let me know if it breaks.
|
363
|
+
Most fields will accept a block, so you can nest whatever you like. Generally I just use this for forms, fieldsets and selects (and those have specs) but if you want to try something new, do it! Let me know if it breaks. You don't have to use the `|var|` notation unless you feel it's helpful.
|
388
364
|
|
389
365
|
form = Campo.literal "%div" do |div|
|
390
|
-
div << Campo.form( "nested" ) do
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
366
|
+
div << Campo.form( "nested" ) do
|
367
|
+
fieldset "Nest" do
|
368
|
+
select "blurg" do
|
369
|
+
option "oopsie"
|
370
|
+
option "daisies"
|
395
371
|
end.labelled "splat"
|
396
|
-
|
372
|
+
text "blah"
|
397
373
|
end
|
398
374
|
end
|
399
375
|
end
|
400
376
|
|
377
|
+
puts Campo.output form
|
378
|
+
|
379
|
+
Output:
|
380
|
+
|
381
|
+
- atts = {} if atts.nil?
|
382
|
+
- atts.default_proc = proc {|hash, key| hash[key] = {} } if atts.default_proc.nil?
|
383
|
+
- inners = {} if inners.nil?
|
384
|
+
- inners.default = "" if inners.default.nil?
|
385
|
+
- @campo_tabindex ||= 0 # for tabindex
|
386
|
+
%div
|
387
|
+
%form{ atts[:nested], id: "nested", method: "POST", name: "nested", }
|
388
|
+
%fieldset{ }
|
389
|
+
%legend{ }Nest
|
390
|
+
%label{ for: "blurg", }
|
391
|
+
splat
|
392
|
+
%select{ atts[:blurg], tabindex: "#{@campo_tabindex += 1}", id: "blurg", name: "blurg", }
|
393
|
+
%option{ atts[:blurg_oopsie], id: "blurg_oopsie", value: "oopsie", name: "blurg", }Oopsie
|
394
|
+
%option{ atts[:blurg_daisies], id: "blurg_daisies", value: "daisies", name: "blurg", }Daisies
|
395
|
+
%label{ for: "blah", }
|
396
|
+
Blah
|
397
|
+
%input{ atts[:blah], tabindex: "#{@campo_tabindex += 1}", id: "blah", type: "text", name: "blah", }
|
398
|
+
|
399
|
+
## Plugins ##
|
400
|
+
|
401
|
+
I've written a couple of plugins. If you wish to write one yourself you'll need to look for the code for now until I write some proper instructions.
|
402
|
+
|
403
|
+
To load a plugin called "Aria":
|
404
|
+
|
405
|
+
Campo.plugin :Aria
|
406
|
+
|
407
|
+
### Aria ###
|
408
|
+
|
409
|
+
Helpful methods for outputting forms that add in the [WAI-ARIA](http://www.w3.org/WAI/intro/aria) attributes into your forms.
|
410
|
+
|
411
|
+
Here's an example of how I've used this with an account registration form to provide information about each field:
|
412
|
+
|
413
|
+
require 'campo'
|
414
|
+
|
415
|
+
Campo.plugin :Aria
|
416
|
+
|
417
|
+
div = Campo.literal "#session.form" do
|
418
|
+
self << Campo.form( "register" ) do
|
419
|
+
fieldset "Register" do
|
420
|
+
literal "%p.warning" do
|
421
|
+
literal "Fields marked with an <em>*</em> are required."
|
422
|
+
end
|
423
|
+
text( "email_address", size: 40 ).describe("A valid email address, please.", class: "validation info description")
|
424
|
+
text( "username", size: 40 ).describe([["Must be 3 characters at least.", {class: 'validate_username required', id: 'username_length'}], ["No spaces or punctuation, only numbers, letters and underscores, please.", {class: 'validate_username required', id: 'username_specialchars'}], ["You may not use your email address as a username.", {class: 'validate_username required', id: 'username_not_email_address'}]], class: "validation info description")
|
425
|
+
password( "password", size: 40 ).describe([["Must be 8 characters at least.",{class: 'validate_password required', id: 'password_length'}],["It's better to add some numbers/punctuation.", class: 'validate_password', id: 'password_specialchars'],["You may not use your email address or username as a password.", {class: 'validate_password required', id: 'password_not_email_address'}],["For strength, try to make it a phrase, and not to be something you've previously used.",{}]], class: "validation info description")
|
426
|
+
bit_of_ruby "= Rack::Csrf.tag(env)"
|
427
|
+
literal "#validation_overall_result.validation.hidden" do
|
428
|
+
literal "There were problems with the form entries. Please check the highlighted fields."
|
429
|
+
end
|
430
|
+
submit
|
431
|
+
end
|
432
|
+
end
|
433
|
+
end
|
434
|
+
|
435
|
+
|
436
|
+
|
437
|
+
puts Campo.output div
|
438
|
+
|
439
|
+
Now let's take a look at the output of the email_address field:
|
440
|
+
|
441
|
+
%label{ for: "email_address", }
|
442
|
+
Email address
|
443
|
+
%span{id: "email_address_description", class: "validation info description", }
|
444
|
+
A valid email address, please.
|
445
|
+
%input{ atts[:email_address], tabindex: "#{@campo_tabindex += 1}", id: "email_address", type: "text", size: "40", name: "email_address", :"aria-describedby" => "email_address_description", }
|
446
|
+
|
447
|
+
Notice how the label contains a span that precedes the input field - this is helpful for screen readers because those that aren't using ARIA will still hit the description. The input field refers to this span using the aria-describedby attribute.
|
448
|
+
|
449
|
+
For a more complex example, look at the output for username:
|
450
|
+
|
451
|
+
%label{ for: "username", }
|
452
|
+
Username
|
453
|
+
%span{id: "username_description", class: "validation info description", }
|
454
|
+
%ul
|
455
|
+
%li{ id: "username_length", class: "validate_username required", }
|
456
|
+
Must be 3 characters at least.
|
457
|
+
%li{ id: "username_specialchars", class: "validate_username required", }
|
458
|
+
No spaces or punctuation, only numbers, letters and underscores, please.
|
459
|
+
%li{ id: "username_not_email_address", class: "validate_username required", }
|
460
|
+
You may not use your email address as a username.
|
461
|
+
%input{ atts[:username], tabindex: "#{@campo_tabindex += 1}", id: "username", type: "text", size: "40", name: "username", :"aria-describedby" => "username_description", }
|
462
|
+
|
463
|
+
For the campo code, we added an array of tuples to the `describe` method. These tuples are then made into an unordered list within the span. The first part of each tuple is the text description, the second part any attributes you with the list item to have. I've used this in a project in conjunction with JQuery to produce dynamic information back to the user of the form. You don't have to pass the second part of the tuple, in other words you can do:
|
464
|
+
|
465
|
+
text("d").describe([["You"], ["Me"], ["Them"]])
|
466
|
+
|
467
|
+
But I would usually expect that you'd want to pass each item an id. It's up to you.
|
468
|
+
|
469
|
+
### Contributing ###
|
470
|
+
|
471
|
+
When contributings, most of all, remember that **any** contribution you can make will be valuable, whether that is putting in a ticket for a feature request (or a bug, but they don't happen here;), cleaning up some grammar, writing some documentation (or even a blog post, let me know!) or a full blooded piece of code - it's **all** welcome and encouraged.
|
472
|
+
|
473
|
+
To contribute some code:
|
474
|
+
|
475
|
+
1. Fork this, then:
|
476
|
+
* `git clone git@github.com:YOUR-USERNAME/Campo.git`
|
477
|
+
* `git remote add upstream git@github.com:yb66/Campo.git`
|
478
|
+
* `git fetch upstream`
|
479
|
+
* `git checkout develop`
|
480
|
+
* Decide on the feature you wish to add.
|
481
|
+
- Give it a snazzy name, such as **kitchen_sink**.
|
482
|
+
- `git checkout -b kitchen_sink`
|
483
|
+
* Install Bundler.
|
484
|
+
- `gem install bundler -r --no-ri --no-rdoc`
|
485
|
+
* Install gems from Gemfile.
|
486
|
+
- `bundle install --binstubs --path vendor`
|
487
|
+
- Any further updates needed, just run `bundle install`, it'll remember the rest.
|
488
|
+
* Write some specs.
|
489
|
+
* Write some code. (Yes, I believe that is the correct order, and you'll never find me doing any different;)
|
490
|
+
* Write some documentation using Yard comments - see [Yard's Getting Started](http://rubydoc.info/docs/yard/file/docs/GettingStarted.md)
|
491
|
+
- Use real English (i.e. full stops and commas, no l33t or LOLZ). I'll accept American English even though it's ugly. Don't be surprised if I 'correct' it.
|
492
|
+
- Code without comments won't get in, I don't have the time to work out what you've done if you're not prepared to spend some time telling me (and everyone else).
|
493
|
+
* Run `reek PATH_TO_FILE_WITH_YOUR_CHANGES` and see if it gives you any good advice. You don't have to do what it says, just consider it.
|
494
|
+
* Run specs to make sure you've not broken anything. If it doesn't pass all the specs it doesn't get in.
|
495
|
+
- Have a look at coverage/index.htm and see if all your code was checked. We're trying for 100% code coverage.
|
496
|
+
* Run `bin/rake docs` to generate documentation.
|
497
|
+
- Open up docs/index.html and check your documentation has been added and is clear.
|
498
|
+
* Add a short summary of your changes to the CHANGES file. Add your name and a link to your bio/website if you like too.
|
499
|
+
* Send me a pull request.
|
500
|
+
- Don't merge into the develop branch!
|
501
|
+
- Don't merge into the master branch!
|
502
|
+
- see [http://nvie.com/posts/a-successful-git-branching-model/](http://nvie.com/posts/a-successful-git-branching-model/) for more on how this is supposed to work.
|
503
|
+
* Wait for worldwide fame.
|
504
|
+
* Shrug and get on with your life when it doesn't arrive, but know you helped quite a few people in their life, even in a small way - 1000 raindrops will fill a bucket!
|
505
|
+
|
506
|
+
|
401
507
|
## Licence ##
|
402
508
|
|
403
509
|
This is under the MIT Licence.
|
404
510
|
|
405
|
-
Copyright (c)
|
511
|
+
Copyright (c) 2012 Iain Barnett
|
406
512
|
|
407
513
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
408
514
|
|