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