erector 0.1.0 → 0.1.25

Sign up to get free protection for your applications and to get access to all the features.
data/README.txt CHANGED
@@ -6,30 +6,30 @@
6
6
 
7
7
  == DESCRIPTION
8
8
 
9
- With Erector, you define views without templates, in natural Ruby code, with all the power of objects, functions, modular
10
- decomposition, etc.
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
- * FIX (list of features or problems)
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 hoe
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 '&<>' # &amp;&lt;&gt; (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&amp;b"></a> (quotes as for text)
105
+ a(:href => raw('&amp;')) # <a href="&amp;"></a>
106
+ text nbsp("Save Doc") # Save&#160;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
+
@@ -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
@@ -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(*args)
40
- helpers.simple_format(*args)
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)
@@ -2,21 +2,21 @@ module Erector
2
2
  class HtmlParts < Array
3
3
  def to_s
4
4
  map do |part|
5
- case part['type']
6
- when 'open'
7
- part['attributes'] ?
8
- "<#{part['tagName']}#{format_attributes(part['attributes'])}>" :
9
- "<#{part['tagName']}>"
10
- when 'close'
11
- "</#{part['tagName']}>"
12
- when 'standalone'
13
- part['attributes'] ?
14
- "<#{part['tagName']}#{format_attributes(part['attributes'])} />" :
15
- "<#{part['tagName']} />"
16
- when 'text'
17
- part['value'].to_s
18
- when 'instruct'
19
- "<?xml#{format_attributes(part['attributes'])}?>"
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
- results << "#{key}=#{value.to_s.inspect}" if value
56
+ stringized << [key.to_s, value]
30
57
  end
31
- results.join ' '
58
+ return stringized.sort{|a, b| b <=> a}
32
59
  end
33
60
  end
34
- end
61
+ end
@@ -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.standalone_tags
5
+ Erector::Widget.full_tags + Erector::Widget.empty_tags
6
6
  end
7
7
 
8
- def standalone_tags
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
- text CGI.escapeHTML(content)
71
+ content.html_escape
61
72
  end
62
73
 
63
74
  def open_tag(tag_name, attributes={})
64
- @doc << {'type' => 'open', 'tagName' => tag_name, 'attributes' => attributes}
75
+ @doc << {:type => :open, :tagName => tag_name, :attributes => attributes}
65
76
  end
66
77
 
67
78
  def text(value)
68
- @doc << {'type' => 'text', 'value' => value}
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(/ /,'&#160;'))
93
+ end
94
+
72
95
  def close_tag(tag_name)
73
- @doc << {'type' => 'close', 'tagName' => tag_name}
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 << {'type' => 'instruct', 'attributes' => attributes}
105
+ @doc << {:type => :instruct, :attributes => attributes}
78
106
  end
79
107
 
80
- def javascript(*args, &blk)
81
- params = args[0] if args[0].is_a?(Hash)
82
- params ||= args[1] if args[1].is_a?(Hash)
83
- unless params
84
- params = {}
85
- args << params
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
- params[:type] = "text/javascript"
88
- script(*args, &blk)
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.to_s
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 __standalone_element__(tag_name, attributes={})
118
- @doc << {'type' => 'standalone', 'tagName' => tag_name, 'attributes' => attributes}
171
+
172
+ def __empty_element__(tag_name, attributes={})
173
+ @doc << {:type => :empty, :tagName => tag_name, :attributes => attributes}
119
174
  end
120
- alias_method :standalone_element, :__standalone_element__
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
- standalone_tags.each do |tag_name|
210
+ empty_tags.each do |tag_name|
152
211
  self.class_eval(
153
212
  "def #{tag_name}(*args, &block)\n" <<
154
- " __standalone_element__('#{tag_name}', *args, &block)\n" <<
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
- h column_def.name
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.each do |object|
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
@@ -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 standalone tags" do
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.standalone_tags
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!" do
13
- it "when passed no arguments; returns an instruct element with version 1 and utf-8" do
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
- end.to_s
17
- html.should include("<?xml")
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 &amp;&lt;&gt;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 &amp;&lt;&gt;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&amp;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=&nbsp;")
166
+ end.to_s.should == "<a href=\"foo?x=&nbsp;\"></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(&quot;foo&quot;)\"></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&amp;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 "#standalone_element" do
108
- it "when receiving attributes, renders a standalone_element with the attributes" do
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
- standalone_element 'input', :name => 'foo[bar]'
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 a standalone_element without attributes" do
198
+ it "when not receiving attributes, renders an empty element without attributes" do
115
199
  Erector::Widget.new do
116
- standalone_element 'br'
200
+ empty_element 'br'
117
201
  end.to_s.should == '<br />'
118
202
  end
119
203
 
120
- it "renders the proper standalone tags" do
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 a standalone element. Expected #{expected}, got #{actual}"
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&#160;&#160;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>&amp;&lt;&gt;&#160;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=\"&amp;&lt;&gt;&#160;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 == "&amp;"
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 a script text/javascript element" do
138
- body = Erector::Widget.new do
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
- text 'alert("hello");'
261
+ rawtext 'if (x < y && x > z) alert("don\'t stop");'
141
262
  end
142
- end.to_s
143
- doc = Hpricot(body)
144
- script_tag = doc.at("script")
145
- script_tag[:type].should == "text/javascript"
146
- script_tag.inner_html.should include('alert("hello");')
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
- before do
22
- view_cache do
23
- widget = CustomHeadingTable.new(
24
- nil,
25
- :row_objects => []
26
- )
27
- widget.to_s
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
- describe Erector::Widgets::Table, "with custom heading" do
37
- before do
38
- view_cache do
39
- widget = CustomHeadingTable.new(
40
- nil,
41
- :row_objects => []
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
- describe Erector::Widgets::Table, "with custom cell content" do
54
- before do
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
- it "renders custom cell html" do
66
- table = doc.at("table")
67
- row = table.search("tr")[1]
68
- row.at("td").inner_html.should == "<span>Hello</span>"
69
- end
70
- end
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
- describe Erector::Widgets::Table, "with default heading and cell definitions" do
73
- before do
74
- @object1 = Struct.new(:column_a, :column_b, :column_c).new(1, 2, 3)
75
- @object2 = Struct.new(:column_a, :column_b, :column_c).new(4, 5, 6)
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
- it "renders column titles" do
87
- title_row = @table.at("tr")
88
- titles = title_row.search("th").collect {|heading| heading.inner_html}
89
- titles.should == [ "Column A", "Column B", "Column C" ]
90
- end
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
- it "renders data" do
93
- data_rows = @table.search("tr")[1..-1]
94
- cell_values = data_rows.collect do |row|
95
- row.search("td").collect {|col| col.inner_html}
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
- cell_values.should == [
99
- ['1', '2', '3'],
100
- ['4', '5', '6']
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.0
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-13 00:00:00 -08:00
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: With Erector, you define views without templates, in natural Ruby code, with all the power of objects, functions, modular decomposition, etc.
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: With Erector, you define views without templates, in natural Ruby code, with all the power of objects, functions, modular decomposition, etc.
81
+ summary: Erector is a Builder-based view framework, inspired by Markaby but overcoming some of its flaws
82
82
  test_files: []
83
83