express_templates 0.3.1 → 0.3.2

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