erector 0.7.1 → 0.7.2

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.
data/README.txt CHANGED
@@ -62,6 +62,20 @@ To install as a Rails plugin:
62
62
  When installing this way, erector is automatically available to your Rails code
63
63
  (no require directive is needed).
64
64
 
65
+ == CREDITS:
66
+
67
+ Core Team:
68
+ * Alex Chaffee
69
+ * Brian Takita
70
+
71
+ Special Thanks To:
72
+ * Abby (Chaffee's muse & Best friend)
73
+ * Jim Kingdon
74
+ * Jeff Dean
75
+ * John Firebaugh
76
+ * Nathan Sobo
77
+ * Nick Kallen
78
+
65
79
  == LICENSE:
66
80
 
67
81
  (The MIT License)
data/VERSION.yml CHANGED
@@ -1,4 +1,4 @@
1
1
  ---
2
2
  :major: 0
3
3
  :minor: 7
4
- :patch: 1
4
+ :patch: 2
data/bin/erector CHANGED
@@ -1,8 +1,13 @@
1
1
  #!/usr/bin/env ruby
2
- dir = File.expand_path(File.dirname(__FILE__))
3
- $LOAD_PATH.unshift("#{dir}/../lib")
4
- require "erector"
5
- require "erector/erect"
2
+
3
+ begin
4
+ require 'erector'
5
+ rescue LoadError
6
+ require 'rubygems'
7
+ require 'erector'
8
+ end
9
+
10
+ require 'erector/erect'
6
11
 
7
12
  unless Erector::Erect.new(ARGV).run
8
13
  exit 1
data/lib/erector.rb CHANGED
@@ -1,19 +1,13 @@
1
- require "rubygems"
2
- dir = File.dirname(__FILE__)
3
- require 'cgi'
4
- require 'yaml'
1
+ require "cgi"
2
+ require "yaml"
5
3
  require "active_support/inflector"
6
4
  require "active_support/inflections"
7
- require "#{dir}/erector/extensions/object"
8
- require "#{dir}/erector/raw_string"
9
- require "#{dir}/erector/externals"
10
- require "#{dir}/erector/widget"
11
- require "#{dir}/erector/inline"
12
- require "#{dir}/erector/unicode"
13
- require "#{dir}/erector/widgets"
14
- require "#{dir}/erector/version"
15
- require "#{dir}/erector/mixin"
16
- if Object.const_defined?(:RAILS_ROOT)
17
- require "#{dir}/erector/rails"
18
- end
19
-
5
+ require "erector/extensions/object"
6
+ require "erector/raw_string"
7
+ require "erector/externals"
8
+ require "erector/widget"
9
+ require "erector/inline"
10
+ require "erector/unicode"
11
+ require "erector/widgets"
12
+ require "erector/version"
13
+ require "erector/mixin"
@@ -20,7 +20,7 @@ module Erector
20
20
  parent = File.dirname(@in_file)
21
21
  grandparent = File.dirname(parent)
22
22
  if File.basename(grandparent) == "views"
23
- ["Views::" + classize(File.basename(parent)) + "::" + base, "Erector::RailsWidget"]
23
+ ["Views::" + classize(File.basename(parent)) + "::" + base, "Erector::Widget"]
24
24
  else
25
25
  [base, "Erector::Widget"]
26
26
  end
@@ -1,28 +1,31 @@
1
1
  module Erector
2
+ class External < Struct.new(:type, :klass, :text, :options)
3
+ def initialize(type, klass, text, options = {})
4
+ super(type.to_sym, klass, text, options)
5
+ end
6
+
7
+ def ==(other)
8
+ (self.type == other.type and
9
+ self.text == other.text and
10
+ self.options == other.options) ? true : false
11
+ end
12
+ end
13
+
2
14
  module Externals
3
15
  def externals(type, klass = nil)
4
16
  type = type.to_sym
5
- assure_externals_declared(type, klass)
6
- x = @@externals[type].dup
7
- if klass
8
- x.select{|value| @@externals[klass].include?(value)}
9
- else
10
- x
17
+ (@@externals ||= []).select do |x|
18
+ x.type == type &&
19
+ (klass.nil? || x.klass == klass)
11
20
  end
12
21
  end
13
22
 
14
- def assure_externals_declared(type, klass)
15
- @@externals ||= {}
16
- @@externals[type] ||= []
17
- @@externals[klass] ||= [] if klass
18
- end
19
-
20
- def external(type, value)
23
+ def external(type, value, options = {})
21
24
  type = type.to_sym
22
25
  klass = self # since it's a class method, self should be the class itself
23
- assure_externals_declared(type, klass)
24
- @@externals[type] << value unless @@externals[type].include?(value)
25
- @@externals[klass] << value unless @@externals[klass].include?(value)
26
+ x = External.new(type, klass, value, options)
27
+ @@externals ||= []
28
+ @@externals << x unless @@externals.include?(x)
26
29
  end
27
30
  end
28
31
 
@@ -4,17 +4,18 @@ module Erector
4
4
  end
5
5
 
6
6
  module Inline
7
- # Evaluates its block (the one that was passed in the constructor) as a new widget's
8
- # content method.
9
- # Since "self" is pointing to the new widget, it can get access
10
- # to parent widget methods via method_missing. Since it executes inside the
11
- # widget it does not
12
- # have access to instance variables of the caller, although it does
13
- # have access to bound variables.
14
- def content
15
- if @block
16
- instance_eval(&@block)
17
- end
7
+ # Executes the widget's block (the one that was passed in the
8
+ # constructor). Since "self" is pointing to the new widget, the block does
9
+ # not naturally have access to parent method methods, so an
10
+ # Erector::Inline widget uses some method_missing black magic to propagate
11
+ # messages to the parent object. Since it executes inside the *called*
12
+ # widget's context, when the block refers to instance variables, it's
13
+ # talking about those of this widget, not the caller. It does, of course,
14
+ # have access to bound local variables of the caller, so you can use those
15
+ # to smuggle in instance variables.
16
+ def call_block
17
+ # note that instance_eval seems to pass in self as a parameter to the block
18
+ instance_eval(&@block) if @block
18
19
  end
19
20
 
20
21
  private
data/lib/erector/mixin.rb CHANGED
@@ -3,9 +3,12 @@ module Erector
3
3
  # Executes the block as if it were the content body of a fresh Erector::Inline,
4
4
  # and returns the #to_s value. Since it executes inside the new widget it does not
5
5
  # have access to instance variables of the caller, although it does
6
- # have access to bound variables.
6
+ # have access to bound variables. Funnily enough, the options are passed in to both
7
+ # to_s *and* to the widget itself, so they show up as instance variables.
7
8
  def erector(options = {}, &block)
8
- Erector.inline(&block).to_s(options)
9
+ vars = options.dup
10
+ vars.delete_if {|key, value| Erector::Widget::RESERVED_INSTANCE_VARS.include?(key)}
11
+ Erector.inline(vars, &block).to_s(options)
9
12
  end
10
13
  end
11
14
  end
data/lib/erector/rails.rb CHANGED
@@ -1,10 +1,27 @@
1
- dir = File.dirname(__FILE__)
2
1
  require "action_controller"
3
- require "#{dir}/rails/rails_version"
4
- require "#{dir}/rails/rails_form_builder"
5
- require "#{dir}/rails/extensions/rails_widget"
6
- require "#{dir}/rails/extensions/action_controller"
7
- require "#{dir}/rails/extensions/action_view"
8
- require "#{dir}/rails/extensions/rails_widget/rails_helpers"
9
- require "#{dir}/rails/template_handlers/rb_handler"
10
- require "#{dir}/rails/template_handlers/ert_handler"
2
+ require "erector/rails/rails_version"
3
+ require "erector/rails/rails_form_builder"
4
+ require "erector/rails/extensions/rails_widget"
5
+ require "erector/rails/extensions/action_controller"
6
+ require "erector/rails/extensions/rails_widget/rails_helpers"
7
+ require "erector/rails/template_handlers/rb_handler"
8
+ require "erector/rails/template_handlers/ert_handler"
9
+
10
+ module Erector
11
+ def self.init_rails(binding)
12
+ # Rails defaults do not include app/views in the eager load path.
13
+ # It needs to be there, because erector views are .rb files.
14
+ if config = eval("config if defined? config", binding)
15
+ view_path = config.view_path
16
+ config.load_paths << view_path unless config.load_paths.include?(view_path)
17
+ config.eager_load_paths << view_path unless config.eager_load_paths.include?(view_path)
18
+
19
+ # Rails probably already ran Initializer#set_load_path and
20
+ # #set_autoload_paths by the time we got here.
21
+ $LOAD_PATH.unshift(view_path) unless $LOAD_PATH.include?(view_path)
22
+ unless ActiveSupport::Dependencies.load_paths.include?(view_path)
23
+ ActiveSupport::Dependencies.load_paths << view_path
24
+ end
25
+ end
26
+ end
27
+ end
@@ -1,23 +1,11 @@
1
1
  ActionController::Base.class_eval do
2
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.merge(:parent => self)).to_s %>"
3
+ render :text => Erector::Rails.render(widget_class, self, assigns)
16
4
  end
