erector 0.7.2 → 0.8.0

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 (67) hide show
  1. data/README.txt +17 -3
  2. data/VERSION.yml +2 -2
  3. data/bin/erector +1 -1
  4. data/lib/erector.rb +22 -2
  5. data/lib/erector/after_initialize.rb +34 -0
  6. data/lib/erector/caching.rb +93 -0
  7. data/lib/erector/convenience.rb +58 -0
  8. data/lib/erector/dependencies.rb +24 -0
  9. data/lib/erector/dependency.rb +21 -0
  10. data/lib/erector/{erect.rb → erect/erect.rb} +14 -4
  11. data/lib/erector/{erected.rb → erect/erected.rb} +6 -4
  12. data/lib/erector/{indenting.rb → erect/indenting.rb} +0 -0
  13. data/lib/erector/{rhtml.treetop → erect/rhtml.treetop} +51 -11
  14. data/lib/erector/errors.rb +12 -0
  15. data/lib/erector/extensions/hash.rb +21 -0
  16. data/lib/erector/externals.rb +88 -24
  17. data/lib/erector/html.rb +352 -0
  18. data/lib/erector/inline.rb +5 -5
  19. data/lib/erector/jquery.rb +36 -0
  20. data/lib/erector/mixin.rb +3 -5
  21. data/lib/erector/needs.rb +94 -0
  22. data/lib/erector/output.rb +117 -0
  23. data/lib/erector/rails.rb +2 -2
  24. data/lib/erector/rails/extensions/action_controller.rb +5 -3
  25. data/lib/erector/rails/extensions/rails_helpers.rb +159 -0
  26. data/lib/erector/rails/extensions/rails_widget.rb +98 -56
  27. data/lib/erector/rails/rails_form_builder.rb +8 -4
  28. data/lib/erector/rails/rails_version.rb +2 -2
  29. data/lib/erector/rails/template_handlers/ert_handler.rb +1 -1
  30. data/lib/erector/rails/template_handlers/rb_handler.rb +42 -1
  31. data/lib/erector/raw_string.rb +2 -2
  32. data/lib/erector/sass.rb +22 -0
  33. data/lib/erector/widget.rb +100 -653
  34. data/lib/erector/widgets.rb +1 -0
  35. data/lib/erector/widgets/external_renderer.rb +51 -0
  36. data/lib/erector/widgets/page.rb +45 -63
  37. data/lib/erector/widgets/table.rb +9 -1
  38. data/spec/erect/erect_rails_spec.rb +19 -17
  39. data/spec/erect/erect_spec.rb +11 -1
  40. data/spec/erect/erected_spec.rb +76 -5
  41. data/spec/erect/rhtml_parser_spec.rb +11 -1
  42. data/spec/erector/caching_spec.rb +267 -0
  43. data/spec/erector/convenience_spec.rb +258 -0
  44. data/spec/erector/dependency_spec.rb +46 -0
  45. data/spec/erector/externals_spec.rb +233 -0
  46. data/spec/erector/html_spec.rb +508 -0
  47. data/spec/erector/indentation_spec.rb +84 -24
  48. data/spec/erector/inline_spec.rb +19 -8
  49. data/spec/erector/jquery_spec.rb +35 -0
  50. data/spec/erector/mixin_spec.rb +1 -1
  51. data/spec/erector/needs_spec.rb +120 -0
  52. data/spec/erector/output_spec.rb +199 -0
  53. data/spec/erector/sample-file.txt +1 -0
  54. data/spec/erector/sass_spec.rb +33 -0
  55. data/spec/erector/widget_spec.rb +113 -932
  56. data/spec/erector/widgets/field_table_spec.rb +6 -6
  57. data/spec/erector/widgets/form_spec.rb +3 -3
  58. data/spec/erector/widgets/page_spec.rb +52 -6
  59. data/spec/erector/widgets/table_spec.rb +4 -4
  60. data/spec/spec_helper.rb +70 -29
  61. metadata +56 -19
  62. data/lib/erector/rails/extensions/rails_widget/rails_helpers.rb +0 -137
  63. data/spec/core_spec_suite.rb +0 -3
  64. data/spec/erector/external_spec.rb +0 -110
  65. data/spec/rails_spec_suite.rb +0 -3
  66. data/spec/spec.opts +0 -1
  67. data/spec/spec_suite.rb +0 -40
@@ -1,84 +1,126 @@
1
1
  module Erector
2
2
  module Rails
3
- # These controller instance variables should not be passed to the
4
- # widget if it does not +need+ them.
5
- NON_NEEDED_CONTROLLER_INSTANCE_VARIABLES = [:@template, :@_request]
6
-
7
- def self.render(widget, controller, assigns = nil)
8
- if widget.is_a?(Class)
9
- unless assigns
10
- needs = widget.get_needed_variables
11
- assigns = {}
12
- variables = controller.instance_variable_names
13
- variables -= controller.protected_instance_variables
14
- variables.each do |name|
15
- assign = name.sub('@', '').to_sym
16
- next if !needs.empty? && !needs.include?(assign) && NON_NEEDED_CONTROLLER_INSTANCE_VARIABLES.include?(name.to_sym)
17
- assigns[assign] = controller.instance_variable_get(name)
18
- end
19
- end
3
+ def self.assigns_for(widget_class, view, local_assigns, is_partial)
4
+ assigns = {}
5
+
6
+ instance_variables = view.instance_variables_for_widget_assignment
7
+ if is_partial || widget_class.ignore_extra_controller_assigns
8
+ instance_variables = remove_unneeded_assigns(widget_class, instance_variables)
9
+ end
10
+
11
+ assigns.merge!(instance_variables) unless is_partial && (! widget_class.controller_assigns_propagate_to_partials)
12
+
13
+ if is_partial
14
+ assigns.merge!(filter_local_assigns_for_partial(widget_class, local_assigns || { }))
15
+ end
16
+
17
+ assigns
18
+ end
20
19
 
