Class: Erector::Widget
Overview
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 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.
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.
Direct Known Subclasses
Constant Summary collapse
- NON_NEWLINEY =
{'i' => true, 'b' => true, 'small' => true, 'img' => true, 'span' => true, 'a' => true, 'input' => true, 'textarea' => true, 'button' => true, 'select' => true }
- SPACES_PER_INDENT =
2
- RESERVED_INSTANCE_VARS =
[:helpers, :assigns, :block, :parent, :output, :prettyprint, :indentation, :at_start_of_line]
- @@prettyprint_default =
false
Class Method Summary collapse
- .after_initialize(instance = nil, &blk) ⇒ Object
- .all_tags ⇒ Object
-
.empty_tags ⇒ Object
Tags which are always self-closing.
-
.full_tags ⇒ Object
Tags which can contain other stuff.
-
.needs(*args) ⇒ Object
Class method by which widget classes can declare that they need certain parameters.
- .prettyprint_default=(enabled) ⇒ Object
Instance Method Summary collapse
- #_render(options = {}, &blk) ⇒ Object
- #any_are_needed? ⇒ Boolean
- #assign_local(name, value) ⇒ Object
- #assign_locals(local_assigns) ⇒ Object
-
#capture(&block) ⇒ Object
Creates a whole new output string, executes the block, then converts the output string to a string and emits it as raw text.
-
#character(code_point_or_name) ⇒ Object
Return a character given its unicode code point or unicode name.
-
#close_tag(tag_name) ⇒ Object
Emits a close tag, consisting of ‘<’, ‘/’, tag name, and ‘>’.
-
#content ⇒ Object
Template method which must be overridden by all widget subclasses.
-
#css(href) ⇒ Object
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.
-
#element(*args, &block) ⇒ Object
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.
-
#empty_element(*args, &block) ⇒ Object
Internal method used to emit a self-closing HTML/XML element, including a tag name and optional attributes (passed in via the default hash).
-
#h(content) ⇒ Object
Returns an HTML-escaped version of its parameter.
-
#html_escape ⇒ Object
(Should we make this hidden?).
-
#initialize(assigns = {}, &block) ⇒ Widget
constructor
A new instance of Widget.
-
#instruct(attributes = {:version => "1.0", :encoding => "UTF-8"}) ⇒ Object
Emits an XML instruction, which looks like this: <?xml version="1.0" encoding="UTF-8"?>.
-
#javascript(*args, &block) ⇒ Object
Emits a javascript block inside a
script
tag, wrapped in CDATA doohickeys like all the cool JS kids do. -
#join(array, separator) ⇒ Object
Emits the result of joining the elements in array with the separator.
-
#nbsp(value = " ") ⇒ Object
Returns a copy of value with spaces replaced by non-breaking space characters.
- #newliney(tag_name) ⇒ Object
-
#open_tag(tag_name, attributes = {}) ⇒ Object
Emits an open tag, comprising ‘<’, tag name, optional attributes, and ‘>’.
- #prettyprint_default ⇒ Object
-
#raw(value) ⇒ Object
Returns text which will not be HTML-escaped.
-
#rawtext(value) ⇒ Object
Emits text which will not be HTML-escaped.
-
#text(value) ⇒ Object
Emits text.
-
#to_a(options = {}, &blk) ⇒ Object
Entry point for rendering a widget (and all its children).
-
#to_pretty ⇒ Object
Render (like to_s) but adding newlines and indentation.
-
#to_s(options = {}, &blk) ⇒ Object
(also: #inspect)
Entry point for rendering a widget (and all its children).
-
#url(href) ⇒ Object
Convenience method to emit an anchor tag whose href and text are the same, e.g.
-
#widget(target, assigns = {}, &block) ⇒ Object
Emits a (nested) widget onto the current widget’s output stream.
-
#write_via(parent) ⇒ Object
To call one widget from another, inside the parent widget’s
content
method, instantiate the child widget and call itswrite_via
method, passing inself
.
Constructor Details
#initialize(assigns = {}, &block) ⇒ Widget
Returns a new instance of Widget.
160 161 162 163 164 165 166 167 168 169 170 171 172 173 |
# File 'lib/erector/widget.rb', line 160 def initialize(assigns={}, &block) unless assigns.is_a? Hash 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." end if (respond_to? :render) && !self.method(:render).to_s.include?("(RailsWidget)") raise "Erector's API has changed. You should rename #{self.class}#render to #content." end @assigns = assigns assign_locals(assigns) @parent = block ? eval("self", block.binding) : nil @block = block self.class.after_initialize self end |
Dynamic Method Handling
This class handles dynamic methods through the method_missing method
#method_missing(name, *args, &block) ⇒ Object (protected)
This is part of the sub-widget/parent feature (see #widget method).
581 582 583 584 585 586 587 588 |
# File 'lib/erector/widget.rb', line 581 def method_missing(name, *args, &block) block ||= lambda {} # captures self HERE if @parent @parent.send(name, *args, &block) else super end end |
Class Method Details
.after_initialize(instance = nil, &blk) ⇒ Object
67 68 69 70 71 72 73 74 75 76 77 78 79 80 |
# File 'lib/erector/widget.rb', line 67 def after_initialize(instance=nil, &blk) if blk after_initialize_parts << blk elsif instance if superclass.respond_to?(:after_initialize) superclass.after_initialize instance end after_initialize_parts.each do |part| instance.instance_eval &part end else raise ArgumentError, "You must provide either an instance or a block" end end |
.all_tags ⇒ Object
37 38 39 |
# File 'lib/erector/widget.rb', line 37 def Erector::Widget. + Erector::Widget. end |
.empty_tags ⇒ Object
Tags which are always self-closing. Click “[Source]” to see the full list.
42 43 44 45 |
# File 'lib/erector/widget.rb', line 42 def ['area', 'base', 'br', 'col', 'frame', 'hr', 'img', 'input', 'link', 'meta'] end |
.full_tags ⇒ Object
Tags which can contain other stuff. Click “[Source]” to see the full list.
48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 |
# File 'lib/erector/widget.rb', line 48 def [ 'a', 'abbr', 'acronym', 'address', 'b', 'bdo', 'big', 'blockquote', 'body', 'button', 'caption', 'center', 'cite', 'code', 'colgroup', 'dd', 'del', 'dfn', 'div', 'dl', 'dt', 'em', 'fieldset', 'form', 'frameset', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'head', 'html', 'i', 'iframe', 'ins', 'kbd', 'label', 'legend', 'li', 'map', 'noframes', 'noscript', 'object', 'ol', 'optgroup', 'option', 'p', 'param', 'pre', 'q', 's', 'samp', 'script', 'select', 'small', 'span', 'strike', 'strong', 'style', 'sub', 'sup', 'table', 'tbody', 'td', 'textarea', 'tfoot', 'th', 'thead', 'title', 'tr', 'tt', 'u', 'ul', 'var' ] end |
.needs(*args) ⇒ Object
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).
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
FancyForm.new(:title => 'Login')
will succeed, as will
FancyForm.new(:title => 'Login', :show_cancel => true)
but
FancyForm.new(:name => 'Login')
will fail.
122 123 124 125 126 |
# File 'lib/erector/widget.rb', line 122 def self.needs(*args) args.each do |arg| (@needs ||= []) << (arg.nil? ? nil : (arg.is_a? Hash) ? arg : arg.to_sym) end end |
.prettyprint_default=(enabled) ⇒ Object
145 146 147 |
# File 'lib/erector/widget.rb', line 145 def self.prettyprint_default=(enabled) @@prettyprint_default = enabled end |
Instance Method Details
#_render(options = {}, &blk) ⇒ Object
278 279 280 281 282 283 284 285 286 287 288 289 290 |
# File 'lib/erector/widget.rb', line 278 def _render( = {}, &blk) = { :output => "", # "" is apparently faster than [] in a long-running process :prettyprint => prettyprint_default, :indentation => 0, :helpers => nil, :content_method_name => :content, }.merge() context([:output], [:prettyprint], [:indentation], [:helpers]) do send([:content_method_name], &blk) output end end |
#any_are_needed? ⇒ Boolean
238 239 240 |
# File 'lib/erector/widget.rb', line 238 def any_are_needed? !self.class.get_needs.empty? end |
#assign_local(name, value) ⇒ Object
227 228 229 230 231 232 233 234 235 236 |
# File 'lib/erector/widget.rb', line 227 def assign_local(name, value) raise ArgumentError, "Sorry, #{name} is a reserved variable name for Erector. Please choose a different name." if RESERVED_INSTANCE_VARS.include?(name) instance_variable_set("@#{name}", value) if any_are_needed? raise ArgumentError, "Sorry, #{name} is a reserved method name for Erector. Please choose a different name." if respond_to?(name) .module_eval do attr_reader name end end end |
#assign_locals(local_assigns) ⇒ Object
200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 |
# File 'lib/erector/widget.rb', line 200 def assign_locals(local_assigns) needed = self.class.get_needs.map{|need| need.is_a?(Hash) ? need.keys : need}.flatten assigned = [] local_assigns.each do |name, value| unless needed.empty? || needed.include?(name) raise "Unknown parameter '#{name}'. #{self.class.name} only accepts #{needed.join(', ')}" end assign_local(name, value) assigned << name end # set variables with default values self.class.get_needs.select{|var| var.is_a? Hash}.each do |hash| hash.each_pair do |name, value| unless assigned.include?(name) assign_local(name, value) assigned << name end end end missing = needed - assigned unless missing.empty? || missing == [nil] raise "Missing parameter#{missing.size == 1 ? '' : 's'}: #{missing.join(', ')}" end end |
#capture(&block) ⇒ Object
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.
482 483 484 485 486 487 488 489 490 491 |
# File 'lib/erector/widget.rb', line 482 def capture(&block) begin original_output = output @output = "" yield raw(output.to_s) ensure @output = original_output end end |
#character(code_point_or_name) ⇒ Object
Return a character given its unicode code point or unicode name.
433 434 435 436 437 438 439 440 441 442 443 444 445 |
# File 'lib/erector/widget.rb', line 433 def character(code_point_or_name) if code_point_or_name.is_a?(Symbol) found = Erector::CHARACTERS[code_point_or_name] if found.nil? raise "Unrecognized character #{code_point_or_name}" end raw("&#x#{sprintf '%x', found};") elsif code_point_or_name.is_a?(Integer) raw("&#x#{sprintf '%x', code_point_or_name};") else raise "Unrecognized argument to character: #{code_point_or_name}" end end |
#close_tag(tag_name) ⇒ Object
Emits a close tag, consisting of ‘<’, ‘/’, tag name, and ‘>’
448 449 450 451 452 453 454 455 456 457 |
# File 'lib/erector/widget.rb', line 448 def close_tag(tag_name) @indentation -= SPACES_PER_INDENT indent() output <<("</#{tag_name}>") if newliney(tag_name) _newline end end |
#content ⇒ Object
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).
300 301 302 303 304 |
# File 'lib/erector/widget.rb', line 300 def content if @block instance_eval(&@block) end end |
#css(href) ⇒ Object
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.
If you want to emit raw CSS inline, use the #style method instead.
558 559 560 |
# File 'lib/erector/widget.rb', line 558 def css(href) link :rel => 'stylesheet', :type => 'text/css', :href => href end |
#element(*args, &block) ⇒ Object
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.
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.
364 365 366 |
# File 'lib/erector/widget.rb', line 364 def element(*args, &block) __element__(*args, &block) end |
#empty_element(*args, &block) ⇒ Object
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.
377 378 379 |
# File 'lib/erector/widget.rb', line 377 def empty_element(*args, &block) __empty_element__(*args, &block) end |
#h(content) ⇒ Object
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”).
386 387 388 |
# File 'lib/erector/widget.rb', line 386 def h(content) content.html_escape end |
#html_escape ⇒ Object
(Should we make this hidden?)
339 340 341 |
# File 'lib/erector/widget.rb', line 339 def html_escape return to_s end |
#instruct(attributes = {:version => "1.0", :encoding => "UTF-8"}) ⇒ Object
Emits an XML instruction, which looks like this: <?xml version="1.0" encoding="UTF-8"?>
474 475 476 |
# File 'lib/erector/widget.rb', line 474 def instruct(attributes={:version => "1.0", :encoding => "UTF-8"}) output << "<?xml#{format_sorted(sort_for_xml_declaration(attributes))}?>" end |
#javascript(*args, &block) ⇒ Object
Emits a javascript block inside a script
tag, wrapped in CDATA doohickeys like all the cool JS kids do.
515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 |
# File 'lib/erector/widget.rb', line 515 def javascript(*args, &block) if args.length > 2 raise ArgumentError, "Cannot accept more than two arguments" end attributes, value = nil, nil arg0 = args[0] if arg0.is_a?(Hash) attributes = arg0 else value = arg0 arg1 = args[1] if arg1.is_a?(Hash) attributes = arg1 end end attributes ||= {} attributes[:type] = "text/javascript" open_tag 'script', attributes # Shouldn't this be a "cdata" HtmlPart? # (maybe, but the syntax is specific to javascript; it isn't # really a generic XML CDATA section. Specifically, # ]]> within value is not treated as ending the # CDATA section by Firefox2 when parsing text/html, # although I guess we could refuse to generate ]]> # there, for the benefit of XML/XHTML parsers). rawtext "\n// <![CDATA[\n" if block instance_eval(&block) else rawtext value end rawtext "\n// ]]>\n" close_tag 'script' rawtext "\n" end |
#join(array, separator) ⇒ Object
Emits the result of joining the elements in array with the separator. The array elements and separator can be Erector::Widget objects, which are rendered, or strings, which are html-escaped and output.
462 463 464 465 466 467 468 469 470 471 |
# File 'lib/erector/widget.rb', line 462 def join(array, separator) first = true array.each do || if !first text separator end first = false text end end |
#nbsp(value = " ") ⇒ Object
Returns a copy of value with spaces replaced by non-breaking space characters. With no arguments, return a single non-breaking space. The output uses the escaping format ‘ ’ since that works in both HTML and XML (as opposed to ‘ ’ which only works in HTML).
428 429 430 |
# File 'lib/erector/widget.rb', line 428 def nbsp(value = " ") raw(value.html_escape.gsub(/ /,' ')) end |
#newliney(tag_name) ⇒ Object
568 569 570 571 572 573 574 |
# File 'lib/erector/widget.rb', line 568 def newliney(tag_name) if @prettyprint !NON_NEWLINEY.include?(tag_name) else false end end |
#open_tag(tag_name, attributes = {}) ⇒ Object
Emits an open tag, comprising ‘<’, tag name, optional attributes, and ‘>’
391 392 393 394 395 396 397 |
# File 'lib/erector/widget.rb', line 391 def open_tag(tag_name, attributes={}) indent_for_open_tag(tag_name) @indentation += SPACES_PER_INDENT output << "<#{tag_name}#{format_attributes(attributes)}>" @at_start_of_line = false end |
#prettyprint_default ⇒ Object
141 142 143 |
# File 'lib/erector/widget.rb', line 141 def prettyprint_default @@prettyprint_default end |
#raw(value) ⇒ Object
Returns text which will not be HTML-escaped.
415 416 417 |
# File 'lib/erector/widget.rb', line 415 def raw(value) RawString.new(value.to_s) end |
#rawtext(value) ⇒ Object
Emits text which will not be HTML-escaped. Same effect as text(raw(s))
420 421 422 |
# File 'lib/erector/widget.rb', line 420 def rawtext(value) text raw(value) end |
#text(value) ⇒ Object
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.
404 405 406 407 408 409 410 411 412 |
# File 'lib/erector/widget.rb', line 404 def text(value) if value.is_a? Widget value else output <<(value.html_escape) end @at_start_of_line = false nil end |
#to_a(options = {}, &blk) ⇒ Object
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
274 275 276 |
# File 'lib/erector/widget.rb', line 274 def to_a( = {}, &blk) _render({:output => []}.merge(), &blk).to_a end |
#to_pretty ⇒ Object
Render (like to_s) but adding newlines and indentation. This is a convenience method; you may just want to call to_s(:prettyprint => true) so you can pass in other rendering options as well.
245 246 247 |
# File 'lib/erector/widget.rb', line 245 def to_pretty to_s(:prettyprint => true) end |
#to_s(options = {}, &blk) ⇒ Object Also known as: inspect
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.
264 265 266 267 |
# File 'lib/erector/widget.rb', line 264 def to_s( = {}, &blk) raise "Erector::Widget#to_s now takes an options hash, not a symbol. Try calling \"to_s(:content_method_name=> :#{})\"" if .is_a? Symbol _render(, &blk).to_s end |
#url(href) ⇒ Object
Convenience method to emit an anchor tag whose href and text are the same, e.g. <a href=“example.com”>example.com</a>
564 565 566 |
# File 'lib/erector/widget.rb', line 564 def url(href) a href, :href => href end |
#widget(target, assigns = {}, &block) ⇒ Object
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.
326 327 328 329 330 331 332 333 334 335 336 |
# File 'lib/erector/widget.rb', line 326 def (target, assigns={}, &block) child = if target.is_a? Class target.new(assigns, &block) else unless assigns.empty? raise "Unexpected second parameter. Did you mean to pass in variables when you instantiated the #{target.class.to_s}?" end target end child.write_via(self) end |
#write_via(parent) ⇒ Object
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.
311 312 313 314 315 316 |
# File 'lib/erector/widget.rb', line 311 def write_via(parent) @parent = parent context(parent.output, parent.prettyprint, parent.indentation, parent.helpers) do content end end |