lib/erector/widget.rb in pivotal-erector-0.6.3 vs lib/erector/widget.rb in pivotal-erector-0.6.4
- old
+ new
@@ -1,48 +1,52 @@
module Erector
# A Widget is the center of the Erector universe.
#
- # To create a widget, extend Erector::Widget and implement
- # the +content+ method. Inside this method you may call any of the tag methods like +span+ or +p+ to emit HTML/XML
- # tags.
- #
- # You can also define a widget on the fly by passing a block to +new+. This block will get executed when the widget's
- # +content+ method is called.
+ # To create a widget, extend Erector::Widget and implement the +content+
+ # method. Inside this method you may call any of the tag methods like +span+
+ # or +p+ to emit HTML/XML tags.
+ #
+ # You can also define a widget on the fly by passing a block to +new+. This
+ # block will get executed when the widget's +content+ method is called.
#
- # To render a widget from the outside, instantiate it and call its +to_s+ method.
+ # To render a widget from the outside, instantiate it and call its +to_s+
+ # method.
#
- # A widget's +new+ method optionally accepts an options hash. Entries in this hash are converted to instance
- # variables, and +attr_reader+ accessors are defined for each.
+ # A widget's +new+ method optionally accepts an options hash. Entries in
+ # this hash are converted to instance variables, and +attr_reader+ accessors
+ # are defined for each.
#
- # TODO: You can add runtime input checking via the +needs+ macro. If any of the variables named via
- # +needs+ are absent, an exception is thrown. Optional variables are specified with +wants+. If a variable appears
- # in the options hash that is in neither the +needs+ nor +wants+ lists, then that too provokes an exception.
- # This mechanism is meant to ameliorate development-time confusion about exactly what parameters are supported
- # by a given widget, avoiding confusing runtime NilClass errors.
- #
- # To call one widget from another, inside the parent widget's +content+ method, instantiate the child widget and call
- # the +widget+ method. This assures that the same output stream
- # is used, which gives better performance than using +capture+ or +to_s+. It also preserves the indentation and
- # helpers of the enclosing class.
- #
- # In this documentation we've tried to keep the distinction clear between methods that *emit* text and those that
- # *return* text. "Emit" means that it writes to the output stream; "return" means that it returns a string
- # like a normal method and leaves it up to the caller to emit that string if it wants.
+ # You can add runtime input checking via the +needs+ macro. See #needs.
+ # This mechanism is meant to ameliorate development-time confusion about
+ # exactly what parameters are supported by a given widget, avoiding
+ # confusing runtime NilClass errors.
+ #
+ # To call one widget from another, inside the parent widget's +content+
+ # method, instantiate the child widget and call the +widget+ method. This
+ # assures that the same output stream is used, which gives better
+ # performance than using +capture+ or +to_s+. It also preserves the
+ # indentation and helpers of the enclosing class.
+ #
+ # In this documentation we've tried to keep the distinction clear between
+ # methods that *emit* text and those that *return* text. "Emit" means that
+ # it writes to the output stream; "return" means that it returns a string
+ # like a normal method and leaves it up to the caller to emit that string if
+ # it wants.
class Widget
class << self
def all_tags
Erector::Widget.full_tags + Erector::Widget.empty_tags
end
- # tags which are always self-closing
+ # Tags which are always self-closing. Click "[Source]" to see the full list.
def empty_tags
['area', 'base', 'br', 'col', 'frame',
'hr', 'img', 'input', 'link', 'meta']
end
- # tags which can contain other stuff
+ # Tags which can contain other stuff. Click "[Source]" to see the full list.
def full_tags
[
'a', 'abbr', 'acronym', 'address',
'b', 'bdo', 'big', 'blockquote', 'body', 'button',
'caption', 'center', 'cite', 'code', 'colgroup',
@@ -79,40 +83,41 @@
def after_initialize_parts
@after_initialize_parts ||= []
end
end
- # Class method by which widget classes can declare that they need certain parameters.
- # If needed parameters are not passed in to #new, then an exception will be thrown
- # (with a hopefully useful message about which parameters are missing). This is intended
- # to catch silly bugs like passing in a parameter called 'name' to a widget that expects
- # a parameter called 'title'. Every variable declared in 'needs' will get an attr_reader
- # accessor declared for it.
+ # Class method by which widget classes can declare that they need certain
+ # parameters. If needed parameters are not passed in to #new, then an
+ # exception will be thrown (with a hopefully useful message about which
+ # parameters are missing). This is intended to catch silly bugs like
+ # passing in a parameter called 'name' to a widget that expects a
+ # parameter called 'title'. Every variable declared in 'needs' will get an
+ # attr_reader accessor declared for it.
#
- # You can also declare default values for parameters using hash syntax. You can put #needs
- # declarations on multiple lines or on the same line; the only caveat is that if there are
- # default values, they all have to be at the end of the line (so they go into the magic
- # hash parameter).
+ # You can also declare default values for parameters using hash syntax.
+ # You can put #needs declarations on multiple lines or on the same line;
+ # the only caveat is that if there are default values, they all have to be
+ # at the end of the line (so they go into the magic hash parameter).
#
- # If a widget has no #needs declaration then it will accept any combination of parameters
- # (and make accessors for them) just like normal. In that case there will be no 'attr_reader's
- # declared.
- # If a widget wants to declare that it
- # takes no parameters, use the special incantation "needs nil" (and don't declare any other
- # needs, or kittens will cry).
+ # If a widget has no #needs declaration then it will accept any
+ # combination of parameters (and make accessors for them) just like
+ # normal. In that case there will be no 'attr_reader's declared. If a
+ # widget wants to declare that it takes no parameters, use the special
+ # incantation "needs nil" (and don't declare any other needs, or kittens
+ # will cry).
#
# Usage:
# class FancyForm < Erector::Widget
# needs :title, :show_okay => true, :show_cancel => false
# ...
# end
- #
- # That means that
+ #
+ # That means that
# FancyForm.new(:title => 'Login')
- # will succeed, as will
+ # will succeed, as will
# FancyForm.new(:title => 'Login', :show_cancel => true)
- # but
+ # but
# FancyForm.new(:name => 'Login')
# will fail.
#
def self.needs(*args)
args.each do |arg|
@@ -235,68 +240,87 @@
# so you can pass in other rendering options as well.
def to_pretty
to_s(:prettyprint => true)
end
- # Entry point for rendering a widget (and all its children). This method creates a new output string (if necessary),
- # calls this widget's #content method and returns the string.
+ # Entry point for rendering a widget (and all its children). This method
+ # creates a new output string (if necessary), calls this widget's #content
+ # method and returns the string.
#
# Options:
# output:: the string to output to. Default: a new empty string
- # prettyprint:: whether Erector should add newlines and indentation. Default: the value of prettyprint_default (which is false by default).
- # indentation:: the amount of spaces to indent. Ignored unless prettyprint is true.
- # helpers:: a helpers object containing utility methods. Usually this is a Rails view object.
- # content_method_name:: in case you want to call a method other than #content, pass its name in here.
- #
- # Note: Prettyprinting is an experimental feature and is subject to change
- # (either in terms of how it is enabled, or in terms of
- # what decisions Erector makes about where to add whitespace).
+ # prettyprint:: whether Erector should add newlines and indentation.
+ # Default: the value of prettyprint_default (which is false
+ # by default).
+ # indentation:: the amount of spaces to indent. Ignored unless prettyprint
+ # is true.
+ # helpers:: a helpers object containing utility methods. Usually this is a
+ # Rails view object.
+ # content_method_name:: in case you want to call a method other than
+ # #content, pass its name in here.
def to_s(options = {}, &blk)
-
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
-
+ _render(options, &blk).to_s
+ end
+
+ # Entry point for rendering a widget (and all its children). Same as #to_s
+ # only it returns an array, for theoretical performance improvements when using a
+ # Rack server (like Sinatra or Rails Metal).
+ #
+ # # Options: see #to_s
+ def to_a(options = {}, &blk)
+ _render(options, &blk).to_a
+ end
+
+ def _render(options = {}, &blk)
options = {
- :output => "",
+ :output => "", # "" is apparently faster than [] in a long-running process
:prettyprint => prettyprint_default,
:indentation => 0,
:helpers => nil,
:content_method_name => :content,
}.merge(options)
context(options[:output], options[:prettyprint], options[:indentation], options[:helpers]) do
send(options[:content_method_name], &blk)
- output.to_s
+ output
end
end
alias_method :inspect, :to_s
- # Template method which must be overridden by all widget subclasses. Inside this method you call the magic
- # #element methods which emit HTML and text to the output string.
+ # Template method which must be overridden by all widget subclasses.
+ # Inside this method you call the magic #element methods which emit HTML
+ # and text to the output string. If you call "super" (or don't override
+ # +content+) then your widget will render any block that was passed into
+ # its constructor (in the current instance context so it can get access
+ # to parent widget methods via method_missing).
def content
if @block
instance_eval(&@block)
end
end
- # To call one widget from another, inside the parent widget's +content+ method, instantiate the child widget and call
- # its +write_via+ method, passing in +self+. This assures that the same output string
- # is used, which gives better performance than using +capture+ or +to_s+.
- # You can also use the +widget+ method.
+ # To call one widget from another, inside the parent widget's +content+
+ # method, instantiate the child widget and call its +write_via+ method,
+ # passing in +self+. This assures that the same output string is used,
+ # which gives better performance than using +capture+ or +to_s+. You can
+ # also use the +widget+ method.
def write_via(parent)
@parent = parent
context(parent.output, parent.prettyprint, parent.indentation, parent.helpers) do
content
end
end
- # Emits a (nested) widget onto the current widget's output stream. Accepts either
- # a class or an instance. If the first argument is a class, then the second argument
- # is a hash used to populate its instance variables. If the first argument is an
- # instance then the hash must be unspecified (or empty).
+ # Emits a (nested) widget onto the current widget's output stream. Accepts
+ # either a class or an instance. If the first argument is a class, then
+ # the second argument is a hash used to populate its instance variables.
+ # If the first argument is an instance then the hash must be unspecified
+ # (or empty).
#
- # The sub-widget will have access to the methods of the parent class, via some method_missing
- # magic and a "parent" pointer.
+ # The sub-widget will have access to the methods of the parent class, via
+ # some method_missing magic and a "parent" pointer.
def widget(target, assigns={}, &block)
child = if target.is_a? Class
target.new(assigns, &block)
else
unless assigns.empty?
@@ -313,67 +337,73 @@
end
#-- methods for subclasses to call
#++
- # Internal method used to emit an HTML/XML element, including an open tag, attributes (optional, via the default hash),
- # contents (also optional), and close tag.
+ # Internal method used to emit an HTML/XML element, including an open tag,
+ # attributes (optional, via the default hash), contents (also optional),
+ # and close tag.
#
- # Using the arcane powers of Ruby, there are magic methods that call +element+ for all the standard
- # HTML tags, like +a+, +body+, +p+, and so forth. Look at the source of #full_tags for the full list.
- # Unfortunately, this big mojo confuses rdoc, so we can't see each method in this rdoc page, but trust
- # us, they're there.
+ # Using the arcane powers of Ruby, there are magic methods that call
+ # +element+ for all the standard HTML tags, like +a+, +body+, +p+, and so
+ # forth. Look at the source of #full_tags for the full list.
+ # Unfortunately, this big mojo confuses rdoc, so we can't see each method
+ # in this rdoc page, but trust us, they're there.
#
- # When calling one of these magic methods, put attributes in the default hash. If there is a string parameter,
- # then it is used as the contents. If there is a block, then it is executed (yielded), and the string parameter is ignored.
- # The block will usually be in the scope of the child widget, which means it has access to all the
- # methods of Widget, which will eventually end up appending text to the +output+ string. See how
- # elegant it is? Not confusing at all if you don't think about it.
+ # When calling one of these magic methods, put attributes in the default
+ # hash. If there is a string parameter, then it is used as the contents.
+ # If there is a block, then it is executed (yielded), and the string
+ # parameter is ignored. The block will usually be in the scope of the
+ # child widget, which means it has access to all the methods of Widget,
+ # which will eventually end up appending text to the +output+ string. See
+ # how elegant it is? Not confusing at all if you don't think about it.
#
def element(*args, &block)
__element__(*args, &block)
end
- # Internal method used to emit a self-closing HTML/XML element, including a tag name and optional attributes
- # (passed in via the default hash).
- #
- # Using the arcane powers of Ruby, there are magic methods that call +empty_element+ for all the standard
- # HTML tags, like +img+, +br+, and so forth. Look at the source of #empty_tags for the full list.
- # Unfortunately, this big mojo confuses rdoc, so we can't see each method in this rdoc page, but trust
- # us, they're there.
+ # Internal method used to emit a self-closing HTML/XML element, including
+ # a tag name and optional attributes (passed in via the default hash).
#
+ # Using the arcane powers of Ruby, there are magic methods that call
+ # +empty_element+ for all the standard HTML tags, like +img+, +br+, and so
+ # forth. Look at the source of #empty_tags for the full list.
+ # Unfortunately, this big mojo confuses rdoc, so we can't see each method
+ # in this rdoc page, but trust us, they're there.
+ #
def empty_element(*args, &block)
__empty_element__(*args, &block)
end
- # Returns an HTML-escaped version of its parameter. Leaves the output string untouched. Note that
- # the #text method automatically HTML-escapes its parameter, so be careful *not* to do something like
- # text(h("2<4")) since that will double-escape the less-than sign (you'll get "2&lt;4" instead of
- # "2<4").
+ # Returns an HTML-escaped version of its parameter. Leaves the output
+ # string untouched. Note that the #text method automatically HTML-escapes
+ # its parameter, so be careful *not* to do something like text(h("2<4"))
+ # since that will double-escape the less-than sign (you'll get
+ # "2&lt;4" instead of "2<4").
def h(content)
content.html_escape
end
# Emits an open tag, comprising '<', tag name, optional attributes, and '>'
def open_tag(tag_name, attributes={})
indent_for_open_tag(tag_name)
@indentation += SPACES_PER_INDENT
- output.concat "<#{tag_name}#{format_attributes(attributes)}>"
+ output << "<#{tag_name}#{format_attributes(attributes)}>"
@at_start_of_line = false
end
- # Emits text. If a string is passed in, it will be HTML-escaped.
- # If a widget or the result of calling methods such as raw
- # is passed in, the HTML will not be HTML-escaped again.
- # If another kind of object is passed in, the result of calling
- # its to_s method will be treated as a string would be.
+ # Emits text. If a string is passed in, it will be HTML-escaped. If a
+ # widget or the result of calling methods such as raw is passed in, the
+ # HTML will not be HTML-escaped again. If another kind of object is passed
+ # in, the result of calling its to_s method will be treated as a string
+ # would be.
def text(value)
if value.is_a? Widget
widget value
else
- output.concat(value.html_escape)
+ output <<(value.html_escape)
end
@at_start_of_line = false
nil
end
@@ -408,16 +438,16 @@
else
raise "Unrecognized argument to character: #{code_point_or_name}"
end
end
- # Emits a close tag, consisting of '<', tag name, and '>'
+ # Emits a close tag, consisting of '<', '/', tag name, and '>'
def close_tag(tag_name)
@indentation -= SPACES_PER_INDENT
indent()
- output.concat("</#{tag_name}>")
+ output <<("</#{tag_name}>")
if newliney(tag_name)
_newline
end
end
@@ -436,16 +466,17 @@
end
end
# Emits an XML instruction, which looks like this: <?xml version=\"1.0\" encoding=\"UTF-8\"?>
def instruct(attributes={:version => "1.0", :encoding => "UTF-8"})
- output.concat "<?xml#{format_sorted(sort_for_xml_declaration(attributes))}?>"
+ output << "<?xml#{format_sorted(sort_for_xml_declaration(attributes))}?>"
end
- # Creates a whole new output string, executes the block, then converts the output string to a string and
- # emits it as raw text. If at all possible you should avoid this method since it hurts performance,
- # and use +content+ or +write_via+ instead.
+ # Creates a whole new output string, executes the block, then converts the
+ # output string to a string and emits it as raw text. If at all possible
+ # you should avoid this method since it hurts performance, and use
+ # +widget+ or +write_via+ instead.
def capture(&block)
begin
original_output = output
@output = ""
yield
@@ -473,11 +504,12 @@
__FILE__,
__LINE__ - 4
)
end
- # Emits a javascript block inside a +script+ tag, wrapped in CDATA doohickeys like all the cool JS kids do.
+ # Emits a javascript block inside a +script+ tag, wrapped in CDATA
+ # doohickeys like all the cool JS kids do.
def javascript(*args, &block)
if args.length > 2
raise ArgumentError, "Cannot accept more than two arguments"
end
attributes, value = nil, nil
@@ -512,20 +544,21 @@
close_tag 'script'
rawtext "\n"
end
- # Convenience method to emit a css file link, which looks like this:
+ # Convenience method to emit a css file link, which looks like this:
# <link href="erector.css" rel="stylesheet" type="text/css" />
- # The parameter is the full contents of the href attribute, including any ".css" extension.
+ # The parameter is the full contents of the href attribute, including any ".css" extension.
#
# If you want to emit raw CSS inline, use the #style method instead.
def css(href)
link :rel => 'stylesheet', :type => 'text/css', :href => href
end
- # Convenience method to emit an anchor tag whose href and text are the same, e.g. <a href="http://example.com">http://example.com</a>
+ # Convenience method to emit an anchor tag whose href and text are the same,
+ # e.g. <a href="http://example.com">http://example.com</a>
def url(href)
a href, :href => href
end
def newliney(tag_name)
@@ -579,20 +612,20 @@
end
def __empty_element__(tag_name, attributes={})
indent_for_open_tag(tag_name)
- output.concat "<#{tag_name}#{format_attributes(attributes)} />"
+ output << "<#{tag_name}#{format_attributes(attributes)} />"
if newliney(tag_name)
_newline
end
end
def _newline
return unless @prettyprint
- output.concat "\n"
+ output << "\n"
@at_start_of_line = true
end
def indent_for_open_tag(tag_name)
return unless @prettyprint
@@ -602,10 +635,10 @@
indent()
end
def indent()
if @at_start_of_line
- output.concat " " * @indentation
+ output << " " * [@indentation, 0].max
end
end
def format_attributes(attributes)
if !attributes || attributes.empty?