20
+ def self.remove_unneeded_assigns(widget_class, assigns)
21
+ needs = widget_class.needed_variables
22
+ if needs.empty?
23
+ assigns
24
+ else
25
+ assigns.reject { |key, value| ! needs.include?(key) }
26
+ end
27
+ end
28
+
29
+ def self.filter_local_assigns_for_partial(widget_class, local_assigns)
30
+ widget_class_variable_name = widget_class.name.underscore
31
+ widget_class_variable_name = $1 if widget_class_variable_name =~ %r{.*/(.*?)$}
32
+
33
+ local_assigns.reject do |name, value|
34
+ name == :object || name == widget_class_variable_name.to_sym
35
+ end
36
+ end
37
+
38
+ def self.render(widget, view, assigns = nil, options = {})
39
+ if widget.is_a?(Class)
40
+ assigns ||= assigns_for(widget, view, nil, false)
21
41
  widget = widget.new(assigns)
22
42
  end
23
43
 
24
- view = controller.response.template
25
44
  view.send(:_evaluate_assigns_and_ivars)
26
45
 
27
46
  view.with_output_buffer do
28
- widget.to_s(
29
- :output => view.output_buffer,
30
- :parent => view,
31
- :helpers => view
32
- )
47
+ # Set parent to the view and use Rails's output buffer.
48
+ widget.to_html(options.merge(:parent => view,
49
+ :output => Output.new { view.output_buffer }))
33
50
  end
34
51
  end
35
52
 
36
53
  module WidgetExtensions
54
+ module ClassMethods
55
+ def ignore_extra_controller_assigns
56
+ out = @ignore_extra_controller_assigns
57
+ out ||= (superclass.ignore_extra_controller_assigns ? :true : :false) if superclass.respond_to?(:ignore_extra_controller_assigns)
58
+ out ||= :true
59
+ out == :true
60
+ end
61
+
62
+ # Often, large Rails applications will assign many controller instance variables.
63
+ # Sometimes these aren't used by a view: ApplicationController might assign
64
+ # variables that are used by many, but not all, views; and various other things
65
+ # may accumulate, especially if you've been using templating systems that are
66
+ # more forgiving than Erector. If you then migrate to Erector, you're stuck using
67
+ # no "needs" declaration at all, because it needs to contain every assigned
68
+ # variable, or Erector will raise an exception.
69
+ #
70
+ # If you set this to true (and it's inherited through to subclasses), however,
71
+ # then "needs" declarations on the widget will cause excess controller variables
72
+ # to be ignored -- they'll be unavailable to the widget (so 'needs' still means
73
+ # something), but they won't cause widget instantiation to fail, either. This
74
+ # can let a large Rails project transition to Erector more smoothly.
75
+ def ignore_extra_controller_assigns=(new_value)
76
+ @ignore_extra_controller_assigns = (new_value ? :true : :false)
77
+ end
78
+
79
+ def controller_assigns_propagate_to_partials
80
+ out = @controller_assigns_propagate_to_partials
81
+ out ||= (superclass.controller_assigns_propagate_to_partials ? :true : :false) if superclass.respond_to?(:controller_assigns_propagate_to_partials)
82
+ out ||= :true
83
+ out == :true
84
+ end
85
+
86
+ # In ERb templates, controller instance variables are available to any partial
87
+ # that gets rendered by the view, no matter how deeply-nested. This effectively
88
+ # makes controller instance variables "globals". In small view hierarchies this
89
+ # probably isn't an issue, but in large ones it can make debugging and
90
+ # reasoning about the code very difficult.
91
+ #
92
+ # If you set this to true (and it's inherited through to subclasses), then any
93
+ # widget that's getting rendered as a partial will only have access to locals
94
+ # explicitly passed to it (render :partial => ..., :locals => ...). (This
95
+ # doesn't change the behavior of widgets that are explicitly rendered, as they
96
+ # don't have this issue.) This can allow for cleaner encapsulation of partials,
97
+ # as they must be passed everything they use and can't rely on controller
98
+ # instance variables.
99
+ def controller_assigns_propagate_to_partials=(new_value)
100
+ @controller_assigns_propagate_to_partials = (new_value ? :true : :false)
101
+ end
102
+ end
103
+
37
104
  def self.included(base)
38
- base.alias_method_chain :output, :parent
39
- base.alias_method_chain :capture, :parent
105
+ base.extend(ClassMethods)
40
106
  end
41
107
 
42
- def output_with_parent
43
- if parent.respond_to?(:output_buffer)
44
- parent.output_buffer.is_a?(String) ? parent.output_buffer : handle_rjs_buffer
108
+ # We need to delegate #capture to parent.capture, so that when
109
+ # the captured block is executed, both erector and Rails output
110
+ # from within the block go to the appropriate buffer.
111
+ def capture(&block)
112
+ if parent.respond_to?(:capture)
113
+ raw(parent.capture(&block).to_s)
45
114
  else
46
- output_without_parent
115
+ super
47
116
  end
48
117
  end
49
118
 
50
- def capture_with_parent(&block)
51
- parent ? raw(parent.capture(&block).to_s) : capture_without_parent(&block)
52
- end
53
-
54
119
  # This is here to force #parent.capture to return the output
55
120
  def __in_erb_template;
56
121
  end
57
-
58
- private
59
-
60
- def handle_rjs_buffer
61
- returning buffer = parent.output_buffer.dup.to_s do
62
- parent.output_buffer.clear
63
- parent.with_output_buffer(buffer) do
64
- buffer << parent.output_buffer.to_s
65
- end
66
- end
67
- end
68
122
  end
69
123
 
70
124
  Erector::Widget.send :include, WidgetExtensions
71
125
  end
72
-
73
- # RailsWidget and InlineRailsWidget are for backward compatibility.
74
- # New code should use Widget, InlineWidget, or Erector.inline.
75
- class RailsWidget < Widget
76
- def self.inline(*args, &block)
77
- InlineRailsWidget.new(*args, &block)
78
- end
79
- end
80
-
81
- class InlineRailsWidget < RailsWidget
82
- include Inline
83
- end
84
126
  end
@@ -4,17 +4,21 @@ module Erector
4
4
 
5
5
  def initialize(object_name, object, template, options, proc)
6
6
  @template = template
7
- @parent = ActionView::Helpers::FormBuilder.new(object_name, object, template, options, proc)
7
+ @parent = ActionView::Base.default_form_builder.new(object_name, object, template, options, proc)
8
8
  end
