form 0.0.0 → 0.0.1.alpha1

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile.lock CHANGED
@@ -1,12 +1,22 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- form (0.0.0)
4
+ form (0.0.1.alpha1)
5
+ i18n
5
6
 
6
7
  GEM
7
8
  remote: http://rubygems.org/
8
9
  specs:
10
+ awesome_print (1.0.2)
11
+ coderay (1.0.5)
9
12
  diff-lcs (1.1.3)
13
+ i18n (0.6.0)
14
+ method_source (0.7.0)
15
+ nokogiri (1.5.0)
16
+ pry (0.9.8.2)
17
+ coderay (~> 1.0.5)
18
+ method_source (~> 0.7)
19
+ slop (>= 2.4.4, < 3)
10
20
  rake (0.9.2.2)
11
21
  rspec (2.8.0)
12
22
  rspec-core (~> 2.8.0)
@@ -16,11 +26,15 @@ GEM
16
26
  rspec-expectations (2.8.0)
17
27
  diff-lcs (~> 1.1.2)
18
28
  rspec-mocks (2.8.0)
29
+ slop (2.4.4)
19
30
 
20
31
  PLATFORMS
21
32
  ruby
22
33
 
23
34
  DEPENDENCIES
35
+ awesome_print
24
36
  form!
37
+ nokogiri (~> 1.5)
38
+ pry
25
39
  rake (~> 0.9)
26
40
  rspec (~> 2.8)
data/README.rdoc CHANGED
@@ -1,6 +1,6 @@
1
1
  = form
2
2
 
3
- A simple form field for Ruby objects.
3
+ A simple form builder for Ruby objects.
4
4
 
5
5
  == Installation
6
6
 
data/examples/hash.rb ADDED
@@ -0,0 +1,27 @@
1
+ $:.unshift File.expand_path("../../lib", __FILE__)
2
+ require "form"
3
+
4
+ params = {
5
+ :name => "John Doe",
6
+ :email => "john@example.org",
7
+ :bio => "Code-addicted.\nThat's all you need to know about me."
8
+ }
9
+
10
+ # The first argument indicates the datasource.
11
+ # In this case, we're using a simple hash.
12
+ # The second argument indicates the root name
13
+ # that will compose the input's name.
14
+ form = Form.new(params, :user)
15
+
16
+ # Just output an input[type=text].
17
+ puts form.text(:name)
18
+
19
+ # Same fashion, just output an input[type=email]
20
+ puts form.email(:email)
21
+
22
+ # More inputs.
23
+ puts form.textarea(:bio)
24
+ puts form.submit(:create)
25
+
26
+ # Output the label for name attribute.
27
+ puts form.label(:name)
@@ -0,0 +1,19 @@
1
+ $:.unshift File.expand_path("../../lib", __FILE__)
2
+ require "form"
3
+ require "ostruct"
4
+
5
+ user = OpenStruct.new({
6
+ :name => "John Doe",
7
+ :email => "john@example.org"
8
+ })
9
+
10
+ # Similar to hash datasource, but using a
11
+ # regular object with attributes instead.
12
+ #
13
+ # Form just doesn't care about datasource's type.
14
+ # Just provide a object that responds to [] method
15
+ # or to the attribute you want.
16
+ form = Form.new(user, :user)
17
+
18
+ puts form.text :name
19
+ puts form.email :email
data/form.gemspec CHANGED
@@ -17,7 +17,10 @@ Gem::Specification.new do |s|
17
17
  s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
18
18
  s.require_paths = ["lib"]
19
19
 
20
- # s.add_dependency "activesupport"
20
+ s.add_dependency "i18n"
21
21
  s.add_development_dependency "rake", "~> 0.9"
22
22
  s.add_development_dependency "rspec", "~> 2.8"
23
+ s.add_development_dependency "nokogiri", "~> 1.5"
24
+ s.add_development_dependency "awesome_print"
25
+ s.add_development_dependency "pry"
23
26
  end