17
5
 
18
6
  def render_with_erector_widget(*options, &block)
19
7
  if options.first.is_a?(Hash) && widget = options.first.delete(:widget)
20
- render_widget widget, @assigns, &block
8
+ render_widget widget, @assigns
21
9
  else
22
10
  render_without_erector_widget *options, &block
23
11
  end
@@ -1,44 +1,84 @@
1
1
  module Erector
2
- class RailsWidget < Widget
3
- def self.inline(*args, &block)
4
- InlineRailsWidget.new(*args, &block)
5
- end
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
6
20
 
7
- def output
8
- process_output_buffer || @output
9
- end
21
+ widget = widget.new(assigns)
22
+ end
23
+
24
+ view = controller.response.template
25
+ view.send(:_evaluate_assigns_and_ivars)
10
26
 
11
- def capture_with_parent(&block)
12
- parent ? parent.capture(&block) : capture_without_parent(&block)
27
+ view.with_output_buffer do
28
+ widget.to_s(
29
+ :output => view.output_buffer,
30
+ :parent => view,
31
+ :helpers => view
32
+ )
33
+ end
13
34
  end
14
35
 
15
- alias_method_chain :capture, :parent
36
+ module WidgetExtensions
37
+ def self.included(base)
38
+ base.alias_method_chain :output, :parent
39
+ base.alias_method_chain :capture, :parent
40
+ end
16
41
 