9
9
 
10
10
  def method_missing(method_name, *args, &block)
11
11
  if parent.respond_to?(method_name)
12
12
  return_value = parent.send(method_name, *args, &block)
13
- template.concat(return_value) if return_value.is_a?(String)
14
- return_value
13
+ if return_value.is_a?(String)
14
+ template.concat(return_value)
15
+ nil
16
+ else
17
+ return_value
18
+ end
15
19
  else
16
20
  super
17
21
  end
18
22
  end
19
23
  end
20
- end
24
+ end
@@ -1,6 +1,6 @@
1
1
  module Erector
2
2
  module Rails
3
- RAILS_VERSION = "2.3.4"
4
- RAILS_VERSION_TAG = "v2.3.4"
3
+ RAILS_VERSION = "2.3.8"
4
+ RAILS_VERSION_TAG = "v2.3.8"
5
5
  end
6
6
  end
@@ -18,7 +18,7 @@ module ActionView #:nodoc:
18
18
  " all[instance_variable] = instance_variable_get(instance_variable)",
19
19
  " all",
20
20
  "end",
21
- "r =::Erector.inline do",
21
+ "r = (controller.ert_template_base_class || ::Erector).inline do",
22
22
  " memoized_instance_variables.each do |instance_variable, value|",
23
23
  " instance_variable_set(instance_variable, value)",
24
24
  " end",
@@ -1,9 +1,50 @@
1
+ class ActionView::Base
2
+ def instance_variables_for_widget_assignment
3
+ instance_variables_for_widget_assignment_for(controller)
4
+ end
5
+
6
+ def instance_variables_for_widget_assignment_for(target)
7
+ assigns = { }
8
+ variables = target.instance_variable_names
9
+ variables -= target.protected_instance_variables if target.respond_to?(:protected_instance_variables)
10
+ variables -= %w{@real_format @request @template @_request}
11
+ variables.each do |name|
12
+ assign = name.sub('@', '').to_sym
13
+ assigns[assign] = target.instance_variable_get(name)
14
+ end
15
+ assigns
16
+ end
17
+ end
18
+
19
+ # Out of the box, the Cells plugin for Rails (http://cells.rubyforge.org/)
20
+ # does not work with Erector, because Erector tries to grab instance variables
21
+ # off the controller, rather than the cell itself.
22
+ #
23
+ # This code patches up Cell::View to make it work, but only if the Cells plugin
24
+ # is installed. (That's the bare "Cell::View" at the top, and rescue NameError
25
+ # at the bottom.) While you'd imagine that unilaterally opening Cell::View
26
+ # and adding the method would work, it doesn't; Cell::View is normally
27
+ # autoloaded, and since we'd end up defining it here, we'd keep it from getting
28
+ # loaded at all.
29
+ begin
30
+ Cell::View
31
+
32
+ class Cell::View < ActionView::Base
33
+ def instance_variables_for_widget_assignment
34
+ instance_variables_for_widget_assignment_for(cell)
35
+ end
36
+ end
37
+ rescue NameError
38
+ end
39
+
1
40
  module Erector
2
41
  class RbHandler < ActionView::TemplateHandler
3
42
  def render(template, local_assigns)
4
43
  require_dependency File.expand_path(template.filename)
5
44
  widget_class = "views/#{template.path_without_format_and_extension}".camelize.constantize
6
- Erector::Rails.render(widget_class, @view.controller)
45
+ is_partial = (File.basename(template.path_without_format_and_extension) =~ /^_/)
46
+ assigns = Erector::Rails.assigns_for(widget_class, @view, local_assigns, is_partial)
47
+ Erector::Rails.render(widget_class, @view, assigns)
7
48
  end
8
49
  end
9
50
  end
@@ -1,8 +1,8 @@
1
1
  module Erector
2
2
  # A string that has a special type so Erector knows to render it directly, not HTML-escaped
3
3
  class RawString < String
4
- def html_escape
5
- self
4
+ def html_safe?
5
+ true
6
6
  end
7
7
  end
8
8
  end
@@ -0,0 +1,22 @@
1
+
2
+ if Object.const_defined?(:Sass)
3
+ module Erector
4
+ # Adds sass support to Erector widgets.
5
+ # Note that sass is provided inside the gem named "haml",
6
+ # not the gem named "sass".
7
+ # To get sass support into your Erector project, install the
8
+ # haml gem -- see http://sass-lang.com/download.html -- and
9
+ # then do something like this:
10
+ # require 'rubygems'
11
+ # require 'sass'
12
+ #
13
+ # Current support is barebones. Please offer suggestions (or better
14
+ # yet, patches) for whether and how to support, e.g., caching,
15
+ # loading from files, precompilation, etc.
16
+ module Sass
17
+ def sass(sass_text)
18
+ style ::Sass::Engine.new(sass_text, :cache => false).render
19
+ end
20
+ end
21
+ end
22
+ end
@@ -7,14 +7,15 @@ module Erector
7
7
  # or +p+ to emit HTML/XML tags.
8
8
  #
9
9
  # You can also define a widget on the fly by passing a block to +new+. This
10
- # block will get executed when the widget's +content+ method is called.
10
+ # block will get executed when the widget's +content+ method is called. See
11
+ # the userguide for important details about the scope of this block when run --
12
+ # http://erector.rubyforge.org/userguide.html#blocks
11
13
  #
12
- # To render a widget from the outside, instantiate it and call its +to_s+
14
+ # To render a widget from the outside, instantiate it and call its +to_html+
13
15
  # method.
14
16
  #
15
17
  # A widget's +new+ method optionally accepts an options hash. Entries in
16
- # this hash are converted to instance variables, and +attr_reader+ accessors
17
- # are defined for each.
18
+ # this hash are converted to instance variables.
18
19
  #
19
20
  # You can add runtime input checking via the +needs+ macro. See #needs.
20
21
  # This mechanism is meant to ameliorate development-time confusion about
@@ -24,7 +25,7 @@ module Erector
24
25
  # To call one widget from another, inside the parent widget's +content+
25
26
  # method, instantiate the child widget and call the +widget+ method. This
26
27
  # assures that the same output stream is used, which gives better
