erector 0.1.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 +93 -0
- data/lib/erector.rb +7 -0
- data/lib/erector/extensions/action_controller.rb +13 -0
- data/lib/erector/extensions/object.rb +5 -0
- data/lib/erector/helpers.rb +51 -0
- data/lib/erector/html_parts.rb +34 -0
- data/lib/erector/widget.rb +180 -0
- data/lib/erector/widgets.rb +1 -0
- data/lib/erector/widgets/table.rb +45 -0
- data/spec/erector/extensions/render_widget_spec.rb +49 -0
- data/spec/erector/widget_spec.rb +226 -0
- data/spec/erector/widgets/table_spec.rb +104 -0
- data/spec/spec_helper.rb +13 -0
- data/spec/spec_suite.rb +4 -0
- data/spec/view_caching.rb +30 -0
- metadata +83 -0
data/README.txt
ADDED
@@ -0,0 +1,93 @@
|
|
1
|
+
= Erector
|
2
|
+
|
3
|
+
* http://erector.rubyforge.org
|
4
|
+
* mailto:alex@pivotallabs.com
|
5
|
+
|
6
|
+
|
7
|
+
== DESCRIPTION
|
8
|
+
|
9
|
+
With Erector, you define views without templates, in natural Ruby code, with all the power of objects, functions, modular
|
10
|
+
decomposition, etc.
|
11
|
+
|
12
|
+
== FEATURES/PROBLEMS:
|
13
|
+
|
14
|
+
* FIX (list of features or problems)
|
15
|
+
|
16
|
+
== SYNOPSIS
|
17
|
+
|
18
|
+
TODO
|
19
|
+
|
20
|
+
== REQUIREMENTS
|
21
|
+
|
22
|
+
* treetop
|
23
|
+
|
24
|
+
== INSTALL
|
25
|
+
|
26
|
+
To install as a gem:
|
27
|
+
|
28
|
+
* sudo gem install hoe
|
29
|
+
|
30
|
+
To install as a plugin:
|
31
|
+
|
32
|
+
* ???
|
33
|
+
|
34
|
+
== LICENSE:
|
35
|
+
|
36
|
+
(The MIT License)
|
37
|
+
|
38
|
+
Copyright (c) 2007-8 Pivotal Labs
|
39
|
+
|
40
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
41
|
+
a copy of this software and associated documentation files (the
|
42
|
+
"Software"), to deal in the Software without restriction, including
|
43
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
44
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
45
|
+
permit persons to whom the Software is furnished to do so, subject to
|
46
|
+
the following conditions:
|
47
|
+
|
48
|
+
The above copyright notice and this permission notice shall be
|
49
|
+
included in all copies or substantial portions of the Software.
|
50
|
+
|
51
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
52
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
53
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
54
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
55
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
56
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
57
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
58
|
+
|
59
|
+
== DOCUMENTATION
|
60
|
+
|
61
|
+
TODO
|
62
|
+
|
63
|
+
=== Layout Inheritance
|
64
|
+
|
65
|
+
Erector replaces the typical Rails layout mechanism with a more natural construct, the use of inheritance. Want a common
|
66
|
+
layout? Just implement a layout superclass and inherit from it. Implement render in the superclass and implement template
|
67
|
+
methods in its subclasses. There's one trick you'll need to use this layout for non-erector templates. Here's an example.
|
68
|
+
|
69
|
+
`application.rb` - The Erector layout superclass
|
70
|
+
|
71
|
+
class Views::Layouts::Application < Erector::Widget
|
72
|
+
attr_accessor :content
|
73
|
+
|
74
|
+
def render
|
75
|
+
html do
|
76
|
+
head { } # head content here
|
77
|
+
# body content here
|
78
|
+
body do
|
79
|
+
text content
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
`application.mab` - The markaby template (adjust for other appropriately templating technologies)
|
86
|
+
|
87
|
+
widget = Views::Layouts::Application.new(self)
|
88
|
+
widget.content = content_for_layout
|
89
|
+
self << widget.to_s
|
90
|
+
|
91
|
+
Here the abstract layout widget is used in a concrete fashion by the template-based layout. Normally, the `content` method
|
92
|
+
would be implemented by subclassing widgets, but the layout template sets it directly and then calls to_s on the layout widget.
|
93
|
+
This allows the same layout to be shared in a backward compatible way.
|
data/lib/erector.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
class ActionController::Base
|
2
|
+
def render_widget(widget_class, assigns=@assigns)
|
3
|
+
render :text => render_widget_to_string(widget_class, assigns)
|
4
|
+
end
|
5
|
+
|
6
|
+
def render_widget_to_string(widget_class, assigns = @assigns)
|
7
|
+
add_variables_to_assigns
|
8
|
+
@rendered_widget = widget_class.new(@template, assigns.merge(:params => params))
|
9
|
+
@rendered_widget.to_s
|
10
|
+
end
|
11
|
+
|
12
|
+
attr_reader :rendered_widget
|
13
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module Erector
|
2
|
+
module Helpers
|
3
|
+
[
|
4
|
+
:link_to,
|
5
|
+
:image_tag,
|
6
|
+
:javascript_include_tag,
|
7
|
+
:stylesheet_link_tag,
|
8
|
+
:link_to_function,
|
9
|
+
:link_to_remote,
|
10
|
+
:sortable_element,
|
11
|
+
:sortable_element_js,
|
12
|
+
:mail_to
|
13
|
+
].each do |helper_name|
|
14
|
+
define_method helper_name do |*args|
|
15
|
+
text helpers.send(helper_name, *args)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def javascript_include_merged(key)
|
20
|
+
helpers.javascript_include_merged(key)
|
21
|
+
end
|
22
|
+
|
23
|
+
def stylesheet_link_merged(key)
|
24
|
+
helpers.stylesheet_link_merged(key)
|
25
|
+
end
|
26
|
+
|
27
|
+
def flash
|
28
|
+
helpers.controller.send(:flash)
|
29
|
+
end
|
30
|
+
|
31
|
+
def session
|
32
|
+
helpers.controller.session
|
33
|
+
end
|
34
|
+
|
35
|
+
def cycle(*args)
|
36
|
+
helpers.cycle(*args)
|
37
|
+
end
|
38
|
+
|
39
|
+
def simple_format(*args)
|
40
|
+
helpers.simple_format(*args)
|
41
|
+
end
|
42
|
+
|
43
|
+
def time_ago_in_words(*args)
|
44
|
+
helpers.time_ago_in_words(*args)
|
45
|
+
end
|
46
|
+
|
47
|
+
def pluralize(*args)
|
48
|
+
helpers.pluralize(*args)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Erector
|
2
|
+
class HtmlParts < Array
|
3
|
+
def to_s
|
4
|
+
map do |part|
|
5
|
+
case part['type']
|
6
|
+
when 'open'
|
7
|
+
part['attributes'] ?
|
8
|
+
"<#{part['tagName']}#{format_attributes(part['attributes'])}>" :
|
9
|
+
"<#{part['tagName']}>"
|
10
|
+
when 'close'
|
11
|
+
"</#{part['tagName']}>"
|
12
|
+
when 'standalone'
|
13
|
+
part['attributes'] ?
|
14
|
+
"<#{part['tagName']}#{format_attributes(part['attributes'])} />" :
|
15
|
+
"<#{part['tagName']} />"
|
16
|
+
when 'text'
|
17
|
+
part['value'].to_s
|
18
|
+
when 'instruct'
|
19
|
+
"<?xml#{format_attributes(part['attributes'])}?>"
|
20
|
+
end
|
21
|
+
end.join
|
22
|
+
end
|
23
|
+
|
24
|
+
protected
|
25
|
+
def format_attributes(attributes)
|
26
|
+
return "" if !attributes || attributes.empty?
|
27
|
+
results = ['']
|
28
|
+
attributes.each do |key, value|
|
29
|
+
results << "#{key}=#{value.to_s.inspect}" if value
|
30
|
+
end
|
31
|
+
results.join ' '
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,180 @@
|
|
1
|
+
module Erector
|
2
|
+
class Widget
|
3
|
+
class << self
|
4
|
+
def all_tags
|
5
|
+
Erector::Widget.full_tags + Erector::Widget.standalone_tags
|
6
|
+
end
|
7
|
+
|
8
|
+
def standalone_tags
|
9
|
+
['area', 'base', 'br', 'hr', 'img', 'input', 'link', 'meta']
|
10
|
+
end
|
11
|
+
|
12
|
+
def full_tags
|
13
|
+
[
|
14
|
+
'a', 'acronym', 'address', 'b', 'bdo', 'big', 'blockquote', 'body',
|
15
|
+
'button', 'caption', 'cite', 'code', 'dd', 'del', 'div', 'dl', 'dt', 'em',
|
16
|
+
'fieldset', 'form', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'head', 'html', 'i',
|
17
|
+
'iframe', 'ins', 'kbd', 'label', 'legend', 'li', 'map',
|
18
|
+
'noframes', 'noscript', 'ol', 'optgroup', 'option', 'p', 'param', 'pre',
|
19
|
+
'samp', 'script', 'select', 'small', 'span', 'strong', 'style', 'sub', 'sup',
|
20
|
+
'table', 'tbody', 'td', 'textarea', 'th', 'thead', 'title', 'tr', 'tt', 'u', 'ul', 'var'
|
21
|
+
]
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
include ActionController::UrlWriter, Helpers
|
26
|
+
attr_reader :helpers
|
27
|
+
attr_reader :assigns
|
28
|
+
attr_reader :doc
|
29
|
+
attr_reader :block
|
30
|
+
attr_reader :parent
|
31
|
+
|
32
|
+
# Each item in @doc is an array containing three values: type, value, attributes
|
33
|
+
def initialize(helpers=nil, assigns={}, doc = HtmlParts.new, &block)
|
34
|
+
@assigns = assigns
|
35
|
+
assigns.each do |name, value|
|
36
|
+
instance_variable_set("@#{name}", value)
|
37
|
+
metaclass.module_eval do
|
38
|
+
attr_reader name
|
39
|
+
end
|
40
|
+
end
|
41
|
+
@helpers = helpers
|
42
|
+
fake_erbout
|
43
|
+
@parent = block ? eval("self", block.binding) : nil
|
44
|
+
@doc = doc
|
45
|
+
@block = block
|
46
|
+
end
|
47
|
+
|
48
|
+
def render
|
49
|
+
if @block
|
50
|
+
instance_eval(&@block)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def widget(widget_class, assigns={}, &block)
|
55
|
+
child = widget_class.new(helpers, assigns, doc, &block)
|
56
|
+
child.render
|
57
|
+
end
|
58
|
+
|
59
|
+
def h(content)
|
60
|
+
text CGI.escapeHTML(content)
|
61
|
+
end
|
62
|
+
|
63
|
+
def open_tag(tag_name, attributes={})
|
64
|
+
@doc << {'type' => 'open', 'tagName' => tag_name, 'attributes' => attributes}
|
65
|
+
end
|
66
|
+
|
67
|
+
def text(value)
|
68
|
+
@doc << {'type' => 'text', 'value' => value}
|
69
|
+
nil
|
70
|
+
end
|
71
|
+
|
72
|
+
def close_tag(tag_name)
|
73
|
+
@doc << {'type' => 'close', 'tagName' => tag_name}
|
74
|
+
end
|
75
|
+
|
76
|
+
def instruct!(attributes={:version => "1.0", :encoding => "UTF-8"})
|
77
|
+
@doc << {'type' => 'instruct', 'attributes' => attributes}
|
78
|
+
end
|
79
|
+
|
80
|
+
def javascript(*args, &blk)
|
81
|
+
params = args[0] if args[0].is_a?(Hash)
|
82
|
+
params ||= args[1] if args[1].is_a?(Hash)
|
83
|
+
unless params
|
84
|
+
params = {}
|
85
|
+
args << params
|
86
|
+
end
|
87
|
+
params[:type] = "text/javascript"
|
88
|
+
script(*args, &blk)
|
89
|
+
end
|
90
|
+
|
91
|
+
def __element__(tag_name, *args, &block)
|
92
|
+
if args.length > 2
|
93
|
+
raise ArgumentError, "Cannot accept more than three arguments"
|
94
|
+
end
|
95
|
+
attributes, value = nil, nil
|
96
|
+
arg0 = args[0]
|
97
|
+
if arg0.is_a?(Hash)
|
98
|
+
attributes = arg0
|
99
|
+
else
|
100
|
+
value = arg0.to_s
|
101
|
+
arg1 = args[1]
|
102
|
+
if arg1.is_a?(Hash)
|
103
|
+
attributes = arg1
|
104
|
+
end
|
105
|
+
end
|
106
|
+
attributes ||= {}
|
107
|
+
open_tag tag_name, attributes
|
108
|
+
if block
|
109
|
+
instance_eval(&block)
|
110
|
+
else
|
111
|
+
text value
|
112
|
+
end
|
113
|
+
close_tag tag_name
|
114
|
+
end
|
115
|
+
alias_method :element, :__element__
|
116
|
+
|
117
|
+
def __standalone_element__(tag_name, attributes={})
|
118
|
+
@doc << {'type' => 'standalone', 'tagName' => tag_name, 'attributes' => attributes}
|
119
|
+
end
|
120
|
+
alias_method :standalone_element, :__standalone_element__
|
121
|
+
|
122
|
+
def capture(&block)
|
123
|
+
begin
|
124
|
+
original_doc = @doc
|
125
|
+
@doc = HtmlParts.new
|
126
|
+
yield
|
127
|
+
@doc.to_s
|
128
|
+
ensure
|
129
|
+
@doc = original_doc
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def to_s(&blk)
|
134
|
+
return @__to_s if @__to_s
|
135
|
+
render(&blk)
|
136
|
+
@__to_s = @doc.to_s
|
137
|
+
end
|
138
|
+
|
139
|
+
alias_method :inspect, :to_s
|
140
|
+
|
141
|
+
full_tags.each do |tag_name|
|
142
|
+
self.class_eval(
|
143
|
+
"def #{tag_name}(*args, &block)\n" <<
|
144
|
+
" __element__('#{tag_name}', *args, &block)\n" <<
|
145
|
+
"end",
|
146
|
+
__FILE__,
|
147
|
+
__LINE__ - 4
|
148
|
+
)
|
149
|
+
end
|
150
|
+
|
151
|
+
standalone_tags.each do |tag_name|
|
152
|
+
self.class_eval(
|
153
|
+
"def #{tag_name}(*args, &block)\n" <<
|
154
|
+
" __standalone_element__('#{tag_name}', *args, &block)\n" <<
|
155
|
+
"end",
|
156
|
+
__FILE__,
|
157
|
+
__LINE__ - 4
|
158
|
+
)
|
159
|
+
end
|
160
|
+
|
161
|
+
protected
|
162
|
+
def method_missing(name, *args, &block)
|
163
|
+
block ||= lambda {} # captures self HERE
|
164
|
+
if @parent
|
165
|
+
@parent.send(name, *args, &block)
|
166
|
+
else
|
167
|
+
super
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
def fake_erbout
|
172
|
+
widget = self
|
173
|
+
@helpers.metaclass.class_eval do
|
174
|
+
define_method :concat do |some_text, binding|
|
175
|
+
widget.text some_text
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require "erector/widgets/table"
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Erector
|
2
|
+
module Widgets
|
3
|
+
class Table < Erector::Widget
|
4
|
+
ColumnDefinition = Struct.new(:id, :name, :cell_proc)
|
5
|
+
class << self
|
6
|
+
def column(id, name=id.to_s.humanize.titleize, &cell_proc)
|
7
|
+
cell_proc ||= proc {|object| text object.__send__(id)}
|
8
|
+
column_definitions << ColumnDefinition.new(id, name, cell_proc)
|
9
|
+
end
|
10
|
+
|
11
|
+
def column_definitions
|
12
|
+
@column_definitions ||= []
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def render
|
17
|
+
table do
|
18
|
+
tr do
|
19
|
+
column_definitions.each do |column_def|
|
20
|
+
th do
|
21
|
+
h column_def.name
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
tbody do
|
26
|
+
@row_objects.each do |object|
|
27
|
+
tr do
|
28
|
+
column_definitions.each do |column_def|
|
29
|
+
td do
|
30
|
+
self.instance_exec(object, &column_def.cell_proc)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
protected
|
40
|
+
def column_definitions
|
41
|
+
self.class.column_definitions
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require File.expand_path("#{File.dirname(__FILE__)}/../../spec_helper")
|
2
|
+
|
3
|
+
module BaseSpec
|
4
|
+
class TestWidgetController < ActionController::Base
|
5
|
+
def index_with_implicit_assigns
|
6
|
+
@foobar = "foobar"
|
7
|
+
render_widget Erector::TestWidget
|
8
|
+
end
|
9
|
+
|
10
|
+
def index_with_explicit_assigns
|
11
|
+
render_widget Erector::TestWidget, :foobar => "foobar"
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class Erector::TestWidget < Erector::Widget
|
16
|
+
def render
|
17
|
+
text @foobar
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
describe TestWidgetController, "#render_widget" do
|
22
|
+
before do
|
23
|
+
@controller = BaseSpec::TestWidgetController.new
|
24
|
+
@request = ActionController::TestRequest.new
|
25
|
+
@response = ActionController::TestResponse.new
|
26
|
+
@controller.send(:initialize_template_class, @response)
|
27
|
+
@controller.send(:assign_shortcuts, @request, @response)
|
28
|
+
class << @controller
|
29
|
+
public :rendered_widget
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
it "assigns to @rendered_widget" do
|
34
|
+
@controller.rendered_widget.should be_nil
|
35
|
+
@controller.render_widget Erector::TestWidget
|
36
|
+
@controller.rendered_widget.should be_instance_of(Erector::TestWidget)
|
37
|
+
end
|
38
|
+
|
39
|
+
it "instantiates a widget with implicit assigns" do
|
40
|
+
@controller.index_with_implicit_assigns
|
41
|
+
@response.body.should == "foobar"
|
42
|
+
end
|
43
|
+
|
44
|
+
it "instantiates a widget with explicit assigns" do
|
45
|
+
@controller.index_with_explicit_assigns
|
46
|
+
@response.body.should == "foobar"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,226 @@
|
|
1
|
+
require File.expand_path("#{File.dirname(__FILE__)}/../spec_helper")
|
2
|
+
|
3
|
+
module WidgetSpec
|
4
|
+
describe Erector::Widget do
|
5
|
+
describe ".all_tags" do
|
6
|
+
it "returns set of full and standalone tags" do
|
7
|
+
Erector::Widget.all_tags.class.should == Array
|
8
|
+
Erector::Widget.all_tags.should == Erector::Widget.full_tags + Erector::Widget.standalone_tags
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
describe "#instruct!" do
|
13
|
+
it "when passed no arguments; returns an instruct element with version 1 and utf-8" do
|
14
|
+
html = Erector::Widget.new do
|
15
|
+
instruct!
|
16
|
+
end.to_s
|
17
|
+
html.should include("<?xml")
|
18
|
+
html.should include('encoding="UTF-8"')
|
19
|
+
html.should include('version="1.0')
|
20
|
+
html.should include("?>")
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
describe "#element" do
|
25
|
+
it "when receiving one argument; returns an empty element" do
|
26
|
+
Erector::Widget.new do
|
27
|
+
element('div')
|
28
|
+
end.to_s.should == "<div></div>"
|
29
|
+
end
|
30
|
+
|
31
|
+
it "with a attribute hash; returns an empty element with the attributes" do
|
32
|
+
html = Erector::Widget.new do
|
33
|
+
element(
|
34
|
+
'div',
|
35
|
+
:class => "foo bar",
|
36
|
+
:style => "display: none; color: white; float: left;",
|
37
|
+
:nil_attribute => nil
|
38
|
+
)
|
39
|
+
end.to_s
|
40
|
+
doc = Hpricot(html)
|
41
|
+
div = doc.at('div')
|
42
|
+
div[:class].should == "foo bar"
|
43
|
+
div[:style].should == "display: none; color: white; float: left;"
|
44
|
+
div[:nil_attribute].should be_nil
|
45
|
+
end
|
46
|
+
|
47
|
+
it "with inner tags; returns nested tags" do
|
48
|
+
widget = Erector::Widget.new do
|
49
|
+
element 'div' do
|
50
|
+
element 'div'
|
51
|
+
end
|
52
|
+
end
|
53
|
+
widget.to_s.should == '<div><div></div></div>'
|
54
|
+
end
|
55
|
+
|
56
|
+
it "with text; returns element with inner text" do
|
57
|
+
Erector::Widget.new do
|
58
|
+
element 'div', 'test text'
|
59
|
+
end.to_s.should == "<div>test text</div>"
|
60
|
+
end
|
61
|
+
|
62
|
+
it "with object other than hash; returns element with inner text == object.to_s" do
|
63
|
+
object = ['a', 'b']
|
64
|
+
Erector::Widget.new do
|
65
|
+
element 'div', object
|
66
|
+
end.to_s.should == "<div>#{object.to_s}</div>"
|
67
|
+
end
|
68
|
+
|
69
|
+
it "with parameters and block; returns element with inner html and attributes" do
|
70
|
+
Erector::Widget.new do
|
71
|
+
element 'div', 'class' => "foobar" do
|
72
|
+
element 'span', 'style' => 'display: none;'
|
73
|
+
end
|
74
|
+
end.to_s.should == '<div class="foobar"><span style="display: none;"></span></div>'
|
75
|
+
end
|
76
|
+
|
77
|
+
it "with content and parameters; returns element with content as inner html and attributes" do
|
78
|
+
Erector::Widget.new do
|
79
|
+
element 'div', 'test text', :style => "display: none;"
|
80
|
+
end.to_s.should == '<div style="display: none;">test text</div>'
|
81
|
+
end
|
82
|
+
|
83
|
+
it "with more than three arguments; raises ArgumentError" do
|
84
|
+
proc do
|
85
|
+
Erector::Widget.new do
|
86
|
+
element 'div', 'foobar', {}, 'fourth'
|
87
|
+
end.to_s
|
88
|
+
end.should raise_error(ArgumentError)
|
89
|
+
end
|
90
|
+
|
91
|
+
it "renders the proper full tags" do
|
92
|
+
Erector::Widget.full_tags.each do |tag_name|
|
93
|
+
expected = "<#{tag_name}></#{tag_name}>"
|
94
|
+
actual = Erector::Widget.new do
|
95
|
+
send(tag_name)
|
96
|
+
end.to_s
|
97
|
+
begin
|
98
|
+
actual.should == expected
|
99
|
+
rescue Spec::Expectations::ExpectationNotMetError => e
|
100
|
+
puts "Expected #{tag_name} to be a full element. Expected #{expected}, got #{actual}"
|
101
|
+
raise e
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
describe "#standalone_element" do
|
108
|
+
it "when receiving attributes, renders a standalone_element with the attributes" do
|
109
|
+
Erector::Widget.new do
|
110
|
+
standalone_element 'input', :name => 'foo[bar]'
|
111
|
+
end.to_s.should == '<input name="foo[bar]" />'
|
112
|
+
end
|
113
|
+
|
114
|
+
it "when not receiving attributes, renders a standalone_element without attributes" do
|
115
|
+
Erector::Widget.new do
|
116
|
+
standalone_element 'br'
|
117
|
+
end.to_s.should == '<br />'
|
118
|
+
end
|
119
|
+
|
120
|
+
it "renders the proper standalone tags" do
|
121
|
+
['area', 'base', 'br', 'hr', 'img', 'input', 'link', 'meta'].each do |tag_name|
|
122
|
+
expected = "<#{tag_name} />"
|
123
|
+
actual = Erector::Widget.new do
|
124
|
+
send(tag_name)
|
125
|
+
end.to_s
|
126
|
+
begin
|
127
|
+
actual.should == expected
|
128
|
+
rescue Spec::Expectations::ExpectationNotMetError => e
|
129
|
+
puts "Expected #{tag_name} to be a standalone element. Expected #{expected}, got #{actual}"
|
130
|
+
raise e
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
describe "#javascript" do
|
137
|
+
it "when receiving a block; renders the content inside of a script text/javascript element" do
|
138
|
+
body = Erector::Widget.new do
|
139
|
+
javascript do
|
140
|
+
text 'alert("hello");'
|
141
|
+
end
|
142
|
+
end.to_s
|
143
|
+
doc = Hpricot(body)
|
144
|
+
script_tag = doc.at("script")
|
145
|
+
script_tag[:type].should == "text/javascript"
|
146
|
+
script_tag.inner_html.should include('alert("hello");')
|
147
|
+
end
|
148
|
+
|
149
|
+
it "when receiving a params hash; renders a source file" do
|
150
|
+
html = Erector::Widget.new do
|
151
|
+
javascript(:src => "/my/js/file.js")
|
152
|
+
end.to_s
|
153
|
+
doc = Hpricot(html)
|
154
|
+
doc.at('/')[:src].should == "/my/js/file.js"
|
155
|
+
end
|
156
|
+
|
157
|
+
it "when receiving text and a params hash; renders a source file" do
|
158
|
+
html = Erector::Widget.new do
|
159
|
+
javascript('alert("hello");', :src => "/my/js/file.js")
|
160
|
+
end.to_s
|
161
|
+
doc = Hpricot(html)
|
162
|
+
script_tag = doc.at('script')
|
163
|
+
script_tag[:src].should == "/my/js/file.js"
|
164
|
+
script_tag.inner_html.should include('alert("hello");')
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
describe '#capture' do
|
169
|
+
it "should return content rather than write it to the buffer" do
|
170
|
+
widget = Erector::Widget.new do
|
171
|
+
captured = capture do
|
172
|
+
p 'Captured Content'
|
173
|
+
end
|
174
|
+
div do
|
175
|
+
text captured
|
176
|
+
end
|
177
|
+
end
|
178
|
+
widget.to_s.should == '<div><p>Captured Content</p></div>'
|
179
|
+
end
|
180
|
+
|
181
|
+
it "works with nested captures" do
|
182
|
+
widget = Erector::Widget.new do
|
183
|
+
captured = capture do
|
184
|
+
captured = capture do
|
185
|
+
p 'Nested Capture'
|
186
|
+
end
|
187
|
+
p 'Captured Content'
|
188
|
+
text captured
|
189
|
+
end
|
190
|
+
div do
|
191
|
+
text captured
|
192
|
+
end
|
193
|
+
end
|
194
|
+
widget.to_s.should == '<div><p>Captured Content</p><p>Nested Capture</p></div>'
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
describe '#widget' do
|
199
|
+
before do
|
200
|
+
class Parent < Erector::Widget
|
201
|
+
def render
|
202
|
+
text 1
|
203
|
+
widget Child do
|
204
|
+
text 2
|
205
|
+
third
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
def third
|
210
|
+
text 3
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
class Child < Erector::Widget
|
215
|
+
def render
|
216
|
+
super
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
it "renders nested widgets in the correct order" do
|
222
|
+
Parent.new.to_s.should == '123'
|
223
|
+
end
|
224
|
+
end
|
225
|
+
end
|
226
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
require File.expand_path("#{File.dirname(__FILE__)}/../../spec_helper")
|
2
|
+
|
3
|
+
module TableSpec
|
4
|
+
class DefaultsTestTable < Erector::Widgets::Table
|
5
|
+
column :column_a
|
6
|
+
column :column_b
|
7
|
+
column :column_c
|
8
|
+
end
|
9
|
+
|
10
|
+
class CustomHeadingTable < Erector::Widgets::Table
|
11
|
+
column :a, "Column - A"
|
12
|
+
end
|
13
|
+
|
14
|
+
class CustomCellTable < Erector::Widgets::Table
|
15
|
+
column :a do |obj|
|
16
|
+
span obj.a
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
describe Erector::Widgets::Table do
|
21
|
+
before do
|
22
|
+
view_cache do
|
23
|
+
widget = CustomHeadingTable.new(
|
24
|
+
nil,
|
25
|
+
:row_objects => []
|
26
|
+
)
|
27
|
+
widget.to_s
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
it "renders a tbody to be compatible with IE6" do
|
32
|
+
doc.at("tbody").should_not be_nil
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
describe Erector::Widgets::Table, "with custom heading" do
|
37
|
+
before do
|
38
|
+
view_cache do
|
39
|
+
widget = CustomHeadingTable.new(
|
40
|
+
nil,
|
41
|
+
:row_objects => []
|
42
|
+
)
|
43
|
+
widget.to_s
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
it "renders a custom heading" do
|
48
|
+
table = doc.at("table")
|
49
|
+
table.at("th").inner_html.should == "Column - A"
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
describe Erector::Widgets::Table, "with custom cell content" do
|
54
|
+
before do
|
55
|
+
@object1 = Struct.new(:a).new("Hello")
|
56
|
+
view_cache do
|
57
|
+
widget = CustomCellTable.new(
|
58
|
+
nil,
|
59
|
+
:row_objects => [@object1]
|
60
|
+
)
|
61
|
+
widget.to_s
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
it "renders custom cell html" do
|
66
|
+
table = doc.at("table")
|
67
|
+
row = table.search("tr")[1]
|
68
|
+
row.at("td").inner_html.should == "<span>Hello</span>"
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
describe Erector::Widgets::Table, "with default heading and cell definitions" do
|
73
|
+
before do
|
74
|
+
@object1 = Struct.new(:column_a, :column_b, :column_c).new(1, 2, 3)
|
75
|
+
@object2 = Struct.new(:column_a, :column_b, :column_c).new(4, 5, 6)
|
76
|
+
view_cache do
|
77
|
+
widget = DefaultsTestTable.new(
|
78
|
+
nil,
|
79
|
+
:row_objects => [@object1, @object2]
|
80
|
+
)
|
81
|
+
widget.to_s
|
82
|
+
end
|
83
|
+
@table = doc.at("table")
|
84
|
+
end
|
85
|
+
|
86
|
+
it "renders column titles" do
|
87
|
+
title_row = @table.at("tr")
|
88
|
+
titles = title_row.search("th").collect {|heading| heading.inner_html}
|
89
|
+
titles.should == [ "Column A", "Column B", "Column C" ]
|
90
|
+
end
|
91
|
+
|
92
|
+
it "renders data" do
|
93
|
+
data_rows = @table.search("tr")[1..-1]
|
94
|
+
cell_values = data_rows.collect do |row|
|
95
|
+
row.search("td").collect {|col| col.inner_html}
|
96
|
+
end
|
97
|
+
|
98
|
+
cell_values.should == [
|
99
|
+
['1', '2', '3'],
|
100
|
+
['4', '5', '6']
|
101
|
+
]
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
dir = File.dirname(__FILE__)
|
2
|
+
require "rubygems"
|
3
|
+
require "active_record"
|
4
|
+
require "spec"
|
5
|
+
require "#{dir}/view_caching"
|
6
|
+
$LOAD_PATH.unshift("#{dir}/../lib")
|
7
|
+
require "erector"
|
8
|
+
require "hpricot"
|
9
|
+
require "action_controller/test_process"
|
10
|
+
|
11
|
+
Spec::Runner.configure do |config|
|
12
|
+
config.include ViewCaching
|
13
|
+
end
|
data/spec/spec_suite.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
module ViewCaching
|
2
|
+
def self.included(mod)
|
3
|
+
mod.extend ClassMethods
|
4
|
+
end
|
5
|
+
module ClassMethods
|
6
|
+
def view_cache
|
7
|
+
@view_cache ||= {}
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def view_cache(&blk)
|
12
|
+
cache = self.class.view_cache
|
13
|
+
if cache.empty?
|
14
|
+
cache[:body] = @body = yield
|
15
|
+
cache[:doc] = @doc = Hpricot(@body)
|
16
|
+
else
|
17
|
+
@body = cache[:body]
|
18
|
+
@doc = cache[:doc]
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def doc
|
23
|
+
@doc
|
24
|
+
end
|
25
|
+
|
26
|
+
def body
|
27
|
+
@body
|
28
|
+
end
|
29
|
+
alias_method :html, :body
|
30
|
+
end
|
metadata
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: erector
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Pivotal Labs
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2008-02-13 00:00:00 -08:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: hoe
|
17
|
+
version_requirement:
|
18
|
+
version_requirements: !ruby/object:Gem::Requirement
|
19
|
+
requirements:
|
20
|
+
- - ">="
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 1.5.0
|
23
|
+
version:
|
24
|
+
description: With Erector, you define views without templates, in natural Ruby code, with all the power of objects, functions, modular decomposition, etc.
|
25
|
+
email:
|
26
|
+
- alex@pivotallabs.com
|
27
|
+
executables: []
|
28
|
+
|
29
|
+
extensions: []
|
30
|
+
|
31
|
+
extra_rdoc_files:
|
32
|
+
- README.txt
|
33
|
+
files:
|
34
|
+
- spec/erector
|
35
|
+
- spec/erector/extensions
|
36
|
+
- spec/erector/extensions/render_widget_spec.rb
|
37
|
+
- spec/erector/widget_spec.rb
|
38
|
+
- spec/erector/widgets
|
39
|
+
- spec/erector/widgets/table_spec.rb
|
40
|
+
- spec/spec_helper.rb
|
41
|
+
- spec/spec_suite.rb
|
42
|
+
- spec/view_caching.rb
|
43
|
+
- lib/erector
|
44
|
+
- lib/erector/extensions
|
45
|
+
- lib/erector/extensions/action_controller.rb
|
46
|
+
- lib/erector/extensions/object.rb
|
47
|
+
- lib/erector/helpers.rb
|
48
|
+
- lib/erector/html_parts.rb
|
49
|
+
- lib/erector/widget.rb
|
50
|
+
- lib/erector/widgets
|
51
|
+
- lib/erector/widgets/table.rb
|
52
|
+
- lib/erector/widgets.rb
|
53
|
+
- lib/erector.rb
|
54
|
+
- README.txt
|
55
|
+
has_rdoc: true
|
56
|
+
homepage: http://erector.rubyforge.org
|
57
|
+
post_install_message:
|
58
|
+
rdoc_options:
|
59
|
+
- --main
|
60
|
+
- README.txt
|
61
|
+
require_paths:
|
62
|
+
- lib
|
63
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - ">="
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: "0"
|
68
|
+
version:
|
69
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
70
|
+
requirements:
|
71
|
+
- - ">="
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
version: "0"
|
74
|
+
version:
|
75
|
+
requirements: []
|
76
|
+
|
77
|
+
rubyforge_project: erector
|
78
|
+
rubygems_version: 1.0.1
|
79
|
+
signing_key:
|
80
|
+
specification_version: 2
|
81
|
+
summary: With Erector, you define views without templates, in natural Ruby code, with all the power of objects, functions, modular decomposition, etc.
|
82
|
+
test_files: []
|
83
|
+
|