17
- # This is here to force #parent.capture to return the output
18
- def __in_erb_template; end
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
45
+ else
46
+ output_without_parent
47
+ end
48
+ end
19
49
 
20
- private
50
+ def capture_with_parent(&block)
51
+ parent ? raw(parent.capture(&block).to_s) : capture_without_parent(&block)
52
+ end
21
53
 
22
- def process_output_buffer
23
- if parent.respond_to?(:output_buffer)
24
- parent.output_buffer.is_a?(String) ? parent.output_buffer : handle_rjs_buffer
25
- else
26
- nil
54
+ # This is here to force #parent.capture to return the output
55
+ def __in_erb_template;
27
56
  end
28
- end
29
57
 
30
- def handle_rjs_buffer
31
- returning buffer = parent.output_buffer.dup.to_s do
32
- parent.output_buffer.clear
33
- parent.with_output_buffer(buffer) do
34
- buffer << parent.output_buffer.to_s
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
35
66
  end
36
67
  end
37
68
  end
69
+
70
+ Erector::Widget.send :include, WidgetExtensions
71
+ 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
38
79
  end
39
80
 
40
81
  class InlineRailsWidget < RailsWidget
41
82
  include Inline
42
83
  end
43
-
44
84
  end
@@ -1,6 +1,6 @@
1
1
  module Erector
2
- class RailsWidget < Widget
3
- module RailsHelpers
2
+ module Rails
3
+ module Helpers
4
4
  include ActionController::UrlWriter
5
5
 
6
6
  # parent returning raw text whose first parameter is HTML escaped