27
- # performance than using +capture+ or +to_s+. It also preserves the
28
+ # performance than using +capture+ or +to_html+. It also preserves the
28
29
  # indentation and helpers of the enclosing class.
29
30
  #
30
31
  # In this documentation we've tried to keep the distinction clear between
@@ -32,252 +33,50 @@ module Erector
32
33
  # it writes to the output stream; "return" means that it returns a string
33
34
  # like a normal method and leaves it up to the caller to emit that string if
34
35
  # it wants.
35
- class Widget
36
- extend Erector::Externals # 'extend'ing since they're class methods, not instance methods
37
-
38
- class << self
39
- def all_tags
40
- Erector::Widget.full_tags + Erector::Widget.empty_tags
41
- end
42
-
43
- # Tags which are always self-closing. Click "[Source]" to see the full list.
44
- def empty_tags
45
- ['area', 'base', 'br', 'col', 'frame',
46
- 'hr', 'img', 'input', 'link', 'meta']
47
- end
48
-
49
- # Tags which can contain other stuff. Click "[Source]" to see the full list.
50
- def full_tags
51
- [
52
- 'a', 'abbr', 'acronym', 'address', 'article', 'aside', 'audio',
53
- 'b', 'bdo', 'big', 'blockquote', 'body', 'button',
54
- 'canvas', 'caption', 'center', 'cite', 'code', 'colgroup', 'command',
55
- 'datalist', 'dd', 'del', 'details', 'dfn', 'dialog', 'div', 'dl', 'dt',
56
- 'em', 'embed',
57
- 'fieldset', 'figure', 'footer', 'form', 'frameset',
58
- 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'head', 'header', 'hgroup', 'html', 'i',
59
- 'iframe', 'ins', 'keygen', 'kbd', 'label', 'legend', 'li',
60
- 'map', 'mark', 'meter',
61
- 'nav', 'noframes', 'noscript',
62
- 'object', 'ol', 'optgroup', 'option',
63
- 'p', 'param', 'pre', 'progress',
64
- 'q', 'ruby', 'rt', 'rp', 's',
65
- 'samp', 'script', 'section', 'select', 'small', 'source', 'span', 'strike',
66
- 'strong', 'style', 'sub', 'sup',
67
- 'table', 'tbody', 'td', 'textarea', 'tfoot',
68
- 'th', 'thead', 'time', 'title', 'tr', 'tt', 'u', 'ul',
69
- 'var', 'video'
70
- ]
71
- end
72
-
73
- def def_empty_tag_method(tag_name)
74
- self.class_eval(<<-SRC, __FILE__, __LINE__)
75
- def #{tag_name}(*args, &block)
76
- __empty_element__('#{tag_name}', *args, &block)
77
- end
78
- SRC
79
- end
80
-
81
- def def_full_tag_method(tag_name)
82
- self.class_eval(<<-SRC, __FILE__, __LINE__)
83
- def #{tag_name}(*args, &block)
84
- __element__(false, '#{tag_name}', *args, &block)
85
- end
86
-
87
- def #{tag_name}!(*args, &block)
88
- __element__(true, '#{tag_name}', *args, &block)
89
- end
90
- SRC
91
- end
92
-
93
- def after_initialize(instance=nil, &blk)
94
- if blk
95
- after_initialize_parts << blk
96
- elsif instance
97
- if superclass.respond_to?(:after_initialize)
98
- superclass.after_initialize instance
99
- end
100
- after_initialize_parts.each do |part|
101
- instance.instance_eval &part
102
- end
103
- else
104
- raise ArgumentError, "You must provide either an instance or a block"
105
- end
106
- end
107
-
108
- protected
109
- def after_initialize_parts
110
- @after_initialize_parts ||= []
111
- end
112
-
113
- end
114
-
115
- # Class method by which widget classes can declare that they need certain
116
- # parameters. If needed parameters are not passed in to #new, then an
117
- # exception will be thrown (with a hopefully useful message about which
118
- # parameters are missing). This is intended to catch silly bugs like
119
- # passing in a parameter called 'name' to a widget that expects a
120
- # parameter called 'title'. Every variable declared in 'needs' will get an
121
- # attr_reader accessor declared for it.
122
- #
123
- # You can also declare default values for parameters using hash syntax.
124
- # You can put #needs declarations on multiple lines or on the same line;
125
- # the only caveat is that if there are default values, they all have to be
126
- # at the end of the line (so they go into the magic hash parameter).
127
- #
128
- # If a widget has no #needs declaration then it will accept any
129
- # combination of parameters (and make accessors for them) just like
130
- # normal. In that case there will be no 'attr_reader's declared. If a
131
- # widget wants to declare that it takes no parameters, use the special
132
- # incantation "needs nil" (and don't declare any other needs, or kittens
133
- # will cry).
134
- #
135
- # Usage:
136
- # class FancyForm < Erector::Widget
137
- # needs :title, :show_okay => true, :show_cancel => false
138
- # ...
139
- # end
140
- #
141
- # That means that
142
- # FancyForm.new(:title => 'Login')
143
- # will succeed, as will
144
- # FancyForm.new(:title => 'Login', :show_cancel => true)
145
- # but
146
- # FancyForm.new(:name => 'Login')
147
- # will fail.
148
- #
149
- def self.needs(*args)
150
- args.each do |arg|
151
- (@needs ||= []) << (arg.nil? ? nil : (arg.is_a? Hash) ? arg : arg.to_sym)
152
- end
153
- end
154
-
155
- protected
156
- def self.get_needs
157
- @needs ||= []
158
-
159
- ancestors[1..-1].inject(@needs.dup) do |needs, ancestor|
160
- needs.push(*ancestor.get_needs) if ancestor.respond_to?(:get_needs)
161
- needs
162
- end
163
- end
164
-
165
- def self.get_needed_variables
166
- get_needs.map{|need| need.is_a?(Hash) ? need.keys : need}.flatten
167
- end
168
-
169
- def self.get_needed_defaults
170
- get_needs.select{|need| need.is_a? Hash}
171
- end
172
-
173
- public
36
+ #
37
+ # Now, seriously, after playing around a bit, go read the user guide. It's
38
+ # fun!
39
+ class AbstractWidget
174
40
  @@prettyprint_default = false
