erector 0.5.1 → 0.6.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (34) hide show
  1. data/README.txt +6 -5
  2. data/VERSION.yml +5 -0
  3. data/bin/{erect → erector} +0 -0
  4. data/lib/erector.rb +3 -1
  5. data/lib/erector/erect.rb +1 -1
  6. data/lib/erector/erected.rb +1 -1
  7. data/lib/erector/rails.rb +2 -1
  8. data/lib/erector/rails/extensions/action_controller.rb +25 -7
  9. data/lib/erector/rails/extensions/rails_widget.rb +38 -0
  10. data/lib/erector/rails/extensions/{widget.rb → rails_widget/helpers.rb} +2 -9
  11. data/lib/erector/rails/rails_version.rb +6 -0
  12. data/lib/erector/rails/template_handlers/action_view_template_handler.rb +43 -11
  13. data/lib/erector/version.rb +4 -2
  14. data/lib/erector/widget.rb +221 -74
  15. data/lib/erector/widgets/table.rb +34 -8
  16. data/spec/erector/indentation_spec.rb +39 -24
  17. data/spec/erector/widget_spec.rb +197 -64
  18. data/spec/erector/widgets/table_spec.rb +3 -3
  19. data/spec/spec.opts +1 -0
  20. data/spec/spec_helper.rb +2 -4
  21. data/spec/spec_suite.rb +6 -12
  22. metadata +24 -70
  23. data/lib/erector/rails/extensions/action_controller/1.2.5/action_controller.rb +0 -17
  24. data/lib/erector/rails/extensions/action_controller/2.2.0/action_controller.rb +0 -26
  25. data/lib/erector/rails/extensions/widget/1.2.5/widget.rb +0 -18
  26. data/lib/erector/rails/extensions/widget/2.2.0/widget.rb +0 -23
  27. data/lib/erector/rails/supported_rails_versions.rb +0 -13
  28. data/lib/erector/rails/template_handlers/1.2.5/action_view_template_handler.rb +0 -32
  29. data/lib/erector/rails/template_handlers/2.0.0/action_view_template_handler.rb +0 -36
  30. data/lib/erector/rails/template_handlers/2.1.0/action_view_template_handler.rb +0 -31
  31. data/lib/erector/rails/template_handlers/2.2.0/action_view_template_handler.rb +0 -46
  32. data/spec/erect/erect_spec.rb +0 -145
  33. data/spec/erect/erected_spec.rb +0 -80
  34. data/spec/erect/rhtml_parser_spec.rb +0 -318
data/README.txt CHANGED
@@ -1,7 +1,8 @@
1
1
  = Erector
2
2
 
3
3
  * http://erector.rubyforge.org
4
- * mailto:erector-devel@rubyforge.org
4
+ * mailto:erector@googlegroups.com
5
+ * http://www.pivotaltracker.com/projects/482
5
6
 
6
7
  == DESCRIPTION
7
8
 
@@ -17,20 +18,20 @@ project site at http://erector.rubyforge.org for more documentation.
17
18
  require 'erector'
18
19
 
19
20
  class Hello < Erector::Widget
20
- def render
21
+ def content
21
22
  html do
22
23
  head do
23
24
  title "Hello"
24
25
  end
25
26
  body do
26
27
  text "Hello, "
27
- b "world!", :class => 'big'
28
+ b "#{target}!", :class => 'big'
28
29
  end
29
30
  end
30
31
  end
31
32
  end
32
33
 
33
- Hello.new.to_s
34
+ Hello.new(:target => 'world').to_s
34
35
  => "<html><head><title>Hello</title></head><body>Hello, <b class=\"big\">world!</b></body></html>"
35
36
 
36
37
  == REQUIREMENTS
@@ -58,7 +59,7 @@ When installing this way, erector is automatically available to your Rails code
58
59
 
59
60
  (The MIT License)
60
61
 
