visionmedia-dm-forms 0.0.2

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.
@@ -0,0 +1,156 @@
1
+
2
+ module DataMapper
3
+ module Form
4
+ class Tag
5
+
6
+ ##
7
+ # Boolean attributes.
8
+
9
+ BOOLEAN_ATTRIBUTES = :disabled, :readonly, :multiple, :checked, :selected
10
+
11
+ ##
12
+ # Elements which should not include auto-generated classes.
13
+
14
+ IGNORE_CLASSES_ON_ELEMENTS = :form, :label, :fieldset, :hidden
15
+
16
+ ##
17
+ # Name of element (input, fieldset, etc).
18
+
19
+ attr_accessor :name
20
+
21
+ ##
22
+ # Options passed to the constructor.
23
+
24
+ attr_accessor :options
25
+
26
+ ##
27
+ # Attributes pulled from #options.
28
+
29
+ attr_accessor :attributes
30
+
31
+ ##
32
+ # Markup placed before the element.
33
+
34
+ attr_accessor :before
35
+
36
+ ##
37
+ # Markup placed after the element.
38
+
39
+ attr_accessor :after
40
+
41
+ ##
42
+ # Tag's description.
43
+
44
+ attr_accessor :description
45
+
46
+ def initialize name, options = {}, &block
47
+ @name, @options, @attributes = name, options, (options[:attributes] ||= {})
48
+ @before, @after = attribute(:before, ''), attribute(:after, '')
49
+ @description = Elements.desc(attribute(:description)) || ''
50
+ @model = attribute :model
51
+ (@attributes[:value] ||= '') << Elements.capture_elements(@model, &block) if block_given?
52
+ end
53
+
54
+ ##
55
+ # Render final markup of this element or 'tag'.
56
+
57
+ def render
58
+ @attributes[:class] = classes unless classes.blank?
59
+ prepare_boolean_attributes
60
+ before << label
61
+ close = self_closing? ? " />" : ">#{inner_html}</#{@name}>"
62
+ open = "<#{@name} #{@attributes.to_html_attributes}"
63
+ tag = before << open << close << description << after << "\n"
64
+ end
65
+ alias :to_s :render
66
+
67
+ ##
68
+ # Prepare boolean attribute values, so that the user may
69
+ # utilize :multiple => true, instead of :multiple => :multiple.
70
+
71
+ def prepare_boolean_attributes
72
+ @attributes.each_pair do |key, value|
73
+ if BOOLEAN_ATTRIBUTES.include? key
74
+ if value
75
+ @attributes[key] = key
76
+ else
77
+ @attributes.delete key
78
+ end
79
+ end
80
+ end
81
+ end
82
+
83
+ ##
84
+ # Wither or not a tag is self-closing (<br />).
85
+
86
+ def self_closing?
87
+ @options[:self_closing]
88
+ end
89
+
90
+ ##
91
+ # The inner HTML of this tag, only available for
92
+ # elements which are not self-closing.
93
+
94
+ def inner_html
95
+ attribute :value, '' unless self_closing?
96
+ end
97
+
98
+ ##
99
+ # Is the element required.
100
+
101
+ def required?
102
+ attribute :required, false
103
+ end
104
+
105
+ ##
106
+ # Generates a label when needed.
107
+
108
+ def label
109
+ @label ||= has_label? ? Elements.label(@attributes.delete(:label), :for => @attributes[:name], :required => required?) : ''
110
+ end
111
+
112
+ ##
113
+ # Wither or not this tag has a label.
114
+
115
+ def has_label?
116
+ !@attributes[:label].blank?
117
+ end
118
+
119
+ ##
120
+ # Returns user generated classes as well as those generated by
121
+ # dm-forms for styling consistancy.
122
+
123
+ def classes
124
+ @classes ||= [@attributes[:class], generate_classes].join(' ').strip if should_add_classes?
125
+ end
126
+
127
+ private
128
+
129
+ ##
130
+ # Generates element class(es) such as form-submit.
131
+
132
+ def generate_classes
133
+ classes = "form-#{@name == :input ? @attributes[:type] : @name}"
134
+ classes << " form-#{@attributes[:name]}" unless @attributes[:name].blank?
135
+ classes
136
+ end
137
+
138
+ ##
139
+ # Wither or not classes should be added to this element.
140
+
141
+ def should_add_classes?
142
+ !(IGNORE_CLASSES_ON_ELEMENTS.include? @name or IGNORE_CLASSES_ON_ELEMENTS.include? @attributes[:type])
143
+ end
144
+
145
+ ##
146
+ # Return an +attribute+ or its +default+ value, removing
147
+ # it from @attributes so that final attributes rendered
148
+ # within the HTML are not cluttered with ad-hoc keys.
149
+
150
+ def attribute attribute, default = nil
151
+ @attributes.delete(attribute) || default
152
+ end
153
+
154
+ end
155
+ end
156
+ end
@@ -0,0 +1,9 @@
1
+
2
+ module DataMapper
3
+ module Form
4
+ module VERSION #:nodoc:
5
+ MAJOR, MINOR, TINY = [0, 0, 2]
6
+ STRING = [MAJOR, MINOR, TINY].join '.'
7
+ end
8
+ end
9
+ end
data/lib/dm-forms.rb ADDED
@@ -0,0 +1,32 @@
1
+ #--
2
+ # Copyright (c) 2008 TJ Holowaychuk
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # "Software"), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ #++
23
+
24
+ $:.unshift File.dirname(__FILE__)
25
+
26
+ require 'rubygems'
27
+ require 'extlib'
28
+ require 'dm-forms/version'
29
+ require 'dm-forms/core_ext'
30
+ require 'dm-forms/tag'
31
+ require 'dm-forms/elements'
32
+ require 'dm-forms/model_elements'
@@ -0,0 +1,56 @@
1
+
2
+
3
+ describe "core ext" do
4
+
5
+ #--
6
+ # Humanize
7
+ #++
8
+
9
+ describe "String#humanize" do
10
+
11
+ it "should humanize strings" do
12
+ 'im_a_Lame_String.Yeah.with.7.words'.humanize.should == 'im a Lame String Yeah with 7 words'
13
+ end
14
+
15
+ it "should humanize symbols" do
16
+ :some_Cool_symbol.humanize.should == 'some Cool symbol'
17
+ :login.humanize.should == 'login'
18
+ end
19
+
20
+ it "should not mess with already human readable strings" do
21
+ 'i am already VERY readable'.humanize.should == 'i am already VERY readable'
22
+ end
23
+
24
+ end
25
+
26
+ #--
27
+ # Indent
28
+ #++
29
+
30
+ describe "String#indent" do
31
+
32
+ it "should indent 2 spaces by default" do
33
+ 'meow'.indent.should == ' meow'
34
+ end
35
+
36
+ it "should indent variable tab lengths" do
37
+ 'meow'.indent(2).should == ' meow'
38
+ end
39
+
40
+ it "should indent multi-line strings" do
41
+ string = <<-EOF.deindent
42
+ foo
43
+ bar
44
+ EOF
45
+ string.indent.should == <<-EOF.deindent
46
+ foo
47
+ bar
48
+ EOF
49
+ string.indent(2).should == <<-EOF.deindent
50
+ foo
51
+ bar
52
+ EOF
53
+ end
54
+
55
+ end
56
+ end
@@ -0,0 +1,209 @@
1
+
2
+ include DataMapper::Form::Elements
3
+
4
+ describe DataMapper::Form::Elements do
5
+
6
+ #--
7
+ # Element aspecs
8
+ #++
9
+
10
+ describe "element aspecs" do
11
+
12
+ it "should allow descriptions" do
13
+ s = textarea :comments, :description => 'Please enter your comments.'
14
+ s.should == <<-HTML.deindent(8)
15
+ <textarea class="form-textarea form-comments" name="comments"></textarea>
16
+ <p class="description">Please enter your comments.</p>
17
+ HTML
18
+ end
19
+
20
+ it "should allow prefixing of arbitrary markup" do
21
+ s = textarea :comments, :before => "<h1>Comments</h1>\n"
22
+ s.should == <<-HTML.deindent(8)
23
+ <h1>Comments</h1>
24
+ <textarea class="form-textarea form-comments" name="comments"></textarea>
25
+ HTML
26
+ end
27
+
28
+ it "should allow arbitrary markup after" do
29
+ s = textarea :comments, :after => "\n<p>Custom markup</p>"
30
+ s.should == <<-HTML.deindent(8)
31
+ <textarea class="form-textarea form-comments" name="comments"></textarea>
32
+ <p>Custom markup</p>
33
+ HTML
34
+ end
35
+
36
+ end
37
+
38
+ #--
39
+ # Elements
40
+ #++
41
+
42
+ describe "elements" do
43
+
44
+ it "should create labels" do
45
+ s = label 'Email', :for => :email
46
+ s.should == %(<label for="email">Email:</label>\n)
47
+ end
48
+
49
+ it "should create required labels" do
50
+ s = label 'Email', :for => :email, :required => true
51
+ s.should == %(<label for="email">Email:<em>*</em></label>\n)
52
+ end
53
+
54
+ it "should create textfields" do
55
+ s = textfield :phone, :value => 'Enter phone number'
56
+ s.should == %(<input type="textfield" class="form-textfield form-phone" value="Enter phone number" name="phone" />\n)
57
+ end
58
+
59
+ it "should create submit buttons" do
60
+ s = submit :op, :value => 'Submit'
61
+ s.should == %(<input type="submit" class="form-submit form-op" value="Submit" name="op" />\n)
62
+ end
63
+
64
+ it "should create buttons" do
65
+ s = button :op, :value => 'Edit'
66
+ s.should == %(<input type="button" class="form-button form-op" value="Edit" name="op" />\n)
67
+ end
68
+
69
+ it "should create image buttons" do
70
+ s = button :op, :value => 'Edit', :src => 'path/to/image.png'
71
+ s.should == %(<input type="image" class="form-image form-op" value="Edit" name="op" src="path/to/image.png" />\n)
72
+ end
73
+
74
+ it "should create a checkbox" do
75
+ s = checkbox :agree_to_terms, :value => 'Agree to terms', :checked => true
76
+ s.should == %(<input type="checkbox" class="form-checkbox form-agree_to_terms" value="Agree to terms" name="agree_to_terms" checked="checked" />\n)
77
+ end
78
+
79
+ it "should create a file field" do
80
+ s = file :image
81
+ s.should == %(<input type="file" class="form-file form-image" name="image" />\n)
82
+ end
83
+
84
+ it "should create hidden fields" do
85
+ s = hidden :_method, :value => 'put'
86
+ s.should == %(<input type="hidden" value="put" name="_method" />\n)
87
+ end
88
+
89
+ it "should create a radio button" do
90
+ s = radio :sex, :value => 'Male'
91
+ s.should == %(<input type="radio" class="form-radio form-sex" value="Male" name="sex" />\n)
92
+ end
93
+
94
+ it "should create textareas" do
95
+ s = textarea :comments, :value => 'Enter your comments here', :label => 'Comments'
96
+ s.should == <<-HTML.deindent(8)
97
+ <label for="comments">Comments:</label>
98
+ <textarea class="form-textarea form-comments" name="comments">Enter your comments here</textarea>
99
+ HTML
100
+ end
101
+
102
+ it "should create fieldsets" do
103
+ s = fieldset :details, :legend => 'Details', :value => 'WAHOO!'
104
+ s.should == <<-HTML.deindent(8)
105
+ <fieldset class="fieldset-details">
106
+ <legend>Details</legend>WAHOO!</fieldset>
107
+ HTML
108
+ end
109
+
110
+ it "should create fieldsets with element capturing block" do
111
+ s = fieldset :details, :id => 'details' do |f|
112
+ f.button :one
113
+ f.button :two
114
+ end
115
+ s.should == <<-HTML.deindent(8)
116
+ <fieldset class="fieldset-details" id="details">
117
+ <legend>Details</legend><input type="button" class="form-button form-one" name="one" />
118
+ <input type="button" class="form-button form-two" name="two" />
119
+ </fieldset>
120
+ HTML
121
+ end
122
+
123
+ it "should create fieldsets without legends, auto-generating them from the fieldset name" do
124
+ s = fieldset :some_legend, :value => 'WAHOO!'
125
+ s.should == <<-HTML.deindent(8)
126
+ <fieldset class="fieldset-some_legend">
127
+ <legend>Some legend</legend>WAHOO!</fieldset>
128
+ HTML
129
+ end
130
+
131
+ it "should create select fields" do
132
+ s = select :color, :options => { :red => 'Red', :green => 'Green', :blue => 'Blue' }
133
+ s.should == <<-HTML.deindent(8)
134
+ <select class="form-select form-color" name="color">
135
+ <option value="blue">Blue</option>
136
+ <option value="red">Red</option>
137
+ <option value="green">Green</option>
138
+ </select>
139
+ HTML
140
+ end
141
+
142
+ it "should create select fields with block" do
143
+ s = select :color do |s|
144
+ s.option :red, 'Red'
145
+ s.option :green, 'Green'
146
+ s.option :blue, 'Blue'
147
+ end
148
+ s.should == <<-HTML.deindent(8)
149
+ <select class="form-select form-color" name="color">
150
+ <option value="red">Red</option>
151
+ <option value="green">Green</option>
152
+ <option value="blue">Blue</option>
153
+ </select>
154
+ HTML
155
+ end
156
+
157
+ end
158
+
159
+ #--
160
+ # Form specifics
161
+ #++
162
+
163
+ describe "forms" do
164
+
165
+ it "should default method to post" do
166
+ s = form :register
167
+ s.should == %(<form method="post" id="form-register"></form>\n)
168
+ end
169
+
170
+ it "should allow custom methods" do
171
+ s = form :register, :method => :get
172
+ s.should == %(<form method="get" id="form-register"></form>\n)
173
+ end
174
+
175
+ it "should allow arbitrary inner html" do
176
+ s = form :register, :method => :get, :value => 'COOKIE!'
177
+ s.should == %(<form method="get" id="form-register">COOKIE!</form>\n)
178
+ end
179
+
180
+ it "should store arbitrary methods in _method" do
181
+ s = form :login, :method => :put do |f|
182
+ f.submit :op, :value => 'Submit'
183
+ end
184
+ s.should == <<-HTML.deindent(8)
185
+ <form method="post" id="form-login"><input type="hidden" value="put" name="_method" />
186
+ <input type="submit" class="form-submit form-op" value="Submit" name="op" />
187
+ </form>
188
+ HTML
189
+ end
190
+
191
+ it "should create with a block for inner html" do
192
+ s = form :login do |f|
193
+ f.textfield :name, :label => 'Username'
194
+ f.textfield :pass, :label => 'Password'
195
+ f.submit :op, :value => 'Login'
196
+ end
197
+ s.should == <<-HTML.deindent(8)
198
+ <form method="post" id="form-login"><label for="name">Username:</label>
199
+ <input type="textfield" class="form-textfield form-name" name="name" />
200
+ <label for="pass">Password:</label>
201
+ <input type="textfield" class="form-textfield form-pass" name="pass" />
202
+ <input type="submit" class="form-submit form-op" value="Login" name="op" />
203
+ </form>
204
+ HTML
205
+ end
206
+
207
+ end
208
+
209
+ end
@@ -0,0 +1,49 @@
1
+
2
+ describe DataMapper::Form::Tag do
3
+
4
+ Tag = DataMapper::Form::Tag
5
+
6
+ it "should generate classes" do
7
+ tag = Tag.new :input, :attributes => { :name => 'email', :type => :textfield }
8
+ tag.send(:classes).should == 'form-textfield form-email'
9
+ end
10
+
11
+ it "should generate classes when no name is present" do
12
+ tag = Tag.new :input, :attributes => { :type => :textfield }
13
+ tag.send(:classes).should == 'form-textfield'
14
+ end
15
+
16
+ it "should auto-assign boolean attributes" do
17
+ tag = Tag.new :input, :attributes => { :name => 'email', :type => :textfield, :disabled => true }
18
+ tag.render.should == %(<input disabled="disabled" type="textfield" class="form-textfield form-email" name="email"></input>\n)
19
+ end
20
+
21
+ it "should generate and merge classes" do
22
+ tag = Tag.new :input, :attributes => { :class => 'foo bar', :name => 'email', :type => :textfield }
23
+ tag.send(:classes).should == 'foo bar form-textfield form-email'
24
+ end
25
+
26
+ it "should grab inner html for non self-closing tags" do
27
+ tag = Tag.new :textarea, :self_closing => false, :attributes => { :value => 'BOOYAH' }
28
+ tag.inner_html.should == 'BOOYAH'
29
+ end
30
+
31
+ it "should allow descriptions and remove from attributes" do
32
+ tag = Tag.new :input, :attributes => { :description => 'testing one two three' }
33
+ tag.description.should == %(\n<p class="description">testing one two three</p>)
34
+ tag.attributes.has_key?(:description).should be_false
35
+ end
36
+
37
+ it "should allow capturing of elements via block" do
38
+ tag = Tag.new :fieldset, :attributes => { :id => 'something' } do |f|
39
+ f.button :one
40
+ f.button :two
41
+ end
42
+ tag.render.should == <<-HTML.deindent
43
+ <fieldset id="something"><input type="button" class="form-button form-one" name="one" />
44
+ <input type="button" class="form-button form-two" name="two" />
45
+ </fieldset>
46
+ HTML
47
+ end
48
+
49
+ end
@@ -0,0 +1,70 @@
1
+
2
+ require 'dm-core'
3
+ require 'dm-validations'
4
+
5
+ include DataMapper::Form::ModelElements
6
+
7
+ DataMapper.setup :default, 'sqlite3::memory:'
8
+
9
+ class User
10
+ include DataMapper::Resource
11
+ property :id, Serial
12
+ property :name, String, :format => /^[\w]+$/
13
+ property :email, String, :format => :email_address
14
+ end
15
+
16
+ DataMapper.auto_migrate!
17
+
18
+ describe DataMapper::Form::ModelElements do
19
+
20
+ before :each do
21
+ @user = User.new :name => 'invalid username', :email => 'is_valid@email.com'
22
+ end
23
+
24
+ it "should create a list of errors when a model is invalid" do
25
+ errors_for(@user).should == <<-HTML.deindent
26
+ <ul class="messages error">
27
+ <li>Name has an invalid format</li>
28
+ </ul>
29
+ HTML
30
+ @user.email = 'invalid email@ something.com'
31
+ errors_for(@user).should == <<-HTML.deindent
32
+ <ul class="messages error">
33
+ <li>Name has an invalid format</li>
34
+ <li>Email has an invalid format</li>
35
+ </ul>
36
+ HTML
37
+ end
38
+
39
+ it "should create model specific forms with errors on elements" do
40
+ results = form_for @user do |f|
41
+ f.textfield :name
42
+ f.textfield :email
43
+ f.submit :op, :value => 'Save'
44
+ end
45
+ results.should == <<-HTML.deindent
46
+ <form method="post" id="form-user"><input type="textfield" class="error form-textfield form-name" name="name" />
47
+ <input type="textfield" class="form-textfield form-email" name="email" />
48
+ <input type="submit" class="form-submit form-op" value="Save" name="op" />
49
+ </form>
50
+ HTML
51
+ end
52
+
53
+ it "should use _method put for updating records" do
54
+ @user.name = 'tj'
55
+ @user.save
56
+ results = form_for @user, :action => '/register' do |f|
57
+ f.textfield :name
58
+ f.textfield :email
59
+ f.submit :op, :value => 'Save'
60
+ end
61
+ results.should == <<-HTML.deindent
62
+ <form method="post" action="/register" id="form-user"><input type="hidden" value="put" name="_method" />
63
+ <input type="textfield" class="form-textfield form-name" name="name" />
64
+ <input type="textfield" class="form-textfield form-email" name="email" />
65
+ <input type="submit" class="form-submit form-op" value="Save" name="op" />
66
+ </form>
67
+ HTML
68
+ end
69
+
70
+ end
@@ -0,0 +1,29 @@
1
+
2
+ require 'rubygems'
3
+ require 'haml'
4
+
5
+ include DataMapper::Form::Elements
6
+
7
+ describe "Haml + dm-forms" do
8
+
9
+ it "should create pretty forms :D" do
10
+ engine = Haml::Engine.new <<-HAML.deindent
11
+ .comments
12
+ %h1 Comments
13
+ = textfield :name
14
+ = textfield :email
15
+ = textarea :comments
16
+ = submit :op, :value => 'Submit'
17
+ HAML
18
+ engine.render.should == <<-HTML.deindent
19
+ <div class='comments'>
20
+ <h1>Comments</h1>
21
+ <input type="textfield" class="form-textfield form-name" name="name" />
22
+ <input type="textfield" class="form-textfield form-email" name="email" />
23
+ <textarea class="form-textarea form-comments" name="comments"></textarea>
24
+ <input type="submit" class="form-submit form-op" value="Submit" name="op" />
25
+ </div>
26
+ HTML
27
+ end
28
+
29
+ end
@@ -0,0 +1,10 @@
1
+
2
+ require 'dm-forms'
3
+ require 'rspec_hpricot_matchers'
4
+ include RspecHpricotMatchers
5
+
6
+ class String
7
+ def deindent spaces = 6
8
+ gsub /^ {0,#{spaces}}/, ''
9
+ end
10
+ end
@@ -0,0 +1,5 @@
1
+
2
+ desc 'Run benchmark suite'
3
+ task :benchmark do
4
+ sh 'ruby examples/benchmarks.rb'
5
+ end
data/tasks/docs.rake ADDED
@@ -0,0 +1,13 @@
1
+
2
+ namespace :docs do
3
+
4
+ desc 'Remove rdoc products'
5
+ task :remove => [:clobber_docs]
6
+
7
+ desc 'Build docs, and open in browser for viewing (specify BROWSER)'
8
+ task :open do
9
+ browser = ENV["BROWSER"] || "safari"
10
+ sh "open -a #{browser} doc/index.html"
11
+ end
12
+
13
+ end
@@ -0,0 +1,3 @@
1
+
2
+ desc 'Build gemspec file'
3
+ task :gemspec => [:build_gemspec]
data/tasks/spec.rake ADDED
@@ -0,0 +1,25 @@
1
+
2
+ require 'spec/rake/spectask'
3
+
4
+ desc "Run all specifications"
5
+ Spec::Rake::SpecTask.new(:spec) do |t|
6
+ t.libs << "lib"
7
+ t.spec_opts = ["--color", "--require", "spec/spec_helper.rb"]
8
+ end
9
+
10
+ namespace :spec do
11
+
12
+ desc "Run all specifications verbosely"
13
+ Spec::Rake::SpecTask.new(:verbose) do |t|
14
+ t.libs << "lib"
15
+ t.spec_opts = ["--color", "--format", "specdoc", "--require", "spec/spec_helper.rb"]
16
+ end
17
+
18
+ desc "Run specific specification verbosely (specify SPEC)"
19
+ Spec::Rake::SpecTask.new(:select) do |t|
20
+ t.libs << "lib"
21
+ t.spec_files = [ENV["SPEC"]]
22
+ t.spec_opts = ["--color", "--format", "specdoc", "--require", "spec/spec_helper.rb"]
23
+ end
24
+
25
+ end