pivotal-erector 0.5.1

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.
Files changed (42) hide show
  1. data/README.txt +81 -0
  2. data/VERSION.yml +4 -0
  3. data/bin/erect +7 -0
  4. data/lib/erector/erect.rb +148 -0
  5. data/lib/erector/erected.rb +63 -0
  6. data/lib/erector/extensions/object.rb +18 -0
  7. data/lib/erector/indenting.rb +36 -0
  8. data/lib/erector/rails/extensions/action_controller/1.2.5/action_controller.rb +17 -0
  9. data/lib/erector/rails/extensions/action_controller/2.2.0/action_controller.rb +26 -0
  10. data/lib/erector/rails/extensions/action_controller.rb +8 -0
  11. data/lib/erector/rails/extensions/action_view.rb +21 -0
  12. data/lib/erector/rails/extensions/widget/1.2.5/widget.rb +18 -0
  13. data/lib/erector/rails/extensions/widget/2.2.0/widget.rb +36 -0
  14. data/lib/erector/rails/extensions/widget.rb +117 -0
  15. data/lib/erector/rails/supported_rails_versions.rb +14 -0
  16. data/lib/erector/rails/template_handlers/1.2.5/action_view_template_handler.rb +32 -0
  17. data/lib/erector/rails/template_handlers/2.0.0/action_view_template_handler.rb +36 -0
  18. data/lib/erector/rails/template_handlers/2.1.0/action_view_template_handler.rb +31 -0
  19. data/lib/erector/rails/template_handlers/2.2.0/action_view_template_handler.rb +46 -0
  20. data/lib/erector/rails/template_handlers/action_view_template_handler.rb +14 -0
  21. data/lib/erector/rails.rb +6 -0
  22. data/lib/erector/raw_string.rb +8 -0
  23. data/lib/erector/rhtml.treetop +156 -0
  24. data/lib/erector/unicode.rb +18185 -0
  25. data/lib/erector/unicode_builder.rb +67 -0
  26. data/lib/erector/version.rb +10 -0
  27. data/lib/erector/widget.rb +510 -0
  28. data/lib/erector/widgets/table.rb +96 -0
  29. data/lib/erector/widgets.rb +2 -0
  30. data/lib/erector.rb +16 -0
  31. data/spec/core_spec_suite.rb +3 -0
  32. data/spec/erect/erect_spec.rb +145 -0
  33. data/spec/erect/erected_spec.rb +80 -0
  34. data/spec/erect/rhtml_parser_spec.rb +318 -0
  35. data/spec/erector/indentation_spec.rb +136 -0
  36. data/spec/erector/unicode_builder_spec.rb +75 -0
  37. data/spec/erector/widget_spec.rb +657 -0
  38. data/spec/erector/widgets/table_spec.rb +99 -0
  39. data/spec/rails_spec_suite.rb +3 -0
  40. data/spec/spec_helper.rb +54 -0
  41. data/spec/spec_suite.rb +45 -0
  42. metadata +118 -0