175
41
  def prettyprint_default
176
42
  @@prettyprint_default
177
43
  end
178
44
 
45
+ def self.prettyprint_default
46
+ @@prettyprint_default
47
+ end
48
+
179
49
  def self.prettyprint_default=(enabled)
180
50
  @@prettyprint_default = enabled
181
51
  end
182
52
 
183
- NON_NEWLINEY = {'i' => true, 'b' => true, 'small' => true,
184
- 'img' => true, 'span' => true, 'a' => true,
185
- 'input' => true, 'textarea' => true, 'button' => true, 'select' => true
186
- }
187
-
188
- SPACES_PER_INDENT = 2
189
-
190
- RESERVED_INSTANCE_VARS = [:helpers, :assigns, :block, :output, :prettyprint, :indentation, :at_start_of_line]
191
-
192
- attr_reader *RESERVED_INSTANCE_VARS
193
- attr_reader :parent
194
- attr_writer :block
195
-
196
- def initialize(assigns={}, &block)
197
- unless assigns.is_a? Hash
198
- raise "Erector's API has changed. Now you should pass only an options hash into Widget.new; the rest come in via to_s, or by using #widget."
199
- end
200
- @assigns = assigns
201
- assign_instance_variables(assigns)
202
- unless @parent
203
- @parent = block ? eval("self", block.binding) : nil
204
- end
205
- @block = block
206
- self.class.after_initialize self
53
+ def self.inline(*args, &block)
54
+ Class.new(self) do
55
+ include Erector::Inline
56
+ end.new(*args, &block)
207
57
  end
208
58
 
209
- #-- methods for other classes to call, left public for ease of testing and documentation
210
- #++
211
-
212
- protected
213
- def context(parent, output, prettyprint = false, indentation = 0, helpers = nil)
214
- #TODO: pass in options hash, maybe, instead of parameters
215
- original_parent = @parent
216
- original_output = @output
217
- original_indendation = @indentation
218
- original_helpers = @helpers
219
- original_prettyprint = @prettyprint
220
- @parent = parent
221
- @output = output
222
- @at_start_of_line = true
223
- raise "indentation must be a number, not #{indentation.inspect}" unless indentation.is_a? Fixnum
224
- @indentation = indentation
225
- @helpers = helpers
226
- @prettyprint = prettyprint
227
- yield
228
- ensure
229
- @parent = original_parent
230
- @output = original_output
231
- @indentation = original_indendation
232
- @helpers = original_helpers
233
- @prettyprint = original_prettyprint
59
+ [:helpers, :assigns, :output, :parent, :block].each do |attr|
60
+ class_eval(<<-SRC, __FILE__, __LINE__ + 1)
61
+ def #{attr}
62
+ @_#{attr}
63
+ end
64
+ SRC
234
65
  end
235
66
 
236
- public
237
- def assign_instance_variables (instance_variables)
238
- needed = self.class.get_needed_variables
239
- assigned = []
240
- instance_variables.each do |name, value|
241
- unless needed.empty? || needed.include?(name)
242
- raise "Unknown parameter '#{name}'. #{self.class.name} only accepts #{needed.join(', ')}"
243
- end
244
- assign_instance_variable(name, value)
245
- assigned << name
67
+ def initialize(assigns = {}, &block)
68
+ unless assigns.is_a? Hash
69
+ raise "Erector widgets are initialized with only a parameter hash. (Other parameters are passed to to_html, or the #widget method.)"
246
70
  end
247
71
 
248
- # set variables with default values
249
- self.class.get_needed_defaults.each do |hash|
250
- hash.each_pair do |name, value|
251
- unless assigned.include?(name)
252
- assign_instance_variable(name, value)
253
- assigned << name
254
- end
255
- end
256
- end
72
+ @_assigns = assigns
257
73
 
258
- missing = needed - assigned
259
- unless missing.empty? || missing == [nil]
260
- raise "Missing parameter#{missing.size == 1 ? '' : 's'}: #{missing.join(', ')}"
74
+ assigns.each do |name, value|
75
+ instance_variable_set(name.to_s[0..0] == '@' ? name : "@#{name}", value)
261
76
  end
262
- end
263
-
264
- def assign_instance_variable (name, value)
265
- raise ArgumentError, "Sorry, #{name} is a reserved variable name for Erector. Please choose a different name." if RESERVED_INSTANCE_VARS.include?(name)
266
- name = name.to_s
267
- ivar_name = (name[0..0] == '@' ? name : "@#{name}")
268
- instance_variable_set(ivar_name, value)
269
- end
270
-
271
- # Render (like to_s) but adding newlines and indentation.
272
- # This is a convenience method; you may just want to call to_s(:prettyprint => true)
273
- # so you can pass in other rendering options as well.
274
- def to_pretty
275
- to_s(:prettyprint => true)
276
- end
277
-
278
- # Render (like to_s) but stripping all tags.
279
- def to_text
280
- CGI.unescapeHTML(to_s(:prettyprint => false).gsub(/<[^>]*>/, ''))
77
+
78
+ @_parent = eval("self", block.binding) if block
79
+ @_block = block
281
80
  end
282
81
 
283
82
  # Entry point for rendering a widget (and all its children). This method
@@ -291,39 +90,40 @@ module Erector
291
90
  # by default).
292
91
  # indentation:: the amount of spaces to indent. Ignored unless prettyprint
293
92
  # is true.
93
+ # max_length:: preferred maximum length of a line. Line wraps will only
94
+ # occur at space characters, so a long word may end up creating
95
+ # a line longer than this. If nil (default), then there is no
96
+ # arbitrary limit to line lengths, and only internal newline
97
+ # characters and prettyprinting will determine newlines in the
98
+ # output.
294
99
  # helpers:: a helpers object containing utility methods. Usually this is a
295
100
  # Rails view object.
296
101
  # content_method_name:: in case you want to call a method other than
297
102
  # #content, pass its name in here.
298
- def to_s(options = {}, &blk)
299
- raise "Erector::Widget#to_s now takes an options hash, not a symbol. Try calling \"to_s(:content_method_name=> :#{options})\"" if options.is_a? Symbol
300
- _render(options, &blk).to_s
103
+ def to_html(options = {})
104
+ raise "Erector::Widget#to_html takes an options hash, not a symbol. Try calling \"to_html(:content_method_name=> :#{options})\"" if options.is_a? Symbol
105
+ _render(options).to_s
301
106
  end