@@ -0,0 +1,130 @@
1
+ module Form
2
+ class Builder
3
+ # The object that will be used to fulfill inputs.
4
+ #
5
+ attr_accessor :data
6
+
7
+ # The input's base name.
8
+ #
9
+ attr_accessor :base_name
10
+
11
+ # Initialize a new form builder.
12
+ #
13
+ def initialize(data = nil, base_name = nil)
14
+ @data = data
15
+ @base_name = base_name
16
+ end
17
+
18
+ # Render a <tt>input[type=text]</tt> input.
19
+ #
20
+ # form.text :name
21
+ #
22
+ def text(name, options = {})
23
+ text_input(name, "text", options)
24
+ end
25
+
26
+ # Render a <tt>input[type=password]</tt> input.
27
+ #
28
+ # form.password :secret
29
+ #
30
+ def password(name, options = {})
31
+ text_input(name, "password", options)
32
+ end
33
+
34
+ # Render a <tt>input[type=file]</tt> input.
35
+ #
36
+ # form.file :avatar
37
+ #
38
+ def file(name, options = {})
39
+ text_input(name, "file", options)
40
+ end
41
+
42
+ # Render a <tt>input[type=hidden]</tt> input.
43
+ #
44
+ # form.hidden :honeypot
45
+ #
46
+ def hidden(name, options = {})
47
+ text_input(name, "hidden", options)
48
+ end
49
+
50
+ # Render a <tt>input[type=number]</tt> input.
51
+ #
52
+ # form.number :quantity
53
+ #
54
+ def number(name, options = {})
55
+ text_input(name, "number", options)
56
+ end
57
+
58
+ # Render a <tt>input[type=email]</tt> input.
59
+ #
60
+ # form.email :email
61
+ #
62
+ def email(name, options = {})
63
+ text_input(name, "email", options)
64
+ end
65
+
66
+ # Render a <tt>input[type=url]</tt> input.
67
+ #
68
+ # form.url :blog
69
+ #
70
+ def url(name, options = {})
71
+ text_input(name, "url", options)
72
+ end
73
+
74
+ # Render a <tt>input[type=search]</tt> input.
75
+ #
76
+ # form.search :query
77
+ #
78
+ def search(name, options = {})
79
+ text_input(name, "search", options)
80
+ end
81
+
82
+ # Render a <tt>input[type=tel]</tt> input.
83
+ #
84
+ # form.phone :work
85
+ #
86
+ def phone(name, options = {})
87
+ text_input(name, "tel", options)
88
+ end
89
+
90
+ # Render a <tt>input[type=submit]</tt> input.
91
+ #
92
+ # form.submit :new
93
+ #
94
+ def submit(label, options = {})
95
+ button_input(label, "submit", options)
96
+ end
97
+
98
+ # Render a <tt>input[type=button]</tt> input.
99
+ #
100
+ # form.button :new
101
+ #
102
+ def button(label, options = {})
103
+ button_input(label, "button", options)
104
+ end
105
+
106
+ # Render a +textarea+ field.
107
+ #
108
+ # form.text_area :content
109
+ #
110
+ def text_area(name, options = {})
111
+ Component::TextArea.new(self, name, options).to_html
112
+ end
113
+ alias_method :textarea, :text_area
114
+
115
+ #
116
+ #
117
+ def label(name, options = {})
118
+ Component::Label.new(self, name, options).to_html
119
+ end
120
+
121
+ private
122
+ def text_input(name, type, options)
123
+ Component::Input.new(self, name, options.merge(type: type)).to_html
124
+ end
125
+
126
+ def button_input(name, type, options)
127
+ Component::Button.new(self, name, options.merge(type: type)).to_html
128
+ end
129
+ end
130
+ end
@@ -0,0 +1,69 @@
1
+ module Form
2
+ module Component
3
+ class Base
4
+ extend Forwardable
5
+
6
+ def_delegators :form, :data
7
+
8
+ # The +Form::Builder+ instance. It will provide the base name
9
+ # and data object.
10
+ #
11
+ attr_accessor :form
12
+
13
+ # The input name. It will be used to compose
14
+ # the full name.
15
+ #
16
+ attr_accessor :name
17
+
18
+ # Hold the input options. It will be used as the input's
19
+ # attributes.
20
+ #
21
+ attr_accessor :options
22
+
23
+ # Initialize the component and set some variables.
24
+ #
25
+ def initialize(form, name, options = {})
26
+ @form = form
27
+ @name = name
28
+ @options = options
29
+ end
30
+
31
+ # Retrieve the value from the form data.
32
+ #
33
+ def value
34
+ if data.respond_to?(name)
35
+ data.public_send(name)
36
+ elsif data.respond_to?(:[])
37
+ data[name.to_sym] || data[name.to_s]
38
+ end
39
+ end
40
+
41
+ # Return a composed name like <tt>user[profile][twitter]</tt>.
42
+ #
43
+ def composed_name
44
+ composed_name = [form.base_name, name].flatten.compact
45
+ "#{composed_name.shift}" + composed_name.map {|n| "[#{n}]"}.join("")
46
+ end
47
+
48
+ # Just pass all arguments to <tt>I18n.t</tt>.
49
+ #
50
+ def t(*args)
51
+ I18n.t(*args)
52
+ end
53
+
54
+ #
55
+ #
56
+ def humanize(name)
57
+ parts = name.to_s.split("_")
58
+ parts.first.gsub!(/\A(.)/) { $1.upcase }
59
+ parts.join(" ")
60
+ end
61
+
62
+ #
63
+ #
64
+ def id_attribute
65
+ [form.base_name, name].flatten.compact.join("-")
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,26 @@
1
+ module Form
2
+ module Component
3
+ class Button < Base
4
+ def attributes
5
+ {
6
+ :value => text,
7
+ :type => :submit
8
+ }.merge(options)
9
+ end
10
+
11
+ def text
12
+ scopes = [
13
+ [:form, :buttons, form.base_name, name].flatten.compact.join(".").to_sym,
14
+ :"form.buttons.#{name}",
15
+ humanize(name)
16
+ ].compact
17
+
18
+ options.fetch :text, t(scopes.shift, default: scopes)
19
+ end
20
+
21
+ def to_html
22
+ Tag.new(:input, attributes).to_s
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,17 @@
1
+ module Form
2
+ module Component
3
+ class Input < Base
4
+ def attributes
5
+ options.merge({
6
+ value: value,
7
+ name: composed_name,
8
+ id: id_attribute
9
+ })
10
+ end
11
+
12
+ def to_html
13
+ Tag.new(:input, attributes).to_s
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,25 @@
1
+ module Form
2
+ module Component
3
+ class Label < Base
4
+ def attributes
5
+ options.except(:text).tap do |attrs|
6
+ attrs[:for] ||= id_attribute
7
+ end
8
+ end
9
+
10
+ def text
11
+ scopes = [
12
+ [:form, :labels, form.base_name, name].flatten.compact.join(".").to_sym,
13
+ :"form.labels.#{name}",
14
+ humanize(name)
15
+ ].compact
16
+
17
+ options.fetch :text, t(scopes.shift, default: scopes)
18
+ end
19
+
20
+ def to_html
21
+ Tag.new(:label, text, attributes).to_s
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,23 @@
1
+ module Form
2
+ module Component
3
+ class TextArea < Base
4
+ def attributes
5
+ defaults.merge(options).merge({
6
+ :name => composed_name,
7
+ :id => id_attribute
8
+ })
9
+ end
10
+
11
+ def defaults
12
+ {
13
+ :cols => 50,
14
+ :rows => 5
15
+ }
16
+ end
17
+
18
+ def to_html
19
+ Tag.new(:textarea, value, attributes).to_s
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,9 @@
1
+ module Form
2
+ module Component
3
+ autoload :Base, "form/component/base"
4
+ autoload :Button, "form/component/button"
5
+ autoload :Input, "form/component/input"
6
+ autoload :Label, "form/component/label"
7
+ autoload :TextArea, "form/component/text_area"
8
+ end
9
+ end
@@ -0,0 +1,10 @@
1
+ class Hash
2
+ # It returns a hash without the specified keys.
3
+ #
4
+ # {:a => 1, "a" => 2, :b => 3}.except(:a, :b)
5
+ # #=> {"a" => 2}
6
+ #
7
+ def except(*keys)
8
+ dup.delete_if {|key, value| keys.include?(key)}
9
+ end
10
+ end
@@ -0,0 +1,3 @@
1
+ en:
2
+ form:
3
+ required: Required
data/lib/form/tag.rb ADDED
@@ -0,0 +1,106 @@
1
+ module Form
2
+ class Tag
3
+ # List which tags don't need to be closed.
4
+ # Did you know that the technical name is "void elements"?
5
+ # http://www.w3.org/TR/html5/syntax.html#void-elements
6
+ #
7
+ VOID_ELEMENTS = [
8
+ :area, :base, :br, :col, :command, :embed,
9
+ :hr, :img, :input, :keygen, :link, :meta,
10
+ :param, :source, :track, :wbr
11
+ ]
12
+
13
+ # The tag name.
14
+ #
15
+ attr_accessor :name
16
+
17
+ # The tag content. It will be ignored in open tags.
18
+ #
19
+ attr_accessor :content
20
+
21
+ # The tag attributes.
22
+ #
23
+ attr_accessor :attributes
24
+
25
+ # Escape the value.
26
+ #
27
+ def self.html_escape(string)
28
+ string.to_s.gsub(/&/, "&amp;").gsub(/\"/, "&quot;").gsub(/>/, "&gt;").gsub(/</, "&lt;")
29
+ end
30
+
31
+ # Create a new tag.
32
+ #
33
+ # Form::Tag.new(:p, "Hello World!", class: "greeting").render
34
+ # #=> <p class="greeting">Hello World!</p>
35
+ #
36
+ def initialize(name, content = "", attributes = {}, &block)
37
+ if content.kind_of?(Hash)
38
+ attributes = content
39
+ content = ""
40
+ end
41
+
42
+ @name = name.to_sym
43
+ @content = content
44
+ @attributes = attributes
45
+
46
+ yield self if block_given?
47
+ end
48
+
49
+ # Detect if this tag omits the closing part.
50
+ #
51
+ def void?
52
+ VOID_ELEMENTS.include?(name)
53
+ end
54
+
55
+ # Append the specified HTML to this tag.
56
+ #
57
+ def <<(element)
58
+ @content << element.to_s
59
+ end
60
+
61
+ # Create nested tags with ease.
62
+ #
63
+ def tag(name, content = "", attributes = {}, &block)
64
+ @content << self.class.new(name, content, attributes, &block).to_s
65
+ end
66
+
67
+ # Build the tag, concating all parts.
68
+ #
69
+ def to_s
70
+ open_tag << (void? ? "" : content.to_s) << close_tag
71
+ end
72
+
73
+ def html_escape(string)
74
+ self.class.html_escape(string)
75
+ end
76
+
77
+ private
78
+ #
79
+ #
80
+ def render_attributes
81
+ attributes.collect do |name, value|
82
+ next unless value && value != ""
83
+ bool?(value) ? " #{name}" : %[ #{name}="#{html_escape(value)}"]
84
+ end.join("")
85
+ end
86
+
87
+ # Check if value is boolean.
88
+ # LolRuby doesn't have a Boolean class or something.
89
+ #
90
+ def bool?(value)
91
+ value.kind_of?(TrueClass) || value.kind_of?(FalseClass)
92
+ end
93
+
94
+ # The opening part of the tag.
95
+ #
96
+ def open_tag
97
+ "<#{name}#{render_attributes}>"
98
+ end
99
+
100
+ # The closing part of the tag.
101
+ #
102
+ def close_tag
103
+ void? ? "" : "</#{name}>"
104
+ end
105
+ end
106
+ end