@@ -131,6 +131,7 @@ module Erector
131
131
  parent.pluralize(*args)
132
132
  end
133
133
  end
134
- include RailsHelpers
134
+
135
+ Erector::Widget.send :include, Helpers
135
136
  end
136
137
  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::RailsWidget.inline do",
21
+ "r =::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,46 +1,11 @@
1
- module ActionView #:nodoc:
2
- module TemplateHandlers #:nodoc:
3
- class RbHandler < 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::RbHandler
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
1
+ module Erector
2
+ class RbHandler < ActionView::TemplateHandler
3
+ def render(template, local_assigns)
4
+ require_dependency File.expand_path(template.filename)
5
+ widget_class = "views/#{template.path_without_format_and_extension}".camelize.constantize
6
+ Erector::Rails.render(widget_class, @view.controller)
44
7
  end
45
8
  end
46
9
  end
10
+
11
+ ActionView::Template.register_template_handler :rb, Erector::RbHandler
@@ -49,24 +49,47 @@ module Erector
49
49
  # Tags which can contain other stuff. Click "[Source]" to see the full list.
50
50
  def full_tags
51
51
  [
52
- 'a', 'abbr', 'acronym', 'address',
52
+ 'a', 'abbr', 'acronym', 'address', 'article', 'aside', 'audio',
53
53
  'b', 'bdo', 'big', 'blockquote', 'body', 'button',
54
- 'caption', 'center', 'cite', 'code', 'colgroup',
55
- 'dd', 'del', 'dfn', 'div', 'dl', 'dt', 'em',
56
- 'embed',
57
- 'fieldset', 'form', 'frameset',
58
- 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'head', 'html', 'i',
59
- 'iframe', 'ins', 'kbd', 'label', 'legend', 'li', 'map',
60
- 'noframes', 'noscript',
61
- 'object', 'ol', 'optgroup', 'option', 'p', 'param', 'pre',
62
- 'q', 's',
63
- 'samp', 'script', 'select', 'small', 'span', 'strike',
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',
64
66
  'strong', 'style', 'sub', 'sup',
65
- 'table', 'tbody', 'td', 'textarea', 'tfoot',
66
- 'th', 'thead', 'title', 'tr', 'tt', 'u', 'ul', 'var'
67
+ 'table', 'tbody', 'td', 'textarea', 'tfoot',
68
+ 'th', 'thead', 'time', 'title', 'tr', 'tt', 'u', 'ul',
69
+ 'var', 'video'
67
70
  ]
68
71
  end
69
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
+
70
93
  def after_initialize(instance=nil, &blk)
71
94
  if blk
72
95
  after_initialize_parts << blk
@@ -139,6 +162,14 @@ module Erector
139
162
  end
140
163
  end
141
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
+
142
173
  public
143
174
  @@prettyprint_default = false
144
175
  def prettyprint_default
@@ -160,6 +191,7 @@ module Erector
160
191
 
161
192
  attr_reader *RESERVED_INSTANCE_VARS
162
193
  attr_reader :parent
194
+ attr_writer :block
163
195
 
164
196
  def initialize(assigns={}, &block)
165
197
  unless assigns.is_a? Hash
@@ -178,12 +210,14 @@ module Erector
178
210
  #++
179
211
 
180
212
  protected
181
- def context(output, prettyprint = false, indentation = 0, helpers = nil)
213
+ def context(parent, output, prettyprint = false, indentation = 0, helpers = nil)
182
214
  #TODO: pass in options hash, maybe, instead of parameters
215
+ original_parent = @parent
183
216
  original_output = @output
184
217
  original_indendation = @indentation
185
218
  original_helpers = @helpers
186
219
  original_prettyprint = @prettyprint
220
+ @parent = parent
187
221
  @output = output
188
222
  @at_start_of_line = true
189
223
  raise "indentation must be a number, not #{indentation.inspect}" unless indentation.is_a? Fixnum
@@ -192,6 +226,7 @@ module Erector
192
226
  @prettyprint = prettyprint
193
227
  yield
194
228
  ensure
229
+ @parent = original_parent
195
230
  @output = original_output
196
231
  @indentation = original_indendation
