erector 0.2.83 → 0.3.105

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,4 +1,6 @@
1
1
  module Erector
2
+
3
+ # An array to which is written a stream of HTML "parts" -- each part being an open tag, a string, a close tag, etc.
2
4
  class HtmlParts < Array
3
5
  def to_s
4
6
  map do |part|
@@ -2,7 +2,7 @@ require 'rubygems'
2
2
  require 'treetop'
3
3
 
4
4
  module Erector
5
- class Indenting < Treetop::Runtime::SyntaxNode
5
+ class Indenting < Treetop::Runtime::SyntaxNode #:nodoc:
6
6
  @@indent = 0
7
7
 
8
8
  def set_indent(x)
@@ -0,0 +1,8 @@
1
+ module Erector
2
+ # A string that has a special type so Erector knows to render it directly, not HTML-escaped
3
+ class RawString < String
4
+ def html_escape
5
+ self
6
+ end
7
+ end
8
+ end
@@ -1,20 +1,42 @@
1
1
  require 'cgi'
2
2
 
3
3
  module Erector
4
+
5
+ # A Widget is the center of the Erector universe.
6
+ #
7
+ # To create a widget, extend Erector::Widget and implement
8
+ # the +render+ method. Inside this method you may call any of the tag methods like +span+ or +p+ to emit HTML/XML
9
+ # tags.
10
+ #
11
+ # You can also define a widget on the fly by passing a block to +new+. This block will get executed when the widget's
12
+ # +render+ method is called.
13
+ #
14
+ # To render a widget from the outside, instantiate it and call its +to_s+ method.
15
+ #
16
+ # To call one widget from another, inside the parent widget's render method, instantiate the child widget and call
17
+ # its +render_to+ method, passing in +self+ (or self.doc if you prefer). This assures that the same HtmlParts stream
18
+ # is used, which gives better performance than using +capture+ or +to_s+.
19
+ #
20
+ # In this documentation we've tried to keep the distinction clear between methods that *emit* text and those that
21
+ # *return* text. "Emit" means that it writes HtmlParts to the doc stream; "return" means that it returns a string
22
+ # like a normal method and leaves it up to the caller to emit that string if it wants.
4
23
  class Widget
5
24
  class << self
6
25
  def all_tags
7
26
  Erector::Widget.full_tags + Erector::Widget.empty_tags
8
27
  end
9
28
 
29
+ # tags which are always self-closing
10
30
  def empty_tags
11
31
  ['area', 'base', 'br', 'hr', 'img', 'input', 'link', 'meta']
12
32
  end
13
33
 
34
+ # tags which can contain other stuff
14
35
  def full_tags
15
36
  [
16
37
  'a', 'acronym', 'address', 'b', 'bdo', 'big', 'blockquote', 'body',
17
- 'button', 'caption', 'cite', 'code', 'dd', 'del', 'div', 'dl', 'dt', 'em',
38
+ 'button', 'caption', 'center', 'cite', 'code',
39
+ 'dd', 'del', 'div', 'dl', 'dt', 'em',
18
40
  'fieldset', 'form', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'head', 'html', 'i',
19
41
  'iframe', 'ins', 'kbd', 'label', 'legend', 'li', 'map',
20
42
  'noframes', 'noscript', 'ol', 'optgroup', 'option', 'p', 'param', 'pre',
@@ -22,6 +44,7 @@ module Erector
22
44
  'table', 'tbody', 'td', 'textarea', 'th', 'thead', 'title', 'tr', 'tt', 'u', 'ul', 'var'
23
45
  ]
24
46
  end
47
+
25
48
  end
26
49
 
27
50
  include ActionController::UrlWriter
@@ -32,72 +55,151 @@ module Erector
32
55
  attr_reader :block
33
56
  attr_reader :parent
34
57
 
35
- # Each item in @doc is an array containing three values: type, value, attributes
36
58
  def initialize(helpers=nil, assigns={}, doc = HtmlParts.new, &block)
37
59
  @assigns = assigns
38
- assigns.each do |name, value|
60
+ assign_locals(assigns)
61
+ @helpers = helpers
62
+ @parent = block ? eval("self", block.binding) : nil
63
+ @doc = doc
64
+ @block = block
65
+ end
66
+
67
+ #-- methods for other classes to call, left public for ease of testing and documentation
68
+ #++
69
+
70
+ def assign_locals(local_assigns)
71
+ local_assigns.each do |name, value|
39
72
  instance_variable_set("@#{name}", value)
40
73
  metaclass.module_eval do
41
74
  attr_reader name
42
75
  end