302
-
303
- # Entry point for rendering a widget (and all its children). Same as #to_s
107
+
108
+ # alias for #to_html
109
+ # @deprecated Please use {#to_html} instead
110
+ def to_s(*args)
111
+ unless defined? @@already_warned_to_s
112
+ $stderr.puts "Erector::Widget#to_s is deprecated. Please use #to_html instead. Called from #{caller.first}"
113
+ @@already_warned_to_s = true
114
+ end
115
+ to_html(*args)
116
+ end
117
+
118
+ # Entry point for rendering a widget (and all its children). Same as #to_html
304
119
  # only it returns an array, for theoretical performance improvements when using a
305
120
  # Rack server (like Sinatra or Rails Metal).
306
121
  #
307
- # # Options: see #to_s
308
- def to_a(options = {}, &blk)
309
- _render({:output => []}.merge(options), &blk).to_a
310
- end
311
-
312
- def _render(options = {}, &blk)
313
- options = {
314
- :output => "", # "" is apparently faster than [] in a long-running process
315
- :prettyprint => prettyprint_default,
316
- :indentation => 0,
317
- :helpers => nil,
318
- :parent => @parent,
319
- :content_method_name => :content,
320
- }.merge(options)
321
- context(options[:parent], options[:output], options[:prettyprint], options[:indentation], options[:helpers]) do
322
- send(options[:content_method_name], &blk)
323
- output
324
- end
122
+ # # Options: see #to_html
123
+ def to_a(options = {})
124
+ _render(options).to_a
325
125
  end
326
-
126
+
327
127
  # Template method which must be overridden by all widget subclasses.
328
128
  # Inside this method you call the magic #element methods which emit HTML
329
129
  # and text to the output string. If you call "super" (or don't override
@@ -347,18 +147,7 @@ module Erector
347
147
  # If you want this block to have access to Erector methods then use
348
148
  # Erector::Inline#content or Erector#inline.
349
149
  def call_block
350
- @block.call(self) if @block
351
- end
352
-
353
- # To call one widget from another, inside the parent widget's +content+
354
- # method, instantiate the child widget and call its +write_via+ method,
355
- # passing in +self+. This assures that the same output string is used,
356
- # which gives better performance than using +capture+ or +to_s+. You can
357
- # also use the +widget+ method.
358
- def write_via(parent)
359
- context(parent, parent.output, parent.prettyprint, parent.indentation, parent.helpers) do
360
- content
361
- end
150
+ @_block.call(self) if @_block
362
151
  end
363
152
 
364
153
  # Emits a (nested) widget onto the current widget's output stream. Accepts
@@ -367,404 +156,62 @@ module Erector
367
156
  # If the first argument is an instance then the hash must be unspecified
368
157
  # (or empty). If a block is passed to this method, then it gets set as the
369
158
  # rendered widget's block.
370
- def widget(target, assigns={}, &block)
371
- child = if target.is_a? Class
372
- target.new(assigns, &block)
373
- else
374
- unless assigns.empty?
375
- raise "Unexpected second parameter. Did you mean to pass in variables when you instantiated the #{target.class.to_s}?"
376
- end
377
- target.block = block unless block.nil?
378
- target
379
- end
380
- child.write_via(self)
381
- end
382
-
383
- # (Should we make this hidden?)
384
- def html_escape
385
- return to_s
386
- end
387
-
388
- #-- methods for subclasses to call
389
- #++
390
-
391
- # Internal method used to emit an HTML/XML element, including an open tag,
392
- # attributes (optional, via the default hash), contents (also optional),
393
- # and close tag.
394
- #
395
- # Using the arcane powers of Ruby, there are magic methods that call
396
- # +element+ for all the standard HTML tags, like +a+, +body+, +p+, and so
397
- # forth. Look at the source of #full_tags for the full list.
398
- # Unfortunately, this big mojo confuses rdoc, so we can't see each method
399
- # in this rdoc page, but trust us, they're there.
400
- #
401
- # When calling one of these magic methods, put attributes in the default
402
- # hash. If there is a string parameter, then it is used as the contents.
403
- # If there is a block, then it is executed (yielded), and the string
404
- # parameter is ignored. The block will usually be in the scope of the
405
- # child widget, which means it has access to all the methods of Widget,
406
- # which will eventually end up appending text to the +output+ string. See
407
- # how elegant it is? Not confusing at all if you don't think about it.
408
- #
409
- def element(*args, &block)
410
- __element__(false, *args, &block)
411
- end
412
-
413
- # Like +element+, but string parameters are not escaped.
414
- def element!(*args, &block)
415
- __element__(true, *args, &block)
416
- end
417
-
418
- # Internal method used to emit a self-closing HTML/XML element, including
419
- # a tag name and optional attributes (passed in via the default hash).
420
159
  #
421
- # Using the arcane powers of Ruby, there are magic methods that call
422
- # +empty_element+ for all the standard HTML tags, like +img+, +br+, and so
423
- # forth. Look at the source of #empty_tags for the full list.
424
- # Unfortunately, this big mojo confuses rdoc, so we can't see each method
425
- # in this rdoc page, but trust us, they're there.
426
- #
427
- def empty_element(*args, &block)
428
- __empty_element__(*args, &block)
429
- end
430
-
431
- # Returns an HTML-escaped version of its parameter. Leaves the output
432
- # string untouched. Note that the #text method automatically HTML-escapes
433
- # its parameter, so be careful *not* to do something like text(h("2<4"))
434
- # since that will double-escape the less-than sign (you'll get
435
- # "2&amp;lt;4" instead of "2&lt;4").
436
- def h(content)
437
- content.html_escape
438
- end
439
-
440
- # Emits an open tag, comprising '<', tag name, optional attributes, and '>'
441
- def open_tag(tag_name, attributes={})
442
- indent_for_open_tag(tag_name)
443
- @indentation += SPACES_PER_INDENT
444
-
445
- output << "<#{tag_name}#{format_attributes(attributes)}>"
446
- @at_start_of_line = false
447
- end
448
-
449
- # Emits text. If a string is passed in, it will be HTML-escaped. If a
450
- # widget or the result of calling methods such as raw is passed in, the
451
- # HTML will not be HTML-escaped again. If another kind of object is passed
452
- # in, the result of calling its to_s method will be treated as a string
453
- # would be.
454
- def text(value)
455
- if value.is_a? Widget
456
- widget value
160
+ # This is the preferred way to call one widget from inside another. This
161
+ # method assures that the same output string is used, which gives better
162
+ # performance than using +capture+ or +to_html+.
163
+ def widget(target, assigns = {}, options = {}, &block)
164
+ if target.is_a? Class
165
+ target.new(assigns, &block)._render_via(self, options)
457
166
  else