61
- Copyright (c) 2007-8 Pivotal Labs
62
+ Copyright (c) 2007-2009 Pivotal Labs
62
63
 
63
64
  Permission is hereby granted, free of charge, to any person obtaining
64
65
  a copy of this software and associated documentation files (the
data/VERSION.yml ADDED
@@ -0,0 +1,5 @@
1
+ ---
2
+ :major: 0
3
+ :minor: 6
4
+ :patch: 3
5
+
File without changes
data/lib/erector.rb CHANGED
@@ -1,7 +1,9 @@
1
1
  require "rubygems"
2
2
  dir = File.dirname(__FILE__)
3
3
  require 'cgi'
4
- require "activesupport"
4
+ require 'yaml'
5
+ require "active_support/inflector"
6
+ require "active_support/inflections"
5
7
  require "#{dir}/erector/extensions/object"
6
8
  require "#{dir}/erector/raw_string"
7
9
  require "#{dir}/erector/widget"
data/lib/erector/erect.rb CHANGED
@@ -11,7 +11,7 @@ module Erector
11
11
  @output_dir = nil
12
12
 
13
13
  opts = OptionParser.new do |opts|
14
- opts.banner = "Usage: erect [options] [file|dir]*"
14
+ opts.banner = "Usage: erector [options] [file|dir]*"
15
15
 
16
16
  opts.separator "Converts from html/rhtml files to erector widgets, or from erector widgets to html files"
17
17
  opts.separator ""
@@ -37,7 +37,7 @@ module Erector
37
37
  else
38
38
  File.open(filename, "w") do |f|
39
39
  f.puts("class #{classname} < Erector::Widget")
40
- f.puts(" def render")
40
+ f.puts(" def content")
41
41
  f.puts(parsed.set_indent(2).convert)
42
42
  f.puts(" end")
43
43
  f.puts("end")
data/lib/erector/rails.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  dir = File.dirname(__FILE__)
2
2
  require "action_controller"
3
- require "#{dir}/rails/extensions/widget"
3
+ require "#{dir}/rails/rails_version"
4
+ require "#{dir}/rails/extensions/rails_widget"
4
5
  require "#{dir}/rails/extensions/action_controller"
5
6
  require "#{dir}/rails/extensions/action_view"
6
7
  require "#{dir}/rails/template_handlers/action_view_template_handler"
@@ -1,8 +1,26 @@
1
- dir = File.dirname(__FILE__)
2
- if (
3
- ActionController::Base.instance_methods + ActionController::Base.private_instance_methods).
4
- include?("add_variables_to_assigns")
5
- require File.expand_path("#{dir}/action_controller/1.2.5/action_controller")
6
- else
7
- require File.expand_path("#{dir}/action_controller/2.2.0/action_controller")
1
+ ActionController::Base.class_eval do
2
+ def render_widget(widget_class, assigns=nil)
3
+ @__widget_class = widget_class
4
+ if assigns
5
+ @__widget_assigns = assigns
6
+ else
7
+ @__widget_assigns = {}
8
+ variables = instance_variable_names
9
+ variables -= protected_instance_variables
10
+ variables.each do |name|
11
+ @__widget_assigns[name.sub('@', "")] = instance_variable_get(name)
12
+ end
13
+ end
14
+ response.template.send(:_evaluate_assigns_and_ivars)
15
+ render :inline => "<% @__widget_class.new(@__widget_assigns).to_s(:output => output_buffer, :helpers => self) %>"
16
+ end
17
+
18
+ def render_with_erector_widget(*options, &block)
19
+ if options.first.is_a?(Hash) && widget = options.first.delete(:widget)
20
+ render_widget widget, @assigns, &block
21
+ else
22
+ render_without_erector_widget *options, &block
23
+ end
24
+ end
25
+ alias_method_chain :render, :erector_widget
8
26
  end
@@ -0,0 +1,38 @@
1
+ module Erector
2
+ class RailsWidget < Widget
3
+ def output
4
+ process_output_buffer || @output
5
+ end
6
+
7
+ def capture_with_helpers(&block)
8
+ helpers ? helpers.capture(&block) : capture_without_helpers(&block)
9
+ end
10
+
11
+ alias_method_chain :capture, :helpers
12
+
13
+ # This is here to force #helpers.capture to return the output
14
+ def __in_erb_template; end
15
+
16
+ private
17
+
18
+ def process_output_buffer
19
+ if helpers.respond_to?(:output_buffer)
20
+ buffer = helpers.output_buffer
21
+ buffer.is_a?(String) ? buffer : handle_rjs_buffer
22
+ else
23
+ nil
24
+ end
25
+ end
26
+
27
+ def handle_rjs_buffer
28
+ returning buffer = helpers.output_buffer.dup.to_s do
29
+ helpers.output_buffer.clear
30
+ helpers.with_output_buffer(buffer) do
31
+ buffer << helpers.output_buffer.to_s
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+
38
+ require "#{File.dirname(__FILE__)}/rails_widget/helpers"
@@ -1,5 +1,5 @@
1
1
  module Erector
2
- Widget.class_eval do
2
+ class RailsWidget < Widget
3
3
  include ActionController::UrlWriter
4
4
 
5
5
  # helpers returning raw text
@@ -105,13 +105,6 @@ module Erector
105
105
 
106
106
  def pluralize(*args)
107
107
  helpers.pluralize(*args)
108
- end
108
+ end
109
109
  end
110
110
  end
111
-
112
- dir = File.dirname(__FILE__)
113
- if ActionView::Base.instance_methods.include?("output_buffer")
114
- require "#{dir}/widget/2.2.0/widget"
115
- else
116
- require "#{dir}/widget/1.2.5/widget"
117
- end
@@ -0,0 +1,6 @@
1
+ module Erector
2
+ module Rails
3
+ RAILS_VERSION = "2.3.2"
4
+ RAILS_VERSION_TAG = "v2.3.2"
5
+ end
6
+ end
@@ -1,14 +1,46 @@
1
- dir = File.dirname(__FILE__)
2
- if ActionView.const_defined?(:TemplateHandlers)
3
- if ::ActionView::TemplateHandlers::const_defined?(:Compilable)
4
- if ActionView.const_defined?(:TemplateHandlers) && ::ActionView::TemplateHandlers::ERB.respond_to?(:erb_trim_mode)
5
- require File.expand_path("#{dir}/2.2.0/action_view_template_handler")
6
- else
7
- require File.expand_path("#{dir}/2.1.0/action_view_template_handler")
1
+ module ActionView #:nodoc:
2
+ module TemplateHandlers #:nodoc:
3
+ class Erector < TemplateHandler
4
+ include Compilable
5
+ def self.line_offset
6
+ 2
7
+ end
8
+
9
+ ActionView::Template.instance_eval do
10
+ register_template_handler :rb, ActionView::TemplateHandlers::Erector
11
+ end
12
+
13
+ def compile(template)
14
+ relative_path_parts = template.path.split('/')
15
+
16
+ is_partial = relative_path_parts.last =~ /^_/
17
+ require_dependency File.expand_path(template.filename)
18
+
19
+ widget_class_parts = relative_path_parts.inject(['Views']) do |class_parts, node|
20
+ class_parts << node.gsub(/^_/, "").gsub(/(\.html)?\.rb$/, '').camelize
21
+ class_parts
22
+ end
23
+ widget_class_name = widget_class_parts.join("::")
24
+ render_method = is_partial ? 'render_partial' : 'content'
25
+
26
+ erb_template = <<-ERB
27
+ <%
28
+ assigns = instance_variables.inject({}) do |hash, name|
29
+ hash[name.sub('@', "")] = instance_variable_get(name)
30
+ hash
31
+ end
32
+
33
+ widget = #{widget_class_name}.new(assigns)
34
+ widget.to_s(:output => output_buffer, :helpers => self, :content_method_name => :#{render_method})
35
+ %>
36
+ ERB
37
+ ::ERB.new(
38
+ erb_template,
39
+ nil,
40
+ ::ActionView::TemplateHandlers::ERB.erb_trim_mode,
41
+ "@output_buffer"
42
+ ).src
43
+ end
8
44
  end
9
- else
10
- require File.expand_path("#{dir}/2.0.0/action_view_template_handler")
11
45
  end
12
- else
13
- require File.expand_path("#{dir}/1.2.5/action_view_template_handler")
14
46
  end
@@ -1,8 +1,10 @@
1
- ##
1
+ ##
2
2
  # Erector view framework
3
3
  module Erector
4
4
  if !Erector.const_defined?(:VERSION)
5
- VERSION = "0.5.1"
5
+ dir = File.dirname(__FILE__)
6
+ version = YAML.load_file(File.expand_path("#{dir}/../../VERSION.yml"))
7
+ VERSION = "#{version[:major]}.#{version[:minor]}.#{version[:patch]}"
6
8
  end
7
9
  end
8
10
 
@@ -3,17 +3,27 @@ module Erector
3
3
  # A Widget is the center of the Erector universe.
4
4
  #
5
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
6
+ # the +content+ method. Inside this method you may call any of the tag methods like +span+ or +p+ to emit HTML/XML
7
7
  # tags.
8
8
  #
9
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.
10
+ # +content+ method is called.
11
11
  #
12
12
  # To render a widget from the outside, instantiate it and call its +to_s+ method.
13
+ #
14
+ # A widget's +new+ method optionally accepts an options hash. Entries in this hash are converted to instance
15
+ # variables, and +attr_reader+ accessors are defined for each.
16
+ #
17
+ # TODO: You can add runtime input checking via the +needs+ macro. If any of the variables named via
18
+ # +needs+ are absent, an exception is thrown. Optional variables are specified with +wants+. If a variable appears
19
+ # in the options hash that is in neither the +needs+ nor +wants+ lists, then that too provokes an exception.
20
+ # This mechanism is meant to ameliorate development-time confusion about exactly what parameters are supported
21
+ # by a given widget, avoiding confusing runtime NilClass errors.
13
22
  #
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+.
23
+ # To call one widget from another, inside the parent widget's +content+ method, instantiate the child widget and call
24
+ # the +widget+ method. This assures that the same output stream
25
+ # is used, which gives better performance than using +capture+ or +to_s+. It also preserves the indentation and
26
+ # helpers of the enclosing class.
17
27
  #
18
28
  # In this documentation we've tried to keep the distinction clear between methods that *emit* text and those that
19
29
  # *return* text. "Emit" means that it writes to the output stream; "return" means that it returns a string
@@ -64,14 +74,72 @@ module Erector
64
74
  raise ArgumentError, "You must provide either an instance or a block"
65
75
  end
66
76
  end
67
-
77
+
68
78
  protected
69
79
  def after_initialize_parts
70
80
  @after_initialize_parts ||= []
71
81
  end
72
82
  end
73
83
 
74
- cattr_accessor :prettyprint_default
84
+ # Class method by which widget classes can declare that they need certain parameters.
85
+ # If needed parameters are not passed in to #new, then an exception will be thrown
86
+ # (with a hopefully useful message about which parameters are missing). This is intended
87
+ # to catch silly bugs like passing in a parameter called 'name' to a widget that expects
88
+ # a parameter called 'title'. Every variable declared in 'needs' will get an attr_reader
89
+ # accessor declared for it.
90
+ #
91
+ # You can also declare default values for parameters using hash syntax. You can put #needs
92
+ # declarations on multiple lines or on the same line; the only caveat is that if there are
93
+ # default values, they all have to be at the end of the line (so they go into the magic
94
+ # hash parameter).
95
+ #
96
+ # If a widget has no #needs declaration then it will accept any combination of parameters
97
+ # (and make accessors for them) just like normal. In that case there will be no 'attr_reader's
98
+ # declared.
99
+ # If a widget wants to declare that it
100
+ # takes no parameters, use the special incantation "needs nil" (and don't declare any other
101
+ # needs, or kittens will cry).
102
+ #
103
+ # Usage:
104
+ # class FancyForm < Erector::Widget
105
+ # needs :title, :show_okay => true, :show_cancel => false
106
+ # ...
107
+ # end
108
+ #
109
+ # That means that
110
+ # FancyForm.new(:title => 'Login')
111
+ # will succeed, as will
112
+ # FancyForm.new(:title => 'Login', :show_cancel => true)
113
+ # but
114
+ # FancyForm.new(:name => 'Login')
115
+ # will fail.
116
+ #
117
+ def self.needs(*args)
118
+ args.each do |arg|
119
+ (@needs ||= []) << (arg.nil? ? nil : (arg.is_a? Hash) ? arg : arg.to_sym)
120
+ end
121
+ end
122
+
123
+ protected
124
+ def self.get_needs
125
+ @needs ||= []
126
+ parent = self.ancestors[1]
127
+ if parent.respond_to? :get_needs
128
+ parent.get_needs + @needs
129
+ else
130
+ @needs
131
+ end
132
+ end
133
+
134
+ public
135
+ @@prettyprint_default = false
136
+ def prettyprint_default
137
+ @@prettyprint_default
138
+ end
139
+
140
+ def self.prettyprint_default=(enabled)
141
+ @@prettyprint_default = enabled
142
+ end
75
143
 
76
144
  NON_NEWLINEY = {'i' => true, 'b' => true, 'small' => true,
77
145
  'img' => true, 'span' => true, 'a' => true,
@@ -80,95 +148,163 @@ module Erector
80
148
 
81
149
  SPACES_PER_INDENT = 2
82
150
 
83
- attr_reader :helpers, :assigns, :block, :parent, :output
84
- attr_accessor :enable_prettyprint
151
+ attr_reader :helpers, :assigns, :block, :parent, :output, :prettyprint, :indentation
85
152
 
86
- def initialize(helpers=nil, assigns={}, output = "", &block)
153
+ def initialize(assigns={}, &block)
154
+ unless assigns.is_a? Hash
155
+ 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."
156
+ end
157
+ if (respond_to? :render) &&
158
+ !self.method(:render).to_s.include?("(RailsWidget)")
159
+ raise "Erector's API has changed. You should rename #{self.class}#render to #content."
160
+ end
87
161
  @assigns = assigns
88
162
  assign_locals(assigns)
89
- @helpers = helpers
90
163
  @parent = block ? eval("self", block.binding) : nil
91
- @output = output
92
164
  @block = block
93
- @at_start_of_line = true
94
- @indent = 0
95
- @enable_prettyprint = prettyprint_default
96
165
  self.class.after_initialize self
97
166
  end
98
167
 
99
168
  #-- methods for other classes to call, left public for ease of testing and documentation
100
169
  #++
101
170
 
171
+ protected
172
+ def context(output, prettyprint = false, indentation = 0, helpers = nil)
173
+ #TODO: pass in options hash, maybe, instead of parameters
174
+ original_output = @output
175
+ original_indendation = @indentation
176
+ original_helpers = @helpers
177
+ original_prettyprint = @prettyprint
178
+ @output = output
179
+ @at_start_of_line = true
180
+ raise "indentation must be a number, not #{indentation.inspect}" unless indentation.is_a? Fixnum
181
+ @indentation = indentation
182
+ @helpers = helpers
183
+ @prettyprint = prettyprint
184
+ yield
185
+ ensure
186
+ @output = original_output
187
+ @indentation = original_indendation
188
+ @helpers = original_helpers
189
+ @prettyprint = original_prettyprint
190
+ end
191
+
192
+ public
102
193
  def assign_locals(local_assigns)
103
- local_assigns.each do |name, value|
104
- instance_variable_set("@#{name}", value)
194
+ needed = self.class.get_needs.map{|need| need.is_a?(Hash) ? need.keys : need}.flatten
195
+ assigned = []
196
+ local_assigns.each do |name, value|
197
+ unless needed.empty? || needed.include?(name)
198
+ raise "Unknown parameter '#{name}'. #{self.class.name} only accepts #{needed.join(', ')}"
199
+ end
200
+ assign_local(name, value)
201
+ assigned << name
202
+ end
203
+
204
+ # set variables with default values
205
+ self.class.get_needs.select{|var| var.is_a? Hash}.each do |hash|
206
+ hash.each_pair do |name, value|
207
+ unless assigned.include?(name)
208
+ assign_local(name, value)
209
+ assigned << name
210
+ end
211
+ end
212
+ end
213
+
214
+ missing = needed - assigned
215
+ unless missing.empty? || missing == [nil]
216
+ raise "Missing parameter#{missing.size == 1 ? '' : 's'}: #{missing.join(', ')}"
217
+ end
218
+ end
219
+
220
+ def assign_local(name, value)
221
+ instance_variable_set("@#{name}", value)
222
+ if any_are_needed?
105
223
  metaclass.module_eval do
106
224
  attr_reader name
107
225
  end
108
226
  end
109
227
  end
110
228
 
111
- # Set whether Erector should add newlines and indentation in to_s.
112
- # This is an experimental feature and is subject to change
113
- # (either in terms of how it is enabled, or in terms of
114
- # what decisions Erector makes about where to add whitespace).
115
- # This flag should be set prior to any rendering being done
116
- # (for example, calls to to_s or to_pretty).
117
- def enable_prettyprint(enable)
118
- self.enable_prettyprint = enable
119
- self
229
+ def any_are_needed?
230
+ !self.class.get_needs.empty?
120
231
  end
121
-
232
+
122
233
  # Render (like to_s) but adding newlines and indentation.
234
+ # This is a convenience method; you may just want to call to_s(:prettyprint => true)
235
+ # so you can pass in other rendering options as well.
123
236
  def to_pretty
124
- enable_prettyprint(true).to_s
237
+ to_s(:prettyprint => true)
125
238
  end
126
239
 
127
- # Entry point for rendering a widget (and all its children). This method creates a new output string,
128
- # calls this widget's #render method and returns the string.
240
+ # Entry point for rendering a widget (and all its children). This method creates a new output string (if necessary),
241
+ # calls this widget's #content method and returns the string.
129
242
  #
130
- # If it's called again later
131
- # then it returns the earlier rendered string, which may lead to higher performance, but may have confusing
132
- # effects if some underlying state has changed. In general we recommend you create a new instance of every
133
- # widget for each render, unless you know what you're doing.
134
- def to_s(render_method_name=:render, &blk)
135
- # The @__to_s variable is used as a cache.
136
- # If it's useful we should add a test for it. -ac
137
- return @__to_s if @__to_s
138
- send(render_method_name, &blk)
139
- @__to_s = output.to_s
243
+ # Options:
244
+ # output:: the string to output to. Default: a new empty string
245
+ # prettyprint:: whether Erector should add newlines and indentation. Default: the value of prettyprint_default (which is false by default).
246
+ # indentation:: the amount of spaces to indent. Ignored unless prettyprint is true.
247
+ # helpers:: a helpers object containing utility methods. Usually this is a Rails view object.
248
+ # content_method_name:: in case you want to call a method other than #content, pass its name in here.
249
+ #
250
+ # Note: Prettyprinting is an experimental feature and is subject to change
251
+ # (either in terms of how it is enabled, or in terms of
252
+ # what decisions Erector makes about where to add whitespace).
253
+ def to_s(options = {}, &blk)
254
+
255
+ 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
256
+
257
+ options = {
258
+ :output => "",
259
+ :prettyprint => prettyprint_default,
260
+ :indentation => 0,
261
+ :helpers => nil,
262
+ :content_method_name => :content,
263
+ }.merge(options)
264
+ context(options[:output], options[:prettyprint], options[:indentation], options[:helpers]) do
265
+ send(options[:content_method_name], &blk)
266
+ output.to_s
267
+ end
140
268
  end
141
269
 
142
270
  alias_method :inspect, :to_s
143
-
271
+
144
272
  # Template method which must be overridden by all widget subclasses. Inside this method you call the magic
145
273
  # #element methods which emit HTML and text to the output string.
146
- def render
274
+ def content
147
275
  if @block
148
276
  instance_eval(&@block)
149
277
  end
150
278
  end
151
279
 
152
- # To call one widget from another, inside the parent widget's render method, instantiate the child widget and call
153
- # its +render_to+ method, passing in +self+ (or self.output if you prefer). This assures that the same output string
280
+ # To call one widget from another, inside the parent widget's +content+ method, instantiate the child widget and call
281
+ # its +write_via+ method, passing in +self+. This assures that the same output string
154
282
  # is used, which gives better performance than using +capture+ or +to_s+.
155
- def render_to(output_or_widget)
156
- if output_or_widget.is_a?(Widget)
157
- @parent = output_or_widget
158
- @output = @parent.output
159
- else
160
- @output = output_or_widget
283
+ # You can also use the +widget+ method.
284
+ def write_via(parent)
285
+ @parent = parent
286
+ context(parent.output, parent.prettyprint, parent.indentation, parent.helpers) do
287
+ content
161
288
  end
162
- render
163
289
  end
164
290
 
165
- # Convenience method for on-the-fly widgets. This is a way of making
166
- # a sub-widget which still has access to the methods of the parent class.
167
- # This is an experimental erector feature which may disappear in future
168
- # versions of erector (see #widget in widget_spec in the Erector tests).
169
- def widget(widget_class, assigns={}, &block)
170
- child = widget_class.new(helpers, assigns, output, &block)
171
- child.render
291
+ # Emits a (nested) widget onto the current widget's output stream. Accepts either
292
+ # a class or an instance. If the first argument is a class, then the second argument
293
+ # is a hash used to populate its instance variables. If the first argument is an
294
+ # instance then the hash must be unspecified (or empty).
295
+ #
296
+ # The sub-widget will have access to the methods of the parent class, via some method_missing
297
+ # magic and a "parent" pointer.
298
+ def widget(target, assigns={}, &block)
299
+ child = if target.is_a? Class
300
+ target.new(assigns, &block)
301
+ else
302
+ unless assigns.empty?
303
+ raise "Unexpected second parameter. Did you mean to pass in variables when you instantiated the #{target.class.to_s}?"
304
+ end
305
+ target
306
+ end
307
+ child.write_via(self)
172
308
  end
173
309
 
174
310
  # (Should we make this hidden?)
@@ -220,7 +356,7 @@ module Erector
220
356
  # Emits an open tag, comprising '<', tag name, optional attributes, and '>'
221
357
  def open_tag(tag_name, attributes={})
222
358
  indent_for_open_tag(tag_name)
223
- @indent += SPACES_PER_INDENT
359
+ @indentation += SPACES_PER_INDENT
224
360
 
225
361
  output.concat "<#{tag_name}#{format_attributes(attributes)}>"
226
362
  @at_start_of_line = false
@@ -232,7 +368,11 @@ module Erector
232
368
  # If another kind of object is passed in, the result of calling
233
369
  # its to_s method will be treated as a string would be.
234
370
  def text(value)
235
- output.concat(value.html_escape)
371
+ if value.is_a? Widget
372
+ widget value
373
+ else
374
+ output.concat(value.html_escape)
375
+ end
236
376
  @at_start_of_line = false
237
377
  nil
238
378
  end
@@ -272,20 +412,19 @@ module Erector
272
412
 
273
413
  # Emits a close tag, consisting of '<', tag name, and '>'
274
414
  def close_tag(tag_name)
275
- @indent -= SPACES_PER_INDENT
415
+ @indentation -= SPACES_PER_INDENT
276
416
  indent()
277
417
 
278
418
  output.concat("</#{tag_name}>")
279
419
 
280
420
  if newliney(tag_name)
281
- output.concat "\n"
282
- @at_start_of_line = true
421
+ _newline
283
422
  end
284
423
  end
285
424
 
286
425
  # Emits the result of joining the elements in array with the separator.
287
426
  # The array elements and separator can be Erector::Widget objects,
288
- # which are rendered, or strings, which are quoted and output.
427
+ # which are rendered, or strings, which are html-escaped and output.
289
428
  def join(array, separator)
290
429
  first = true
291
430
  array.each do |widget_or_text|
@@ -304,7 +443,7 @@ module Erector
304
443
 
305
444
  # Creates a whole new output string, executes the block, then converts the output string to a string and
306
445
  # emits it as raw text. If at all possible you should avoid this method since it hurts performance,
307
- # and use #render_to instead.
446
+ # and use +content+ or +write_via+ instead.
308
447
  def capture(&block)
309
448
  begin
310
449
  original_output = output
@@ -375,10 +514,11 @@ module Erector
375
514
  rawtext "\n"
376
515
  end
377
516
 
378
- # Convenience method to emit a css file link, which looks like this: <link href="erector.css" rel="stylesheet" type="text/css" />
517
+ # Convenience method to emit a css file link, which looks like this:
518
+ # <link href="erector.css" rel="stylesheet" type="text/css" />
379
519
  # The parameter is the full contents of the href attribute, including any ".css" extension.
380
520
  #
381
- # If you want to emit raw CSS inline, use the #script method instead.
521
+ # If you want to emit raw CSS inline, use the #style method instead.
382
522
  def css(href)
383
523
  link :rel => 'stylesheet', :type => 'text/css', :href => href
384
524
  end
@@ -389,7 +529,7 @@ module Erector
389
529
  end
390
530
 
391
531
  def newliney(tag_name)
392
- if @enable_prettyprint
532
+ if @prettyprint
393
533
  !NON_NEWLINEY.include?(tag_name)
394
534
  else
395
535
  false
@@ -427,6 +567,9 @@ protected
427
567
  end
428
568
  attributes ||= {}
429
569
  open_tag tag_name, attributes
570
+ if block && value
571
+ raise ArgumentError, "You can't pass both a block and a value to #{tag_name} -- please choose one."
572
+ end
430
573
  if block
431
574
  instance_eval(&block)
432
575
  else
@@ -441,23 +584,27 @@ protected
441
584
  output.concat "<#{tag_name}#{format_attributes(attributes)} />"
442
585
 
443
586
  if newliney(tag_name)
444
- output.concat "\n"
445
- @at_start_of_line = true
587
+ _newline
446
588
  end
447
589
  end
590
+
591
+ def _newline
592
+ return unless @prettyprint
593
+ output.concat "\n"
594
+ @at_start_of_line = true
595
+ end
448
596
 
449
597
  def indent_for_open_tag(tag_name)
598
+ return unless @prettyprint
450
599
  if !@at_start_of_line && newliney(tag_name)
451
- output.concat "\n"
452
- @at_start_of_line = true
600
+ _newline
453
601
  end
454
-
455
602
  indent()
456
603
  end
457
604
 
458
605
  def indent()
459
606
  if @at_start_of_line
460
- output.concat " " * @indent
607
+ output.concat " " * @indentation
461
608
  end
462
609
  end
463
610