express_templates 0.3.1 → 0.3.2
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.
- checksums.yaml +4 -4
- data/README.md +19 -5
- data/lib/express_templates/components.rb +2 -0
- data/lib/express_templates/components/capabilities/parenting.rb +1 -1
- data/lib/express_templates/components/capabilities/wrapping.rb +1 -1
- data/lib/express_templates/components/content_for.rb +5 -1
- data/lib/express_templates/components/for_each.rb +1 -1
- data/lib/express_templates/components/form_for.rb +375 -8
- data/lib/express_templates/components/table_for.rb +54 -10
- data/lib/express_templates/components/tree_for.rb +46 -5
- data/lib/express_templates/expander.rb +1 -5
- data/lib/express_templates/macro.rb +1 -0
- data/lib/express_templates/version.rb +1 -1
- data/test/components/container_test.rb +16 -1
- data/test/components/form_for_test.rb +197 -0
- data/test/components/table_for_test.rb +128 -33
- data/test/components/tree_for_test.rb +103 -0
- data/test/dummy/app/views/hello/show.html.et +1 -1
- data/test/dummy/config/environments/production.rb +1 -1
- data/test/dummy/config/environments/test.rb +2 -1
- data/test/dummy/log/test.log +558 -146934
- data/test/dummy/test/controllers/hello_controller_test.rb +1 -1
- data/test/dummy/tmp/cache/assets/test/sprockets/13fe41fee1fe35b49d145bcc06610705 +0 -0
- data/test/dummy/tmp/cache/assets/test/sprockets/2f5173deea6c795b8fdde723bb4b63af +0 -0
- data/test/dummy/tmp/cache/assets/test/sprockets/357970feca3ac29060c1e3861e2c0953 +0 -0
- data/test/dummy/tmp/cache/assets/test/sprockets/cffd775d018f68ce5dba1ee0d951a994 +0 -0
- data/test/dummy/tmp/cache/assets/test/sprockets/d771ace226fc8215a3572e0aa35bb0d6 +0 -0
- data/test/dummy/tmp/cache/assets/test/sprockets/f7cbd26ba1d28d48de824f0e94586655 +0 -0
- data/test/test_helper.rb +2 -0
- metadata +13 -9
- data/test/dummy/log/development.log +0 -6478
@@ -8,17 +8,19 @@ module ExpressTemplates
|
|
8
8
|
#
|
9
9
|
# Example:
|
10
10
|
#
|
11
|
-
#
|
11
|
+
# ````ruby
|
12
12
|
# table_for(:people) do |t|
|
13
13
|
# t.column :name
|
14
14
|
# t.column :email
|
15
15
|
# t.column :phone
|
16
|
+
# t.column :hourly_rate, header: "Rate",
|
17
|
+
# formatter: -> (amount) {'$%0.2f' % amount rescue 'N/A'}
|
16
18
|
# end
|
17
|
-
#
|
19
|
+
# ````
|
18
20
|
#
|
19
21
|
# This assumes that a @people variable will exist in the
|
20
22
|
# view and that it will be a collection whose members respond to
|
21
|
-
# :name, :email,
|
23
|
+
# :name, :email, :phone, :hourly_rate
|
22
24
|
#
|
23
25
|
# This will result in markup like the following:
|
24
26
|
#
|
@@ -28,6 +30,7 @@ module ExpressTemplates
|
|
28
30
|
# <th class="name">Name</th>
|
29
31
|
# <th class="email">Email</th>
|
30
32
|
# <th class="phone">Phone</th>
|
33
|
+
# <th class="hourly_rate">Rate</th>
|
31
34
|
# </tr>
|
32
35
|
# </thead>
|
33
36
|
# <tbody>
|
@@ -35,10 +38,17 @@ module ExpressTemplates
|
|
35
38
|
# <td class="name">Steven Talcott Smith</td>
|
36
39
|
# <td class="email">steve@aelogica.com</td>
|
37
40
|
# <td class="phone">415-555-1212</td>
|
41
|
+
# <td class="hourly_rate">$250.00</td>
|
38
42
|
# </tr>
|
39
43
|
# </tbody>
|
40
44
|
# </table>
|
41
45
|
#
|
46
|
+
# Note that column options include :formatter and :header.
|
47
|
+
#
|
48
|
+
# :formatter may be a stabby lambda which is passed the value to be formatted.
|
49
|
+
#
|
50
|
+
# :header may be either a string
|
51
|
+
#
|
42
52
|
class TableFor < Base
|
43
53
|
include Capabilities::Configurable
|
44
54
|
include Capabilities::Building
|
@@ -68,15 +78,21 @@ module ExpressTemplates
|
|
68
78
|
}
|
69
79
|
}
|
70
80
|
tbody {
|
71
|
-
for_each(my[:id]) {
|
81
|
+
for_each("@#{my[:id]}".to_sym) {
|
72
82
|
|
73
|
-
tr(id:
|
83
|
+
tr(id: "#{my[:id].to_s.singularize}-{{#{my[:id].to_s.singularize}.id}}",
|
74
84
|
class: my[:id].to_s.singularize) {
|
75
85
|
|
76
86
|
columns.each do |column|
|
77
|
-
|
78
|
-
column.
|
79
|
-
|
87
|
+
if(column.has_actions?)
|
88
|
+
td(class: column.name) {
|
89
|
+
column.show_actions(my[:id].to_s)
|
90
|
+
}
|
91
|
+
else
|
92
|
+
td(class: column.name) {
|
93
|
+
column.format(my[:id].to_s.singularize)
|
94
|
+
}
|
95
|
+
end
|
80
96
|
end
|
81
97
|
}
|
82
98
|
}
|
@@ -98,6 +114,12 @@ module ExpressTemplates
|
|
98
114
|
@name = name
|
99
115
|
@options = options
|
100
116
|
@formatter = options[:formatter]
|
117
|
+
@header = options[:header]
|
118
|
+
@actions = options[:actions] || []
|
119
|
+
end
|
120
|
+
|
121
|
+
def has_actions?
|
122
|
+
@actions.any?
|
101
123
|
end
|
102
124
|
|
103
125
|
def format(item_name)
|
@@ -108,12 +130,34 @@ module ExpressTemplates
|
|
108
130
|
end
|
109
131
|
end
|
110
132
|
|
133
|
+
def show_actions(item_name)
|
134
|
+
action_links = StringIO.new
|
135
|
+
@actions.each do |action|
|
136
|
+
action_name = action.to_s
|
137
|
+
if action_name.eql?('edit')
|
138
|
+
action_links.puts "<a href='/#{item_name}/{{#{item_name.singularize}.id}}/edit'>Edit</a>"
|
139
|
+
elsif action_name.eql?('delete')
|
140
|
+
action_links.puts "<a href='/#{item_name}/{{#{item_name.singularize}.id}}' data-method='delete' data-confirm='Are you sure?'>Delete</a>"
|
141
|
+
elsif action_name.eql?('show')
|
142
|
+
action_links.puts "<a href='/#{item_name}/{{#{item_name.singularize}.id}}'>Show</a>"
|
143
|
+
end
|
144
|
+
end
|
145
|
+
action_links.string
|
146
|
+
end
|
147
|
+
|
111
148
|
def title
|
112
|
-
|
149
|
+
case
|
150
|
+
when @header.nil?
|
151
|
+
@name.to_s.try(:titleize)
|
152
|
+
when @header.kind_of?(String)
|
153
|
+
@header
|
154
|
+
when @header.kind_of?(Proc)
|
155
|
+
"{{(#{@header.source}).call(#{@name})}}"
|
156
|
+
end
|
113
157
|
end
|
114
158
|
end
|
115
159
|
|
116
160
|
end
|
117
161
|
|
118
162
|
end
|
119
|
-
end
|
163
|
+
end
|
@@ -22,20 +22,61 @@ module ExpressTemplates
|
|
22
22
|
# <ul id="roles" class="roles tree">
|
23
23
|
# <li>SuperAdmin
|
24
24
|
# <ul>
|
25
|
-
# <li>Admin
|
25
|
+
# <li>Admin
|
26
26
|
# <ul>
|
27
|
-
# <li>Publisher
|
27
|
+
# <li>Publisher
|
28
28
|
# <ul>
|
29
29
|
# <li>Author</li>
|
30
30
|
# </ul>
|
31
|
+
# </li>
|
31
32
|
# <li>Auditor</li>
|
32
|
-
# </
|
33
|
+
# </ul>
|
33
34
|
# </li>
|
34
|
-
# </
|
35
|
+
# </ul>
|
35
36
|
# </li>
|
36
|
-
# </
|
37
|
+
# </ul>
|
37
38
|
#
|
38
39
|
class TreeFor < Container
|
40
|
+
def node_renderer
|
41
|
+
return (-> (node, renderer) {
|
42
|
+
ExpressTemplates::Indenter.for(:tree) do |ws, wsnl|
|
43
|
+
"#{wsnl}<li>"+
|
44
|
+
_yield +
|
45
|
+
if node.children.any?
|
46
|
+
ExpressTemplates::Indenter.for(:tree) do |ws, wsnl|
|
47
|
+
"#{wsnl}<ul>" +
|
48
|
+
node.children.map do |child|
|
49
|
+
renderer.call(child, renderer)
|
50
|
+
end.join +
|
51
|
+
"#{wsnl}</ul>"
|
52
|
+
end +
|
53
|
+
"#{wsnl}</li>"
|
54
|
+
else
|
55
|
+
"</li>"
|
56
|
+
end
|
57
|
+
end
|
58
|
+
}).source.sub(/\W_yield\W/, compile_children.lstrip)
|
59
|
+
end
|
60
|
+
|
61
|
+
def compile
|
62
|
+
collection = _variablize(@options[:id])
|
63
|
+
member = @options[:id].to_s.singularize
|
64
|
+
return 'ExpressTemplates::Components::TreeFor.render_in(self) {
|
65
|
+
node_renderer = '+node_renderer.gsub(/node/, member)+'
|
66
|
+
ExpressTemplates::Indenter.for(:tree) do |ws, wsnl|
|
67
|
+
"#{ws}<ul id=\"roles\" class=\"roles tree\">" +
|
68
|
+
'+collection+'.map do |'+member+'|
|
69
|
+
node_renderer.call('+member+', node_renderer)
|
70
|
+
end.join +
|
71
|
+
"#{wsnl}</ul>\n"
|
72
|
+
end
|
73
|
+
}'
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
def _variablize(sym)
|
78
|
+
"@#{sym}"
|
79
|
+
end
|
39
80
|
end
|
40
81
|
end
|
41
82
|
end
|
@@ -21,7 +21,7 @@ module ExpressTemplates
|
|
21
21
|
def expand(source=nil, &block)
|
22
22
|
case
|
23
23
|
when block.nil? && source
|
24
|
-
modified =
|
24
|
+
modified = _replace_yield_with_yielder(source)
|
25
25
|
instance_eval(modified, @template.inspect)
|
26
26
|
when block
|
27
27
|
instance_exec &block
|
@@ -92,10 +92,6 @@ module ExpressTemplates
|
|
92
92
|
source.gsub(/(\W)(yield)(\([^\)]*\))?/, '\1 (stack << ExpressTemplates::Markup::Yielder.new\3)')
|
93
93
|
end
|
94
94
|
|
95
|
-
def _wrap_instance_vars(source)
|
96
|
-
source.gsub(/(\W)(@\w+)(\W)?/, '\1 (stack << ExpressTemplates::Markup::Wrapper.new("\2") )\3')
|
97
|
-
end
|
98
|
-
|
99
95
|
class Stack
|
100
96
|
def initialize
|
101
97
|
@stack = [[]]
|
@@ -38,7 +38,7 @@ class ContainerTest < ActiveSupport::TestCase
|
|
38
38
|
child2.verify
|
39
39
|
end
|
40
40
|
|
41
|
-
test "
|
41
|
+
test "renders children in place of _yield" do
|
42
42
|
container = TestContainer.new
|
43
43
|
child1, child2 = mock_children
|
44
44
|
container.children = [child1, child2]
|
@@ -46,4 +46,19 @@ class ContainerTest < ActiveSupport::TestCase
|
|
46
46
|
assert_equal "<p>onetwo</p>", eval(container.compile)
|
47
47
|
end
|
48
48
|
|
49
|
+
class Context
|
50
|
+
def a_helper
|
51
|
+
"foo"
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
test "children with interpolations" do
|
56
|
+
markup = ExpressTemplates.render(Context.new) do
|
57
|
+
row {
|
58
|
+
p %q(Should say: {{a_helper}}.)
|
59
|
+
}
|
60
|
+
end
|
61
|
+
assert_equal "<div class=\"row\"><p>Should say: foo.</p></div>", markup
|
62
|
+
end
|
63
|
+
|
49
64
|
end
|
@@ -0,0 +1,197 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
require 'ostruct'
|
3
|
+
|
4
|
+
class FormForTest < ActiveSupport::TestCase
|
5
|
+
class Context
|
6
|
+
def initialize(resource)
|
7
|
+
@resource = resource
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def resource
|
12
|
+
OpenStruct.new(
|
13
|
+
id: 1,
|
14
|
+
name: 'Foo',
|
15
|
+
body: 'Hello world',
|
16
|
+
email: 'some@email.com',
|
17
|
+
phone: '123123123',
|
18
|
+
url: 'http://someurl.com',
|
19
|
+
number: 123,
|
20
|
+
dropdown: 'yes',
|
21
|
+
gender: 'Male'
|
22
|
+
)
|
23
|
+
end
|
24
|
+
|
25
|
+
def setup
|
26
|
+
@example_compiled = -> {
|
27
|
+
ExpressTemplates::Components::FormFor.render_in(self) {
|
28
|
+
"<form action=\"/resources\" method=\"put\" url=\"/yolo\">
|
29
|
+
<div style=\"display:none\">
|
30
|
+
"+%Q(#{utf8_enforcer_tag})+%Q(#{method_tag(:post)})+%Q(#{token_tag})+"
|
31
|
+
</div>
|
32
|
+
|
33
|
+
<div class=\"\">
|
34
|
+
"+%Q(#{label_tag(:name, "post title")})+%Q(#{text_field_tag(:name, @resource.name, label: "post title")})+"
|
35
|
+
</div>
|
36
|
+
|
37
|
+
<div class=\"\">
|
38
|
+
"+%Q(#{label_tag(:body, nil)})+%Q(#{text_field_tag(:body, @resource.body, class: "string")})+"
|
39
|
+
</div>
|
40
|
+
|
41
|
+
<div class=\"field input\">
|
42
|
+
"+%Q(#{label_tag(:email, nil)})+%Q(#{email_field_tag(:email, @resource.email, wrapper_class: "field input")})+"
|
43
|
+
</div>
|
44
|
+
|
45
|
+
<div class=\"\">
|
46
|
+
"+%Q(#{label_tag(:phone, nil)})+%Q(#{phone_field_tag(:phone, @resource.phone, {})})+"
|
47
|
+
</div>
|
48
|
+
|
49
|
+
<div class=\"\">
|
50
|
+
"+%Q(#{label_tag(:url, nil)})+%Q(#{url_field_tag(:url, @resource.url, {})})+"
|
51
|
+
</div>
|
52
|
+
|
53
|
+
<div class=\"\">
|
54
|
+
"+%Q(#{label_tag(:number, nil)})+%Q(#{number_field_tag(:number, @resource.number, {})})+"
|
55
|
+
</div>
|
56
|
+
|
57
|
+
<div class=\"\">"+%Q(#{submit_tag("Save it!", {})})+"</div>
|
58
|
+
</form>
|
59
|
+
"
|
60
|
+
}
|
61
|
+
}
|
62
|
+
end
|
63
|
+
|
64
|
+
def example_compiled_src
|
65
|
+
# necessary because the #source method is not perfect yet
|
66
|
+
# ideally we would have #source_body
|
67
|
+
@example_compiled.source_body
|
68
|
+
end
|
69
|
+
|
70
|
+
def simple_form(resource)
|
71
|
+
ctx = Context.new(resource)
|
72
|
+
fragment = -> {
|
73
|
+
form_for(:resource, method: :put, url: '/yolo') do |f|
|
74
|
+
f.text_field :name, label: 'post title'
|
75
|
+
f.text_field :body, class: 'string'
|
76
|
+
f.email_field :email, wrapper_class: 'field input'
|
77
|
+
f.phone_field :phone
|
78
|
+
f.url_field :url
|
79
|
+
f.number_field :number
|
80
|
+
f.submit 'Save it!'
|
81
|
+
end
|
82
|
+
}
|
83
|
+
return ctx, fragment
|
84
|
+
end
|
85
|
+
|
86
|
+
def select_form(resource)
|
87
|
+
ctx = Context.new(resource)
|
88
|
+
fragment = -> {
|
89
|
+
form_for(:resource, method: :put) do |f|
|
90
|
+
f.select :dropdown, ['yes', 'no'], selected: 'yes'
|
91
|
+
f.select :dropdown, '{{ options_from_collection_for_select(@choices, "id", "name") }}'
|
92
|
+
end
|
93
|
+
}
|
94
|
+
return ctx, fragment
|
95
|
+
end
|
96
|
+
|
97
|
+
def radio_form(resource)
|
98
|
+
ctx = Context.new(resource)
|
99
|
+
fragment = -> {
|
100
|
+
form_for(:resource) do |f|
|
101
|
+
f.radio :age, [[1, 'One'],[2, 'Two']], :first, :last
|
102
|
+
end
|
103
|
+
}
|
104
|
+
return ctx, fragment
|
105
|
+
end
|
106
|
+
|
107
|
+
def checkbox_form(resource)
|
108
|
+
ctx = Context.new(resource)
|
109
|
+
fragment = -> {
|
110
|
+
form_for(:resource, method: :put) do |f|
|
111
|
+
f.checkbox :age, [[1, 'One'], [2, 'Two']], :first, :last
|
112
|
+
end
|
113
|
+
}
|
114
|
+
return ctx, fragment
|
115
|
+
end
|
116
|
+
|
117
|
+
test "fields compiled source is legible and transparent" do
|
118
|
+
ExpressTemplates::Markup::Tag.formatted do
|
119
|
+
# ctx, fragment = simple_form(resource)
|
120
|
+
# assert_equal example_compiled_src, ExpressTemplates.compile(&fragment)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
test "select compiled source is legible and transparent" do
|
125
|
+
@example_compiled = -> {
|
126
|
+
ExpressTemplates::Components::FormFor.render_in(self) {
|
127
|
+
"<form action=\"/resources/#{@resource.id}\" method=\"post\">
|
128
|
+
<div style=\"display:none\">
|
129
|
+
"+%Q(#{utf8_enforcer_tag})+%Q(#{method_tag(:patch)})+%Q(#{token_tag})+"
|
130
|
+
</div>
|
131
|
+
|
132
|
+
<div class=\"\">
|
133
|
+
"+%Q(#{label_tag("resource_dropdown", "Dropdown")})+%Q(#{select_tag("resource[dropdown]", '<option selected=selected>yes</option><option >no</option>'.html_safe, {})})+"
|
134
|
+
</div>
|
135
|
+
|
136
|
+
<div class=\"\">
|
137
|
+
"+%Q(#{label_tag("resource_dropdown", "Dropdown")})+%Q(#{select_tag("resource[dropdown]", options_from_collection_for_select(@choices, "id", "name") , {})})+"
|
138
|
+
</div>
|
139
|
+
</form>
|
140
|
+
"
|
141
|
+
}
|
142
|
+
}
|
143
|
+
ExpressTemplates::Markup::Tag.formatted do
|
144
|
+
ctx, fragment = select_form(resource)
|
145
|
+
assert_equal example_compiled_src, ExpressTemplates.compile(&fragment)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
test "radio compiled source is legible and transparent" do
|
150
|
+
@example_compiled = -> {
|
151
|
+
ExpressTemplates::Components::FormFor.render_in(self) {
|
152
|
+
"<form action=\"/resources\" method=\"post\">
|
153
|
+
<div style=\"display:none\">
|
154
|
+
"+%Q(#{utf8_enforcer_tag})+%Q(#{method_tag(:post)})+%Q(#{token_tag})+"
|
155
|
+
</div>
|
156
|
+
|
157
|
+
<div class=\"\">
|
158
|
+
"+%Q(#{collection_radio_buttons(:resource, :age, [[1, "One"], [2, "Two"]], :first, :last, {}) do |b|
|
159
|
+
b.label(class: 'radio') { b.radio_button + b.text }
|
160
|
+
end})+"
|
161
|
+
</div>
|
162
|
+
</form>
|
163
|
+
"
|
164
|
+
}
|
165
|
+
}
|
166
|
+
|
167
|
+
ExpressTemplates::Markup::Tag.formatted do
|
168
|
+
ctx, fragment = radio_form(resource)
|
169
|
+
assert_equal example_compiled_src, ExpressTemplates.compile(&fragment)
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
test "checkbox compiled source is legible and transparent" do
|
174
|
+
@example_compiled = -> {
|
175
|
+
ExpressTemplates::Components::FormFor.render_in(self) {
|
176
|
+
"<form action=\"/resources/#{@resource.id}\" method=\"post\">
|
177
|
+
<div style=\"display:none\">
|
178
|
+
"+%Q(#{utf8_enforcer_tag})+%Q(#{method_tag(:patch)})+%Q(#{token_tag})+"
|
179
|
+
</div>
|
180
|
+
|
181
|
+
<div class=\"\">
|
182
|
+
"+%Q(#{collection_check_boxes(:resource, :age, [[1, "One"], [2, "Two"]], :first, :last, {}) do |b|
|
183
|
+
b.label(class: 'checkbox') { b.check_box + b.text }
|
184
|
+
end})+"
|
185
|
+
</div>
|
186
|
+
</form>
|
187
|
+
"
|
188
|
+
}
|
189
|
+
}
|
190
|
+
|
191
|
+
ExpressTemplates::Markup::Tag.formatted do
|
192
|
+
ctx, fragment = checkbox_form(resource)
|
193
|
+
assert_equal example_compiled_src, ExpressTemplates.compile(&fragment)
|
194
|
+
end
|
195
|
+
|
196
|
+
end
|
197
|
+
end
|