458
- output <<(value.html_escape)
459
- end
460
- @at_start_of_line = false
461
- nil
462
- end
463
-
464
- # Returns text which will *not* be HTML-escaped.
465
- def raw(value)
466
- RawString.new(value.to_s)
467
- end
468
-
469
- # Emits text which will *not* be HTML-escaped. Same effect as text(raw(s))
470
- def text!(value)
471
- text raw(value)
472
- end
473
-
474
- alias rawtext text!
475
-
476
- # Returns a copy of value with spaces replaced by non-breaking space characters.
477
- # With no arguments, return a single non-breaking space.
478
- # The output uses the escaping format '&#160;' since that works
479
- # in both HTML and XML (as opposed to '&nbsp;' which only works in HTML).
480
- def nbsp(value = " ")
481
- raw(value.html_escape.gsub(/ /,'&#160;'))
482
- end
483
-
484
- # Return a character given its unicode code point or unicode name.
485
- def character(code_point_or_name)
486
- if code_point_or_name.is_a?(Symbol)
487
- found = Erector::CHARACTERS[code_point_or_name]
488
- if found.nil?
489
- raise "Unrecognized character #{code_point_or_name}"
490
- end
491
- raw("&#x#{sprintf '%x', found};")
492
- elsif code_point_or_name.is_a?(Integer)
493
- raw("&#x#{sprintf '%x', code_point_or_name};")
494
- else
495
- raise "Unrecognized argument to character: #{code_point_or_name}"
496
- end
497
- end
498
-
499
- # Emits a close tag, consisting of '<', '/', tag name, and '>'
500
- def close_tag(tag_name)
501
- @indentation -= SPACES_PER_INDENT
502
- indent()
503
-
504
- output <<("</#{tag_name}>")
505
-
506
- if newliney?(tag_name)
507
- _newline
508
- end
509
- end
510
-
511
- # Emits the result of joining the elements in array with the separator.
512
- # The array elements and separator can be Erector::Widget objects,
513
- # which are rendered, or strings, which are html-escaped and output.
514
- def join(array, separator)
515
- first = true
516
- array.each do |widget_or_text|
517
- if !first
518
- text separator
167
+ unless assigns.empty?
168
+ raise "Unexpected second parameter. Did you mean to pass in assigns when you instantiated the #{target.class.to_s}?"
519
169
  end
520
- first = false
521
- text widget_or_text
170
+ target._render_via(self, options, &block)
522
171
  end
523
172
  end
524
173
 
525
- # Emits an XML instruction, which looks like this: <?xml version=\"1.0\" encoding=\"UTF-8\"?>
526
- def instruct(attributes={:version => "1.0", :encoding => "UTF-8"})
527
- output << "<?xml#{format_sorted(sort_for_xml_declaration(attributes))}?>"
528
- end
529
-
530
- # Emits an HTML comment (&lt;!-- ... --&gt;) surrounding +text+ and/or the output of +block+.
531
- # see http://www.w3.org/TR/html4/intro/sgmltut.html#h-3.2.4
532
- #
533
- # If +text+ is an Internet Explorer conditional comment condition such as "[if IE]",
534
- # the output includes the opening condition and closing "[endif]". See
535
- # http://www.quirksmode.org/css/condcom.html
536
- #
537
- # Since "Authors should avoid putting two or more adjacent hyphens inside comments,"
538
- # we emit a warning if you do that.
539
- def comment(text = '', &block)
540
- puts "Warning: Authors should avoid putting two or more adjacent hyphens inside comments." if text =~ /--/
541
-
542
- conditional = text =~ /\[if .*\]/
543
-
544
- rawtext "<!--"
545
- rawtext text
546
- rawtext ">" if conditional
547
-
548
- if block
549
- rawtext "\n"
550
- block.call
551
- rawtext "\n"
552
- end
553
-
554
- rawtext "<![endif]" if conditional
555
- rawtext "-->\n"
556
- end
557
-
558
174
  # Creates a whole new output string, executes the block, then converts the
559
175
  # output string to a string and returns it as raw text. If at all possible
