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 ADDED
Binary file
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ .DS_Store
2
+ *.swp
3
+
4
+ .rvmrc
5
+ *.tmproj
6
+ key
7
+ log/
8
+ files/
9
+ thin/
10
+ .sass-cache/
11
+ run/
12
+ app/.sass-cache/
13
+ scratch/
14
+ *.gem
15
+ coverage/
16
+ .yardoc/
17
+ docs/
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