43
76
  end
44
- @helpers = helpers
45
- @parent = block ? eval("self", block.binding) : nil
46
- @doc = doc
47
- @block = block
48
77
  end
49
78
 
79
+ # Entry point for rendering a widget (and all its children). This method creates a new HtmlParts doc stream,
80
+ # calls this widget's #render method, converts the HtmlParts to a string, and returns the string.
81
+ #
82
+ # If it's called again later
83
+ # then it returns the earlier rendered string, which leads to higher performance, but may have confusing
84
+ # effects if some underlying state has changed. In general we recommend you create a new instance of every
85
+ # widget for each render, unless you know what you're doing.
86
+ def to_s(&blk)
87
+ # The @__to_s variable is used as a cache.
88
+ # If it's useful we should add a test for it. -ac
89
+ return @__to_s if @__to_s
90
+ render(&blk)
91
+ @__to_s = @doc.to_s
92
+ end
93
+
94
+ alias_method :inspect, :to_s
95
+
96
+ # Template method which must be overridden by all widget subclasses. Inside this method you call the magic
97
+ # #element methods which emit HTML and text to the HtmlParts stream.
50
98
  def render
51
99
  if @block
52
100
  instance_eval(&@block)
53
101
  end
54
102
  end
55
103
 
56
- def render_to(doc)
57
- @doc = doc
58
- render
59
- end
60
-
61
- def render_for(parent)
62
- @parent = parent
63
- @doc = parent.doc
104
+ # To call one widget from another, inside the parent widget's render method, instantiate the child widget and call
105
+ # its +render_to+ method, passing in +self+ (or self.doc if you prefer). This assures that the same HtmlParts stream
106
+ # is used, which gives better performance than using +capture+ or +to_s+.
107
+ def render_to(doc_or_widget)
108
+ if doc_or_widget.is_a?(Widget)
109
+ @parent = doc_or_widget
110
+ @doc = @parent.doc
111
+ else
112
+ @doc = doc_or_widget
113
+ end
64
114
  render
65
115
  end
66
116
 
117
+ # Convenience method for on-the-fly widgets. (Should we make this hidden? How is it used?)
67
118
  def widget(widget_class, assigns={}, &block)
68
119
  child = widget_class.new(helpers, assigns, doc, &block)
69
120
  child.render
70
121
  end
71
122
 
123
+ # (Should we make this hidden?)
124
+ def html_escape
125
+ return to_s
126
+ end
127
+
128
+ #-- methods for subclasses to call
129
+ #++
130
+
131
+ # Internal method used to emit an HTML/XML element, including an open tag, attributes (optional, via the default hash),
132
+ # contents (also optional), and close tag.
133
+ #
134
+ # Using the arcane powers of Ruby, there are magic methods that call +element+ for all the standard
135
+ # HTML tags, like +a+, +body+, +p+, and so forth. Look at the source of #full_tags for the full list.
136
+ # Unfortunately, this big mojo confuses rdoc, so we can't see each method in this rdoc page, but trust
137
+ # us, they're there.
138
+ #
139
+ # When calling one of these magic methods, put attributes in the default hash. If there is a string parameter,
140
+ # then it is used as the contents. If there is a block, then it is executed (yielded), and the string parameter is ignored.
141
+ # The block will usually be in the scope of the child widget, which means it has access to all the
142
+ # methods of Widget, which will eventually end up appending text to the +doc+ HtmlParts stream. See how
143
+ # elegant it is? Not confusing at all if you don't think about it.
144
+ #
145
+ def element(*args, &block)
146
+ __element__(*args, &block)
147
+ end
148
+
149
+ # Internal method used to emit a self-closing HTML/XML element, including a tag name and optional attributes
150
+ # (passed in via the default hash).
151
+ #
152
+ # Using the arcane powers of Ruby, there are magic methods that call +empty_element+ for all the standard
153
+ # HTML tags, like +img+, +br+, and so forth. Look at the source of #empty_tags for the full list.
154
+ # Unfortunately, this big mojo confuses rdoc, so we can't see each method in this rdoc page, but trust
155
+ # us, they're there.
156
+ #
157
+ def empty_element(*args, &block)
158
+ __empty_element__(*args, &block)
159
+ end
160
+
161
+ # Returns an HTML-escaped version of its parameter. Leaves the HtmlParts stream untouched. Note that
162
+ # the #text method automatically HTML-escapes its parameter, so be careful *not* to do something like
163
+ # text(h("2<4")) since that will double-escape the less-than sign (you'll get "2&amp;lt;4" instead of
164
+ # "2&lt;4").
72
165
  def h(content)