560
- # you should avoid this method since it hurts performance, and use
561
- # +widget+ or +write_via+ instead.
562
- def capture(&block)
563
- begin
564
- original_output = output
565
- @output = ""
566
- yield
567
- raw(output.to_s)
568
- ensure
569
- @output = original_output
570
- end
571
- end
572
-
573
- full_tags.each do |tag_name|
574
- def_full_tag_method(tag_name)
575
- end
576
-
577
- empty_tags.each do |tag_name|
578
- def_empty_tag_method(tag_name)
579
- end
580
-
581
- # Emits a javascript block inside a +script+ tag, wrapped in CDATA
582
- # doohickeys like all the cool JS kids do.
583
- def javascript(*args, &block)
584
- if args.length > 2
585
- raise ArgumentError, "Cannot accept more than two arguments"
586
- end
587
- attributes, value = nil, nil
588
- arg0 = args[0]
589
- if arg0.is_a?(Hash)
590
- attributes = arg0
591
- else
592
- value = arg0
593
- arg1 = args[1]
594
- if arg1.is_a?(Hash)
595
- attributes = arg1
596
- end
597
- end
598
- attributes ||= {}
599
- attributes[:type] = "text/javascript"
600
- open_tag 'script', attributes
601
-
602
- # Shouldn't this be a "cdata" HtmlPart?
603
- # (maybe, but the syntax is specific to javascript; it isn't
604
- # really a generic XML CDATA section. Specifically,
605
- # ]]> within value is not treated as ending the
606
- # CDATA section by Firefox2 when parsing text/html,
607
- # although I guess we could refuse to generate ]]>
608
- # there, for the benefit of XML/XHTML parsers).
609
- rawtext "\n// <![CDATA[\n"
610
- if block
611
- instance_eval(&block)
612
- else
613
- rawtext value
614
- end
615
- rawtext "\n// ]]>\n"
616
-
617
- close_tag 'script'
618
- rawtext "\n"
619
- end
620
-
621
- # Convenience method to emit a css file link, which looks like this:
622
- # <link href="erector.css" rel="stylesheet" type="text/css" />
623
- # The parameter is the full contents of the href attribute, including any ".css" extension.
624
- #
625
- # If you want to emit raw CSS inline, use the #style method instead.
626
- def css(href, options = {})
627
- link({:rel => 'stylesheet', :type => 'text/css', :href => href}.merge(options))
628
- end
629
-
630
- # Convenience method to emit an anchor tag whose href and text are the same,
631
- # e.g. <a href="http://example.com">http://example.com</a>
632
- def url(href, options = {})
633
- a href, ({:href => href}.merge(options))
634
- end
635
-
636
- # makes a unique id based on the widget's class name and object id
637
- # that you can use as the HTML id of an emitted element
638
- def dom_id
639
- "#{self.class.name.gsub(/:+/,"_")}_#{self.object_id}"
640
- end
641
-
642
- # emits a jQuery script that is to be run on document ready
643
- def jquery(txt)
644
- javascript do
645
- jquery_ready txt
646
- end
176
+ # you should avoid this method since it hurts performance, and use +widget+
177
+ # instead.
178
+ def capture
179
+ original, @_output = output, Output.new
180
+ yield
181
+ original.widgets.concat(output.widgets) # todo: test!!!
182
+ output.to_s
183
+ ensure
184
+ @_output = original
647
185
  end
648
186
 
649
187
  protected
650
- def jquery_ready(txt)
651
- rawtext "\n"
652
- rawtext "jQuery(document).ready(function($){\n"
653
- rawtext txt
654
- rawtext "\n});"
655
- end
656
-
657
- ### internal utility methods
658
-
659
- protected
660
- def __element__(raw, tag_name, *args, &block)
661
- if args.length > 2
662
- raise ArgumentError, "Cannot accept more than four arguments"
663
- end
664
- attributes, value = nil, nil
665
- arg0 = args[0]
666
- if arg0.is_a?(Hash)
667
- attributes = arg0
668
- else
669
- value = arg0
670
- arg1 = args[1]
671
- if arg1.is_a?(Hash)
672
- attributes = arg1
673
- end
674
- end
675
- attributes ||= {}
676
- open_tag tag_name, attributes
677
- if block && value
678
- raise ArgumentError, "You can't pass both a block and a value to #{tag_name} -- please choose one."
679
- end
680
- if block
681
- block.call
682
- elsif raw
683
- text! value
684
- else
685
- text value
686
- end
687
- close_tag tag_name
688
- end
689
-
690
- def __empty_element__(tag_name, attributes={})
691
- indent_for_open_tag(tag_name)
692
-
693
- output << "<#{tag_name}#{format_attributes(attributes)} />"
694
-
695
- if newliney?(tag_name)
696
- _newline
697
- end
698
- end
699
-
700
- def _newline
701
- return unless @prettyprint
702
- output << "\n"
703
- @at_start_of_line = true
704
- end
705
-
706
- def indent_for_open_tag(tag_name)
707
- return unless @prettyprint
708
- if !@at_start_of_line && newliney?(tag_name)
709
- _newline
710
- end
711
- indent()
712
- end
713
-
714
- def indent()
715
- if @at_start_of_line
716
- output << " " * [@indentation, 0].max
717
- end
718
- end
719
-
720
- def format_attributes(attributes)
721
- if !attributes || attributes.empty?
722
- ""
723
- else
724
- format_sorted(sorted(attributes))
725
- end
726
- end
727
-
728
- def format_sorted(sorted)
729
- results = ['']
730
- sorted.each do |key, value|
731
- if value
732
- if value.is_a?(Array)
733
- value = value.flatten
734
- next if value.empty?
735
- value = value.join(' ')
736
- end
737
- results << "#{key}=\"#{value.html_escape}\""
738
- end
739
- end
740
- return results.join(' ')
741
- end
742
-
743
- def sorted(attributes)
744
- stringized = []
745
- attributes.each do |key, value|
746
- stringized << [key.to_s, value]
747
- end
748
- return stringized.sort
749
- end
188
+ def _render(options = {}, &block)
189
+ @_block = block if block
190
+ @_parent = options[:parent] || parent
191
+ @_helpers = options[:helpers] || parent
192
+ @_output = options[:output]
193
+ @_output = Output.new(options) unless output.is_a?(Output)
750
194
 
751
- def sort_for_xml_declaration(attributes)
752
- # correct order is "version, encoding, standalone" (XML 1.0 section 2.8).
753
- # But we only try to put version before encoding for now.
754
- stringized = []
755
- attributes.each do |key, value|
756
- stringized << [key.to_s, value]
757
- end
758
- return stringized.sort{|a, b| b <=> a}
195
+ output.widgets << self.class
196
+ send(options[:content_method_name] || :content)
197
+ output
759
198
  end
760
199
 
761
- def newliney?(tag_name)
762
- if @prettyprint
763
- !NON_NEWLINEY.include?(tag_name)
764
- else
765
- false
766
- end
200
+ def _render_via(parent, options = {}, &block)
201
+ _render(options.merge(:parent => parent,
202
+ :output => parent.output,
203
+ :helpers => parent.helpers), &block)
767
204
  end
205
+ end
768
206
 
207
+ class Widget < AbstractWidget
208
+ include Erector::HTML
209
+ include Erector::Needs
210
+ include Erector::Caching
211
+ include Erector::Externals
212
+ include Erector::Convenience
213
+ include Erector::JQuery
214
+ include Erector::AfterInitialize
215
+ include Erector::Sass if Object.const_defined?(:Sass)
769
216
  end
770
217
  end