campo 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data.tar.gz.sig +0 -0
- data/.gitignore +17 -0
- data/CHANGES +65 -0
- data/README.markdown +276 -0
- data/campo.gemspec +23 -0
- data/lib/campo.rb +507 -0
- data/lib/campo/version.rb +3 -0
- data/spec/campo_spec.rb +889 -0
- data/spec/spec_helper.rb +9 -0
- metadata +111 -0
- metadata.gz.sig +0 -0
data.tar.gz.sig
ADDED
Binary file
|
data/.gitignore
ADDED
data/CHANGES
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
v0.2.0 All Base fields can now take a block and the convenience methodsl, allowing them to nest other elements regardless of whether the parent is a form or a select. This basically makes it a lot easier to use a literal as the root of the document.
|
2
|
+
|
3
|
+
v0.1.1 Label naming a bit better... possibly! It breaks on _ and capitalises when trying for a default.
|
4
|
+
|
5
|
+
v0.1.0 Added support for helper methods in amongst haml attributes. More convenience methods like `radio`. Support for nesting of fieldsets. `.select` takes a block too.
|
6
|
+
|
7
|
+
v0.0.30 Changed main output to allow a whole form to be wrapped in a literal, for instance when wanting to wrap it in a div. Fragments now require 'false' to be passed along with the tag class. No change for whole forms.
|
8
|
+
|
9
|
+
v0.0.29 Added more tests and a couple of convenience methods - bit_of_ruby and literal.
|
10
|
+
|
11
|
+
v0.0.28 Somehow a typo that caused a bug got committed. Fixed it.
|
12
|
+
|
13
|
+
v0.0.27 changes to allow fragments to be output without the variable declarations at the top, and for all children to output too.
|
14
|
+
|
15
|
+
v0.0.26 Bugfix
|
16
|
+
|
17
|
+
v0.0.25 Added checkbox convenience method.
|
18
|
+
|
19
|
+
v0.0.24 Fixed inners for textarea, properly evaluated now, and slight change to submit button so it's not passed on submit as a parameter.
|
20
|
+
|
21
|
+
v0.0.23 Inners are capitalised and attributes downcased, to make using convenience methods more convenient.
|
22
|
+
|
23
|
+
v0.0.22 Bugfixes, bit of internal rejigging, and fieldset convenience method now takes a block, it's more natural.
|
24
|
+
|
25
|
+
v0.0.21 Added convenience method for Textarea.
|
26
|
+
|
27
|
+
v0.0.20 Bugfixes to Textarea and some other bits of assignment code, as it just didn't work.
|
28
|
+
|
29
|
+
v0.0.19 Added convenience method for submit button.
|
30
|
+
|
31
|
+
v0.0.18 Fix to errors caused by attributes that aren't strings; labelling can be added by .method or by push.
|
32
|
+
|
33
|
+
v0.0.17 Bug fix/internal API change, forced by changes in 0.0.16
|
34
|
+
|
35
|
+
v0.0.16 can pass a ruby insert for Haml to a select tag
|
36
|
+
|
37
|
+
v0.0.15 Select#with_default can now be used with an and array and/or a block.
|
38
|
+
|
39
|
+
v0.0.14 Added some specs and made minor changes to code.
|
40
|
+
|
41
|
+
v0.0.13 Added convenience methods for adding text and select tags (with a default option tag), and problem with spaces in names/values for attribute methods.
|
42
|
+
|
43
|
+
v0.0.12 Tab index for fields is automatically generated.
|
44
|
+
|
45
|
+
v0.0.11 No need to explicitly give a label the name, it picks it up from the field.
|
46
|
+
|
47
|
+
v0.0.10 All options get id's and locals too.
|
48
|
+
|
49
|
+
v0.0.9 Added fieldsets and legends.
|
50
|
+
|
51
|
+
v0.0.8 Adds id's to input fields, making accessible labels.
|
52
|
+
|
53
|
+
v0.0.7 Select tags with dynamic option tags possible.
|
54
|
+
|
55
|
+
v0.0.6 Added in another local, called inners for dynamic inner stuff. Changed locals to atts as it was messing up sinatra/haml. Added in more defaults to locals to avoid errors.
|
56
|
+
|
57
|
+
v0.0.5 Added in locals, which makes the parts of the form (possibly) dynamic.
|
58
|
+
|
59
|
+
v0.0.4 Moved output delegates to blocks on path to adding locals easier.
|
60
|
+
|
61
|
+
v0.0.3 Simplified API, removed unnecessary classes
|
62
|
+
|
63
|
+
v0.0.2 Submit button and convenience method for output.
|
64
|
+
|
65
|
+
v0.0.1 Input text fields, textarea, with labels.
|
data/README.markdown
ADDED
@@ -0,0 +1,276 @@
|
|
1
|
+
# Campo #
|
2
|
+
|
3
|
+
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. http://haml-lang.com/docs/yardoc/file.HAML\_REFERENCE.html#attribute_methods. More on that below.
|
4
|
+
|
5
|
+
Btw, I'll be using this with Sinatra, if you're using Rails you'll need to work out how that's done as I don't know.
|
6
|
+
|
7
|
+
## Why though? ##
|
8
|
+
|
9
|
+
However nice Haml is, it's still a lot of effort to build a form. If you've got lots of forms it's worse. The long term plan is to link this in to Sequel.
|
10
|
+
|
11
|
+
## Example! ##
|
12
|
+
|
13
|
+
Here's an example form:
|
14
|
+
|
15
|
+
# This bit is to simulate the output I'd usually get from calling a database model for a lookup table
|
16
|
+
genders = [["1", "Male"], ["2", "Female"]]
|
17
|
+
|
18
|
+
# Now starts the real action #
|
19
|
+
|
20
|
+
form = Campo.form "myform", action: "/my/form/update/"
|
21
|
+
|
22
|
+
form.fieldset "Your details" do |f|
|
23
|
+
f.text "full_name", size: 60
|
24
|
+
f.text "dob", "Date of birth: ", size: 8
|
25
|
+
|
26
|
+
f.select( "gender_id", {opts: genders }).with_default.labelled( "Gender: " )
|
27
|
+
|
28
|
+
f.select "teas" do |s|
|
29
|
+
s.with_default
|
30
|
+
s.option("ceylon")
|
31
|
+
s.option("breakfast")
|
32
|
+
s.option("earl grey")
|
33
|
+
s.option("oolong")
|
34
|
+
s.option("sencha")
|
35
|
+
end.labelled "Favourite tea:"
|
36
|
+
|
37
|
+
f.text "occupation", "Occupation: ", size: 60
|
38
|
+
f.text "phone_landline", "Phone landline : ", size: 20
|
39
|
+
f.text "phone_mobile", "Phone mobile : ", size: 20
|
40
|
+
|
41
|
+
f.submit "Save"
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
and the output:
|
46
|
+
|
47
|
+
|
48
|
+
puts Campo.output( form )
|
49
|
+
|
50
|
+
- atts = {} if atts.nil?
|
51
|
+
- atts.default = {} if atts.default.nil?
|
52
|
+
- inners.default = "" if inners.default.nil?
|
53
|
+
- i = 0 # for tabindex
|
54
|
+
|
55
|
+
%form{ atts[:myform], method: "POST", action: "/my/form/update/", name: "myform", }
|
56
|
+
%fieldset{ }
|
57
|
+
%legend{ }Your details
|
58
|
+
%label{ for: "full_name", }
|
59
|
+
Full name:
|
60
|
+
%input{ atts[:full_name], tabindex: "#{i += 1}", type: "text", id: "full_name", size: "60", name: "full_name", }
|
61
|
+
%label{ for: "dob", }
|
62
|
+
Date of birth:
|
63
|
+
%input{ atts[:dob], tabindex: "#{i += 1}", type: "text", id: "dob", size: "8", name: "dob", }
|
64
|
+
%label{ for: "gender_id", }
|
65
|
+
Gender:
|
66
|
+
%select{ atts[:gender_id], tabindex: "#{i += 1}", name: "gender_id", }
|
67
|
+
%option{ value: "", disabled: "disabled", name: "gender_id", }Choose one:
|
68
|
+
%option{ atts[:gender_id_1], value: "1", id: "gender_id_1", name: "gender_id", }Male
|
69
|
+
%option{ atts[:gender_id_2], value: "2", id: "gender_id_2", name: "gender_id", }Female
|
70
|
+
%label{ for: "teas", }
|
71
|
+
Favourite tea:
|
72
|
+
%select{ atts[:teas], tabindex: "#{i += 1}", name: "teas", }
|
73
|
+
%option{ value: "", disabled: "disabled", name: "teas", }Choose one:
|
74
|
+
%option{ atts[:teas_ceylon], value: "ceylon", id: "teas_ceylon", name: "teas", }Ceylon
|
75
|
+
%option{ atts[:teas_breakfast], value: "breakfast", id: "teas_breakfast", name: "teas", }Breakfast
|
76
|
+
%option{ atts[:teas_earl_grey], value: "earl grey", id: "teas_earl_grey", name: "teas", }Earl grey
|
77
|
+
%option{ atts[:teas_oolong], value: "oolong", id: "teas_oolong", name: "teas", }Oolong
|
78
|
+
%option{ atts[:teas_sencha], value: "sencha", id: "teas_sencha", name: "teas", }Sencha
|
79
|
+
%label{ for: "occupation", }
|
80
|
+
Occupation:
|
81
|
+
%input{ atts[:occupation], tabindex: "#{i += 1}", type: "text", id: "occupation", size: "60", name: "occupation", }
|
82
|
+
%label{ for: "phone_landline", }
|
83
|
+
Phone (landline):
|
84
|
+
%input{ atts[:phone_landline], tabindex: "#{i += 1}", type: "text", id: "phone_landline", size: "20", name: "phone_landline", }
|
85
|
+
%label{ for: "phone_mobile", }
|
86
|
+
Phone (mobile):
|
87
|
+
%input{ atts[:phone_mobile], tabindex: "#{i += 1}", type: "text", id: "phone_mobile", size: "20", name: "phone_mobile", }
|
88
|
+
%input{ atts[:Save_Save], tabindex: "#{i += 1}", type: "submit", id: "Save_Save", value: "Save", }
|
89
|
+
|
90
|
+
and that outputs:
|
91
|
+
|
92
|
+
puts Haml::Engine.new( Campo.output( form ) ).render
|
93
|
+
|
94
|
+
<form action='/my/form/update/' method='POST' name='myform'>
|
95
|
+
<fieldset>
|
96
|
+
<legend>Your details</legend>
|
97
|
+
<label for='full_name'>
|
98
|
+
Full name:
|
99
|
+
<input id='full_name' name='full_name' size='60' tabindex='1' type='text' />
|
100
|
+
</label>
|
101
|
+
Date of birth:
|
102
|
+
<input id='dob' name='dob' size='8' tabindex='2' type='text' />
|
103
|
+
</label>
|
104
|
+
<label for='gender_id'>
|
105
|
+
Gender:
|
106
|
+
<select name='gender_id' tabindex='3'>
|
107
|
+
<option disabled='disabled' name='gender_id' value=''>Choose one:</option>
|
108
|
+
<option id='gender_id_1' name='gender_id' value='1'>Male</option>
|
109
|
+
<option id='gender_id_2' name='gender_id' value='2'>Female</option>
|
110
|
+
</select>
|
111
|
+
</label>
|
112
|
+
<label for='teas'>
|
113
|
+
Favourite tea:
|
114
|
+
<select name='teas' tabindex='4'>
|
115
|
+
<option disabled='disabled' name='teas' value=''>Choose one:</option>
|
116
|
+
<option id='teas_ceylon' name='teas' value='ceylon'>Ceylon</option>
|
117
|
+
<option id='teas_breakfast' name='teas' value='breakfast'>Breakfast</option>
|
118
|
+
<option id='teas_earl_grey' name='teas' value='earl grey'>Earl grey</option>
|
119
|
+
<option id='teas_oolong' name='teas' value='oolong'>Oolong</option>
|
120
|
+
<option id='teas_sencha' name='teas' value='sencha'>Sencha</option>
|
121
|
+
</select>
|
122
|
+
</label>
|
123
|
+
<label for='occupation'>
|
124
|
+
Occupation:
|
125
|
+
<input id='occupation' name='occupation' size='60' tabindex='5' type='text' />
|
126
|
+
</label>
|
127
|
+
<label for='phone_landline'>
|
128
|
+
Phone (landline):
|
129
|
+
<input id='phone_landline' name='phone_landline' size='20' tabindex='6' type='text' />
|
130
|
+
</label>
|
131
|
+
<label for='phone_mobile'>
|
132
|
+
Phone (mobile):
|
133
|
+
<input id='phone_mobile' name='phone_mobile' size='20' tabindex='7' type='text' />
|
134
|
+
</label>
|
135
|
+
<input id='Save_Save' tabindex='8' type='submit' value='Save' />
|
136
|
+
</fieldset>
|
137
|
+
</form>
|
138
|
+
|
139
|
+
|
140
|
+
Back to the dynamic attributes mentioned earlier. What does this mean? You can pass in a local to dynamically alter the form based on server side logic.
|
141
|
+
|
142
|
+
These get added to the top, to provide sane defaults:
|
143
|
+
|
144
|
+
- atts = {} if atts.nil?
|
145
|
+
- atts.default = {} if atts.default.nil?
|
146
|
+
- inners = {} if inners.nil?
|
147
|
+
- inners.default = "" if inners.default.nil?
|
148
|
+
|
149
|
+
In the select tag (below), notice how each tag gets a local variable added to the front. You can either fill that variable with a hash pair, or an empty hash gets passed and nothing happens.
|
150
|
+
|
151
|
+
Here's the Campo code:
|
152
|
+
|
153
|
+
form = Campo.form "best_bands", action: "/best/bands/" do |form|
|
154
|
+
form.select("bands").option("Suede").option("Blur").option("Oasis").option("Echobelly").option("Pulp").option("Supergrass").with_default.labelled("Favourite band:")
|
155
|
+
end
|
156
|
+
|
157
|
+
or
|
158
|
+
|
159
|
+
form = Campo.form "best_bands", action: "/best/bands/"
|
160
|
+
form.select("bands") do |s|
|
161
|
+
s.with_default
|
162
|
+
s.option("Suede")
|
163
|
+
s.option("Blur")
|
164
|
+
s.option("Oasis")
|
165
|
+
s.option("Echobelly")
|
166
|
+
s.option("Pulp")
|
167
|
+
s.option("Supergrass")
|
168
|
+
end.labelled("Favourite band:")
|
169
|
+
|
170
|
+
(or mix and match blocks, .new, arrays and hashes)
|
171
|
+
|
172
|
+
Campo.output form # generate the haml
|
173
|
+
|
174
|
+
|
175
|
+
And the Haml generated:
|
176
|
+
|
177
|
+
%form{ atts[:best_bands], method: "POST", action: "/best/bands/", name: "best_bands", }
|
178
|
+
%label{ for: "bands", }
|
179
|
+
Favourite band:
|
180
|
+
%select{ atts[:bands], tabindex: "#{i += 1}", name: "bands", }
|
181
|
+
%option{ value: "", disabled: "disabled", name: "bands", }Choose one:
|
182
|
+
%option{ atts[:bands_suede], value: "Suede", id: "bands_suede", name: "bands", }Suede
|
183
|
+
%option{ atts[:bands_blur], value: "Blur", id: "bands_blur", name: "bands", }Blur
|
184
|
+
%option{ atts[:bands_oasis], value: "Oasis", id: "bands_oasis", name: "bands", }Oasis
|
185
|
+
%option{ atts[:bands_echobelly], value: "Echobelly", id: "bands_echobelly", name: "bands", }Echobelly
|
186
|
+
%option{ atts[:bands_pulp], value: "Pulp", id: "bands_pulp", name: "bands", }Pulp
|
187
|
+
%option{ atts[:bands_supergrass], value: "Supergrass", id: "bands_supergrass", name: "bands", }Supergrass
|
188
|
+
|
189
|
+
|
190
|
+
If you wanted to select "Blur" dynamically (and you should, but I'd accept Suede) you might do:
|
191
|
+
|
192
|
+
atts[:bands_blur] = {selected: "selected"}
|
193
|
+
|
194
|
+
and pass it in to the form when the view is rendered, and the tag would change from:
|
195
|
+
|
196
|
+
<option id='bands_blur' name='bands' value='Blur'>Blur</option>
|
197
|
+
|
198
|
+
to:
|
199
|
+
|
200
|
+
<option selected='selected' id='bands_blur' name='bands' value='Blur'>Blur</option>
|
201
|
+
|
202
|
+
You can do this with any kind of attribute you wish to add. For example:
|
203
|
+
|
204
|
+
|
205
|
+
atts[:bands_blur] = {not_worth_listening_to: "selected"}
|
206
|
+
|
207
|
+
If you want to use helpers in the attributes, like sinatra's `uri` helper, then add a quote to the front:
|
208
|
+
|
209
|
+
form = Campo::Form.new "best_bands", action: %Q!"uri("/best/bands/")!
|
210
|
+
|
211
|
+
outputs:
|
212
|
+
|
213
|
+
%form{ atts[:best_bands], method: "POST", action: uri("/best/bands/"), name: "best_bands", }
|
214
|
+
|
215
|
+
If the helper isn't among the attributes, add a "=" to the front as you would in the haml:
|
216
|
+
|
217
|
+
form.bit_of_ruby( "= 5 + 1" ) }
|
218
|
+
|
219
|
+
outputs:
|
220
|
+
|
221
|
+
%form{ atts[:best_bands], method: "POST", action: uri("/best/bands/"), name: "best_bands", }
|
222
|
+
= 5 + 1
|
223
|
+
|
224
|
+
It's really just a literal:
|
225
|
+
|
226
|
+
|
227
|
+
form = Campo.form "favourite_teas", action: %Q!"uri("/fav/teas/")! do |form|
|
228
|
+
form.select("teas").with_default.option("ceylon").option("breakfast").option("earl grey").labelled("Favourite tea:")
|
229
|
+
form.literal %Q<%p= "I like tea!">
|
230
|
+
end
|
231
|
+
Campo.output form
|
232
|
+
|
233
|
+
|
234
|
+
%form{ atts[:favourite_teas], method: "POST", action: uri("/fav/teas/"), name: "favourite_teas", }
|
235
|
+
%label{ for: "teas", }
|
236
|
+
Favourite tea:
|
237
|
+
%select{ atts[:teas], tabindex: "#{i += 1}", name: "teas", }
|
238
|
+
%option{ value: "", disabled: "disabled", name: "teas", }Choose one:
|
239
|
+
%option{ atts[:teas_ceylon], value: "ceylon", id: "teas_ceylon", name: "teas", }Ceylon
|
240
|
+
%option{ atts[:teas_breakfast], value: "breakfast", id: "teas_breakfast", name: "teas", }Breakfast
|
241
|
+
%option{ atts[:teas_earl_grey], value: "earl grey", id: "teas_earl_grey", name: "teas", }Earl grey
|
242
|
+
%p= "I like tea!"
|
243
|
+
|
244
|
+
puts Haml::Engine.new( Campo.output form ).render
|
245
|
+
|
246
|
+
<form action='/fav/teas/' method='POST' name='favourite_teas'>
|
247
|
+
<label for='teas'>
|
248
|
+
Favourite tea:
|
249
|
+
<select name='teas' tabindex='1'>
|
250
|
+
<option disabled='disabled' name='teas' value=''>Choose one:</option>
|
251
|
+
<option id='teas_ceylon' name='teas' value='ceylon'>Ceylon</option>
|
252
|
+
<option id='teas_breakfast' name='teas' value='breakfast'>Breakfast</option>
|
253
|
+
<option id='teas_earl_grey' name='teas' value='earl grey'>Earl grey</option>
|
254
|
+
</select>
|
255
|
+
</label>
|
256
|
+
<p>I like tea!</p>
|
257
|
+
</form>
|
258
|
+
|
259
|
+
You can use literals to wrap forms in divs too:
|
260
|
+
|
261
|
+
doc = Campo.literal ".centred.form" do |wrapper|
|
262
|
+
wrapper << form # the form defined already above
|
263
|
+
end
|
264
|
+
|
265
|
+
puts Campo.output doc
|
266
|
+
|
267
|
+
.centred.form
|
268
|
+
%form{ atts[:favourite_teas], method: "POST", action: uri("/fav/teas/"), name: "favourite_teas", }
|
269
|
+
%label{ for: "teas", }
|
270
|
+
Favourite tea:
|
271
|
+
%select{ atts[:teas], tabindex: "#{i += 1}", name: "teas", }
|
272
|
+
%option{ value: "", disabled: "disabled", name: "teas", }Choose one:
|
273
|
+
%option{ atts[:teas_ceylon], value: "ceylon", id: "teas_ceylon", name: "teas", }Ceylon
|
274
|
+
%option{ atts[:teas_breakfast], value: "breakfast", id: "teas_breakfast", name: "teas", }Breakfast
|
275
|
+
%option{ atts[:teas_earl_grey], value: "earl grey", id: "teas_earl_grey", name: "teas", }Earl grey
|
276
|
+
%p= "I like tea!"
|
data/campo.gemspec
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('./lib')
|
3
|
+
$:.unshift lib unless $:.include?(lib)
|
4
|
+
require './lib/campo/version.rb'
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = "campo"
|
8
|
+
s.summary = "Form builder for Haml"
|
9
|
+
s.description = <<-EOF
|
10
|
+
Form builder for Haml
|
11
|
+
EOF
|
12
|
+
s.version = Campo::VERSION
|
13
|
+
s.platform = Gem::Platform::RUBY
|
14
|
+
s.require_path = "lib"
|
15
|
+
s.required_ruby_version = ">= 1.9.2"
|
16
|
+
s.authors = ["Iain Barnett"]
|
17
|
+
s.files = `git ls-files`.split("\n")
|
18
|
+
s.add_dependency("haml", "~> 3.1.1")
|
19
|
+
s.email = ["iainspeed @nospam@ gmail.com"]
|
20
|
+
s.test_files = `git ls-files -- {test,spec,features}`.split("\n")
|
21
|
+
s.signing_key = ENV['HOME'] + '/.ssh/gem-private_key.pem'
|
22
|
+
s.cert_chain = [ENV['HOME'] + '/.ssh/gem-public_cert.pem']
|
23
|
+
end
|
data/lib/campo.rb
ADDED
@@ -0,0 +1,507 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
module Campo
|
4
|
+
module Childish
|
5
|
+
def push=( child )
|
6
|
+
@fields << child
|
7
|
+
child.parent = self
|
8
|
+
self
|
9
|
+
end
|
10
|
+
|
11
|
+
alias :<< :push=
|
12
|
+
|
13
|
+
attr_accessor :parent
|
14
|
+
end # Childish
|
15
|
+
|
16
|
+
module Iding
|
17
|
+
def id_tag( val )
|
18
|
+
val.nil? ? "" : "_#{val}"
|
19
|
+
end
|
20
|
+
end # Iding
|
21
|
+
|
22
|
+
module Convenience
|
23
|
+
|
24
|
+
|
25
|
+
# @param [optional, Hash] attributes Any attributes you wish to add to the haml element.
|
26
|
+
# @example Fieldset as a block is easiest to read
|
27
|
+
# form.fieldset("Your details") do |f|
|
28
|
+
# f.text( "full_name", size: 60 )
|
29
|
+
# f.text( "dob", "Date of birth: ", size: 8 )
|
30
|
+
# end
|
31
|
+
def fieldset( text, attributes={}, &block )
|
32
|
+
fieldset = (Fieldset.new(attributes) << Legend.new( text ))
|
33
|
+
block.call( fieldset ) if block
|
34
|
+
self << fieldset
|
35
|
+
fieldset
|
36
|
+
end
|
37
|
+
|
38
|
+
# @example Add a bit of code to the markup
|
39
|
+
# form.bit_of_ruby( "= 5 + 1" ) }
|
40
|
+
def bit_of_ruby( *args )
|
41
|
+
tag = Campo::Haml_Ruby_Insert.new( *args )
|
42
|
+
self << tag
|
43
|
+
tag
|
44
|
+
end
|
45
|
+
|
46
|
+
alias :haml_ruby_insert :bit_of_ruby
|
47
|
+
|
48
|
+
# @example Output a literal string
|
49
|
+
# form.literal %Q!%p= "This is a paragraph "!
|
50
|
+
def literal( *args )
|
51
|
+
tag = Campo::Literal.new( *args )
|
52
|
+
self << tag
|
53
|
+
tag
|
54
|
+
end
|
55
|
+
|
56
|
+
# @example
|
57
|
+
# # Select with a block of options
|
58
|
+
# f.select("teas") do |s|
|
59
|
+
# s.with_default
|
60
|
+
# s.option("ceylon")
|
61
|
+
# s.option("breakfast")
|
62
|
+
# s.option("earl grey")
|
63
|
+
# s.option("oolong")
|
64
|
+
# s.option("sencha")
|
65
|
+
# end.labelled("Favourite tea:")
|
66
|
+
#
|
67
|
+
# # Select using chain of options
|
68
|
+
# form.select("bands").option("Suede").option("Blur").option("Oasis").option("Echobelly").option("Pulp").option("Supergrass").with_default.labelled("Favourite band:")
|
69
|
+
#
|
70
|
+
# @see Select
|
71
|
+
def select( *args, &block )
|
72
|
+
select = Campo::Select.new( *args, &block )
|
73
|
+
self << select
|
74
|
+
select
|
75
|
+
end
|
76
|
+
|
77
|
+
# Add an input with type of text
|
78
|
+
# @param [String] name The name html attribute.
|
79
|
+
# @param [optional, String, nil] label Give the label a name. Defaults to a capitalised name with _ replaced by spaces.
|
80
|
+
# @param [optional, Hash] attributes Any attributes you wish to add to the haml element.
|
81
|
+
# @example
|
82
|
+
# f.text "full_name", size: 60
|
83
|
+
# f.text "dob", "Date of birth: ", size: 8
|
84
|
+
# @return [Input]
|
85
|
+
# With the attribute `type=text`
|
86
|
+
def text( name, label=nil, attributes={} )
|
87
|
+
input( name, :text, label, attributes )
|
88
|
+
end
|
89
|
+
|
90
|
+
# @param (see #text)
|
91
|
+
def radio( name, label=nil, attributes={} )
|
92
|
+
input( name, :radio, label, attributes )
|
93
|
+
end
|
94
|
+
|
95
|
+
# @param (see #text)
|
96
|
+
def checkbox( name, label=nil, attributes={} )
|
97
|
+
input( name, :checkbox, label, attributes )
|
98
|
+
end
|
99
|
+
|
100
|
+
|
101
|
+
# @param (see #text)
|
102
|
+
# @param [:symbol] type The type html attribute.
|
103
|
+
def input( name, type, label=nil, attributes={} )
|
104
|
+
if label.kind_of? Hash
|
105
|
+
attributes = label
|
106
|
+
label = nil
|
107
|
+
end
|
108
|
+
|
109
|
+
field = Campo::Input.new( name, type, attributes ).labelled( label )
|
110
|
+
self << field
|
111
|
+
field
|
112
|
+
end
|
113
|
+
|
114
|
+
# @param [optional,String] name
|
115
|
+
# @param [optional, Hash] attributes Any attributes you wish to add to the haml element.
|
116
|
+
def submit( name="Submit", label_inner=nil, attributes={} )
|
117
|
+
submit = Campo::Input.new( name, :submit, {value: name}.merge(attributes) )
|
118
|
+
self << submit
|
119
|
+
submit
|
120
|
+
end
|
121
|
+
|
122
|
+
|
123
|
+
def textarea( *args )
|
124
|
+
textarea = Campo::Textarea.new( *args )
|
125
|
+
self << textarea
|
126
|
+
textarea
|
127
|
+
end
|
128
|
+
end # Convenience
|
129
|
+
|
130
|
+
module Helpers
|
131
|
+
# [ [id, lookup, selected || false], ... ]
|
132
|
+
def self.options_builder( name, opts )
|
133
|
+
return [] if opts.nil? || opts.empty?
|
134
|
+
|
135
|
+
opts.map do |opt|
|
136
|
+
id, lookup, selected, atts = opt
|
137
|
+
selected = selected ? true : false
|
138
|
+
atts = atts.nil? ? { } : atts
|
139
|
+
|
140
|
+
Campo::Option.new( name, id, lookup, selected, atts )
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
def self.options_outputter( opts=[] )
|
145
|
+
return "" if opts.nil? || opts.empty?
|
146
|
+
opts.map{|o| "#{o.output}\n" }.reduce(:+)
|
147
|
+
end
|
148
|
+
end # Helpers
|
149
|
+
|
150
|
+
@atts = {}
|
151
|
+
|
152
|
+
class << self
|
153
|
+
attr_accessor :atts
|
154
|
+
end
|
155
|
+
|
156
|
+
class Base
|
157
|
+
include Childish
|
158
|
+
include Iding
|
159
|
+
include Convenience
|
160
|
+
|
161
|
+
DEFAULT = { tabindex: nil }
|
162
|
+
|
163
|
+
attr_accessor :attributes, :fields
|
164
|
+
|
165
|
+
def initialize( name, attributes={}, &block )
|
166
|
+
@attributes = DEFAULT.merge( attributes.merge({name: name}) ).reject{|k,v| v.nil? }
|
167
|
+
@fields = []
|
168
|
+
block.call( self ) if block
|
169
|
+
self
|
170
|
+
end
|
171
|
+
|
172
|
+
def on_output( &block )
|
173
|
+
@output_listener = block
|
174
|
+
end
|
175
|
+
|
176
|
+
def output( n=0, tab=2 )
|
177
|
+
@output_listener.call n, tab
|
178
|
+
end
|
179
|
+
|
180
|
+
def labelled( inner=nil )
|
181
|
+
inner ||= self.attributes[:name].gsub("_"," ").capitalize
|
182
|
+
parent = self.parent
|
183
|
+
label = Label.new( %Q!#{@attributes[:name] + id_tag(@attributes[:value]).gsub(/\W/, "_")}!, inner ) << self
|
184
|
+
retval = if parent.nil?
|
185
|
+
label
|
186
|
+
else
|
187
|
+
parent.fields.delete self
|
188
|
+
parent << label
|
189
|
+
label
|
190
|
+
end
|
191
|
+
|
192
|
+
retval
|
193
|
+
end # labelled
|
194
|
+
|
195
|
+
def self.unhash( hash, skip=nil )
|
196
|
+
hash.reject{|k,v| v.nil? }.reject{|k,v| k.to_sym == skip.to_sym unless skip.nil? }.reduce(""){|mem, (k,v)| mem + %Q!#{k}: #{Base.quotable(v)}, !}
|
197
|
+
end
|
198
|
+
|
199
|
+
# if the string provided begins with a double quote but does not end in one, make it an unquoted string on output
|
200
|
+
# else, wrap it in quotes
|
201
|
+
def self.quotable( s )
|
202
|
+
retval = if s.respond_to?(:start_with?) && s.start_with?( %Q!"! ) &! s.end_with?( %Q!"! )
|
203
|
+
s[1.. -1] # chop the first character
|
204
|
+
else
|
205
|
+
%Q!"#{s}"! # wrap
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
|
210
|
+
def self.output( top, so_far="", count=0, tab=2 )
|
211
|
+
so_far << "#{top.output( count, tab )}\n"
|
212
|
+
count += 1
|
213
|
+
if top.respond_to?( :fields ) && top.fields.length >= 1
|
214
|
+
top.fields.each do |field|
|
215
|
+
so_far = Base.output( field, so_far, count, tab )
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
so_far
|
220
|
+
end
|
221
|
+
|
222
|
+
end # Base
|
223
|
+
|
224
|
+
# @see Convenience#literal
|
225
|
+
def self.literal( *args, &block )
|
226
|
+
Campo::Literal.new( *args, &block )
|
227
|
+
end
|
228
|
+
|
229
|
+
# Pass anything but the form for the first argument to *not* have the local variable defaults added to the top
|
230
|
+
# @example
|
231
|
+
# Campo.output form # would add the default locals
|
232
|
+
# # these won't
|
233
|
+
# Campo.output :partial, input_field
|
234
|
+
# Campo.output false, label
|
235
|
+
# Campo.output true, fieldset
|
236
|
+
def self.output( *args )
|
237
|
+
s = <<STR
|
238
|
+
- atts = {} if atts.nil?
|
239
|
+
- atts.default = {} if atts.default.nil?
|
240
|
+
- inners = {} if inners.nil?
|
241
|
+
- inners.default = "" if inners.default.nil?
|
242
|
+
- i = 0 # for tabindex
|
243
|
+
|
244
|
+
STR
|
245
|
+
|
246
|
+
|
247
|
+
# default to true
|
248
|
+
whole_form = if args.first.kind_of? Campo::Base
|
249
|
+
true
|
250
|
+
else
|
251
|
+
args.shift
|
252
|
+
false
|
253
|
+
end
|
254
|
+
|
255
|
+
output = Base.output( *args )
|
256
|
+
output = s + output if whole_form
|
257
|
+
output
|
258
|
+
end # self.output
|
259
|
+
|
260
|
+
# end Campo methods
|
261
|
+
|
262
|
+
|
263
|
+
|
264
|
+
class Form < Base
|
265
|
+
DEFAULT = { method: "POST" }
|
266
|
+
|
267
|
+
# @param [String] name The form's name (html) attribute.
|
268
|
+
# @param [optional, Hash] attributes Html attributes. They can be anything you like. Defaults follow:
|
269
|
+
# @option attributes [String] :method ("POST")
|
270
|
+
# @example
|
271
|
+
# form = Campo::Form.new "example", "/path/to/post/to/" do |form|
|
272
|
+
# form.text "first_field"
|
273
|
+
# #... more fields follow
|
274
|
+
# end
|
275
|
+
def initialize(name, attributes={} )
|
276
|
+
super( name, DEFAULT.merge( attributes ) )
|
277
|
+
self.on_output do |n=0, tab=2|
|
278
|
+
%Q!#{" " * n * tab}%form{ atts[:#{name.gsub(/\W/, "_").downcase}], #{Base.unhash( @attributes )} }!
|
279
|
+
end
|
280
|
+
|
281
|
+
self
|
282
|
+
end
|
283
|
+
|
284
|
+
|
285
|
+
end # Form
|
286
|
+
|
287
|
+
# Generally, the first method you'll call.
|
288
|
+
# @example
|
289
|
+
# # Form with a block
|
290
|
+
# form = Campo.form "form1", action: "/go/for/it/" do |f|
|
291
|
+
# f.text "Hello"
|
292
|
+
# #... more fields follow
|
293
|
+
# end
|
294
|
+
#
|
295
|
+
# @param [String] name The form's name (html) attribute.
|
296
|
+
# @param [optional, Hash] attributes Html attributes. They can be anything you like. Defaults follow:
|
297
|
+
# @option attributes [String] :method ("POST") The method attribute for the form.
|
298
|
+
# @see Form#initialize
|
299
|
+
def self.form( name, attributes={}, &block )
|
300
|
+
Form.new( name, attributes, &block )
|
301
|
+
end
|
302
|
+
|
303
|
+
|
304
|
+
class Haml_Ruby_Insert < Base
|
305
|
+
def initialize( s )
|
306
|
+
super( nil ) # no name needed
|
307
|
+
@s = s.start_with?( '=' ) ? s : "= #{s}"
|
308
|
+
|
309
|
+
self.on_output do |n=0, tab=2|
|
310
|
+
(" " * n * tab) + @s
|
311
|
+
end
|
312
|
+
end
|
313
|
+
end # Haml_Ruby_Insert
|
314
|
+
|
315
|
+
|
316
|
+
# add whatever you need to with a literal
|
317
|
+
class Literal < Base
|
318
|
+
def initialize( s )
|
319
|
+
super( nil ) # no name needed
|
320
|
+
@s = s
|
321
|
+
|
322
|
+
self.on_output do |n=0, tab=2|
|
323
|
+
(" " * n * tab) + @s
|
324
|
+
end
|
325
|
+
self
|
326
|
+
end
|
327
|
+
end # Literal
|
328
|
+
|
329
|
+
class Select < Base
|
330
|
+
def initialize( name, params={} )
|
331
|
+
opts = params[:opts] || []
|
332
|
+
attributes = params[:attributes] || {}
|
333
|
+
haml_insert = params[:haml_insert] || nil
|
334
|
+
|
335
|
+
super( name, { tabindex: %q!#{i += 1}! }.merge(attributes) )
|
336
|
+
|
337
|
+
self.on_output do |n=0, tab=2|
|
338
|
+
%Q!#{" " * n * tab}%select{ atts[:#{name.gsub(/\W/, "_").downcase}], #{Base.unhash( @attributes )} }!
|
339
|
+
end
|
340
|
+
|
341
|
+
self.fields += Helpers.options_builder( name, opts ) unless opts.nil? || opts.empty?
|
342
|
+
|
343
|
+
self.fields << Haml_Ruby_Insert.new( haml_insert ) unless haml_insert.nil?
|
344
|
+
|
345
|
+
|
346
|
+
self
|
347
|
+
end # initialize
|
348
|
+
|
349
|
+
# @example (see Convenience#select)
|
350
|
+
def option( *args )
|
351
|
+
value = args.shift
|
352
|
+
inner = args.shift
|
353
|
+
selected, attributes = *args
|
354
|
+
inner = value.capitalize if inner.nil?
|
355
|
+
self << Campo::Option.new( @attributes[:name], value, inner, selected, attributes )
|
356
|
+
self
|
357
|
+
end
|
358
|
+
|
359
|
+
|
360
|
+
# @example
|
361
|
+
# As a default:
|
362
|
+
# form.select("teas").with_default.option("ceylon")
|
363
|
+
# # output:
|
364
|
+
# %select{ atts[:teas], tabindex: "#{i += 1}", name: "teas", }
|
365
|
+
# %option{ value: "", disabled: "disabled", name: "teas", }Choose one:
|
366
|
+
# %option{ atts[:teas_ceylon], value: "ceylon", id: "teas_ceylon", name: "teas", }Ceylon
|
367
|
+
#
|
368
|
+
# form.select("teas").with_default("My fave tea is:").option("ceylon")
|
369
|
+
# # output:
|
370
|
+
# %select{ atts[:teas], tabindex: "#{i += 1}", name: "teas", }
|
371
|
+
# %option{ value: "", disabled: "disabled", name: "teas", }My fave tea is:
|
372
|
+
# %option{ atts[:teas_ceylon], value: "ceylon", id: "teas_ceylon", name: "teas", }Ceylon
|
373
|
+
def with_default( inner="Choose one:" )
|
374
|
+
self.fields.unshift Campo::Option.new( @attributes[:name], "", inner , nil, {disabled: "disabled" } )
|
375
|
+
self
|
376
|
+
end
|
377
|
+
|
378
|
+
# def mark_as_selected( val )
|
379
|
+
# fields.find {|field| field.value == val }.selected = {selected: "selected"}
|
380
|
+
# end
|
381
|
+
end # Select
|
382
|
+
|
383
|
+
|
384
|
+
class Option < Base
|
385
|
+
attr_accessor :value, :checked
|
386
|
+
|
387
|
+
def initialize( name, value, inner=nil, selected=nil, attributes={} )
|
388
|
+
attributes ||= {}
|
389
|
+
if inner.kind_of? TrueClass
|
390
|
+
selected = attributes
|
391
|
+
inner = nil
|
392
|
+
end
|
393
|
+
|
394
|
+
@inner = inner || value
|
395
|
+
|
396
|
+
if selected.kind_of? Hash
|
397
|
+
attributes = selected
|
398
|
+
selected = {}
|
399
|
+
end
|
400
|
+
|
401
|
+
attributes = { id: "#{(name.gsub(/\W/, "_") + id_tag(value).gsub(/\W/, "_")).downcase}" }.merge(attributes) unless value.nil? || value.to_s.empty?
|
402
|
+
|
403
|
+
super( name, {
|
404
|
+
value: value,
|
405
|
+
selected: (selected ? "selected" : nil)
|
406
|
+
}.merge( attributes ) )
|
407
|
+
|
408
|
+
atts_string = "atts[:#{@attributes[:id]}]," unless @attributes[:id].nil?
|
409
|
+
|
410
|
+
self.on_output do |n=0, tab=2|
|
411
|
+
%Q!#{" " * n * tab}%option{ #{atts_string} #{Base.unhash( @attributes )} }#{@inner}!
|
412
|
+
end
|
413
|
+
|
414
|
+
end #initialize
|
415
|
+
end # Option
|
416
|
+
|
417
|
+
|
418
|
+
# form << Campo::Input.new( "submit", :submit )
|
419
|
+
class Input < Base
|
420
|
+
|
421
|
+
#{ type: nil, value: nil, name: nil }
|
422
|
+
#{ size: nil, maxlength: nil, type: "text" }
|
423
|
+
#{ size: nil, maxlength: nil, type: "hidden" }
|
424
|
+
#{ type: "submit" }
|
425
|
+
def initialize( name, type=:text, attributes={} )
|
426
|
+
super( name,
|
427
|
+
{ type: type.to_s,
|
428
|
+
id: "#{name}#{id_tag(attributes[:value]).gsub(/\W/, "_")}",
|
429
|
+
tabindex: %q!#{i += 1}!,
|
430
|
+
}.merge( attributes ) )
|
431
|
+
|
432
|
+
|
433
|
+
@attributes.delete(:name) if type == :submit
|
434
|
+
|
435
|
+
self.on_output do |n=0, tab=2|
|
436
|
+
%Q!#{" " * n * tab}%input{ atts[:#{name.gsub(/\W/, "_")}#{id_tag(attributes[:value]).gsub(/\W/, "_")}], #{Base.unhash( @attributes )} }!
|
437
|
+
end
|
438
|
+
end
|
439
|
+
end
|
440
|
+
|
441
|
+
class Fieldset < Base
|
442
|
+
|
443
|
+
def initialize( attributes={} )
|
444
|
+
super( nil, attributes )
|
445
|
+
@attributes.delete(:name)
|
446
|
+
|
447
|
+
self.on_output do |n=0, tab=2|
|
448
|
+
%Q!#{" " * n * tab}%fieldset{ #{Base.unhash( @attributes )} }!
|
449
|
+
end
|
450
|
+
end # initialize
|
451
|
+
end # Fieldset
|
452
|
+
|
453
|
+
|
454
|
+
class Legend < Base
|
455
|
+
|
456
|
+
def initialize( inner, attributes={} )
|
457
|
+
super( nil, attributes )
|
458
|
+
@attributes.delete(:name)
|
459
|
+
@inner = inner
|
460
|
+
|
461
|
+
self.on_output do |n=0, tab=2|
|
462
|
+
%Q!#{" " * n * tab}%legend{ #{Base.unhash( @attributes )} }#{@inner}!
|
463
|
+
end
|
464
|
+
end # initialize
|
465
|
+
end # Fieldset
|
466
|
+
|
467
|
+
|
468
|
+
class Label < Base
|
469
|
+
|
470
|
+
DEFAULT = { for: nil }
|
471
|
+
|
472
|
+
attr_reader :attributes, :fields
|
473
|
+
|
474
|
+
def initialize( name, inner=nil, attributes={} )
|
475
|
+
if inner.kind_of? Hash
|
476
|
+
attributes = inner
|
477
|
+
inner = nil
|
478
|
+
end
|
479
|
+
super( name, attributes )
|
480
|
+
|
481
|
+
@inner = inner
|
482
|
+
|
483
|
+
self.on_output do |n=0, tab=2|
|
484
|
+
%Q!#{" " * n * tab}%label{ for: "#{@attributes[:name]}", #{Base.unhash( @attributes, :name )} }\n#{" " * (n + 1) * tab}#{@inner}!
|
485
|
+
end
|
486
|
+
end
|
487
|
+
|
488
|
+
end # Label
|
489
|
+
|
490
|
+
|
491
|
+
class Textarea < Base
|
492
|
+
DEFAULT = { cols: 40, rows: 10, tabindex: %q!#{i += 1}! }
|
493
|
+
|
494
|
+
def initialize( name, inner=nil, attributes={} )
|
495
|
+
if inner.kind_of? Hash
|
496
|
+
attributes = inner
|
497
|
+
inner = nil
|
498
|
+
end
|
499
|
+
super( name, DEFAULT.merge( attributes ) )
|
500
|
+
@inner = inner
|
501
|
+
self.on_output do |n=0, tab=2|
|
502
|
+
%Q!#{" " * n * tab}%textarea{ atts[:#{name.gsub(/\W/, "_")}], #{Base.unhash( @attributes )} }= inners[:#{name.gsub(/\W/, "_")}] !
|
503
|
+
end
|
504
|
+
end
|
505
|
+
end # Textarea
|
506
|
+
|
507
|
+
end
|