73
166
  content.html_escape
74
167
  end
75
168
 
169
+ # Emits an open tag, comprising '<', tag name, optional attributes, and '>'
76
170
  def open_tag(tag_name, attributes={})
77
171
  @doc << {:type => :open, :tagName => tag_name, :attributes => attributes}
78
172
  end
79
173
 
174
+ # Emits text which will be HTML-escaped.
80
175
  def text(value)
81
176
  @doc << {:type => :text, :value => value}
82
177
  nil
83
178
  end
84
179
 
180
+ # Returns text which will *not* be HTML-escaped.
85
181
  def raw(value)
86
182
  RawString.new(value.to_s)
87
183
  end
88
184
 
185
+ # Returns text which will *not* be HTML-escaped. Same effect as text(raw(s))
89
186
  def rawtext(value)
90
187
  text raw(value)
91
188
  end
92
189
 
190
+ # Returns a copy of value with spaces replaced by non-breaking space characters.
191
+ # The output uses the entity-escaping format '&#160;' since that works
192
+ # in both HTML and XML (as opposed to '&nbsp;' which only works in HTML).
93
193
  def nbsp(value)
94
194
  raw(value.html_escape.gsub(/ /,'&#160;'))
95
195
  end
96
196
 
197
+ # Emits a close tag, consisting of '<', tag name, and '>'
97
198
  def close_tag(tag_name)
98
199
  @doc << {:type => :close, :tagName => tag_name}
99
200
  end
100
201
 
202
+ # Emits an XML instruction, which looks like this: <?xml version=\"1.0\" encoding=\"UTF-8\"?>
101
203
  def instruct(attributes={:version => "1.0", :encoding => "UTF-8"})
102
204
  @doc << {:type => :instruct, :attributes => attributes}
103
205
  end
@@ -107,6 +209,41 @@ module Erector
107
209
  @doc << {:type => :instruct, :attributes => attributes}
108
210
  end
109
211
 
212
+ # Creates a whole new doc stream, executes the block, then converts the doc stream to a string and
213
+ # emits it as raw text. If at all possible you should avoid this method since it hurts performance,
214
+ # and use #render_to instead.
215
+ def capture(&block)
216
+ begin
217
+ original_doc = @doc
218
+ @doc = HtmlParts.new
219
+ yield
220
+ raw(@doc.to_s)
221
+ ensure
222
+ @doc = original_doc
223
+ end
224
+ end
225
+
226
+ full_tags.each do |tag_name|
227
+ self.class_eval(
228
+ "def #{tag_name}(*args, &block)\n" <<
229
+ " __element__('#{tag_name}', *args, &block)\n" <<
230
+ "end",
231
+ __FILE__,
232
+ __LINE__ - 4
233
+ )
234
+ end
235
+
236
+ empty_tags.each do |tag_name|
237
+ self.class_eval(
238
+ "def #{tag_name}(*args, &block)\n" <<
239
+ " __empty_element__('#{tag_name}', *args, &block)\n" <<
240
+ "end",
241
+ __FILE__,
242
+ __LINE__ - 4
243
+ )
244
+ end
245
+
246
+ # Emits a javascript block inside a +script+ tag, wrapped in CDATA doohickeys like all the cool JS kids do.
110
247
  def javascript(*args, &block)
111
248
  if args.length > 2
112
249
  raise ArgumentError, "Cannot accept more than two arguments"
@@ -142,8 +279,79 @@ module Erector
142
279
  rawtext "\n// ]]>\n"
143
280
 
144
281
  close_tag 'script'
145
- text "\n"
282
+ rawtext "\n"
146
283
  end