197
232
  @helpers = original_helpers
@@ -200,7 +235,7 @@ module Erector
200
235
 
201
236
  public
202
237
  def assign_instance_variables (instance_variables)
203
- needed = self.class.get_needs.map{|need| need.is_a?(Hash) ? need.keys : need}.flatten
238
+ needed = self.class.get_needed_variables
204
239
  assigned = []
205
240
  instance_variables.each do |name, value|
206
241
  unless needed.empty? || needed.include?(name)
@@ -211,7 +246,7 @@ module Erector
211
246
  end
212
247
 
213
248
  # set variables with default values
214
- self.class.get_needs.select{|var| var.is_a? Hash}.each do |hash|
249
+ self.class.get_needed_defaults.each do |hash|
215
250
  hash.each_pair do |name, value|
216
251
  unless assigned.include?(name)
217
252
  assign_instance_variable(name, value)
@@ -239,6 +274,11 @@ module Erector
239
274
  def to_pretty
240
275
  to_s(:prettyprint => true)
241
276
  end
277
+
278
+ # Render (like to_s) but stripping all tags.
279
+ def to_text
280
+ CGI.unescapeHTML(to_s(:prettyprint => false).gsub(/<[^>]*>/, ''))
281
+ end
242
282
 
243
283
  # Entry point for rendering a widget (and all its children). This method
244
284
  # creates a new output string (if necessary), calls this widget's #content
@@ -275,9 +315,10 @@ module Erector
275
315
  :prettyprint => prettyprint_default,
276
316
  :indentation => 0,
277
317
  :helpers => nil,
318
+ :parent => @parent,
278
319
  :content_method_name => :content,
279
320
  }.merge(options)
280
- context(options[:output], options[:prettyprint], options[:indentation], options[:helpers]) do
321
+ context(options[:parent], options[:output], options[:prettyprint], options[:indentation], options[:helpers]) do
281
322
  send(options[:content_method_name], &blk)
282
323
  output
283
324
  end
@@ -286,13 +327,27 @@ module Erector
286
327
  # Template method which must be overridden by all widget subclasses.
287
328
  # Inside this method you call the magic #element methods which emit HTML
288
329
  # and text to the output string. If you call "super" (or don't override
289
- # +content+) then your widget will render any block that was passed into
290
- # its constructor. If you want this block to have access to Erector methods
291
- # then see Erector::Inline#content or Erector#inline.
330
+ # +content+, or explicitly call "call_block") then your widget will
331
+ # execute the block that was passed into its constructor. The semantics of
332
+ # this block are confusing; make sure to read the rdoc for Erector#call_block
292
333
  def content
293
- if @block
294
- @block.call
295
- end
334
+ call_block
335
+ end
336
+
337
+ # When this method is executed, the default block that was passed in to
338
+ # the widget's constructor will be executed. The semantics of this
339
+ # block -- that is, what "self" is, and whether it has access to
340
+ # Erector methods like "div" and "text", and the widget's instance
341
+ # variables -- can be quite confusing. The rule is, most of the time the
342
+ # block is evaluated using "call" or "yield", which means that its scope
343
+ # is that of the caller. So if that caller is not an Erector widget, it
344
+ # will *not* have access to the Erector methods, but it *will* have access
345
+ # to instance variables and methods of the calling object.
346
+ #
347
+ # If you want this block to have access to Erector methods then use
348
+ # Erector::Inline#content or Erector#inline.
349
+ def call_block
350
+ @block.call(self) if @block
296
351
  end
297
352
 
298
353
  # To call one widget from another, inside the parent widget's +content+
@@ -301,8 +356,7 @@ module Erector
301
356
  # which gives better performance than using +capture+ or +to_s+. You can
302
357
  # also use the +widget+ method.
303
358
  def write_via(parent)
304
- @parent = parent
305
- context(parent.output, parent.prettyprint, parent.indentation, parent.helpers) do
359
+ context(parent, parent.output, parent.prettyprint, parent.indentation, parent.helpers) do
306
360
  content
307
361
  end
308
362
  end
