erector 0.7.1 → 0.7.2

Sign up to get free protection for your applications and to get access to all the features.
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