erector 0.7.2 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.txt +17 -3
- data/VERSION.yml +2 -2
- data/bin/erector +1 -1
- data/lib/erector.rb +22 -2
- data/lib/erector/after_initialize.rb +34 -0
- data/lib/erector/caching.rb +93 -0
- data/lib/erector/convenience.rb +58 -0
- data/lib/erector/dependencies.rb +24 -0
- data/lib/erector/dependency.rb +21 -0
- data/lib/erector/{erect.rb → erect/erect.rb} +14 -4
- data/lib/erector/{erected.rb → erect/erected.rb} +6 -4
- data/lib/erector/{indenting.rb → erect/indenting.rb} +0 -0
- data/lib/erector/{rhtml.treetop → erect/rhtml.treetop} +51 -11
- data/lib/erector/errors.rb +12 -0
- data/lib/erector/extensions/hash.rb +21 -0
- data/lib/erector/externals.rb +88 -24
- data/lib/erector/html.rb +352 -0
- data/lib/erector/inline.rb +5 -5
- data/lib/erector/jquery.rb +36 -0
- data/lib/erector/mixin.rb +3 -5
- data/lib/erector/needs.rb +94 -0
- data/lib/erector/output.rb +117 -0
- data/lib/erector/rails.rb +2 -2
- data/lib/erector/rails/extensions/action_controller.rb +5 -3
- data/lib/erector/rails/extensions/rails_helpers.rb +159 -0
- data/lib/erector/rails/extensions/rails_widget.rb +98 -56
- data/lib/erector/rails/rails_form_builder.rb +8 -4
- data/lib/erector/rails/rails_version.rb +2 -2
- data/lib/erector/rails/template_handlers/ert_handler.rb +1 -1
- data/lib/erector/rails/template_handlers/rb_handler.rb +42 -1
- data/lib/erector/raw_string.rb +2 -2
- data/lib/erector/sass.rb +22 -0
- data/lib/erector/widget.rb +100 -653
- data/lib/erector/widgets.rb +1 -0
- data/lib/erector/widgets/external_renderer.rb +51 -0
- data/lib/erector/widgets/page.rb +45 -63
- data/lib/erector/widgets/table.rb +9 -1
- data/spec/erect/erect_rails_spec.rb +19 -17
- data/spec/erect/erect_spec.rb +11 -1
- data/spec/erect/erected_spec.rb +76 -5
- data/spec/erect/rhtml_parser_spec.rb +11 -1
- data/spec/erector/caching_spec.rb +267 -0
- data/spec/erector/convenience_spec.rb +258 -0
- data/spec/erector/dependency_spec.rb +46 -0
- data/spec/erector/externals_spec.rb +233 -0
- data/spec/erector/html_spec.rb +508 -0
- data/spec/erector/indentation_spec.rb +84 -24
- data/spec/erector/inline_spec.rb +19 -8
- data/spec/erector/jquery_spec.rb +35 -0
- data/spec/erector/mixin_spec.rb +1 -1
- data/spec/erector/needs_spec.rb +120 -0
- data/spec/erector/output_spec.rb +199 -0
- data/spec/erector/sample-file.txt +1 -0
- data/spec/erector/sass_spec.rb +33 -0
- data/spec/erector/widget_spec.rb +113 -932
- data/spec/erector/widgets/field_table_spec.rb +6 -6
- data/spec/erector/widgets/form_spec.rb +3 -3
- data/spec/erector/widgets/page_spec.rb +52 -6
- data/spec/erector/widgets/table_spec.rb +4 -4
- data/spec/spec_helper.rb +70 -29
- metadata +56 -19
- data/lib/erector/rails/extensions/rails_widget/rails_helpers.rb +0 -137
- data/spec/core_spec_suite.rb +0 -3
- data/spec/erector/external_spec.rb +0 -110
- data/spec/rails_spec_suite.rb +0 -3
- data/spec/spec.opts +0 -1
- data/spec/spec_suite.rb +0 -40
@@ -1,84 +1,126 @@
|
|
1
1
|
module Erector
|
2
2
|
module Rails
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
end
|
3
|
+
def self.assigns_for(widget_class, view, local_assigns, is_partial)
|
4
|
+
assigns = {}
|
5
|
+
|
6
|
+
instance_variables = view.instance_variables_for_widget_assignment
|
7
|
+
if is_partial || widget_class.ignore_extra_controller_assigns
|
8
|
+
instance_variables = remove_unneeded_assigns(widget_class, instance_variables)
|
9
|
+
end
|
10
|
+
|
11
|
+
assigns.merge!(instance_variables) unless is_partial && (! widget_class.controller_assigns_propagate_to_partials)
|
12
|
+
|
13
|
+
if is_partial
|
14
|
+
assigns.merge!(filter_local_assigns_for_partial(widget_class, local_assigns || { }))
|
15
|
+
end
|
16
|
+
|
17
|
+
assigns
|
18
|
+
end
|
20
19
|
|
20
|
+
def self.remove_unneeded_assigns(widget_class, assigns)
|
21
|
+
needs = widget_class.needed_variables
|
22
|
+
if needs.empty?
|
23
|
+
assigns
|
24
|
+
else
|
25
|
+
assigns.reject { |key, value| ! needs.include?(key) }
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.filter_local_assigns_for_partial(widget_class, local_assigns)
|
30
|
+
widget_class_variable_name = widget_class.name.underscore
|
31
|
+
widget_class_variable_name = $1 if widget_class_variable_name =~ %r{.*/(.*?)$}
|
32
|
+
|
33
|
+
local_assigns.reject do |name, value|
|
34
|
+
name == :object || name == widget_class_variable_name.to_sym
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.render(widget, view, assigns = nil, options = {})
|
39
|
+
if widget.is_a?(Class)
|
40
|
+
assigns ||= assigns_for(widget, view, nil, false)
|
21
41
|
widget = widget.new(assigns)
|
22
42
|
end
|
23
43
|
|
24
|
-
view = controller.response.template
|
25
44
|
view.send(:_evaluate_assigns_and_ivars)
|
26
45
|
|
27
46
|
view.with_output_buffer do
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
:helpers => view
|
32
|
-
)
|
47
|
+
# Set parent to the view and use Rails's output buffer.
|
48
|
+
widget.to_html(options.merge(:parent => view,
|
49
|
+
:output => Output.new { view.output_buffer }))
|
33
50
|
end
|
34
51
|
end
|
35
52
|
|
36
53
|
module WidgetExtensions
|
54
|
+
module ClassMethods
|
55
|
+
def ignore_extra_controller_assigns
|
56
|
+
out = @ignore_extra_controller_assigns
|
57
|
+
out ||= (superclass.ignore_extra_controller_assigns ? :true : :false) if superclass.respond_to?(:ignore_extra_controller_assigns)
|
58
|
+
out ||= :true
|
59
|
+
out == :true
|
60
|
+
end
|
61
|
+
|
62
|
+
# Often, large Rails applications will assign many controller instance variables.
|
63
|
+
# Sometimes these aren't used by a view: ApplicationController might assign
|
64
|
+
# variables that are used by many, but not all, views; and various other things
|
65
|
+
# may accumulate, especially if you've been using templating systems that are
|
66
|
+
# more forgiving than Erector. If you then migrate to Erector, you're stuck using
|
67
|
+
# no "needs" declaration at all, because it needs to contain every assigned
|
68
|
+
# variable, or Erector will raise an exception.
|
69
|
+
#
|
70
|
+
# If you set this to true (and it's inherited through to subclasses), however,
|
71
|
+
# then "needs" declarations on the widget will cause excess controller variables
|
72
|
+
# to be ignored -- they'll be unavailable to the widget (so 'needs' still means
|
73
|
+
# something), but they won't cause widget instantiation to fail, either. This
|
74
|
+
# can let a large Rails project transition to Erector more smoothly.
|
75
|
+
def ignore_extra_controller_assigns=(new_value)
|
76
|
+
@ignore_extra_controller_assigns = (new_value ? :true : :false)
|
77
|
+
end
|
78
|
+
|
79
|
+
def controller_assigns_propagate_to_partials
|
80
|
+
out = @controller_assigns_propagate_to_partials
|
81
|
+
out ||= (superclass.controller_assigns_propagate_to_partials ? :true : :false) if superclass.respond_to?(:controller_assigns_propagate_to_partials)
|
82
|
+
out ||= :true
|
83
|
+
out == :true
|
84
|
+
end
|
85
|
+
|
86
|
+
# In ERb templates, controller instance variables are available to any partial
|
87
|
+
# that gets rendered by the view, no matter how deeply-nested. This effectively
|
88
|
+
# makes controller instance variables "globals". In small view hierarchies this
|
89
|
+
# probably isn't an issue, but in large ones it can make debugging and
|
90
|
+
# reasoning about the code very difficult.
|
91
|
+
#
|
92
|
+
# If you set this to true (and it's inherited through to subclasses), then any
|
93
|
+
# widget that's getting rendered as a partial will only have access to locals
|
94
|
+
# explicitly passed to it (render :partial => ..., :locals => ...). (This
|
95
|
+
# doesn't change the behavior of widgets that are explicitly rendered, as they
|
96
|
+
# don't have this issue.) This can allow for cleaner encapsulation of partials,
|
97
|
+
# as they must be passed everything they use and can't rely on controller
|
98
|
+
# instance variables.
|
99
|
+
def controller_assigns_propagate_to_partials=(new_value)
|
100
|
+
@controller_assigns_propagate_to_partials = (new_value ? :true : :false)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
37
104
|
def self.included(base)
|
38
|
-
base.
|
39
|
-
base.alias_method_chain :capture, :parent
|
105
|
+
base.extend(ClassMethods)
|
40
106
|
end
|
41
107
|
|
42
|
-
|
43
|
-
|
44
|
-
|
108
|
+
# We need to delegate #capture to parent.capture, so that when
|
109
|
+
# the captured block is executed, both erector and Rails output
|
110
|
+
# from within the block go to the appropriate buffer.
|
111
|
+
def capture(&block)
|
112
|
+
if parent.respond_to?(:capture)
|
113
|
+
raw(parent.capture(&block).to_s)
|
45
114
|
else
|
46
|
-
|
115
|
+
super
|
47
116
|
end
|
48
117
|
end
|
49
118
|
|
50
|
-
def capture_with_parent(&block)
|
51
|
-
parent ? raw(parent.capture(&block).to_s) : capture_without_parent(&block)
|
52
|
-
end
|
53
|
-
|
54
119
|
# This is here to force #parent.capture to return the output
|
55
120
|
def __in_erb_template;
|
56
121
|
end
|
57
|
-
|
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
|
66
|
-
end
|
67
|
-
end
|
68
122
|
end
|
69
123
|
|
70
124
|
Erector::Widget.send :include, WidgetExtensions
|
71
125
|
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
|
79
|
-
end
|
80
|
-
|
81
|
-
class InlineRailsWidget < RailsWidget
|
82
|
-
include Inline
|
83
|
-
end
|
84
126
|
end
|
@@ -4,17 +4,21 @@ module Erector
|
|
4
4
|
|
5
5
|
def initialize(object_name, object, template, options, proc)
|
6
6
|
@template = template
|
7
|
-
@parent = ActionView::
|
7
|
+
@parent = ActionView::Base.default_form_builder.new(object_name, object, template, options, proc)
|
8
8
|
end
|
9
9
|
|
10
10
|
def method_missing(method_name, *args, &block)
|
11
11
|
if parent.respond_to?(method_name)
|
12
12
|
return_value = parent.send(method_name, *args, &block)
|
13
|
-
|
14
|
-
|
13
|
+
if return_value.is_a?(String)
|
14
|
+
template.concat(return_value)
|
15
|
+
nil
|
16
|
+
else
|
17
|
+
return_value
|
18
|
+
end
|
15
19
|
else
|
16
20
|
super
|
17
21
|
end
|
18
22
|
end
|
19
23
|
end
|
20
|
-
end
|
24
|
+
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
|
21
|
+
"r = (controller.ert_template_base_class || ::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,9 +1,50 @@
|
|
1
|
+
class ActionView::Base
|
2
|
+
def instance_variables_for_widget_assignment
|
3
|
+
instance_variables_for_widget_assignment_for(controller)
|
4
|
+
end
|
5
|
+
|
6
|
+
def instance_variables_for_widget_assignment_for(target)
|
7
|
+
assigns = { }
|
8
|
+
variables = target.instance_variable_names
|
9
|
+
variables -= target.protected_instance_variables if target.respond_to?(:protected_instance_variables)
|
10
|
+
variables -= %w{@real_format @request @template @_request}
|
11
|
+
variables.each do |name|
|
12
|
+
assign = name.sub('@', '').to_sym
|
13
|
+
assigns[assign] = target.instance_variable_get(name)
|
14
|
+
end
|
15
|
+
assigns
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
# Out of the box, the Cells plugin for Rails (http://cells.rubyforge.org/)
|
20
|
+
# does not work with Erector, because Erector tries to grab instance variables
|
21
|
+
# off the controller, rather than the cell itself.
|
22
|
+
#
|
23
|
+
# This code patches up Cell::View to make it work, but only if the Cells plugin
|
24
|
+
# is installed. (That's the bare "Cell::View" at the top, and rescue NameError
|
25
|
+
# at the bottom.) While you'd imagine that unilaterally opening Cell::View
|
26
|
+
# and adding the method would work, it doesn't; Cell::View is normally
|
27
|
+
# autoloaded, and since we'd end up defining it here, we'd keep it from getting
|
28
|
+
# loaded at all.
|
29
|
+
begin
|
30
|
+
Cell::View
|
31
|
+
|
32
|
+
class Cell::View < ActionView::Base
|
33
|
+
def instance_variables_for_widget_assignment
|
34
|
+
instance_variables_for_widget_assignment_for(cell)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
rescue NameError
|
38
|
+
end
|
39
|
+
|
1
40
|
module Erector
|
2
41
|
class RbHandler < ActionView::TemplateHandler
|
3
42
|
def render(template, local_assigns)
|
4
43
|
require_dependency File.expand_path(template.filename)
|
5
44
|
widget_class = "views/#{template.path_without_format_and_extension}".camelize.constantize
|
6
|
-
|
45
|
+
is_partial = (File.basename(template.path_without_format_and_extension) =~ /^_/)
|
46
|
+
assigns = Erector::Rails.assigns_for(widget_class, @view, local_assigns, is_partial)
|
47
|
+
Erector::Rails.render(widget_class, @view, assigns)
|
7
48
|
end
|
8
49
|
end
|
9
50
|
end
|
data/lib/erector/raw_string.rb
CHANGED
data/lib/erector/sass.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
|
2
|
+
if Object.const_defined?(:Sass)
|
3
|
+
module Erector
|
4
|
+
# Adds sass support to Erector widgets.
|
5
|
+
# Note that sass is provided inside the gem named "haml",
|
6
|
+
# not the gem named "sass".
|
7
|
+
# To get sass support into your Erector project, install the
|
8
|
+
# haml gem -- see http://sass-lang.com/download.html -- and
|
9
|
+
# then do something like this:
|
10
|
+
# require 'rubygems'
|
11
|
+
# require 'sass'
|
12
|
+
#
|
13
|
+
# Current support is barebones. Please offer suggestions (or better
|
14
|
+
# yet, patches) for whether and how to support, e.g., caching,
|
15
|
+
# loading from files, precompilation, etc.
|
16
|
+
module Sass
|
17
|
+
def sass(sass_text)
|
18
|
+
style ::Sass::Engine.new(sass_text, :cache => false).render
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
data/lib/erector/widget.rb
CHANGED
@@ -7,14 +7,15 @@ module Erector
|
|
7
7
|
# or +p+ to emit HTML/XML tags.
|
8
8
|
#
|
9
9
|
# You can also define a widget on the fly by passing a block to +new+. This
|
10
|
-
# block will get executed when the widget's +content+ method is called.
|
10
|
+
# block will get executed when the widget's +content+ method is called. See
|
11
|
+
# the userguide for important details about the scope of this block when run --
|
12
|
+
# http://erector.rubyforge.org/userguide.html#blocks
|
11
13
|
#
|
12
|
-
# To render a widget from the outside, instantiate it and call its +
|
14
|
+
# To render a widget from the outside, instantiate it and call its +to_html+
|
13
15
|
# method.
|
14
16
|
#
|
15
17
|
# A widget's +new+ method optionally accepts an options hash. Entries in
|
16
|
-
# this hash are converted to instance variables
|
17
|
-
# are defined for each.
|
18
|
+
# this hash are converted to instance variables.
|
18
19
|
#
|
19
20
|
# You can add runtime input checking via the +needs+ macro. See #needs.
|
20
21
|
# This mechanism is meant to ameliorate development-time confusion about
|
@@ -24,7 +25,7 @@ module Erector
|
|
24
25
|
# To call one widget from another, inside the parent widget's +content+
|
25
26
|
# method, instantiate the child widget and call the +widget+ method. This
|
26
27
|
# assures that the same output stream is used, which gives better
|
27
|
-
# performance than using +capture+ or +
|
28
|
+
# performance than using +capture+ or +to_html+. It also preserves the
|
28
29
|
# indentation and helpers of the enclosing class.
|
29
30
|
#
|
30
31
|
# In this documentation we've tried to keep the distinction clear between
|
@@ -32,252 +33,50 @@ module Erector
|
|
32
33
|
# it writes to the output stream; "return" means that it returns a string
|
33
34
|
# like a normal method and leaves it up to the caller to emit that string if
|
34
35
|
# it wants.
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
def all_tags
|
40
|
-
Erector::Widget.full_tags + Erector::Widget.empty_tags
|
41
|
-
end
|
42
|
-
|
43
|
-
# Tags which are always self-closing. Click "[Source]" to see the full list.
|
44
|
-
def empty_tags
|
45
|
-
['area', 'base', 'br', 'col', 'frame',
|
46
|
-
'hr', 'img', 'input', 'link', 'meta']
|
47
|
-
end
|
48
|
-
|
49
|
-
# Tags which can contain other stuff. Click "[Source]" to see the full list.
|
50
|
-
def full_tags
|
51
|
-
[
|
52
|
-
'a', 'abbr', 'acronym', 'address', 'article', 'aside', 'audio',
|
53
|
-
'b', 'bdo', 'big', 'blockquote', 'body', 'button',
|
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',
|
66
|
-
'strong', 'style', 'sub', 'sup',
|
67
|
-
'table', 'tbody', 'td', 'textarea', 'tfoot',
|
68
|
-
'th', 'thead', 'time', 'title', 'tr', 'tt', 'u', 'ul',
|
69
|
-
'var', 'video'
|
70
|
-
]
|
71
|
-
end
|
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
|
-
|
93
|
-
def after_initialize(instance=nil, &blk)
|
94
|
-
if blk
|
95
|
-
after_initialize_parts << blk
|
96
|
-
elsif instance
|
97
|
-
if superclass.respond_to?(:after_initialize)
|
98
|
-
superclass.after_initialize instance
|
99
|
-
end
|
100
|
-
after_initialize_parts.each do |part|
|
101
|
-
instance.instance_eval &part
|
102
|
-
end
|
103
|
-
else
|
104
|
-
raise ArgumentError, "You must provide either an instance or a block"
|
105
|
-
end
|
106
|
-
end
|
107
|
-
|
108
|
-
protected
|
109
|
-
def after_initialize_parts
|
110
|
-
@after_initialize_parts ||= []
|
111
|
-
end
|
112
|
-
|
113
|
-
end
|
114
|
-
|
115
|
-
# Class method by which widget classes can declare that they need certain
|
116
|
-
# parameters. If needed parameters are not passed in to #new, then an
|
117
|
-
# exception will be thrown (with a hopefully useful message about which
|
118
|
-
# parameters are missing). This is intended to catch silly bugs like
|
119
|
-
# passing in a parameter called 'name' to a widget that expects a
|
120
|
-
# parameter called 'title'. Every variable declared in 'needs' will get an
|
121
|
-
# attr_reader accessor declared for it.
|
122
|
-
#
|
123
|
-
# You can also declare default values for parameters using hash syntax.
|
124
|
-
# You can put #needs declarations on multiple lines or on the same line;
|
125
|
-
# the only caveat is that if there are default values, they all have to be
|
126
|
-
# at the end of the line (so they go into the magic hash parameter).
|
127
|
-
#
|
128
|
-
# If a widget has no #needs declaration then it will accept any
|
129
|
-
# combination of parameters (and make accessors for them) just like
|
130
|
-
# normal. In that case there will be no 'attr_reader's declared. If a
|
131
|
-
# widget wants to declare that it takes no parameters, use the special
|
132
|
-
# incantation "needs nil" (and don't declare any other needs, or kittens
|
133
|
-
# will cry).
|
134
|
-
#
|
135
|
-
# Usage:
|
136
|
-
# class FancyForm < Erector::Widget
|
137
|
-
# needs :title, :show_okay => true, :show_cancel => false
|
138
|
-
# ...
|
139
|
-
# end
|
140
|
-
#
|
141
|
-
# That means that
|
142
|
-
# FancyForm.new(:title => 'Login')
|
143
|
-
# will succeed, as will
|
144
|
-
# FancyForm.new(:title => 'Login', :show_cancel => true)
|
145
|
-
# but
|
146
|
-
# FancyForm.new(:name => 'Login')
|
147
|
-
# will fail.
|
148
|
-
#
|
149
|
-
def self.needs(*args)
|
150
|
-
args.each do |arg|
|
151
|
-
(@needs ||= []) << (arg.nil? ? nil : (arg.is_a? Hash) ? arg : arg.to_sym)
|
152
|
-
end
|
153
|
-
end
|
154
|
-
|
155
|
-
protected
|
156
|
-
def self.get_needs
|
157
|
-
@needs ||= []
|
158
|
-
|
159
|
-
ancestors[1..-1].inject(@needs.dup) do |needs, ancestor|
|
160
|
-
needs.push(*ancestor.get_needs) if ancestor.respond_to?(:get_needs)
|
161
|
-
needs
|
162
|
-
end
|
163
|
-
end
|
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
|
-
|
173
|
-
public
|
36
|
+
#
|
37
|
+
# Now, seriously, after playing around a bit, go read the user guide. It's
|
38
|
+
# fun!
|
39
|
+
class AbstractWidget
|
174
40
|
@@prettyprint_default = false
|
175
41
|
def prettyprint_default
|
176
42
|
@@prettyprint_default
|
177
43
|
end
|
178
44
|
|
45
|
+
def self.prettyprint_default
|
46
|
+
@@prettyprint_default
|
47
|
+
end
|
48
|
+
|
179
49
|
def self.prettyprint_default=(enabled)
|
180
50
|
@@prettyprint_default = enabled
|
181
51
|
end
|
182
52
|
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
SPACES_PER_INDENT = 2
|
189
|
-
|
190
|
-
RESERVED_INSTANCE_VARS = [:helpers, :assigns, :block, :output, :prettyprint, :indentation, :at_start_of_line]
|
191
|
-
|
192
|
-
attr_reader *RESERVED_INSTANCE_VARS
|
193
|
-
attr_reader :parent
|
194
|
-
attr_writer :block
|
195
|
-
|
196
|
-
def initialize(assigns={}, &block)
|
197
|
-
unless assigns.is_a? Hash
|
198
|
-
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."
|
199
|
-
end
|
200
|
-
@assigns = assigns
|
201
|
-
assign_instance_variables(assigns)
|
202
|
-
unless @parent
|
203
|
-
@parent = block ? eval("self", block.binding) : nil
|
204
|
-
end
|
205
|
-
@block = block
|
206
|
-
self.class.after_initialize self
|
53
|
+
def self.inline(*args, &block)
|
54
|
+
Class.new(self) do
|
55
|
+
include Erector::Inline
|
56
|
+
end.new(*args, &block)
|
207
57
|
end
|
208
58
|
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
original_parent = @parent
|
216
|
-
original_output = @output
|
217
|
-
original_indendation = @indentation
|
218
|
-
original_helpers = @helpers
|
219
|
-
original_prettyprint = @prettyprint
|
220
|
-
@parent = parent
|
221
|
-
@output = output
|
222
|
-
@at_start_of_line = true
|
223
|
-
raise "indentation must be a number, not #{indentation.inspect}" unless indentation.is_a? Fixnum
|
224
|
-
@indentation = indentation
|
225
|
-
@helpers = helpers
|
226
|
-
@prettyprint = prettyprint
|
227
|
-
yield
|
228
|
-
ensure
|
229
|
-
@parent = original_parent
|
230
|
-
@output = original_output
|
231
|
-
@indentation = original_indendation
|
232
|
-
@helpers = original_helpers
|
233
|
-
@prettyprint = original_prettyprint
|
59
|
+
[:helpers, :assigns, :output, :parent, :block].each do |attr|
|
60
|
+
class_eval(<<-SRC, __FILE__, __LINE__ + 1)
|
61
|
+
def #{attr}
|
62
|
+
@_#{attr}
|
63
|
+
end
|
64
|
+
SRC
|
234
65
|
end
|
235
66
|
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
assigned = []
|
240
|
-
instance_variables.each do |name, value|
|
241
|
-
unless needed.empty? || needed.include?(name)
|
242
|
-
raise "Unknown parameter '#{name}'. #{self.class.name} only accepts #{needed.join(', ')}"
|
243
|
-
end
|
244
|
-
assign_instance_variable(name, value)
|
245
|
-
assigned << name
|
67
|
+
def initialize(assigns = {}, &block)
|
68
|
+
unless assigns.is_a? Hash
|
69
|
+
raise "Erector widgets are initialized with only a parameter hash. (Other parameters are passed to to_html, or the #widget method.)"
|
246
70
|
end
|
247
71
|
|
248
|
-
|
249
|
-
self.class.get_needed_defaults.each do |hash|
|
250
|
-
hash.each_pair do |name, value|
|
251
|
-
unless assigned.include?(name)
|
252
|
-
assign_instance_variable(name, value)
|
253
|
-
assigned << name
|
254
|
-
end
|
255
|
-
end
|
256
|
-
end
|
72
|
+
@_assigns = assigns
|
257
73
|
|
258
|
-
|
259
|
-
|
260
|
-
raise "Missing parameter#{missing.size == 1 ? '' : 's'}: #{missing.join(', ')}"
|
74
|
+
assigns.each do |name, value|
|
75
|
+
instance_variable_set(name.to_s[0..0] == '@' ? name : "@#{name}", value)
|
261
76
|
end
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
raise ArgumentError, "Sorry, #{name} is a reserved variable name for Erector. Please choose a different name." if RESERVED_INSTANCE_VARS.include?(name)
|
266
|
-
name = name.to_s
|
267
|
-
ivar_name = (name[0..0] == '@' ? name : "@#{name}")
|
268
|
-
instance_variable_set(ivar_name, value)
|
269
|
-
end
|
270
|
-
|
271
|
-
# Render (like to_s) but adding newlines and indentation.
|
272
|
-
# This is a convenience method; you may just want to call to_s(:prettyprint => true)
|
273
|
-
# so you can pass in other rendering options as well.
|
274
|
-
def to_pretty
|
275
|
-
to_s(:prettyprint => true)
|
276
|
-
end
|
277
|
-
|
278
|
-
# Render (like to_s) but stripping all tags.
|
279
|
-
def to_text
|
280
|
-
CGI.unescapeHTML(to_s(:prettyprint => false).gsub(/<[^>]*>/, ''))
|
77
|
+
|
78
|
+
@_parent = eval("self", block.binding) if block
|
79
|
+
@_block = block
|
281
80
|
end
|
282
81
|
|
283
82
|
# Entry point for rendering a widget (and all its children). This method
|
@@ -291,39 +90,40 @@ module Erector
|
|
291
90
|
# by default).
|
292
91
|
# indentation:: the amount of spaces to indent. Ignored unless prettyprint
|
293
92
|
# is true.
|
93
|
+
# max_length:: preferred maximum length of a line. Line wraps will only
|
94
|
+
# occur at space characters, so a long word may end up creating
|
95
|
+
# a line longer than this. If nil (default), then there is no
|
96
|
+
# arbitrary limit to line lengths, and only internal newline
|
97
|
+
# characters and prettyprinting will determine newlines in the
|
98
|
+
# output.
|
294
99
|
# helpers:: a helpers object containing utility methods. Usually this is a
|
295
100
|
# Rails view object.
|
296
101
|
# content_method_name:: in case you want to call a method other than
|
297
102
|
# #content, pass its name in here.
|
298
|
-
def
|
299
|
-
raise "Erector::Widget#
|
300
|
-
_render(options
|
103
|
+
def to_html(options = {})
|
104
|
+
raise "Erector::Widget#to_html takes an options hash, not a symbol. Try calling \"to_html(:content_method_name=> :#{options})\"" if options.is_a? Symbol
|
105
|
+
_render(options).to_s
|
301
106
|
end
|
302
|
-
|
303
|
-
#
|
107
|
+
|
108
|
+
# alias for #to_html
|
109
|
+
# @deprecated Please use {#to_html} instead
|
110
|
+
def to_s(*args)
|
111
|
+
unless defined? @@already_warned_to_s
|
112
|
+
$stderr.puts "Erector::Widget#to_s is deprecated. Please use #to_html instead. Called from #{caller.first}"
|
113
|
+
@@already_warned_to_s = true
|
114
|
+
end
|
115
|
+
to_html(*args)
|
116
|
+
end
|
117
|
+
|
118
|
+
# Entry point for rendering a widget (and all its children). Same as #to_html
|
304
119
|
# only it returns an array, for theoretical performance improvements when using a
|
305
120
|
# Rack server (like Sinatra or Rails Metal).
|
306
121
|
#
|
307
|
-
# # Options: see #
|
308
|
-
def to_a(options = {}
|
309
|
-
_render(
|
310
|
-
end
|
311
|
-
|
312
|
-
def _render(options = {}, &blk)
|
313
|
-
options = {
|
314
|
-
:output => "", # "" is apparently faster than [] in a long-running process
|
315
|
-
:prettyprint => prettyprint_default,
|
316
|
-
:indentation => 0,
|
317
|
-
:helpers => nil,
|
318
|
-
:parent => @parent,
|
319
|
-
:content_method_name => :content,
|
320
|
-
}.merge(options)
|
321
|
-
context(options[:parent], options[:output], options[:prettyprint], options[:indentation], options[:helpers]) do
|
322
|
-
send(options[:content_method_name], &blk)
|
323
|
-
output
|
324
|
-
end
|
122
|
+
# # Options: see #to_html
|
123
|
+
def to_a(options = {})
|
124
|
+
_render(options).to_a
|
325
125
|
end
|
326
|
-
|
126
|
+
|
327
127
|
# Template method which must be overridden by all widget subclasses.
|
328
128
|
# Inside this method you call the magic #element methods which emit HTML
|
329
129
|
# and text to the output string. If you call "super" (or don't override
|
@@ -347,18 +147,7 @@ module Erector
|
|
347
147
|
# If you want this block to have access to Erector methods then use
|
348
148
|
# Erector::Inline#content or Erector#inline.
|
349
149
|
def call_block
|
350
|
-
@
|
351
|
-
end
|
352
|
-
|
353
|
-
# To call one widget from another, inside the parent widget's +content+
|
354
|
-
# method, instantiate the child widget and call its +write_via+ method,
|
355
|
-
# passing in +self+. This assures that the same output string is used,
|
356
|
-
# which gives better performance than using +capture+ or +to_s+. You can
|
357
|
-
# also use the +widget+ method.
|
358
|
-
def write_via(parent)
|
359
|
-
context(parent, parent.output, parent.prettyprint, parent.indentation, parent.helpers) do
|
360
|
-
content
|
361
|
-
end
|
150
|
+
@_block.call(self) if @_block
|
362
151
|
end
|
363
152
|
|
364
153
|
# Emits a (nested) widget onto the current widget's output stream. Accepts
|
@@ -367,404 +156,62 @@ module Erector
|
|
367
156
|
# If the first argument is an instance then the hash must be unspecified
|
368
157
|
# (or empty). If a block is passed to this method, then it gets set as the
|
369
158
|
# rendered widget's block.
|
370
|
-
def widget(target, assigns={}, &block)
|
371
|
-
child = if target.is_a? Class
|
372
|
-
target.new(assigns, &block)
|
373
|
-
else
|
374
|
-
unless assigns.empty?
|
375
|
-
raise "Unexpected second parameter. Did you mean to pass in variables when you instantiated the #{target.class.to_s}?"
|
376
|
-
end
|
377
|
-
target.block = block unless block.nil?
|
378
|
-
target
|
379
|
-
end
|
380
|
-
child.write_via(self)
|
381
|
-
end
|
382
|
-
|
383
|
-
# (Should we make this hidden?)
|
384
|
-
def html_escape
|
385
|
-
return to_s
|
386
|
-
end
|
387
|
-
|
388
|
-
#-- methods for subclasses to call
|
389
|
-
#++
|
390
|
-
|
391
|
-
# Internal method used to emit an HTML/XML element, including an open tag,
|
392
|
-
# attributes (optional, via the default hash), contents (also optional),
|
393
|
-
# and close tag.
|
394
|
-
#
|
395
|
-
# Using the arcane powers of Ruby, there are magic methods that call
|
396
|
-
# +element+ for all the standard HTML tags, like +a+, +body+, +p+, and so
|
397
|
-
# forth. Look at the source of #full_tags for the full list.
|
398
|
-
# Unfortunately, this big mojo confuses rdoc, so we can't see each method
|
399
|
-
# in this rdoc page, but trust us, they're there.
|
400
|
-
#
|
401
|
-
# When calling one of these magic methods, put attributes in the default
|
402
|
-
# hash. If there is a string parameter, then it is used as the contents.
|
403
|
-
# If there is a block, then it is executed (yielded), and the string
|
404
|
-
# parameter is ignored. The block will usually be in the scope of the
|
405
|
-
# child widget, which means it has access to all the methods of Widget,
|
406
|
-
# which will eventually end up appending text to the +output+ string. See
|
407
|
-
# how elegant it is? Not confusing at all if you don't think about it.
|
408
|
-
#
|
409
|
-
def element(*args, &block)
|
410
|
-
__element__(false, *args, &block)
|
411
|
-
end
|
412
|
-
|
413
|
-
# Like +element+, but string parameters are not escaped.
|
414
|
-
def element!(*args, &block)
|
415
|
-
__element__(true, *args, &block)
|
416
|
-
end
|
417
|
-
|
418
|
-
# Internal method used to emit a self-closing HTML/XML element, including
|
419
|
-
# a tag name and optional attributes (passed in via the default hash).
|
420
159
|
#
|
421
|
-
#
|
422
|
-
#
|
423
|
-
#
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
def empty_element(*args, &block)
|
428
|
-
__empty_element__(*args, &block)
|
429
|
-
end
|
430
|
-
|
431
|
-
# Returns an HTML-escaped version of its parameter. Leaves the output
|
432
|
-
# string untouched. Note that the #text method automatically HTML-escapes
|
433
|
-
# its parameter, so be careful *not* to do something like text(h("2<4"))
|
434
|
-
# since that will double-escape the less-than sign (you'll get
|
435
|
-
# "2&lt;4" instead of "2<4").
|
436
|
-
def h(content)
|
437
|
-
content.html_escape
|
438
|
-
end
|
439
|
-
|
440
|
-
# Emits an open tag, comprising '<', tag name, optional attributes, and '>'
|
441
|
-
def open_tag(tag_name, attributes={})
|
442
|
-
indent_for_open_tag(tag_name)
|
443
|
-
@indentation += SPACES_PER_INDENT
|
444
|
-
|
445
|
-
output << "<#{tag_name}#{format_attributes(attributes)}>"
|
446
|
-
@at_start_of_line = false
|
447
|
-
end
|
448
|
-
|
449
|
-
# Emits text. If a string is passed in, it will be HTML-escaped. If a
|
450
|
-
# widget or the result of calling methods such as raw is passed in, the
|
451
|
-
# HTML will not be HTML-escaped again. If another kind of object is passed
|
452
|
-
# in, the result of calling its to_s method will be treated as a string
|
453
|
-
# would be.
|
454
|
-
def text(value)
|
455
|
-
if value.is_a? Widget
|
456
|
-
widget value
|
160
|
+
# This is the preferred way to call one widget from inside another. This
|
161
|
+
# method assures that the same output string is used, which gives better
|
162
|
+
# performance than using +capture+ or +to_html+.
|
163
|
+
def widget(target, assigns = {}, options = {}, &block)
|
164
|
+
if target.is_a? Class
|
165
|
+
target.new(assigns, &block)._render_via(self, options)
|
457
166
|
else
|
458
|
-
|
459
|
-
|
460
|
-
@at_start_of_line = false
|
461
|
-
nil
|
462
|
-
end
|
463
|
-
|
464
|
-
# Returns text which will *not* be HTML-escaped.
|
465
|
-
def raw(value)
|
466
|
-
RawString.new(value.to_s)
|
467
|
-
end
|
468
|
-
|
469
|
-
# Emits text which will *not* be HTML-escaped. Same effect as text(raw(s))
|
470
|
-
def text!(value)
|
471
|
-
text raw(value)
|
472
|
-
end
|
473
|
-
|
474
|
-
alias rawtext text!
|
475
|
-
|
476
|
-
# Returns a copy of value with spaces replaced by non-breaking space characters.
|
477
|
-
# With no arguments, return a single non-breaking space.
|
478
|
-
# The output uses the escaping format ' ' since that works
|
479
|
-
# in both HTML and XML (as opposed to ' ' which only works in HTML).
|
480
|
-
def nbsp(value = " ")
|
481
|
-
raw(value.html_escape.gsub(/ /,' '))
|
482
|
-
end
|
483
|
-
|
484
|
-
# Return a character given its unicode code point or unicode name.
|
485
|
-
def character(code_point_or_name)
|
486
|
-
if code_point_or_name.is_a?(Symbol)
|
487
|
-
found = Erector::CHARACTERS[code_point_or_name]
|
488
|
-
if found.nil?
|
489
|
-
raise "Unrecognized character #{code_point_or_name}"
|
490
|
-
end
|
491
|
-
raw("&#x#{sprintf '%x', found};")
|
492
|
-
elsif code_point_or_name.is_a?(Integer)
|
493
|
-
raw("&#x#{sprintf '%x', code_point_or_name};")
|
494
|
-
else
|
495
|
-
raise "Unrecognized argument to character: #{code_point_or_name}"
|
496
|
-
end
|
497
|
-
end
|
498
|
-
|
499
|
-
# Emits a close tag, consisting of '<', '/', tag name, and '>'
|
500
|
-
def close_tag(tag_name)
|
501
|
-
@indentation -= SPACES_PER_INDENT
|
502
|
-
indent()
|
503
|
-
|
504
|
-
output <<("</#{tag_name}>")
|
505
|
-
|
506
|
-
if newliney?(tag_name)
|
507
|
-
_newline
|
508
|
-
end
|
509
|
-
end
|
510
|
-
|
511
|
-
# Emits the result of joining the elements in array with the separator.
|
512
|
-
# The array elements and separator can be Erector::Widget objects,
|
513
|
-
# which are rendered, or strings, which are html-escaped and output.
|
514
|
-
def join(array, separator)
|
515
|
-
first = true
|
516
|
-
array.each do |widget_or_text|
|
517
|
-
if !first
|
518
|
-
text separator
|
167
|
+
unless assigns.empty?
|
168
|
+
raise "Unexpected second parameter. Did you mean to pass in assigns when you instantiated the #{target.class.to_s}?"
|
519
169
|
end
|
520
|
-
|
521
|
-
text widget_or_text
|
170
|
+
target._render_via(self, options, &block)
|
522
171
|
end
|
523
172
|
end
|
524
173
|
|
525
|
-
# Emits an XML instruction, which looks like this: <?xml version=\"1.0\" encoding=\"UTF-8\"?>
|
526
|
-
def instruct(attributes={:version => "1.0", :encoding => "UTF-8"})
|
527
|
-
output << "<?xml#{format_sorted(sort_for_xml_declaration(attributes))}?>"
|
528
|
-
end
|
529
|
-
|
530
|
-
# Emits an HTML comment (<!-- ... -->) surrounding +text+ and/or the output of +block+.
|
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
|
-
#
|
537
|
-
# Since "Authors should avoid putting two or more adjacent hyphens inside comments,"
|
538
|
-
# we emit a warning if you do that.
|
539
|
-
def comment(text = '', &block)
|
540
|
-
puts "Warning: Authors should avoid putting two or more adjacent hyphens inside comments." if 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"
|
556
|
-
end
|
557
|
-
|
558
174
|
# Creates a whole new output string, executes the block, then converts the
|
559
175
|
# output string to a string and returns it as raw text. If at all possible
|
560
|
-
# you should avoid this method since it hurts performance, and use
|
561
|
-
#
|
562
|
-
def capture
|
563
|
-
|
564
|
-
|
565
|
-
|
566
|
-
|
567
|
-
|
568
|
-
|
569
|
-
@output = original_output
|
570
|
-
end
|
571
|
-
end
|
572
|
-
|
573
|
-
full_tags.each do |tag_name|
|
574
|
-
def_full_tag_method(tag_name)
|
575
|
-
end
|
576
|
-
|
577
|
-
empty_tags.each do |tag_name|
|
578
|
-
def_empty_tag_method(tag_name)
|
579
|
-
end
|
580
|
-
|
581
|
-
# Emits a javascript block inside a +script+ tag, wrapped in CDATA
|
582
|
-
# doohickeys like all the cool JS kids do.
|
583
|
-
def javascript(*args, &block)
|
584
|
-
if args.length > 2
|
585
|
-
raise ArgumentError, "Cannot accept more than two arguments"
|
586
|
-
end
|
587
|
-
attributes, value = nil, nil
|
588
|
-
arg0 = args[0]
|
589
|
-
if arg0.is_a?(Hash)
|
590
|
-
attributes = arg0
|
591
|
-
else
|
592
|
-
value = arg0
|
593
|
-
arg1 = args[1]
|
594
|
-
if arg1.is_a?(Hash)
|
595
|
-
attributes = arg1
|
596
|
-
end
|
597
|
-
end
|
598
|
-
attributes ||= {}
|
599
|
-
attributes[:type] = "text/javascript"
|
600
|
-
open_tag 'script', attributes
|
601
|
-
|
602
|
-
# Shouldn't this be a "cdata" HtmlPart?
|
603
|
-
# (maybe, but the syntax is specific to javascript; it isn't
|
604
|
-
# really a generic XML CDATA section. Specifically,
|
605
|
-
# ]]> within value is not treated as ending the
|
606
|
-
# CDATA section by Firefox2 when parsing text/html,
|
607
|
-
# although I guess we could refuse to generate ]]>
|
608
|
-
# there, for the benefit of XML/XHTML parsers).
|
609
|
-
rawtext "\n// <![CDATA[\n"
|
610
|
-
if block
|
611
|
-
instance_eval(&block)
|
612
|
-
else
|
613
|
-
rawtext value
|
614
|
-
end
|
615
|
-
rawtext "\n// ]]>\n"
|
616
|
-
|
617
|
-
close_tag 'script'
|
618
|
-
rawtext "\n"
|
619
|
-
end
|
620
|
-
|
621
|
-
# Convenience method to emit a css file link, which looks like this:
|
622
|
-
# <link href="erector.css" rel="stylesheet" type="text/css" />
|
623
|
-
# The parameter is the full contents of the href attribute, including any ".css" extension.
|
624
|
-
#
|
625
|
-
# If you want to emit raw CSS inline, use the #style method instead.
|
626
|
-
def css(href, options = {})
|
627
|
-
link({:rel => 'stylesheet', :type => 'text/css', :href => href}.merge(options))
|
628
|
-
end
|
629
|
-
|
630
|
-
# Convenience method to emit an anchor tag whose href and text are the same,
|
631
|
-
# e.g. <a href="http://example.com">http://example.com</a>
|
632
|
-
def url(href, options = {})
|
633
|
-
a href, ({:href => href}.merge(options))
|
634
|
-
end
|
635
|
-
|
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
|
641
|
-
|
642
|
-
# emits a jQuery script that is to be run on document ready
|
643
|
-
def jquery(txt)
|
644
|
-
javascript do
|
645
|
-
jquery_ready txt
|
646
|
-
end
|
176
|
+
# you should avoid this method since it hurts performance, and use +widget+
|
177
|
+
# instead.
|
178
|
+
def capture
|
179
|
+
original, @_output = output, Output.new
|
180
|
+
yield
|
181
|
+
original.widgets.concat(output.widgets) # todo: test!!!
|
182
|
+
output.to_s
|
183
|
+
ensure
|
184
|
+
@_output = original
|
647
185
|
end
|
648
186
|
|
649
187
|
protected
|
650
|
-
def
|
651
|
-
|
652
|
-
|
653
|
-
|
654
|
-
|
655
|
-
|
656
|
-
|
657
|
-
### internal utility methods
|
658
|
-
|
659
|
-
protected
|
660
|
-
def __element__(raw, tag_name, *args, &block)
|
661
|
-
if args.length > 2
|
662
|
-
raise ArgumentError, "Cannot accept more than four arguments"
|
663
|
-
end
|
664
|
-
attributes, value = nil, nil
|
665
|
-
arg0 = args[0]
|
666
|
-
if arg0.is_a?(Hash)
|
667
|
-
attributes = arg0
|
668
|
-
else
|
669
|
-
value = arg0
|
670
|
-
arg1 = args[1]
|
671
|
-
if arg1.is_a?(Hash)
|
672
|
-
attributes = arg1
|
673
|
-
end
|
674
|
-
end
|
675
|
-
attributes ||= {}
|
676
|
-
open_tag tag_name, attributes
|
677
|
-
if block && value
|
678
|
-
raise ArgumentError, "You can't pass both a block and a value to #{tag_name} -- please choose one."
|
679
|
-
end
|
680
|
-
if block
|
681
|
-
block.call
|
682
|
-
elsif raw
|
683
|
-
text! value
|
684
|
-
else
|
685
|
-
text value
|
686
|
-
end
|
687
|
-
close_tag tag_name
|
688
|
-
end
|
689
|
-
|
690
|
-
def __empty_element__(tag_name, attributes={})
|
691
|
-
indent_for_open_tag(tag_name)
|
692
|
-
|
693
|
-
output << "<#{tag_name}#{format_attributes(attributes)} />"
|
694
|
-
|
695
|
-
if newliney?(tag_name)
|
696
|
-
_newline
|
697
|
-
end
|
698
|
-
end
|
699
|
-
|
700
|
-
def _newline
|
701
|
-
return unless @prettyprint
|
702
|
-
output << "\n"
|
703
|
-
@at_start_of_line = true
|
704
|
-
end
|
705
|
-
|
706
|
-
def indent_for_open_tag(tag_name)
|
707
|
-
return unless @prettyprint
|
708
|
-
if !@at_start_of_line && newliney?(tag_name)
|
709
|
-
_newline
|
710
|
-
end
|
711
|
-
indent()
|
712
|
-
end
|
713
|
-
|
714
|
-
def indent()
|
715
|
-
if @at_start_of_line
|
716
|
-
output << " " * [@indentation, 0].max
|
717
|
-
end
|
718
|
-
end
|
719
|
-
|
720
|
-
def format_attributes(attributes)
|
721
|
-
if !attributes || attributes.empty?
|
722
|
-
""
|
723
|
-
else
|
724
|
-
format_sorted(sorted(attributes))
|
725
|
-
end
|
726
|
-
end
|
727
|
-
|
728
|
-
def format_sorted(sorted)
|
729
|
-
results = ['']
|
730
|
-
sorted.each do |key, value|
|
731
|
-
if value
|
732
|
-
if value.is_a?(Array)
|
733
|
-
value = value.flatten
|
734
|
-
next if value.empty?
|
735
|
-
value = value.join(' ')
|
736
|
-
end
|
737
|
-
results << "#{key}=\"#{value.html_escape}\""
|
738
|
-
end
|
739
|
-
end
|
740
|
-
return results.join(' ')
|
741
|
-
end
|
742
|
-
|
743
|
-
def sorted(attributes)
|
744
|
-
stringized = []
|
745
|
-
attributes.each do |key, value|
|
746
|
-
stringized << [key.to_s, value]
|
747
|
-
end
|
748
|
-
return stringized.sort
|
749
|
-
end
|
188
|
+
def _render(options = {}, &block)
|
189
|
+
@_block = block if block
|
190
|
+
@_parent = options[:parent] || parent
|
191
|
+
@_helpers = options[:helpers] || parent
|
192
|
+
@_output = options[:output]
|
193
|
+
@_output = Output.new(options) unless output.is_a?(Output)
|
750
194
|
|
751
|
-
|
752
|
-
|
753
|
-
|
754
|
-
stringized = []
|
755
|
-
attributes.each do |key, value|
|
756
|
-
stringized << [key.to_s, value]
|
757
|
-
end
|
758
|
-
return stringized.sort{|a, b| b <=> a}
|
195
|
+
output.widgets << self.class
|
196
|
+
send(options[:content_method_name] || :content)
|
197
|
+
output
|
759
198
|
end
|
760
199
|
|
761
|
-
def
|
762
|
-
|
763
|
-
|
764
|
-
|
765
|
-
false
|
766
|
-
end
|
200
|
+
def _render_via(parent, options = {}, &block)
|
201
|
+
_render(options.merge(:parent => parent,
|
202
|
+
:output => parent.output,
|
203
|
+
:helpers => parent.helpers), &block)
|
767
204
|
end
|
205
|
+
end
|
768
206
|
|
207
|
+
class Widget < AbstractWidget
|
208
|
+
include Erector::HTML
|
209
|
+
include Erector::Needs
|
210
|
+
include Erector::Caching
|
211
|
+
include Erector::Externals
|
212
|
+
include Erector::Convenience
|
213
|
+
include Erector::JQuery
|
214
|
+
include Erector::AfterInitialize
|
215
|
+
include Erector::Sass if Object.const_defined?(:Sass)
|
769
216
|
end
|
770
217
|
end
|