@@ -0,0 +1,67 @@
1
+ # Note that this class is only used in building erector itself
2
+ # (and even then, only needs to be run when there is a new
3
+ # UnicodeData.txt file from unicode.org).
4
+ class Erector::UnicodeBuilder
5
+
6
+ def initialize(input, output)
7
+ @input = input
8
+ @output = output
9
+ @first = true
10
+ end
11
+
12
+ def generate()
13
+ @output.puts "Erector::CHARACTERS = {"
14
+ process_file
15
+ @output.puts "}"
16
+ end
17
+
18
+ def process_file()
19
+ while !@input.eof
20
+ line = @input.gets.strip
21
+ if (line == "")
22
+ next;
23
+ end
24
+
25
+ process_line(line)
26
+ end
27
+ if (!@first)
28
+ @output.puts
29
+ end
30
+ end
31
+
32
+ def output_line(line)
33
+ if (!@first)
34
+ @output.puts(',')
35
+ end
36
+
37
+ @output.print(line)
38
+
39
+ @first = false
40
+ end
41
+
42
+ def process_line(line)
43
+ fields = line.split(';')
44
+ code_point = fields[0]
45
+ name = fields[1]
46
+ alternate_name = fields[10]
47
+
48
+ if /^</.match(name)
49
+ return ""
50
+ end
51
+
52
+ output name, code_point
53
+ if (!alternate_name.nil? && alternate_name != "")
54
+ output alternate_name, code_point
55
+ end
56
+ end
57
+
58
+ def output(name, code_point)
59
+ output_line " :#{namify(name)} => 0x#{code_point.downcase}"
60
+ end
61
+
62
+ def namify(name)
63
+ name.downcase.gsub(/[- ]/, '_')
64
+ end
65
+
66
+ end
67
+
@@ -0,0 +1,10 @@
1
+ ##
2
+ # Erector view framework
3
+ module Erector
4
+ if !Erector.const_defined?(:VERSION)
5
+ dir = File.dirname(__FILE__)
6
+ version = YAML.load_file(File.expand_path("#{dir}/../../VERSION.yml"))
7
+ VERSION = "#{version['major']}.#{version['minor']}.#{version['patch']}"
8
+ end
9
+ end
10
+
@@ -0,0 +1,510 @@
1
+ module Erector
2
+
3
+ # A Widget is the center of the Erector universe.
4
+ #
5
+ # To create a widget, extend Erector::Widget and implement
6
+ # the +render+ method. Inside this method you may call any of the tag methods like +span+ or +p+ to emit HTML/XML
7
+ # tags.
8
+ #
9
+ # You can also define a widget on the fly by passing a block to +new+. This block will get executed when the widget's
10
+ # +render+ method is called.
11
+ #
12
+ # To render a widget from the outside, instantiate it and call its +to_s+ method.
13
+ #
14
+ # To call one widget from another, inside the parent widget's render method, instantiate the child widget and call
15
+ # its +render_to+ method, passing in +self+ (or self.output if you prefer). This assures that the same output
16
+ # is used, which gives better performance than using +capture+ or +to_s+.
17
+ #
18
+ # In this documentation we've tried to keep the distinction clear between methods that *emit* text and those that
19
+ # *return* text. "Emit" means that it writes to the output stream; "return" means that it returns a string
20
+ # like a normal method and leaves it up to the caller to emit that string if it wants.
21
+ class Widget
22
+ class << self
23
+ def all_tags
24
+ Erector::Widget.full_tags + Erector::Widget.empty_tags
25
+ end
26
+
27
+ # tags which are always self-closing
28
+ def empty_tags
29
+ ['area', 'base', 'br', 'col', 'frame',
30
+ 'hr', 'img', 'input', 'link', 'meta']
31
+ end
32
+
33
+ # tags which can contain other stuff
34
+ def full_tags
35
+ [
36
+ 'a', 'abbr', 'acronym', 'address',
37
+ 'b', 'bdo', 'big', 'blockquote', 'body', 'button',
38
+ 'caption', 'center', 'cite', 'code', 'colgroup',
39
+ 'dd', 'del', 'dfn', 'div', 'dl', 'dt', 'em',
40
+ 'fieldset', 'form', 'frameset',
41
+ 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'head', 'html', 'i',
42
+ 'iframe', 'ins', 'kbd', 'label', 'legend', 'li', 'map',
43
+ 'noframes', 'noscript',
44
+ 'object', 'ol', 'optgroup', 'option', 'p', 'param', 'pre',
45
+ 'q', 's',
46
+ 'samp', 'script', 'select', 'small', 'span', 'strike',
47
+ 'strong', 'style', 'sub', 'sup',
48
+ 'table', 'tbody', 'td', 'textarea', 'tfoot',
49
+ 'th', 'thead', 'title', 'tr', 'tt', 'u', 'ul', 'var'
50
+ ]
51
+ end
52
+
53
+ def after_initialize(instance=nil, &blk)
54
+ if blk
55
+ after_initialize_parts << blk
56
+ elsif instance
57
+ if superclass.respond_to?(:after_initialize)
58
+ superclass.after_initialize instance
59
+ end
60
+ after_initialize_parts.each do |part|
61
+ instance.instance_eval &part
62
+ end
63
+ else
64
+ raise ArgumentError, "You must provide either an instance or a block"
65
+ end
66
+ end
67
+
68
+ protected
69
+ def after_initialize_parts
70
+ @after_initialize_parts ||= []
71
+ end
72
+ end
73
+
74
+ @@prettyprint_default = false
75
+ def prettyprint_default
76
+ @@prettyprint_default
77
+ end
78
+
79
+ def self.prettyprint_default=(enabled)
80
+ @@prettyprint_default = enabled
81
+ end
82
+
83
+ NON_NEWLINEY = {'i' => true, 'b' => true, 'small' => true,
84
+ 'img' => true, 'span' => true, 'a' => true,
85
+ 'input' => true, 'textarea' => true, 'button' => true, 'select' => true
86
+ }
87
+
88
+ SPACES_PER_INDENT = 2
89
+
90
+ attr_reader :helpers, :assigns, :block, :parent, :output
91
+ attr_accessor :enable_prettyprint
92
+
93
+ def initialize(helpers=nil, assigns={}, output = "", &block)
94
+ @assigns = assigns
95
+ assign_locals(assigns)
96
+ @helpers = helpers
97
+ @parent = block ? eval("self", block.binding) : nil
98
+ @output = output
99
+ @block = block
100
+ @at_start_of_line = true
101
+ @indent = 0
102
+ @enable_prettyprint = prettyprint_default
103
+ self.class.after_initialize self
104
+ end
105
+
106
+ #-- methods for other classes to call, left public for ease of testing and documentation
107
+ #++
108
+
109
+ def assign_locals(local_assigns)
110
+ local_assigns.each do |name, value|
111
+ instance_variable_set("@#{name}", value)
112
+ metaclass.module_eval do
113
+ attr_reader name
114
+ end
115
+ end
116
+ end
117
+
118
+ # Set whether Erector should add newlines and indentation in to_s.
119
+ # This is an experimental feature and is subject to change
120
+ # (either in terms of how it is enabled, or in terms of
121
+ # what decisions Erector makes about where to add whitespace).
122
+ # This flag should be set prior to any rendering being done
123
+ # (for example, calls to to_s or to_pretty).
124
+ def enable_prettyprint(enable)
125
+ self.enable_prettyprint = enable
126
+ self
127
+ end
128
+
129
+ # Render (like to_s) but adding newlines and indentation.
130
+ def to_pretty
131
+ enable_prettyprint(true).to_s
132
+ end
133
+
134
+ # Entry point for rendering a widget (and all its children). This method creates a new output string,
135
+ # calls this widget's #render method and returns the string.
136
+ #
137
+ # If it's called again later
138
+ # then it returns the earlier rendered string, which may lead to higher performance, but may have confusing
139
+ # effects if some underlying state has changed. In general we recommend you create a new instance of every
140
+ # widget for each render, unless you know what you're doing.
141
+ def to_s(render_method_name=:render, &blk)
142
+ # The @__to_s variable is used as a cache.
143
+ # If it's useful we should add a test for it. -ac
144
+ return @__to_s if @__to_s
145
+ send(render_method_name, &blk)
146
+ @__to_s = output.to_s
147
+ end
148
+
149
+ alias_method :inspect, :to_s
150
+
151
+ # Template method which must be overridden by all widget subclasses. Inside this method you call the magic
152
+ # #element methods which emit HTML and text to the output string.
153
+ def render
154
+ if @block
155
+ instance_eval(&@block)
156
+ end
157
+ end
158
+
159
+ # To call one widget from another, inside the parent widget's render method, instantiate the child widget and call
160
+ # its +render_to+ method, passing in +self+ (or self.output if you prefer). This assures that the same output string
161
+ # is used, which gives better performance than using +capture+ or +to_s+.
162
+ def render_to(output_or_widget)
163
+ if output_or_widget.is_a?(Widget)
164
+ @parent = output_or_widget
165
+ @output = @parent.output
166
+ else
167
+ @output = output_or_widget
168
+ end
169
+ render
170
+ end
171
+
172
+ # Convenience method for on-the-fly widgets. This is a way of making
173
+ # a sub-widget which still has access to the methods of the parent class.
174
+ # This is an experimental erector feature which may disappear in future
175
+ # versions of erector (see #widget in widget_spec in the Erector tests).
176
+ def widget(widget_class, assigns={}, &block)
177
+ child = widget_class.new(helpers, assigns, output, &block)
178
+ child.render
179
+ end
180
+
181
+ # (Should we make this hidden?)
182
+ def html_escape
183
+ return to_s
184
+ end
185
+
186
+ #-- methods for subclasses to call
187
+ #++
188
+
189
+ # Internal method used to emit an HTML/XML element, including an open tag, attributes (optional, via the default hash),
190
+ # contents (also optional), and close tag.
191
+ #
192
+ # Using the arcane powers of Ruby, there are magic methods that call +element+ for all the standard
193
+ # HTML tags, like +a+, +body+, +p+, and so forth. Look at the source of #full_tags for the full list.
194
+ # Unfortunately, this big mojo confuses rdoc, so we can't see each method in this rdoc page, but trust
195
+ # us, they're there.
196
+ #
197
+ # When calling one of these magic methods, put attributes in the default hash. If there is a string parameter,
198
+ # then it is used as the contents. If there is a block, then it is executed (yielded), and the string parameter is ignored.
199
+ # The block will usually be in the scope of the child widget, which means it has access to all the
200
+ # methods of Widget, which will eventually end up appending text to the +output+ string. See how
201
+ # elegant it is? Not confusing at all if you don't think about it.
202
+ #
203
+ def element(*args, &block)
204
+ __element__(*args, &block)
205
+ end
206
+
207
+ # Internal method used to emit a self-closing HTML/XML element, including a tag name and optional attributes
208
+ # (passed in via the default hash).
209
+ #
210
+ # Using the arcane powers of Ruby, there are magic methods that call +empty_element+ for all the standard
211
+ # HTML tags, like +img+, +br+, and so forth. Look at the source of #empty_tags for the full list.
212
+ # Unfortunately, this big mojo confuses rdoc, so we can't see each method in this rdoc page, but trust
213
+ # us, they're there.
214
+ #
215
+ def empty_element(*args, &block)
216
+ __empty_element__(*args, &block)
217
+ end
218
+
219
+ # Returns an HTML-escaped version of its parameter. Leaves the output string untouched. Note that
220
+ # the #text method automatically HTML-escapes its parameter, so be careful *not* to do something like
221
+ # text(h("2<4")) since that will double-escape the less-than sign (you'll get "2&amp;lt;4" instead of
222
+ # "2&lt;4").
223
+ def h(content)
224
+ content.html_escape
225
+ end
226
+
227
+ # Emits an open tag, comprising '<', tag name, optional attributes, and '>'
228
+ def open_tag(tag_name, attributes={})
229
+ indent_for_open_tag(tag_name)
230
+ @indent += SPACES_PER_INDENT
231
+
232
+ output.concat "<#{tag_name}#{format_attributes(attributes)}>"
233
+ @at_start_of_line = false
234
+ end
235
+
236
+ # Emits text. If a string is passed in, it will be HTML-escaped.
237
+ # If a widget or the result of calling methods such as raw
238
+ # is passed in, the HTML will not be HTML-escaped again.
239
+ # If another kind of object is passed in, the result of calling
240
+ # its to_s method will be treated as a string would be.
241
+ def text(value)
242
+ output.concat(value.html_escape)
243
+ @at_start_of_line = false
244
+ nil
245
+ end
246
+
247
+ # Returns text which will *not* be HTML-escaped.
248
+ def raw(value)
249
+ RawString.new(value.to_s)
250
+ end
251
+
252
+ # Emits text which will *not* be HTML-escaped. Same effect as text(raw(s))
253
+ def rawtext(value)
254
+ text raw(value)
255
+ end
256
+
257
+ # Returns a copy of value with spaces replaced by non-breaking space characters.
258
+ # With no arguments, return a single non-breaking space.
259
+ # The output uses the escaping format '&#160;' since that works
260
+ # in both HTML and XML (as opposed to '&nbsp;' which only works in HTML).
261
+ def nbsp(value = " ")
262
+ raw(value.html_escape.gsub(/ /,'&#160;'))
263
+ end
264
+
265
+ # Return a character given its unicode code point or unicode name.
266
+ def character(code_point_or_name)
267
+ if code_point_or_name.is_a?(Symbol)
268
+ found = Erector::CHARACTERS[code_point_or_name]
269
+ if found.nil?
270
+ raise "Unrecognized character #{code_point_or_name}"
271
+ end
272
+ raw("&#x#{sprintf '%x', found};")
273
+ elsif code_point_or_name.is_a?(Integer)
274
+ raw("&#x#{sprintf '%x', code_point_or_name};")
275
+ else
276
+ raise "Unrecognized argument to character: #{code_point_or_name}"
277
+ end
278
+ end
279
+
280
+ # Emits a close tag, consisting of '<', tag name, and '>'
281
+ def close_tag(tag_name)
282
+ @indent -= SPACES_PER_INDENT
283
+ indent()
284
+
285
+ output.concat("</#{tag_name}>")
286
+
287
+ if newliney(tag_name)
288
+ output.concat "\n"
289
+ @at_start_of_line = true
290
+ end
291
+ end
292
+
293
+ # Emits the result of joining the elements in array with the separator.
294
+ # The array elements and separator can be Erector::Widget objects,
295
+ # which are rendered, or strings, which are quoted and output.
296
+ def join(array, separator)
297
+ first = true
298
+ array.each do |widget_or_text|
299
+ if !first
300
+ text separator
301
+ end
302
+ first = false
303
+ text widget_or_text
304
+ end
305
+ end
306
+
307
+ # Emits an XML instruction, which looks like this: <?xml version=\"1.0\" encoding=\"UTF-8\"?>
308
+ def instruct(attributes={:version => "1.0", :encoding => "UTF-8"})
309
+ output.concat "<?xml#{format_sorted(sort_for_xml_declaration(attributes))}?>"
310
+ end
311
+
312
+ # Creates a whole new output string, executes the block, then converts the output string to a string and
313
+ # emits it as raw text. If at all possible you should avoid this method since it hurts performance,
314
+ # and use #render_to instead.
315
+ def capture(&block)
316
+ begin
317
+ original_output = output
318
+ @output = ""
319
+ yield
320
+ raw(output.to_s)
321
+ ensure
322
+ @output = original_output
323
+ end
324
+ end
325
+
326
+ full_tags.each do |tag_name|
327
+ self.class_eval(
328
+ "def #{tag_name}(*args, &block)\n" <<
329
+ " __element__('#{tag_name}', *args, &block)\n" <<
330
+ "end",
331
+ __FILE__,
332
+ __LINE__ - 4
333
+ )
334
+ end
335
+
336
+ empty_tags.each do |tag_name|
337
+ self.class_eval(
338
+ "def #{tag_name}(*args, &block)\n" <<
339
+ " __empty_element__('#{tag_name}', *args, &block)\n" <<
340
+ "end",
341
+ __FILE__,
342
+ __LINE__ - 4
343
+ )
344
+ end
345
+
346
+ # Emits a javascript block inside a +script+ tag, wrapped in CDATA doohickeys like all the cool JS kids do.
347
+ def javascript(*args, &block)
348
+ if args.length > 2
349
+ raise ArgumentError, "Cannot accept more than two arguments"
350
+ end
351
+ attributes, value = nil, nil
352
+ arg0 = args[0]
353
+ if arg0.is_a?(Hash)
354
+ attributes = arg0
355
+ else
356
+ value = arg0
357
+ arg1 = args[1]
358
+ if arg1.is_a?(Hash)
359
+ attributes = arg1
360
+ end
361
+ end
362
+ attributes ||= {}
363
+ attributes[:type] = "text/javascript"
364
+ open_tag 'script', attributes
365
+
366
+ # Shouldn't this be a "cdata" HtmlPart?
367
+ # (maybe, but the syntax is specific to javascript; it isn't
368
+ # really a generic XML CDATA section. Specifically,
369
+ # ]]> within value is not treated as ending the
370
+ # CDATA section by Firefox2 when parsing text/html,
371
+ # although I guess we could refuse to generate ]]>
372
+ # there, for the benefit of XML/XHTML parsers).
373
+ rawtext "\n// <![CDATA[\n"
374
+ if block
375
+ instance_eval(&block)
376
+ else
377
+ rawtext value
378
+ end
379
+ rawtext "\n// ]]>\n"
380
+
381
+ close_tag 'script'
382
+ rawtext "\n"
383
+ end
384
+
385
+ # Convenience method to emit a css file link, which looks like this: <link href="erector.css" rel="stylesheet" type="text/css" />
386
+ # The parameter is the full contents of the href attribute, including any ".css" extension.
387
+ #
388
+ # If you want to emit raw CSS inline, use the #script method instead.
389
+ def css(href)
390
+ link :rel => 'stylesheet', :type => 'text/css', :href => href
391
+ end
392
+
393
+ # 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>
394
+ def url(href)
395
+ a href, :href => href
396
+ end
397
+
398
+ def newliney(tag_name)
399
+ if @enable_prettyprint
400
+ !NON_NEWLINEY.include?(tag_name)
401
+ else
402
+ false
403
+ end
404
+ end
405
+
406
+ ### internal utility methods
407
+
408
+ protected
409
+
410
+ # This is part of the sub-widget/parent feature (see #widget method).
411
+ def method_missing(name, *args, &block)
412
+ block ||= lambda {} # captures self HERE
413
+ if @parent
414
+ @parent.send(name, *args, &block)
415
+ else
416
+ super
417
+ end
418
+ end
419
+
420
+ def __element__(tag_name, *args, &block)
421
+ if args.length > 2
422
+ raise ArgumentError, "Cannot accept more than three arguments"
423
+ end
424
+ attributes, value = nil, nil
425
+ arg0 = args[0]
426
+ if arg0.is_a?(Hash)
427
+ attributes = arg0
428
+ else
429
+ value = arg0
430
+ arg1 = args[1]
431
+ if arg1.is_a?(Hash)
432
+ attributes = arg1
433
+ end
434
+ end
435
+ attributes ||= {}
436
+ open_tag tag_name, attributes
437
+ if block
438
+ instance_eval(&block)
439
+ else
440
+ text value
441
+ end
442
+ close_tag tag_name
443
+ end
444
+
445
+ def __empty_element__(tag_name, attributes={})
446
+ indent_for_open_tag(tag_name)
447
+
448
+ output.concat "<#{tag_name}#{format_attributes(attributes)} />"
449
+
450
+ if newliney(tag_name)
451
+ output.concat "\n"
452
+ @at_start_of_line = true
453
+ end
454
+ end
455
+
456
+ def indent_for_open_tag(tag_name)
457
+ if !@at_start_of_line && newliney(tag_name)
458
+ output.concat "\n"
459
+ @at_start_of_line = true
460
+ end
461
+
462
+ indent()
463
+ end
464
+
465
+ def indent()
466
+ if @at_start_of_line
467
+ output.concat " " * @indent
468
+ end
469
+ end
470
+
471
+ def format_attributes(attributes)
472
+ if !attributes || attributes.empty?
473
+ ""
474
+ else
475
+ format_sorted(sorted(attributes))
476
+ end
477
+ end
478
+
479
+ def format_sorted(sorted)
480
+ results = ['']
481
+ sorted.each do |key, value|
482
+ if value
483
+ if value.is_a?(Array)
484
+ value = [value].flatten.join(' ')
485
+ end
486
+ results << "#{key}=\"#{value.html_escape}\""
487
+ end
488
+ end
489
+ return results.join(' ')
490
+ end
491
+
492
+ def sorted(attributes)
493
+ stringized = []
494
+ attributes.each do |key, value|
495
+ stringized << [key.to_s, value]
496
+ end
497
+ return stringized.sort
498
+ end
499
+
500
+ def sort_for_xml_declaration(attributes)
501
+ # correct order is "version, encoding, standalone" (XML 1.0 section 2.8).
502
+ # But we only try to put version before encoding for now.
503
+ stringized = []
504
+ attributes.each do |key, value|
505
+ stringized << [key.to_s, value]
506
+ end
507
+ return stringized.sort{|a, b| b <=> a}
508
+ end
509
+ end
510
+ end
@@ -0,0 +1,96 @@
1
+ module Erector
2
+ module Widgets #:nodoc:
3
+ # The Table widget provides the ability to render a table from a
4
+ # list of objects (one for each row).
5
+ #
6
+ # Because the default for the column titles utilizes the ActiveSupport
7
+ # Inflector#titleize method, this widget requires active_support to be loaded.
8
+ #
9
+ # class UsersTable < Erector::Widgets::Table
10
+ # column :first_name
11
+ # column :last_name
12
+ # column :email
13
+ # row_classes :even, :odd
14
+ # end
15
+ #
16
+ # render_widget UsersTable, :row_objects => [user_1, user_2, user_3]
17
+ class Table < Erector::Widget
18
+ ColumnDefinition = Struct.new(:id, :name, :cell_proc)
19
+ class << self
20
+ # Define a column, optionally specifying the name (the heading
21
+ # that the user sees) and a block which renders the cell given
22
+ # a row object. If the block is not specified, the cell contains
23
+ # the result of calling a method whose name is id.
24
+ #
25
+ # The name can be a string or a proc.
26
+ def column(id, name=id.to_s.titleize, &cell_proc)
27
+ cell_proc ||= proc {|object| text object.__send__(id)}
28
+ column_definitions << ColumnDefinition.new(id, name, cell_proc)
29
+ end
30
+
31
+ def column_definitions #:nodoc:
32
+ @column_definitions ||= []
33
+ end
34
+
35
+ # A list of HTML classes to apply to the rows in turn. After the
36
+ # list is exhausted, start again at the start. The most
37
+ # common use for this is to specify one class for odd rows
38
+ # and a different class for even rows.
39
+ def row_classes(*row_classes)
40
+ @row_class_list = row_classes
41
+ end
42
+ attr_reader :row_class_list
43
+ end
44
+
45
+ # The standard erector render method.
46
+ def render
47
+ table do
48
+ thead do
49
+ tr do
50
+ column_definitions.each do |column_def|
51
+ th do
52
+ if column_def.name.is_a?(Proc)
53
+ self.instance_exec(column_def.id, &column_def.name)
54
+ else
55
+ text column_def.name
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
61
+ tbody do
62
+ @row_objects.each_with_index do |object, index|
63
+ row object, index
64
+ end
65
+ end
66
+ end
67
+ end
68
+
69
+ protected
70
+ def row(object, index) #:nodoc:
71
+ tr(:class => row_css_class(object, index)) do
72
+ column_definitions.each do |column_def|
73
+ td do
74
+ self.instance_exec(object, &column_def.cell_proc)
75
+ end
76
+ end
77
+ end
78
+ end
79
+
80
+ # You can override this method to provide a class for a row
81
+ # (as an alternative to calling row_classes).
82
+ def row_css_class(object, index)
83
+ cycle(index)
84
+ end
85
+
86
+ def column_definitions #:nodoc:
87
+ self.class.column_definitions
88
+ end
89
+
90
+ def cycle(index) #:nodoc:
91
+ list = self.class.row_class_list
92
+ list ? list[index % list.length] : ''
93
+ end
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,2 @@
1
+ require "#{File.dirname(__FILE__)}/widgets/table"
2
+
data/lib/erector.rb ADDED
@@ -0,0 +1,16 @@
1
+ require "rubygems"
2
+ dir = File.dirname(__FILE__)
3
+ require 'cgi'
4
+ require 'yaml'
5
+ require "active_support/inflector"
6
+ require "active_support/inflections"
7
+ require "#{dir}/erector/extensions/object"
8
+ require "#{dir}/erector/raw_string"
9
+ require "#{dir}/erector/widget"
10
+ require "#{dir}/erector/unicode"
11
+ require "#{dir}/erector/widgets"
12
+ require "#{dir}/erector/version"
13
+ if Object.const_defined?(:RAILS_ROOT)
14
+ require "#{dir}/erector/rails"
15
+ end
16
+