@@ -311,10 +365,8 @@ module Erector
311
365
  # either a class or an instance. If the first argument is a class, then
312
366
  # the second argument is a hash used to populate its instance variables.
313
367
  # If the first argument is an instance then the hash must be unspecified
314
- # (or empty).
315
- #
316
- # The sub-widget will have access to the methods of the parent class, via
317
- # some method_missing magic and a "parent" pointer.
368
+ # (or empty). If a block is passed to this method, then it gets set as the
369
+ # rendered widget's block.
318
370
  def widget(target, assigns={}, &block)
319
371
  child = if target.is_a? Class
320
372
  target.new(assigns, &block)
@@ -322,6 +374,7 @@ module Erector
322
374
  unless assigns.empty?
323
375
  raise "Unexpected second parameter. Did you mean to pass in variables when you instantiated the #{target.class.to_s}?"
324
376
  end
377
+ target.block = block unless block.nil?
325
378
  target
326
379
  end
327
380
  child.write_via(self)
@@ -354,9 +407,14 @@ module Erector
354
407
  # how elegant it is? Not confusing at all if you don't think about it.
355
408
  #
356
409
  def element(*args, &block)
357
- __element__(*args, &block)
410
+ __element__(false, *args, &block)
358
411
  end
359
-
412
+
413
+ # Like +element+, but string parameters are not escaped.
414
+ def element!(*args, &block)
415
+ __element__(true, *args, &block)
416
+ end
417
+
360
418
  # Internal method used to emit a self-closing HTML/XML element, including
361
419
  # a tag name and optional attributes (passed in via the default hash).
362
420
  #
@@ -409,10 +467,12 @@ module Erector
409
467
  end
410
468
 
411
469
  # Emits text which will *not* be HTML-escaped. Same effect as text(raw(s))
412
- def rawtext(value)
470
+ def text!(value)
413
471
  text raw(value)
414
472
  end
415
473
 
474
+ alias rawtext text!
475
+
416
476
  # Returns a copy of value with spaces replaced by non-breaking space characters.
417
477
  # With no arguments, return a single non-breaking space.
418
478
  # The output uses the escaping format '&#160;' since that works
@@ -443,7 +503,7 @@ module Erector
443
503
 
444
504
  output <<("</#{tag_name}>")
445
505
 
446
- if newliney(tag_name)
506
+ if newliney?(tag_name)
447
507
  _newline
448
508
  end
449
509
  end
@@ -467,17 +527,36 @@ module Erector
467
527
  output << "<?xml#{format_sorted(sort_for_xml_declaration(attributes))}?>"
468
528
  end
469
529
 
470
- # Emits an HTML comment, which looks like this: &lt;!--foo--&gt;
530
+ # Emits an HTML comment (&lt;!-- ... --&gt;) surrounding +text+ and/or the output of +block+.
471
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
+ #
472
537
  # Since "Authors should avoid putting two or more adjacent hyphens inside comments,"
473
538
  # we emit a warning if you do that.
474
- def comment(text)
539
+ def comment(text = '', &block)
475
540
  puts "Warning: Authors should avoid putting two or more adjacent hyphens inside comments." if text =~ /--/
476
- output << "<!--#{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"
477
556
  end
478
557
 
479
558
  # Creates a whole new output string, executes the block, then converts the
480
- # output string to a string and emits it as raw text. If at all possible
559
+ # output string to a string and returns it as raw text. If at all possible
481
560
  # you should avoid this method since it hurts performance, and use
482
561
  # +widget+ or +write_via+ instead.
483
562
  def capture(&block)
@@ -492,23 +571,11 @@ module Erector
492
571
  end
493
572
 
494
573
  full_tags.each do |tag_name|
495
- self.class_eval(
496
- "def #{tag_name}(*args, &block)\n" <<
497
- " __element__('#{tag_name}', *args, &block)\n" <<
498
- "end",
499
- __FILE__,
500
- __LINE__ - 4
501
- )
574
+ def_full_tag_method(tag_name)
502
575
  end
503
576
 
504
577
  empty_tags.each do |tag_name|
