campo 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data.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
|