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.
Files changed (31) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +19 -5
  3. data/lib/express_templates/components.rb +2 -0
  4. data/lib/express_templates/components/capabilities/parenting.rb +1 -1
  5. data/lib/express_templates/components/capabilities/wrapping.rb +1 -1
  6. data/lib/express_templates/components/content_for.rb +5 -1
  7. data/lib/express_templates/components/for_each.rb +1 -1
  8. data/lib/express_templates/components/form_for.rb +375 -8
  9. data/lib/express_templates/components/table_for.rb +54 -10
  10. data/lib/express_templates/components/tree_for.rb +46 -5
  11. data/lib/express_templates/expander.rb +1 -5
  12. data/lib/express_templates/macro.rb +1 -0
  13. data/lib/express_templates/version.rb +1 -1
  14. data/test/components/container_test.rb +16 -1
  15. data/test/components/form_for_test.rb +197 -0
  16. data/test/components/table_for_test.rb +128 -33
  17. data/test/components/tree_for_test.rb +103 -0
  18. data/test/dummy/app/views/hello/show.html.et +1 -1
  19. data/test/dummy/config/environments/production.rb +1 -1
  20. data/test/dummy/config/environments/test.rb +2 -1
  21. data/test/dummy/log/test.log +558 -146934
  22. data/test/dummy/test/controllers/hello_controller_test.rb +1 -1
  23. data/test/dummy/tmp/cache/assets/test/sprockets/13fe41fee1fe35b49d145bcc06610705 +0 -0
  24. data/test/dummy/tmp/cache/assets/test/sprockets/2f5173deea6c795b8fdde723bb4b63af +0 -0
  25. data/test/dummy/tmp/cache/assets/test/sprockets/357970feca3ac29060c1e3861e2c0953 +0 -0
  26. data/test/dummy/tmp/cache/assets/test/sprockets/cffd775d018f68ce5dba1ee0d951a994 +0 -0
  27. data/test/dummy/tmp/cache/assets/test/sprockets/d771ace226fc8215a3572e0aa35bb0d6 +0 -0
  28. data/test/dummy/tmp/cache/assets/test/sprockets/f7cbd26ba1d28d48de824f0e94586655 +0 -0
  29. data/test/test_helper.rb +2 -0
  30. metadata +13 -9
  31. data/test/dummy/log/development.log +0 -6478
@@ -8,17 +8,19 @@ module ExpressTemplates
8
8
  #
9
9
  # Example:
10
10
  #
11
- # ```ruby
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, and :phone
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: -> {"item-#{item.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
- td(class: column.name) {
78
- column.format(:item)
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
- @name.to_s.try(:titleize)
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</li>
25
+ # <li>Admin
26
26
  # <ul>
27
- # <li>Publisher</li>
27
+ # <li>Publisher
28
28
  # <ul>
29
29
  # <li>Author</li>
30
30
  # </ul>
31
+ # </li>
31
32
  # <li>Auditor</li>
32
- # </ol>
33
+ # </ul>
33
34
  # </li>
34
- # </ol>
35
+ # </ul>
35
36
  # </li>
36
- # </ol>
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 = _wrap_instance_vars( _replace_yield_with_yielder(source) )
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 = [[]]
@@ -33,6 +33,7 @@ module ExpressTemplates
33
33
  @options.merge!(child_or_option)
34
34
  when child_or_option.kind_of?(Symbol)
35
35
  @options.merge!(id: child_or_option.to_s)
36
+ when child_or_option.nil?
36
37
  else
37
38
  @children << child_or_option
38
39
  end
@@ -1,3 +1,3 @@
1
1
  module ExpressTemplates
2
- VERSION = "0.3.1"
2
+ VERSION = "0.3.2"
3
3
  end
@@ -38,7 +38,7 @@ class ContainerTest < ActiveSupport::TestCase
38
38
  child2.verify
39
39
  end
40
40
 
41
- test ".render_with_children renders children in place of _yield" do
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