505
- self.class_eval(
506
- "def #{tag_name}(*args, &block)\n" <<
507
- " __empty_element__('#{tag_name}', *args, &block)\n" <<
508
- "end",
509
- __FILE__,
510
- __LINE__ - 4
511
- )
578
+ def_empty_tag_method(tag_name)
512
579
  end
513
580
 
514
581
  # Emits a javascript block inside a +script+ tag, wrapped in CDATA
@@ -556,23 +623,21 @@ module Erector
556
623
  # The parameter is the full contents of the href attribute, including any ".css" extension.
557
624
  #
558
625
  # If you want to emit raw CSS inline, use the #style method instead.
559
- def css(href)
560
- link :rel => 'stylesheet', :type => 'text/css', :href => href
626
+ def css(href, options = {})
627
+ link({:rel => 'stylesheet', :type => 'text/css', :href => href}.merge(options))
561
628
  end
562
629
 
563
630
  # Convenience method to emit an anchor tag whose href and text are the same,
564
631
  # e.g. <a href="http://example.com">http://example.com</a>
565
- def url(href)
566
- a href, :href => href
632
+ def url(href, options = {})
633
+ a href, ({:href => href}.merge(options))
567
634
  end
568
635
 
569
- def newliney(tag_name)
570
- if @prettyprint
571
- !NON_NEWLINEY.include?(tag_name)
572
- else
573
- false
574
- end
575
- end
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
576
641
 
577
642
  # emits a jQuery script that is to be run on document ready
578
643
  def jquery(txt)
@@ -584,7 +649,7 @@ module Erector
584
649
  protected
585
650
  def jquery_ready(txt)
586
651
  rawtext "\n"
587
- rawtext "$(document).ready(function(){\n"
652
+ rawtext "jQuery(document).ready(function($){\n"
588
653
  rawtext txt
589
654
  rawtext "\n});"
590
655
  end
@@ -592,10 +657,9 @@ module Erector
592
657
  ### internal utility methods
593
658
 
594
659
  protected
595
-
596
- def __element__(tag_name, *args, &block)
660
+ def __element__(raw, tag_name, *args, &block)
597
661
  if args.length > 2
598
- raise ArgumentError, "Cannot accept more than three arguments"
662
+ raise ArgumentError, "Cannot accept more than four arguments"
599
663
  end
600
664
  attributes, value = nil, nil
601
665
  arg0 = args[0]
@@ -614,7 +678,9 @@ protected
614
678
  raise ArgumentError, "You can't pass both a block and a value to #{tag_name} -- please choose one."
615
679
  end
616
680
  if block
617
- instance_eval(&block)
681
+ block.call
682
+ elsif raw
683
+ text! value
618
684
  else
619
685
  text value
620
686
  end
@@ -626,7 +692,7 @@ protected
626
692
 
627
693
  output << "<#{tag_name}#{format_attributes(attributes)} />"
628
694
 
629
- if newliney(tag_name)
695
+ if newliney?(tag_name)
630
696
  _newline
631
697
  end
632
698
  end
@@ -639,7 +705,7 @@ protected
639
705
 
640
706
  def indent_for_open_tag(tag_name)
641
707
  return unless @prettyprint
642
- if !@at_start_of_line && newliney(tag_name)
708
+ if !@at_start_of_line && newliney?(tag_name)
643
709
  _newline
644
710
  end
645
711
  indent()
@@ -664,7 +730,9 @@ protected
664
730
  sorted.each do |key, value|
665
731
  if value
666
732
  if value.is_a?(Array)
667
- value = [value].flatten.join(' ')
733
+ value = value.flatten
734
+ next if value.empty?
735
+ value = value.join(' ')
668
736
  end
669
737
  results << "#{key}=\"#{value.html_escape}\""
670
738
  end
@@ -689,5 +757,14 @@ protected
689
757
  end
690
758
  return stringized.sort{|a, b| b <=> a}
691
759
  end
760
+
761
+ def newliney?(tag_name)
762
+ if @prettyprint
763
+ !NON_NEWLINEY.include?(tag_name)
764
+ else
765
+ false
766
+ end
767
+ end
768
+
692
769
  end
693
770
  end