erector 0.1.0 → 0.1.25
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/README.txt +90 -9
- data/lib/erector.rb +6 -0
- data/lib/erector/helpers.rb +15 -8
- data/lib/erector/html_parts.rb +45 -18
- data/lib/erector/widget.rb +96 -25
- data/lib/erector/widgets/table.rb +17 -3
- data/spec/erector/widget_spec.rb +179 -27
- data/spec/erector/widgets/table_spec.rb +72 -68
- metadata +4 -4
data/README.txt
CHANGED
@@ -6,30 +6,30 @@
|
|
6
6
|
|
7
7
|
== DESCRIPTION
|
8
8
|
|
9
|
-
|
10
|
-
decomposition,
|
9
|
+
Erector is a Builder-based view framework, inspired by Markaby but overcoming some of its flaws. In Erector all views are
|
10
|
+
objects, not template files, which allows the full power of OO (inheritance, modular decomposition, encapsulation) in views.
|
11
11
|
|
12
12
|
== FEATURES/PROBLEMS:
|
13
13
|
|
14
|
-
*
|
14
|
+
This is a *prerelease work in progress* and this gem is **NOT READY FOR USE** by anyone who's not on the Erector team yet. We'll be rolling out a
|
15
|
+
version 0.2.0 soon which should include howto documentation and such.
|
15
16
|
|
16
17
|
== SYNOPSIS
|
17
18
|
|
18
|
-
TODO
|
19
|
+
TODO (HOWTO, sample code, etc.)
|
19
20
|
|
20
21
|
== REQUIREMENTS
|
21
22
|
|
22
|
-
* treetop
|
23
23
|
|
24
24
|
== INSTALL
|
25
25
|
|
26
26
|
To install as a gem:
|
27
27
|
|
28
|
-
* sudo gem install
|
28
|
+
* sudo gem install erector
|
29
29
|
|
30
30
|
To install as a plugin:
|
31
31
|
|
32
|
-
*
|
32
|
+
* TODO
|
33
33
|
|
34
34
|
== LICENSE:
|
35
35
|
|
@@ -56,9 +56,57 @@ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
|
56
56
|
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
57
57
|
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
58
58
|
|
59
|
-
== DOCUMENTATION
|
59
|
+
== USER DOCUMENTATION
|
60
60
|
|
61
|
-
TODO
|
61
|
+
TODO (more on how you get started, and call it from rails)
|
62
|
+
|
63
|
+
The basic way to construct some HTML/XML with erector is to pass a
|
64
|
+
block to Erector::Widget.new. For example:
|
65
|
+
|
66
|
+
html = Erector::Widget.new do
|
67
|
+
p "Hello, world!"
|
68
|
+
end
|
69
|
+
html.to_s #=> <p>Hello, world!</p>
|
70
|
+
|
71
|
+
Or, subclass Erector::Widget and implement a render method:
|
72
|
+
|
73
|
+
class Hello < Erector::Widget
|
74
|
+
def render
|
75
|
+
html do
|
76
|
+
head do
|
77
|
+
title "Hello"
|
78
|
+
end
|
79
|
+
body do
|
80
|
+
text "Hello, "
|
81
|
+
b "world!"
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
Hello.new.to_s
|
87
|
+
#=> <html><head><title>Hello</title></head><body>Hello, <b>world!</b></body></html>
|
88
|
+
|
89
|
+
Here are the basics:
|
90
|
+
|
91
|
+
element('foo') # <foo></foo>
|
92
|
+
empty_element('foo') # <foo />
|
93
|
+
html # <html></html> (likewise for other common html tags)
|
94
|
+
b "foo" # <b>foo</b>
|
95
|
+
text 'foo' # foo
|
96
|
+
text '&<>' # &<> (what you generally want, especially
|
97
|
+
# if the text came from the user or a database)
|
98
|
+
text raw('&<>') # &<> (back door for raw html)
|
99
|
+
rawtext('&<>') # &<> (alias for text(raw()))
|
100
|
+
html { text foo } # <html>foo</html>
|
101
|
+
html "foo" # <html>foo</html>
|
102
|
+
html foo # <html>bar</html> (if the method foo returns the string "bar")
|
103
|
+
a(:href => 'foo.html') # <a href="foo.html"></a>
|
104
|
+
a(:href => 'q?a&b') # <a href="q?a&b"></a> (quotes as for text)
|
105
|
+
a(:href => raw('&')) # <a href="&"></a>
|
106
|
+
text nbsp("Save Doc") # Save Doc (turns spaces into non-breaking spaces)
|
107
|
+
instruct # <?xml version="1.0" encoding="UTF-8"?>
|
108
|
+
|
109
|
+
TODO: document more obscure features like capture, Table, :class => ['one', 'two']
|
62
110
|
|
63
111
|
=== Layout Inheritance
|
64
112
|
|
@@ -91,3 +139,36 @@ methods in its subclasses. There's one trick you'll need to use this layout for
|
|
91
139
|
Here the abstract layout widget is used in a concrete fashion by the template-based layout. Normally, the `content` method
|
92
140
|
would be implemented by subclassing widgets, but the layout template sets it directly and then calls to_s on the layout widget.
|
93
141
|
This allows the same layout to be shared in a backward compatible way.
|
142
|
+
|
143
|
+
== DEVELOPER NOTES
|
144
|
+
|
145
|
+
* Check out project from rubyforge:
|
146
|
+
|
147
|
+
svn co svn+ssh://developername@rubyforge.org/var/svn/erector/trunk erector
|
148
|
+
|
149
|
+
* Install gems:
|
150
|
+
|
151
|
+
sudo gem install rake rails rspec rubyforge hpricot
|
152
|
+
|
153
|
+
* Run specs:
|
154
|
+
|
155
|
+
rake
|
156
|
+
|
157
|
+
* Check out the available rake tasks:
|
158
|
+
|
159
|
+
rake -T
|
160
|
+
|
161
|
+
|
162
|
+
=== VERSIONING POLICY
|
163
|
+
|
164
|
+
* Versions are of the form major.minor.tiny
|
165
|
+
* Tiny revisions fix bugs or documentation
|
166
|
+
* Minor revisions add API calls, or change behavior
|
167
|
+
* Minor revisions may also remove API calls, but these must be clearly announced in History.txt, with instructions on how to migrate
|
168
|
+
* Major revisions are about marketing more than technical needs. We will stay in major version 0 until we're happy taking the "alpha" label off it. And if we ever do a major overhaul of the API, especially one that breaks backwards compatibility, we will probably want to increment the major version.
|
169
|
+
* We will not be shy about incrementing version numbers -- if we end up going to version 0.943.67 then so be it.
|
170
|
+
* Developers should attempt to add lines in History.txt to reflect their checkins. These should reflect feature-level changes, not just one line per checkin. The top section of History.txt is used as the Release Notes by the "rake publish" task and will appear on the RubyForge file page.
|
171
|
+
* Someone making a release must fill in the version number in History.txt as well as in Rakefile. Note that "rake publish" requires a "VERSION=1.2.3" parameter to confirm you're releasing the version you intend.
|
172
|
+
* As soon as a release is made and published, the publisher should go into History.txt and make a new section. Since we won't yet know what the next version will be called, the new section will be noted by a single "==" at the top of the file.
|
173
|
+
|
174
|
+
|
data/lib/erector.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
require "rubygems"
|
1
2
|
require "action_controller"
|
2
3
|
require "erector/extensions/action_controller"
|
3
4
|
require "erector/extensions/object"
|
@@ -5,3 +6,8 @@ require "erector/helpers"
|
|
5
6
|
require "erector/html_parts"
|
6
7
|
require "erector/widget"
|
7
8
|
require "erector/widgets"
|
9
|
+
|
10
|
+
##
|
11
|
+
# Erector view framework
|
12
|
+
module Erector
|
13
|
+
end
|
data/lib/erector/helpers.rb
CHANGED
@@ -1,18 +1,25 @@
|
|
1
1
|
module Erector
|
2
2
|
module Helpers
|
3
3
|
[
|
4
|
-
:link_to,
|
5
4
|
:image_tag,
|
6
5
|
:javascript_include_tag,
|
7
6
|
:stylesheet_link_tag,
|
8
|
-
:link_to_function,
|
9
|
-
:link_to_remote,
|
10
7
|
:sortable_element,
|
11
|
-
:sortable_element_js
|
12
|
-
:mail_to
|
8
|
+
:sortable_element_js
|
13
9
|
].each do |helper_name|
|
14
10
|
define_method helper_name do |*args|
|
15
|
-
text helpers.send(helper_name, *args)
|
11
|
+
text raw(helpers.send(helper_name, *args))
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
[
|
16
|
+
:link_to_function,
|
17
|
+
:link_to,
|
18
|
+
:link_to_remote,
|
19
|
+
:mail_to
|
20
|
+
].each do |link_helper|
|
21
|
+
define_method link_helper do |link_text, *args|
|
22
|
+
text raw(helpers.send(link_helper, h(link_text), *args))
|
16
23
|
end
|
17
24
|
end
|
18
25
|
|
@@ -36,8 +43,8 @@ module Erector
|
|
36
43
|
helpers.cycle(*args)
|
37
44
|
end
|
38
45
|
|
39
|
-
def simple_format(
|
40
|
-
|
46
|
+
def simple_format(string)
|
47
|
+
p raw(string.to_s.html_escape.gsub(/\r\n?/, "\n").gsub(/\n/, "<br/>\n"))
|
41
48
|
end
|
42
49
|
|
43
50
|
def time_ago_in_words(*args)
|
data/lib/erector/html_parts.rb
CHANGED
@@ -2,21 +2,21 @@ module Erector
|
|
2
2
|
class HtmlParts < Array
|
3
3
|
def to_s
|
4
4
|
map do |part|
|
5
|
-
case part[
|
6
|
-
when
|
7
|
-
part[
|
8
|
-
"<#{part[
|
9
|
-
"<#{part[
|
10
|
-
when
|
11
|
-
"</#{part[
|
12
|
-
when
|
13
|
-
part[
|
14
|
-
"<#{part[
|
15
|
-
"<#{part[
|
16
|
-
when
|
17
|
-
part[
|
18
|
-
when
|
19
|
-
"<?xml#{
|
5
|
+
case part[:type].to_sym
|
6
|
+
when :open
|
7
|
+
part[:attributes] ?
|
8
|
+
"<#{part[:tagName]}#{format_attributes(part[:attributes])}>" :
|
9
|
+
"<#{part[:tagName]}>"
|
10
|
+
when :close
|
11
|
+
"</#{part[:tagName]}>"
|
12
|
+
when :empty
|
13
|
+
part[:attributes] ?
|
14
|
+
"<#{part[:tagName]}#{format_attributes(part[:attributes])} />" :
|
15
|
+
"<#{part[:tagName]} />"
|
16
|
+
when :text
|
17
|
+
part[:value].html_escape
|
18
|
+
when :instruct
|
19
|
+
"<?xml#{format_sorted(sort_for_xml_declaration(part[:attributes]))}?>"
|
20
20
|
end
|
21
21
|
end.join
|
22
22
|
end
|
@@ -24,11 +24,38 @@ module Erector
|
|
24
24
|
protected
|
25
25
|
def format_attributes(attributes)
|
26
26
|
return "" if !attributes || attributes.empty?
|
27
|
+
return format_sorted(sorted(attributes))
|
28
|
+
end
|
29
|
+
|
30
|
+
def format_sorted(sorted)
|
27
31
|
results = ['']
|
32
|
+
sorted.each do |key, value|
|
33
|
+
if value
|
34
|
+
if value.is_a?(Array)
|
35
|
+
value = [value].flatten.join(' ')
|
36
|
+
end
|
37
|
+
results << "#{key}=\"#{value.html_escape}\""
|
38
|
+
end
|
39
|
+
end
|
40
|
+
return results.join(' ')
|
41
|
+
end
|
42
|
+
|
43
|
+
def sorted(attributes)
|
44
|
+
stringized = []
|
45
|
+
attributes.each do |key, value|
|
46
|
+
stringized << [key.to_s, value]
|
47
|
+
end
|
48
|
+
return stringized.sort
|
49
|
+
end
|
50
|
+
|
51
|
+
def sort_for_xml_declaration(attributes)
|
52
|
+
# correct order is "version, encoding, standalone" (XML 1.0 section 2.8).
|
53
|
+
# But we only try to put version before encoding for now.
|
54
|
+
stringized = []
|
28
55
|
attributes.each do |key, value|
|
29
|
-
|
56
|
+
stringized << [key.to_s, value]
|
30
57
|
end
|
31
|
-
|
58
|
+
return stringized.sort{|a, b| b <=> a}
|
32
59
|
end
|
33
60
|
end
|
34
|
-
end
|
61
|
+
end
|
data/lib/erector/widget.rb
CHANGED
@@ -2,10 +2,10 @@ module Erector
|
|
2
2
|
class Widget
|
3
3
|
class << self
|
4
4
|
def all_tags
|
5
|
-
Erector::Widget.full_tags + Erector::Widget.
|
5
|
+
Erector::Widget.full_tags + Erector::Widget.empty_tags
|
6
6
|
end
|
7
7
|
|
8
|
-
def
|
8
|
+
def empty_tags
|
9
9
|
['area', 'base', 'br', 'hr', 'img', 'input', 'link', 'meta']
|
10
10
|
end
|
11
11
|
|
@@ -50,6 +50,17 @@ module Erector
|
|
50
50
|
instance_eval(&@block)
|
51
51
|
end
|
52
52
|
end
|
53
|
+
|
54
|
+
def render_to(doc)
|
55
|
+
@doc = doc
|
56
|
+
render
|
57
|
+
end
|
58
|
+
|
59
|
+
def render_for(parent)
|
60
|
+
@parent = parent
|
61
|
+
@doc = parent.doc
|
62
|
+
render
|
63
|
+
end
|
53
64
|
|
54
65
|
def widget(widget_class, assigns={}, &block)
|
55
66
|
child = widget_class.new(helpers, assigns, doc, &block)
|
@@ -57,35 +68,79 @@ module Erector
|
|
57
68
|
end
|
58
69
|
|
59
70
|
def h(content)
|
60
|
-
|
71
|
+
content.html_escape
|
61
72
|
end
|
62
73
|
|
63
74
|
def open_tag(tag_name, attributes={})
|
64
|
-
@doc << {
|
75
|
+
@doc << {:type => :open, :tagName => tag_name, :attributes => attributes}
|
65
76
|
end
|
66
77
|
|
67
78
|
def text(value)
|
68
|
-
@doc << {
|
79
|
+
@doc << {:type => :text, :value => value}
|
69
80
|
nil
|
70
81
|
end
|
71
82
|
|
83
|
+
def raw(value)
|
84
|
+
RawString.new(value.to_s)
|
85
|
+
end
|
86
|
+
|
87
|
+
def rawtext(value)
|
88
|
+
text raw(value)
|
89
|
+
end
|
90
|
+
|
91
|
+
def nbsp(value)
|
92
|
+
raw(value.html_escape.gsub(/ /,' '))
|
93
|
+
end
|
94
|
+
|
72
95
|
def close_tag(tag_name)
|
73
|
-
@doc << {
|
96
|
+
@doc << {:type => :close, :tagName => tag_name}
|
97
|
+
end
|
98
|
+
|
99
|
+
def instruct(attributes={:version => "1.0", :encoding => "UTF-8"})
|
100
|
+
@doc << {:type => :instruct, :attributes => attributes}
|
74
101
|
end
|
75
102
|
|
103
|
+
# Deprecated synonym of instruct
|
76
104
|
def instruct!(attributes={:version => "1.0", :encoding => "UTF-8"})
|
77
|
-
@doc << {
|
105
|
+
@doc << {:type => :instruct, :attributes => attributes}
|
78
106
|
end
|
79
107
|
|
80
|
-
def javascript(*args, &
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
108
|
+
def javascript(*args, &block)
|
109
|
+
if args.length > 2
|
110
|
+
raise ArgumentError, "Cannot accept more than two arguments"
|
111
|
+
end
|
112
|
+
attributes, value = nil, nil
|
113
|
+
arg0 = args[0]
|
114
|
+
if arg0.is_a?(Hash)
|
115
|
+
attributes = arg0
|
116
|
+
else
|
117
|
+
value = arg0
|
118
|
+
arg1 = args[1]
|
119
|
+
if arg1.is_a?(Hash)
|
120
|
+
attributes = arg1
|
121
|
+
end
|
122
|
+
end
|
123
|
+
attributes ||= {}
|
124
|
+
attributes[:type] = "text/javascript"
|
125
|
+
open_tag 'script', attributes
|
126
|
+
|
127
|
+
# Shouldn't this be a "cdata" HtmlPart?
|
128
|
+
# (maybe, but the syntax is specific to javascript; it isn't
|
129
|
+
# really a generic XML CDATA section. Specifically,
|
130
|
+
# ]]> within value is not treated as ending the
|
131
|
+
# CDATA section by Firefox2 when parsing text/html,
|
132
|
+
# although I guess we could refuse to generate ]]>
|
133
|
+
# there, for the benefit of XML/XHTML parsers).
|
134
|
+
rawtext "\n// <![CDATA[\n"
|
135
|
+
if block
|
136
|
+
instance_eval(&block)
|
137
|
+
else
|
138
|
+
rawtext value
|
86
139
|
end
|
87
|
-
|
88
|
-
|
140
|
+
rawtext "\n// ]]>\n"
|
141
|
+
|
142
|
+
close_tag 'script'
|
143
|
+
text "\n"
|
89
144
|
end
|
90
145
|
|
91
146
|
def __element__(tag_name, *args, &block)
|
@@ -97,7 +152,7 @@ module Erector
|
|
97
152
|
if arg0.is_a?(Hash)
|
98
153
|
attributes = arg0
|
99
154
|
else
|
100
|
-
value = arg0
|
155
|
+
value = arg0
|
101
156
|
arg1 = args[1]
|
102
157
|
if arg1.is_a?(Hash)
|
103
158
|
attributes = arg1
|
@@ -113,18 +168,18 @@ module Erector
|
|
113
168
|
close_tag tag_name
|
114
169
|
end
|
115
170
|
alias_method :element, :__element__
|
116
|
-
|
117
|
-
def
|
118
|
-
@doc << {
|
171
|
+
|
172
|
+
def __empty_element__(tag_name, attributes={})
|
173
|
+
@doc << {:type => :empty, :tagName => tag_name, :attributes => attributes}
|
119
174
|
end
|
120
|
-
alias_method :
|
175
|
+
alias_method :empty_element, :__empty_element__
|
121
176
|
|
122
177
|
def capture(&block)
|
123
178
|
begin
|
124
179
|
original_doc = @doc
|
125
180
|
@doc = HtmlParts.new
|
126
181
|
yield
|
127
|
-
@doc.to_s
|
182
|
+
raw(@doc.to_s)
|
128
183
|
ensure
|
129
184
|
@doc = original_doc
|
130
185
|
end
|
@@ -136,6 +191,10 @@ module Erector
|
|
136
191
|
@__to_s = @doc.to_s
|
137
192
|
end
|
138
193
|
|
194
|
+
def html_escape()
|
195
|
+
return to_s()
|
196
|
+
end
|
197
|
+
|
139
198
|
alias_method :inspect, :to_s
|
140
199
|
|
141
200
|
full_tags.each do |tag_name|
|
@@ -148,10 +207,10 @@ module Erector
|
|
148
207
|
)
|
149
208
|
end
|
150
209
|
|
151
|
-
|
210
|
+
empty_tags.each do |tag_name|
|
152
211
|
self.class_eval(
|
153
212
|
"def #{tag_name}(*args, &block)\n" <<
|
154
|
-
"
|
213
|
+
" __empty_element__('#{tag_name}', *args, &block)\n" <<
|
155
214
|
"end",
|
156
215
|
__FILE__,
|
157
216
|
__LINE__ - 4
|
@@ -172,9 +231,21 @@ module Erector
|
|
172
231
|
widget = self
|
173
232
|
@helpers.metaclass.class_eval do
|
174
233
|
define_method :concat do |some_text, binding|
|
175
|
-
widget.text some_text
|
234
|
+
widget.text widget.raw(some_text)
|
176
235
|
end
|
177
236
|
end
|
178
237
|
end
|
179
238
|
end
|
180
|
-
end
|
239
|
+
end
|
240
|
+
|
241
|
+
class RawString < String
|
242
|
+
def html_escape
|
243
|
+
self
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
247
|
+
class Object
|
248
|
+
def html_escape
|
249
|
+
return CGI.escapeHTML(to_s())
|
250
|
+
end
|
251
|
+
end
|
@@ -11,6 +11,11 @@ module Erector
|
|
11
11
|
def column_definitions
|
12
12
|
@column_definitions ||= []
|
13
13
|
end
|
14
|
+
|
15
|
+
def row_classes(*row_classes)
|
16
|
+
@row_class_list = row_classes
|
17
|
+
end
|
18
|
+
attr_reader :row_class_list
|
14
19
|
end
|
15
20
|
|
16
21
|
def render
|
@@ -18,13 +23,17 @@ module Erector
|
|
18
23
|
tr do
|
19
24
|
column_definitions.each do |column_def|
|
20
25
|
th do
|
21
|
-
|
26
|
+
if column_def.name.is_a?(Proc)
|
27
|
+
self.instance_exec(column_def.id, &column_def.name)
|
28
|
+
else
|
29
|
+
text column_def.name
|
22
30
|
end
|
31
|
+
end
|
23
32
|
end
|
24
33
|
end
|
25
34
|
tbody do
|
26
|
-
@row_objects.
|
27
|
-
tr do
|
35
|
+
@row_objects.each_with_index do |object, index|
|
36
|
+
tr(:class => cycle(index)) do
|
28
37
|
column_definitions.each do |column_def|
|
29
38
|
td do
|
30
39
|
self.instance_exec(object, &column_def.cell_proc)
|
@@ -40,6 +49,11 @@ module Erector
|
|
40
49
|
def column_definitions
|
41
50
|
self.class.column_definitions
|
42
51
|
end
|
52
|
+
|
53
|
+
def cycle(index)
|
54
|
+
list = self.class.row_class_list
|
55
|
+
list ? list[index % list.length] : ''
|
56
|
+
end
|
43
57
|
end
|
44
58
|
end
|
45
59
|
end
|
data/spec/erector/widget_spec.rb
CHANGED
@@ -3,21 +3,18 @@ require File.expand_path("#{File.dirname(__FILE__)}/../spec_helper")
|
|
3
3
|
module WidgetSpec
|
4
4
|
describe Erector::Widget do
|
5
5
|
describe ".all_tags" do
|
6
|
-
it "returns set of full and
|
6
|
+
it "returns set of full and empty tags" do
|
7
7
|
Erector::Widget.all_tags.class.should == Array
|
8
|
-
Erector::Widget.all_tags.should == Erector::Widget.full_tags + Erector::Widget.
|
8
|
+
Erector::Widget.all_tags.should == Erector::Widget.full_tags + Erector::Widget.empty_tags
|
9
9
|
end
|
10
10
|
end
|
11
11
|
|
12
|
-
describe "#instruct
|
13
|
-
it "when passed no arguments; returns an
|
12
|
+
describe "#instruct" do
|
13
|
+
it "when passed no arguments; returns an XML declaration with version 1 and utf-8" do
|
14
14
|
html = Erector::Widget.new do
|
15
|
-
instruct
|
16
|
-
|
17
|
-
|
18
|
-
html.should include('encoding="UTF-8"')
|
19
|
-
html.should include('version="1.0')
|
20
|
-
html.should include("?>")
|
15
|
+
instruct
|
16
|
+
# version must precede encoding, per XML 1.0 4th edition (section 2.8)
|
17
|
+
end.to_s.should == "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
|
21
18
|
end
|
22
19
|
end
|
23
20
|
|
@@ -44,6 +41,32 @@ module WidgetSpec
|
|
44
41
|
div[:nil_attribute].should be_nil
|
45
42
|
end
|
46
43
|
|
44
|
+
it "with an array of CSS classes, returns a tag with the classes separated" do
|
45
|
+
Erector::Widget.new do
|
46
|
+
element('div', :class => [:foo, :bar])
|
47
|
+
end.to_s.should == "<div class=\"foo bar\"></div>";
|
48
|
+
end
|
49
|
+
|
50
|
+
it "with an array of CSS classes as strings, returns a tag with the classes separated" do
|
51
|
+
Erector::Widget.new do
|
52
|
+
element('div', :class => ['foo', 'bar'])
|
53
|
+
end.to_s.should == "<div class=\"foo bar\"></div>";
|
54
|
+
end
|
55
|
+
|
56
|
+
it "with a CSS class which is a string, just use that as the attribute value" do
|
57
|
+
Erector::Widget.new do
|
58
|
+
element('div', :class => "foo bar")
|
59
|
+
end.to_s.should == "<div class=\"foo bar\"></div>";
|
60
|
+
end
|
61
|
+
|
62
|
+
it "with many attributes, alphabetize them" do
|
63
|
+
Erector::Widget.new do
|
64
|
+
empty_element('foo', :alpha => "", :betty => "5", :aardvark => "tough",
|
65
|
+
:carol => "", :demon => "", :erector => "", :pi => "3.14", :omicron => "", :zebra => "", :brain => "")
|
66
|
+
end.to_s.should == "<foo aardvark=\"tough\" alpha=\"\" betty=\"5\" brain=\"\" carol=\"\" demon=\"\" " \
|
67
|
+
"erector=\"\" omicron=\"\" pi=\"3.14\" zebra=\"\" />";
|
68
|
+
end
|
69
|
+
|
47
70
|
it "with inner tags; returns nested tags" do
|
48
71
|
widget = Erector::Widget.new do
|
49
72
|
element 'div' do
|
@@ -102,22 +125,83 @@ module WidgetSpec
|
|
102
125
|
end
|
103
126
|
end
|
104
127
|
end
|
128
|
+
|
129
|
+
it "when outputting text; quotes it" do
|
130
|
+
Erector::Widget.new do
|
131
|
+
element 'div', 'test &<>text'
|
132
|
+
end.to_s.should == "<div>test &<>text</div>"
|
133
|
+
end
|
134
|
+
|
135
|
+
it "when outputting text via text; quotes it" do
|
136
|
+
Erector::Widget.new do
|
137
|
+
element 'div' do
|
138
|
+
text "test &<>text"
|
139
|
+
end
|
140
|
+
end.to_s.should == "<div>test &<>text</div>"
|
141
|
+
end
|
142
|
+
|
143
|
+
it "when outputting attribute value; quotes it" do
|
144
|
+
Erector::Widget.new do
|
145
|
+
element 'a', :href => "foo.cgi?a&b"
|
146
|
+
end.to_s.should == "<a href=\"foo.cgi?a&b\"></a>"
|
147
|
+
end
|
148
|
+
|
149
|
+
it "with raw text, does not quote it" do
|
150
|
+
Erector::Widget.new do
|
151
|
+
element 'div' do
|
152
|
+
text raw("<b>bold</b>")
|
153
|
+
end
|
154
|
+
end.to_s.should == "<div><b>bold</b></div>"
|
155
|
+
end
|
156
|
+
|
157
|
+
it "with raw text and no block, does not quote it" do
|
158
|
+
Erector::Widget.new do
|
159
|
+
element 'div', raw("<b>bold</b>")
|
160
|
+
end.to_s.should == "<div><b>bold</b></div>"
|
161
|
+
end
|
162
|
+
|
163
|
+
it "with raw attribute, does not quote it" do
|
164
|
+
Erector::Widget.new do
|
165
|
+
element 'a', :href => raw("foo?x= ")
|
166
|
+
end.to_s.should == "<a href=\"foo?x= \"></a>"
|
167
|
+
end
|
168
|
+
|
169
|
+
it "with quote in attribute, quotes it" do
|
170
|
+
Erector::Widget.new do
|
171
|
+
element 'a', :onload => "alert(\"foo\")"
|
172
|
+
end.to_s.should == "<a onload=\"alert("foo")\"></a>"
|
173
|
+
end
|
174
|
+
|
175
|
+
it "with a non-string, non-raw, calls to_s and quotes" do
|
176
|
+
Erector::Widget.new do
|
177
|
+
element 'a' do
|
178
|
+
text [7, "foo&bar"]
|
179
|
+
end
|
180
|
+
end.to_s.should == "<a>7foo&bar</a>"
|
181
|
+
end
|
182
|
+
|
183
|
+
it "calls to_s" do
|
184
|
+
Erector::Widget.new do
|
185
|
+
img :width=>50
|
186
|
+
end.to_s.should == "<img width=\"50\" />"
|
187
|
+
end
|
188
|
+
|
105
189
|
end
|
106
190
|
|
107
|
-
describe "#
|
108
|
-
it "when receiving attributes, renders
|
191
|
+
describe "#empty_element" do
|
192
|
+
it "when receiving attributes, renders an empty element with the attributes" do
|
109
193
|
Erector::Widget.new do
|
110
|
-
|
194
|
+
empty_element 'input', :name => 'foo[bar]'
|
111
195
|
end.to_s.should == '<input name="foo[bar]" />'
|
112
196
|
end
|
113
197
|
|
114
|
-
it "when not receiving attributes, renders
|
198
|
+
it "when not receiving attributes, renders an empty element without attributes" do
|
115
199
|
Erector::Widget.new do
|
116
|
-
|
200
|
+
empty_element 'br'
|
117
201
|
end.to_s.should == '<br />'
|
118
202
|
end
|
119
203
|
|
120
|
-
it "renders the proper
|
204
|
+
it "renders the proper empty-element tags" do
|
121
205
|
['area', 'base', 'br', 'hr', 'img', 'input', 'link', 'meta'].each do |tag_name|
|
122
206
|
expected = "<#{tag_name} />"
|
123
207
|
actual = Erector::Widget.new do
|
@@ -126,24 +210,63 @@ module WidgetSpec
|
|
126
210
|
begin
|
127
211
|
actual.should == expected
|
128
212
|
rescue Spec::Expectations::ExpectationNotMetError => e
|
129
|
-
puts "Expected #{tag_name} to be
|
213
|
+
puts "Expected #{tag_name} to be an empty-element tag. Expected #{expected}, got #{actual}"
|
130
214
|
raise e
|
131
215
|
end
|
132
216
|
end
|
133
217
|
end
|
134
218
|
end
|
135
219
|
|
220
|
+
describe "nbsp" do
|
221
|
+
it "turns consecutive spaces into consecutive non-breaking spaces" do
|
222
|
+
Erector::Widget.new do
|
223
|
+
text nbsp("a b")
|
224
|
+
end.to_s.should == "a  b"
|
225
|
+
end
|
226
|
+
|
227
|
+
it "works in text context" do
|
228
|
+
Erector::Widget.new do
|
229
|
+
element 'a' do
|
230
|
+
text nbsp("&<> foo")
|
231
|
+
end
|
232
|
+
end.to_s.should == "<a>&<> foo</a>"
|
233
|
+
end
|
234
|
+
|
235
|
+
it "works in attribute value context" do
|
236
|
+
Erector::Widget.new do
|
237
|
+
element 'a', :href => nbsp("&<> foo")
|
238
|
+
end.to_s.should == "<a href=\"&<> foo\"></a>"
|
239
|
+
end
|
240
|
+
|
241
|
+
end
|
242
|
+
|
243
|
+
describe '#h' do
|
244
|
+
before do
|
245
|
+
@widget = Erector::Widget.new
|
246
|
+
end
|
247
|
+
|
248
|
+
it "escapes regular strings" do
|
249
|
+
@widget.h("&").should == "&"
|
250
|
+
end
|
251
|
+
|
252
|
+
it "does not escape raw strings" do
|
253
|
+
@widget.h(@widget.raw("&")).should == "&"
|
254
|
+
end
|
255
|
+
end
|
256
|
+
|
136
257
|
describe "#javascript" do
|
137
|
-
it "when receiving a block; renders the content inside of
|
138
|
-
|
258
|
+
it "when receiving a block; renders the content inside of script text/javascript tags" do
|
259
|
+
Erector::Widget.new do
|
139
260
|
javascript do
|
140
|
-
|
261
|
+
rawtext 'if (x < y && x > z) alert("don\'t stop");'
|
141
262
|
end
|
142
|
-
end.to_s
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
263
|
+
end.to_s.should == <<EXPECTED
|
264
|
+
<script type="text/javascript">
|
265
|
+
// <![CDATA[
|
266
|
+
if (x < y && x > z) alert("don't stop");
|
267
|
+
// ]]>
|
268
|
+
</script>
|
269
|
+
EXPECTED
|
147
270
|
end
|
148
271
|
|
149
272
|
it "when receiving a params hash; renders a source file" do
|
@@ -156,13 +279,30 @@ module WidgetSpec
|
|
156
279
|
|
157
280
|
it "when receiving text and a params hash; renders a source file" do
|
158
281
|
html = Erector::Widget.new do
|
159
|
-
javascript('alert("hello");', :src => "/my/js/file.js")
|
282
|
+
javascript('alert("&<>\'hello");', :src => "/my/js/file.js")
|
160
283
|
end.to_s
|
161
284
|
doc = Hpricot(html)
|
162
285
|
script_tag = doc.at('script')
|
163
286
|
script_tag[:src].should == "/my/js/file.js"
|
164
|
-
script_tag.inner_html.should include('alert("hello");')
|
287
|
+
script_tag.inner_html.should include('alert("&<>\'hello");')
|
288
|
+
end
|
289
|
+
|
290
|
+
it "with too many arguments; raises ArgumentError" do
|
291
|
+
proc do
|
292
|
+
Erector::Widget.new do
|
293
|
+
javascript 'foobar', {}, 'fourth'
|
294
|
+
end.to_s
|
295
|
+
end.should raise_error(ArgumentError)
|
165
296
|
end
|
297
|
+
|
298
|
+
it "script method doesn't do any magic" do
|
299
|
+
Erector::Widget.new do
|
300
|
+
script(:type => "text/javascript") do
|
301
|
+
rawtext "if (x < y || x > z) onEnterGetTo('/search?a=b&c=d')"
|
302
|
+
end
|
303
|
+
end.to_s.should == "<script type=\"text/javascript\">if (x < y || x > z) onEnterGetTo('/search?a=b&c=d')</script>"
|
304
|
+
end
|
305
|
+
|
166
306
|
end
|
167
307
|
|
168
308
|
describe '#capture' do
|
@@ -195,6 +335,18 @@ module WidgetSpec
|
|
195
335
|
end
|
196
336
|
end
|
197
337
|
|
338
|
+
describe 'nested' do
|
339
|
+
it "can insert another widget without raw" do
|
340
|
+
inner = Erector::Widget.new do
|
341
|
+
p "foo"
|
342
|
+
end
|
343
|
+
|
344
|
+
outer = Erector::Widget.new do
|
345
|
+
div inner
|
346
|
+
end.to_s.should == '<div><p>foo</p></div>'
|
347
|
+
end
|
348
|
+
end
|
349
|
+
|
198
350
|
describe '#widget' do
|
199
351
|
before do
|
200
352
|
class Parent < Erector::Widget
|
@@ -5,10 +5,12 @@ module TableSpec
|
|
5
5
|
column :column_a
|
6
6
|
column :column_b
|
7
7
|
column :column_c
|
8
|
+
row_classes :even, :odd
|
8
9
|
end
|
9
10
|
|
10
11
|
class CustomHeadingTable < Erector::Widgets::Table
|
11
12
|
column :a, "Column - A"
|
13
|
+
column :b, lambda {|id| span id}
|
12
14
|
end
|
13
15
|
|
14
16
|
class CustomCellTable < Erector::Widgets::Table
|
@@ -17,88 +19,90 @@ module TableSpec
|
|
17
19
|
end
|
18
20
|
end
|
19
21
|
|
20
|
-
describe Erector::Widgets::Table do
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
22
|
+
describe ::Erector::Widgets::Table do
|
23
|
+
describe "with custom heading" do
|
24
|
+
before do
|
25
|
+
view_cache do
|
26
|
+
widget = CustomHeadingTable.new(
|
27
|
+
nil,
|
28
|
+
:row_objects => []
|
29
|
+
)
|
30
|
+
widget.to_s
|
31
|
+
end
|
28
32
|
end
|
29
|
-
end
|
30
|
-
|
31
|
-
it "renders a tbody to be compatible with IE6" do
|
32
|
-
doc.at("tbody").should_not be_nil
|
33
|
-
end
|
34
|
-
end
|
35
33
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
)
|
43
|
-
widget.to_s
|
34
|
+
it "renders a custom heading text and procs" do
|
35
|
+
table = doc.at("table")
|
36
|
+
table.search("th").map {|c| c.inner_html}.should == [
|
37
|
+
"Column - A",
|
38
|
+
"<span>b</span>"
|
39
|
+
]
|
44
40
|
end
|
45
|
-
end
|
46
|
-
|
47
|
-
it "renders a custom heading" do
|
48
|
-
table = doc.at("table")
|
49
|
-
table.at("th").inner_html.should == "Column - A"
|
50
|
-
end
|
51
|
-
end
|
52
41
|
|
53
|
-
|
54
|
-
|
55
|
-
@object1 = Struct.new(:a).new("Hello")
|
56
|
-
view_cache do
|
57
|
-
widget = CustomCellTable.new(
|
58
|
-
nil,
|
59
|
-
:row_objects => [@object1]
|
60
|
-
)
|
61
|
-
widget.to_s
|
42
|
+
it "renders a tbody to be compatible with IE6" do
|
43
|
+
doc.at("tbody").should_not be_nil
|
62
44
|
end
|
63
45
|
end
|
64
46
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
47
|
+
describe "with custom cell content" do
|
48
|
+
before do
|
49
|
+
@object1 = Struct.new(:a).new("Hello")
|
50
|
+
view_cache do
|
51
|
+
widget = CustomCellTable.new(
|
52
|
+
nil,
|
53
|
+
:row_objects => [@object1]
|
54
|
+
)
|
55
|
+
widget.to_s
|
56
|
+
end
|
57
|
+
end
|
71
58
|
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
view_cache do
|
77
|
-
widget = DefaultsTestTable.new(
|
78
|
-
nil,
|
79
|
-
:row_objects => [@object1, @object2]
|
80
|
-
)
|
81
|
-
widget.to_s
|
59
|
+
it "renders custom cell html" do
|
60
|
+
table = doc.at("table")
|
61
|
+
row = table.search("tr")[1]
|
62
|
+
row.at("td").inner_html.should == "<span>Hello</span>"
|
82
63
|
end
|
83
|
-
@table = doc.at("table")
|
84
64
|
end
|
85
65
|
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
66
|
+
describe "with default heading and cell definitions" do
|
67
|
+
before do
|
68
|
+
@object1 = Struct.new(:column_a, :column_b, :column_c).new(1, 2, 3)
|
69
|
+
@object2 = Struct.new(:column_a, :column_b, :column_c).new(4, 5, 6)
|
70
|
+
@object3 = Struct.new(:column_a, :column_b, :column_c).new(7, 8, 9)
|
71
|
+
view_cache do
|
72
|
+
widget = DefaultsTestTable.new(
|
73
|
+
nil,
|
74
|
+
:row_objects => [@object1, @object2, @object3]
|
75
|
+
)
|
76
|
+
widget.to_s
|
77
|
+
end
|
78
|
+
@table = doc.at("table")
|
79
|
+
end
|
91
80
|
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
81
|
+
it "renders column titles" do
|
82
|
+
title_row = @table.at("tr")
|
83
|
+
titles = title_row.search("th").collect {|heading| heading.inner_html}
|
84
|
+
titles.should == [ "Column A", "Column B", "Column C" ]
|
96
85
|
end
|
97
86
|
|
98
|
-
|
99
|
-
[
|
100
|
-
|
101
|
-
|
87
|
+
it "renders data" do
|
88
|
+
data_rows = @table.search("tr")[1..-1]
|
89
|
+
cell_values = data_rows.collect do |row|
|
90
|
+
row.search("td").collect {|col| col.inner_html}
|
91
|
+
end
|
92
|
+
|
93
|
+
cell_values.should == [
|
94
|
+
['1', '2', '3'],
|
95
|
+
['4', '5', '6'],
|
96
|
+
['7', '8', '9'],
|
97
|
+
]
|
98
|
+
end
|
99
|
+
|
100
|
+
it "renders the row classes" do
|
101
|
+
data_rows = @table.search("tr")[1..-1]
|
102
|
+
data_rows[0]['class'].should == 'even'
|
103
|
+
data_rows[1]['class'].should == 'odd'
|
104
|
+
data_rows[2]['class'].should == 'even'
|
105
|
+
end
|
102
106
|
end
|
103
107
|
end
|
104
108
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: erector
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.25
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Pivotal Labs
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2008-02-
|
12
|
+
date: 2008-02-26 00:00:00 -08:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
@@ -21,7 +21,7 @@ dependencies:
|
|
21
21
|
- !ruby/object:Gem::Version
|
22
22
|
version: 1.5.0
|
23
23
|
version:
|
24
|
-
description:
|
24
|
+
description: Erector is a Builder-based view framework, inspired by Markaby but overcoming some of its flaws. In Erector all views are objects, not template files, which allows the full power of OO (inheritance, modular decomposition, encapsulation) in views.
|
25
25
|
email:
|
26
26
|
- alex@pivotallabs.com
|
27
27
|
executables: []
|
@@ -78,6 +78,6 @@ rubyforge_project: erector
|
|
78
78
|
rubygems_version: 1.0.1
|
79
79
|
signing_key:
|
80
80
|
specification_version: 2
|
81
|
-
summary:
|
81
|
+
summary: Erector is a Builder-based view framework, inspired by Markaby but overcoming some of its flaws
|
82
82
|
test_files: []
|
83
83
|
|