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
- #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 |
Class Method Details
.after_initialize(instance = nil, &blk) ⇒ Object
68 69 70 71 72 73 74 75 76 77 78 79 80 81 |
# File 'lib/erector/widget.rb', line 68 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 66 |
# 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', 'embed', '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.
123 124 125 126 127 |
# File 'lib/erector/widget.rb', line 123 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
268 269 270 271 272 273 274 275 276 277 278 279 280 |
# File 'lib/erector/widget.rb', line 268 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 |
#assign_local(name, value) ⇒ Object
227 228 229 230 |
# 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) 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.
472 473 474 475 476 477 478 479 480 481 |
# File 'lib/erector/widget.rb', line 472 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.
423 424 425 426 427 428 429 430 431 432 433 434 435 |
# File 'lib/erector/widget.rb', line 423 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 ‘>’
438 439 440 441 442 443 444 445 446 447 |
# File 'lib/erector/widget.rb', line 438 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. If you want this block to have access to Erector methods then see Erector::Inline#content.
290 291 292 293 294 |
# File 'lib/erector/widget.rb', line 290 def content if @block @block.call 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.
548 549 550 |
# File 'lib/erector/widget.rb', line 548 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.
354 355 356 |
# File 'lib/erector/widget.rb', line 354 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.
367 368 369 |
# File 'lib/erector/widget.rb', line 367 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”).
376 377 378 |
# File 'lib/erector/widget.rb', line 376 def h(content) content.html_escape end |
#html_escape ⇒ Object
(Should we make this hidden?)
329 330 331 |
# File 'lib/erector/widget.rb', line 329 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"?>
464 465 466 |
# File 'lib/erector/widget.rb', line 464 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.
505 506 507 508 509 510 511 512 513 514 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 |
# File 'lib/erector/widget.rb', line 505 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.
452 453 454 455 456 457 458 459 460 461 |
# File 'lib/erector/widget.rb', line 452 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).
418 419 420 |
# File 'lib/erector/widget.rb', line 418 def nbsp(value = " ") raw(value.html_escape.gsub(/ /,' ')) end |
#newliney(tag_name) ⇒ Object
558 559 560 561 562 563 564 |
# File 'lib/erector/widget.rb', line 558 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 ‘>’
381 382 383 384 385 386 387 |
# File 'lib/erector/widget.rb', line 381 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.
405 406 407 |
# File 'lib/erector/widget.rb', line 405 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))
410 411 412 |
# File 'lib/erector/widget.rb', line 410 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.
394 395 396 397 398 399 400 401 402 |
# File 'lib/erector/widget.rb', line 394 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
264 265 266 |
# File 'lib/erector/widget.rb', line 264 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.
235 236 237 |
# File 'lib/erector/widget.rb', line 235 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.
254 255 256 257 |
# File 'lib/erector/widget.rb', line 254 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>
554 555 556 |
# File 'lib/erector/widget.rb', line 554 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.
316 317 318 319 320 321 322 323 324 325 326 |
# File 'lib/erector/widget.rb', line 316 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.
301 302 303 304 305 306 |
# File 'lib/erector/widget.rb', line 301 def write_via(parent) @parent = parent context(parent.output, parent.prettyprint, parent.indentation, parent.helpers) do content end end |