284
+
285
+ # Convenience method to emit a css file link, which looks like this: <link href="erector.css" rel="stylesheet" type="text/css" />
286
+ # The parameter is the full contents of the href attribute, including any ".css" extension.
287
+ #
288
+ # If you want to emit raw CSS inline, use the #script method instead.
289
+ def css(href)
290
+ link :rel => 'stylesheet', :type => 'text/css', :href => href
291
+ end
292
+
293
+ # Convenience method to emit an anchor tag whose href and text are the same, e.g. <a href="http://example.com">http://example.com</a>
294
+ def url(href)
295
+ a href, :href => href
296
+ end
297
+
298
+ ### internal utility methods
299
+
300
+ protected
301
+
302
+ def method_missing(name, *args, &block)
303
+ block ||= lambda {} # captures self HERE
304
+ if @parent
305
+ @parent.send(name, *args, &block)
306
+ else
307
+ super
308
+ end
309
+ end
310
+
311
+ def fake_erbout(&blk)
312
+ # override concat on the helpers object (which is usually a Rails view object)
313
+ unless @helpers.respond_to?(:concat_without_erector)
314
+ @helpers.metaclass.class_eval do
315
+ # alias_method :capture_without_erector, :capture
316
+ # define_method :capture do |*args|
317
+ # result = nil
318
+ # widget = @erector_widget_stack.first
319
+ # begin
320
+ # original_doc = widget.doc
321
+ # widget.doc = HtmlParts.new
322
+ # capture_without_erector(*args)
323
+ # result = raw(widget.doc.to_s)
324
+ # ensure
325
+ # widget.doc = original_doc
326
+ # end
327
+ # result
328
+ # end
329
+
330
+ alias_method :concat_without_erector, :concat
331
+ define_method :concat do |*args|
332
+ some_text, binding = args
333
+ raise "widget stack too big" if @erector_widget_stack.size > 10
334
+ if @erector_widget_stack.empty?
335
+ concat_without_erector(some_text, binding)
336
+ else
337
+ @erector_widget_stack.first.rawtext(some_text)
338
+ end
339
+ end
340
+ define_method :erector_widget_stack do
341
+ @erector_widget_stack ||= []
342
+ end
343
+ end
344
+ end
345
+
346
+ @helpers.erector_widget_stack.unshift(self)
347
+ yield
348
+ ensure
349
+ if @helpers.respond_to?(:concat_without_erector)
350
+ @helpers.erector_widget_stack.shift
351
+ end
352
+ end
353
+
354
+ private
147
355
 
148
356
  def __element__(tag_name, *args, &block)
149
357
  if args.length > 2
@@ -169,102 +377,10 @@ module Erector
169
377
  end
170
378
  close_tag tag_name
171
379
  end
172
- alias_method :element, :__element__
173
380
 
174
381
  def __empty_element__(tag_name, attributes={})
175
382
  @doc << {:type => :empty, :tagName => tag_name, :attributes => attributes}
176
383
  end
177
- alias_method :empty_element, :__empty_element__
178
-
179
- def capture(&block)
180
- begin
181
- original_doc = @doc
182
- @doc = HtmlParts.new
183
- yield
184
- raw(@doc.to_s)
185
- ensure
186
- @doc = original_doc
187
- end
188
- end
189
-
190
- def to_s(&blk)
191
- return @__to_s if @__to_s
192
- render(&blk)
193
- @__to_s = @doc.to_s
194
- end
195
-
196
- def html_escape
197
- return to_s
198
- end
199
-
200
- alias_method :inspect, :to_s
201
-
202
- full_tags.each do |tag_name|
203
- self.class_eval(
204
- "def #{tag_name}(*args, &block)\n" <<
205
- " __element__('#{tag_name}', *args, &block)\n" <<
206
- "end",
207
- __FILE__,
208
- __LINE__ - 4
209
- )
210
- end
211
-
212
- empty_tags.each do |tag_name|
213
- self.class_eval(
214
- "def #{tag_name}(*args, &block)\n" <<
215
- " __empty_element__('#{tag_name}', *args, &block)\n" <<
216
- "end",
217
- __FILE__,
218
- __LINE__ - 4
219
- )
220
- end
221
-
222
- protected
223
- def method_missing(name, *args, &block)
224
- block ||= lambda {} # captures self HERE
225
- if @parent
226
- @parent.send(name, *args, &block)
227
- else
228
- super
229
- end
230
- end
231
-
232
- def fake_erbout(&blk)
233
- widget = self
234
- @helpers.metaclass.class_eval do
235
- raise "Cannot nest fake_erbout" if instance_methods.include?('concat_without_erector')
236
- alias_method :concat_without_erector, :concat
237
- define_method :concat do |some_text, binding|
238
- widget.rawtext(some_text)
239
- end
240
- end
241
- yield
242
- ensure
243
- @helpers.metaclass.class_eval do
244
- alias_method :concat, :concat_without_erector
245
- remove_method :concat_without_erector
246
- end
247
- end
248
- end
249
- end
250
-
251
- class RawString < String
252
- def html_escape
253
- self
254
- end
255
- end
256
-
257
- class Object
258
- def html_escape
259
- return CGI.escapeHTML(to_s)
260
- end
261
-
262
- def html_unescape
263
- CGI.unescapeHTML(to_s)
264
- end
265
-
266
- # OMGWTF
267
- def escape_single_quotes
268
- self.gsub(/[']/, '\\\\\'')
384
+
269
385
  end
270
386
  end