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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: fa578b4bff63a2306c2281d561cf315fa245230c
4
- data.tar.gz: e717b4ddb4f8d244ed21cd48a39c4613c98f3738
3
+ metadata.gz: 833fc8181d87deb3b99763fb91dfdad3b48c6c9e
4
+ data.tar.gz: cb4037d2a9bcd7c2e3084ea9ec2026693f71da5e
5
5
  SHA512:
6
- metadata.gz: 76620daed87fdd884e9ea2089858c76d7fb1bac437af8c8983bea28c904b718f98f63cab711ce41762d40a59e709eced84744a17431d6d56076c0fc45ae39337
7
- data.tar.gz: f5f198c833e6afbb2dd92eda33deb1fe81f384947ccf5972f4dc11825d675a30e84105bfa010a978729ea15f8bda89e5161684309fbb087f26e8d3ce17b31016
6
+ metadata.gz: e2040b7bf2a3fb485bd6d454532c41b3467700efaac36f50333fcd9d8c16d0055f77bd3888640f11f9dc972540504697cf9e92818aaab8ba19c24ecbcc9cd71b
7
+ data.tar.gz: 79e23baf7ea8315fbd06f85b6433b658c233847973de3ddc07d20ddc7cb086c183aee8858bc856e1d24a7c3dd9e2ad234a6151715c096245b8223217f76994aa
data/README.md CHANGED
@@ -58,11 +58,11 @@ ExpressTemplates introduces an earlier step in this process, "expansion", which
58
58
 
59
59
  ## Constraints - Important!
60
60
 
61
- ExpressTemplates imposes some constraints. The most important constraint is that your view templates must be declaritive in syntax and style. Declaritve code is easier to read and reason about. It also requires fewer tests since declarative code is build on presumably well-tested primitives.
61
+ ExpressTemplates imposes some constraints. The most important constraint is that your view templates must be declarative in style. They should not contain conditional logic. Declarative code is easier to read and reason about. It also requires fewer tests since declarative code is build on presumably well-tested primitives.
62
62
 
63
- With ExpressTemplates, one *must not* simply introduce conditional logic or iterators anywhere one feels like it in template code. All logic *must* be encapsulated in components. Strange things will happen if you try to put logic in the template or a template fragment.
63
+ With ExpressTemplates, one *must not* place conditional logic or iterators anywhere in template code. All logic *must* be encapsulated in components. Strange things will happen if you try to put logic in the template or a template fragment. In the future I may issue warnings or disallow it by examinging the output of Ruby's tokenizer.
64
64
 
65
- If you have been around long enough to see a few Rails codebases grow out of control, and you have had to manage or watch the efforts of less-experienced developers closely, you know that one of the major causes of trouble and wasted effort is copy-and-paste code and logic errors in the view. Another common problem is the "blank slate" issue wherein every view must be constructed new with little chance for proper reuse of code. Diligent use of partials and helpers can do much to DRY up view code but deciding where to put them or how to share them between projects can be difficult. Rarely is view logic tested except in integration.
65
+ If you have been around long enough to see a few Rails codebases grow out of control, and you have had to manage or watch the efforts of less-experienced developers closely, you know that one of the major causes of trouble and wasted effort is copy-and-paste code and logic errors in the view. Another common problem is the "blank slate" issue wherein every view must be constructed new with little chance for higher-level reuse of code. Diligent use of partials and helpers can do much to DRY up view code but deciding where to put them or how to share them between projects can be difficult. Rarely is view logic tested except in integration.
66
66
 
67
67
  ExpressTemplates only enforces what is already considered a best practice by many, while introducing new possilibities for well-ordered UX libraries similar to what developers working with commercial frameworks for desktop operating systems or mobile devices enjoy.
68
68
 
@@ -82,7 +82,13 @@ ul {
82
82
 
83
83
  Let us suppose that an @three variable exists in the view context with the value "three". This would yield the following markup:
84
84
 
85
- <ul><li>one</li><li>two</li><li>three</li>
85
+ ```html
86
+ <ul>
87
+ <li>one</li>
88
+ <li>two</li>
89
+ <li>three</li>
90
+ </ul>
91
+ ```
86
92
 
87
93
  yield and local variables which we may expect to be available in a ViewContext are also wrapped for evaluation later.
88
94
 
@@ -129,7 +135,15 @@ div.active {
129
135
 
130
136
  Now when the template renders, it will yield:
131
137
 
132
- <div class="active"><ul><li>one</li><li>two</li><li>three</li></ul></div>
138
+ ```html
139
+ <div class="active">
140
+ <ul>
141
+ <li>one</li>
142
+ <li>two</li>
143
+ <li>three</li>
144
+ </ul>
145
+ </div>
146
+ ```
133
147
 
134
148
  ## Background
135
149
 
@@ -12,5 +12,7 @@ require 'express_templates/components/unless_block'
12
12
  require 'express_templates/components/row'
13
13
  require 'express_templates/components/column'
14
14
  require 'express_templates/components/form_rails_support'
15
+ require 'express_templates/components/form_for'
15
16
  require 'express_templates/components/content_for'
16
17
  require 'express_templates/components/table_for'
18
+ require 'express_templates/components/tree_for'
@@ -45,7 +45,7 @@ module ExpressTemplates
45
45
 
46
46
  def compile
47
47
  null_wrapped_children = "null_wrap { #{compile_children} }"
48
- wrap_children_src = self.class[:markup].source.gsub(/\W_yield\W/, null_wrapped_children)
48
+ wrap_children_src = self.class[:markup].source.gsub(/(\s)_yield(\s)/, '\1'+null_wrapped_children+'\2')
49
49
  _compile_fragment(Proc.from_source(wrap_children_src))
50
50
  end
51
51
 
@@ -16,7 +16,7 @@ module ExpressTemplates
16
16
  #
17
17
  # for_each -> { menu_items }
18
18
  #
19
- # wrap_with :wrapper
19
+ # wrap_with :menu_wrapper
20
20
  #
21
21
  # end
22
22
  #
@@ -10,6 +10,10 @@ module ExpressTemplates
10
10
  # h1 "Title"
11
11
  # }
12
12
  # ```
13
+ # Or:
14
+ # ```ruby
15
+ # content_for(:page_title), "People"
16
+ # ```
13
17
  class ContentFor < Container
14
18
  include Capabilities::Configurable
15
19
  def compile
@@ -35,4 +39,4 @@ module ExpressTemplates
35
39
  end
36
40
  end
37
41
  end
38
- end
42
+ end
@@ -25,4 +25,4 @@ module ExpressTemplates
25
25
  end
26
26
  end
27
27
  end
28
- end
28
+ end
@@ -5,15 +5,382 @@ module ExpressTemplates
5
5
  #
6
6
  # Example:
7
7
  #
8
- # ```ruby
9
- # form_for(:people) do |t|
10
- # t.text_field :name
11
- # t.email_field :email
12
- # t.phone_field :phone
8
+ # ````ruby
9
+ # form_for(:people) do |f|
10
+ # f.text_field :name
11
+ # f.email_field :email
12
+ # f.phone_field :phone, wrapper_class: 'field phone'
13
+ # f.submit 'Save'
13
14
  # end
14
- # ```
15
+ # ````
15
16
  #
16
- class FormFor < Container
17
+ # This assumes that @people variable will exist in the
18
+ # view and that it will be a collection whose members respond to :name, :email etc.
19
+ #
20
+ # This will result in markup like the following:
21
+ #
22
+ # ````
23
+ # <form action="/people" method="post">
24
+ # <div style="display:none">
25
+ # <input name="utf8" type="hidden" value="✓">
26
+ # <input type="hidden" name="_method" value="post">
27
+ # <input type="hidden" name="authenticity_token" value="5ZDztTe1N4tc03QSjmMdSVqAw==">
28
+ # </div>
29
+ #
30
+ # <div>
31
+ # <label for="person_name">Name</label>
32
+ # <input type="text" name="person[name]" id="person_name" />
33
+ # </div>
34
+ #
35
+ # <div>
36
+ # <label for="person_email">Email</label>
37
+ # <input type="email" name="person[email]" id="person_email" />
38
+ # </div>
39
+ #
40
+ # <div class="field phone">
41
+ # <label for="person_phone">Phone</label>
42
+ # <input type="tel" name="person[phone]" id="person_phone" />
43
+ # </div>
44
+ #
45
+ # <div>
46
+ # <input type="submit" name="commit" value="Save" />
47
+ # </div>
48
+ # </form>
49
+ # ````
50
+ #
51
+ # As seen above, each field is accompanied by a label and is wrapped by a div.
52
+ # The div can be further styled by adding css classes to it via the `wrapper_class` option.
53
+ #
54
+ # Example:
55
+ #
56
+ # ````ruby
57
+ # form_for(:posts) do
58
+ # f.text_field :title, wrapped_class: 'string optional'
59
+ # end
60
+ #
61
+ # Will result to generating HTML like so:
62
+ #
63
+ # ````
64
+ # ...
65
+ # <div class='string optional'>
66
+ # <label for='post_title'>Title</label>
67
+ # <input type="text" name="post[title]" id="post_title" />
68
+ # </div>
69
+ # ...
70
+ # ````
71
+ #
72
+ # In addition to this, label text can also be customized using the `label` option:
73
+ #
74
+ # ````ruby
75
+ # f.email_field :email_address, label: 'Your Email'
76
+ # ````
77
+ #
78
+ # Compiles to;
79
+ # ````
80
+ # <label for='user_email'>Your Email</label>
81
+ # <input type='email' name='user[email]' id='user_email' />
82
+ # ````
83
+ class FormFor < Base
84
+ include Capabilities::Configurable
85
+ include Capabilities::Building
86
+
87
+ def initialize(*args)
88
+ # TODO: need a better way to select the form options
89
+ @form_options = args.select { |x| x.is_a?(Hash) }
90
+ super(*args)
91
+ _process_args!(args) # from Configurable
92
+ yield(self) if block_given?
93
+ end
94
+
95
+ attr :fields
96
+
97
+ # Express Templates uses Rails' form helpers to generate the fields with labels
98
+ #
99
+ # Example:
100
+ #
101
+ # ````ruby
102
+ # form_for(:people) do |f|
103
+ # f.phone_field :phone
104
+ # end
105
+ # ````
106
+ #
107
+ # This will precompile as
108
+ #
109
+ # ````ruby
110
+ # ...
111
+ # phone_field_tag :phone, @people.phone, {}
112
+ # ...
113
+ # ````
114
+ #
115
+ # Fields can also have custom classes via the `class` option:
116
+ #
117
+ # Example:
118
+ #
119
+ # ````ruby
120
+ # f.url_field :website_url, class: 'url-string'
121
+ # ````
122
+ #
123
+ # Compiles to:
124
+ #
125
+ # ````
126
+ # <input type='url' name='post[website_url]' id='post_website_url' class='url-string' />
127
+ # ````
128
+ #
129
+ # You can also add in the basic options supported by the
130
+ # phone_field_tag [here](http://api.rubyonrails.org/classes/ActionView/Helpers/FormTagHelper.html#method-i-phone_field_tag)
131
+ #
132
+ # This applies to all the other '_field_tags' listed
133
+ # [here](http://api.rubyonrails.org/classes/ActionView/Helpers/FormTagHelper.html)
134
+ #
135
+ #
136
+ # = Fields
137
+ # ````ruby
138
+ # f.text_field :name
139
+ # # <div>
140
+ # # <label for="person_name">Name</label>
141
+ # # <input type="text" name="person[name]" id="person_name" />
142
+ # # </div>
143
+ #
144
+ # f.text_field :name, label: 'post title'
145
+ # # <div>
146
+ # # <label for="person_name">Post Title</label>
147
+ # # <input type="text" name="person[name]" id="person_name" />
148
+ # # </div>
149
+ #
150
+ # f.text_field :name, class: 'string'
151
+ # # <div>
152
+ # # <label for="person_name">Name</label>
153
+ # # <input type="text" name="person[name]" class='string' id="person_name" />
154
+ # # </div>
155
+ #
156
+ # f.text_field :name, wrapper_class: 'field input'
157
+ # # <div class="field input">
158
+ # # <label for="person_name">Name</label>
159
+ # # <input type="text" name="person[name]" id="person_name" />
160
+ # # </div>
161
+ #
162
+ # ````
163
+ %w(email phone text password color date datetime
164
+ datetime_local hidden number range
165
+ search telephone time url week).each do |type|
166
+ define_method("#{type}_field") do |name, options={}|
167
+ @fields ||= []
168
+ @fields << Field.new(name, options, type.to_sym)
169
+ end
170
+ end
171
+
172
+ # ==== Examples
173
+ # f.select :gender, ['Male', 'Female'], selected: 'Male'
174
+ # # <div>
175
+ # # <label for="person_gender">Gender</label>
176
+ # # <select id="person_gender" name="person[gender]">
177
+ # # <option selected="selected" value="Male">Male</option>
178
+ # # <option selected="selected" value="Female">Female</option>
179
+ # # </select>
180
+ # # </div>
181
+ # f.select :name, options_from_collection_for_select(@people, "id", "name")
182
+ # # <div>
183
+ # # <label for="person_name">Name</label>
184
+ # # <select id="people_name" name="people[name]"><option value="1">David</option></select>
185
+ # # </div>
186
+ #
187
+ def select(name, select_options, options = {})
188
+ @fields ||= []
189
+ @fields << Select.new(name, options.merge!(select_options: select_options))
190
+ end
191
+
192
+ # ==== Examples
193
+ # f.radio :card, [[1, 'One'], [2, 'Two']], :first, :last
194
+ # # <div>
195
+ # # <label class="radio" for="user_age_1"><input type="radio" value="1" name="user[age]" id="user_age_1">One</label>
196
+ # # <label class="radio" for="user_age_2"><input type="radio" value="2" name="user[age]" id="user_age_2">Two</label>
197
+ # # </div>
198
+ #
199
+ def radio(name, collection, value_method, text_method, options = {})
200
+ @fields ||= []
201
+ @fields << Radio.new(name, options.merge!(collection: collection, value_method: value_method, text_method: text_method))
202
+ end
203
+
204
+ # ==== Examples
205
+ # f.checkbox :age, [[1, 'One'], [2, 'Two']], :first, :last
206
+ # # <div>
207
+ # # <label class="checkbox" for="user_age_1">
208
+ # # <input type="checkbox" value="1" name="user[age][]" id="user_age_1">
209
+ # # "One"
210
+ # # </label>
211
+ # # <label>
212
+ # # <input type="checkbox" value="2" name="user[age][]" id="user_age_2">
213
+ # # "Two"
214
+ # # </label>
215
+ # # </div>
216
+ #
217
+ def checkbox(name, collection, value_method, text_method, options = {})
218
+ @fields ||= []
219
+ @fields << Checkbox.new(name, options.merge!(collection: collection, value_method: value_method, text_method: text_method))
220
+ end
221
+
222
+ # ==== Examples
223
+ # f.text_area :name
224
+ # # <div>
225
+ # # <label for="user_name">Name</label>
226
+ # # <textarea name="user[name]" id="user_name" >
227
+ # # </div>
228
+ # f.text_area :name, label: 'post title'
229
+ # # <div>
230
+ # # <label for="user_name">Post Title</label>
231
+ # # <textarea name="user[name]" id="user_name" >
232
+ # # </div>
233
+ # f.text_area :name, wrapper_class: 'field input'
234
+ # # <div class="field input">
235
+ # # <label for="user_name">Name</label>
236
+ # # <textarea name="user[name]" id="user_name" >
237
+ # # </div>
238
+ # f.text_area :name, class: 'string'
239
+ # # <div>
240
+ # # <label for="user_name>Name</label>
241
+ # # <textarea name="user[name] id="user_name" class="string" >
242
+ # # </div>
243
+ #
244
+ def text_area(name, options = {})
245
+ @fields ||= []
246
+ @fields << TextArea.new(name, options)
247
+ end
248
+
249
+ # ==== Examples
250
+ # f.submit "Save Changes"
251
+ # # <div>
252
+ # # <input type="submit" name="commit" value="Save Changes" />
253
+ # # </div>
254
+ #
255
+ def submit(name = 'Submit Changes', options = {})
256
+ @fields ||=[]
257
+ @fields << Field.new(name, options, :submit)
258
+ end
259
+
260
+ emits -> {
261
+ resource_name = my[:id].to_s
262
+ form_options = @form_options.first
263
+ form_method = form_options.delete :method if form_options
264
+
265
+ form_method = if form_method == :put
266
+ :patch
267
+ else
268
+ form_options.present? ? form_options[:method] : :post
269
+ end
270
+
271
+ form_action = if form_method == :patch
272
+ %Q(/#{resource_name.pluralize}/{{@#{resource_name}.id}})
273
+ else
274
+ %Q(/#{resource_name.pluralize})
275
+ end
276
+
277
+ form_args = {action: form_action, method: :post}
278
+ form_args.merge!(form_options) unless form_options.nil?
279
+
280
+ form(form_args) {
281
+ form_rails_support form_method
282
+ fields.each do |field|
283
+ field_name = field.name
284
+ field_type = field.type.to_s
285
+ resource_field_name = "#{resource_name.singularize}[#{field_name}]"
286
+ label_name = "#{resource_name.singularize}_#{field_name}"
287
+ field_label = field.label || field_name.to_s.capitalize
288
+
289
+ div(class: field.wrapper_class) {
290
+ if field_type == 'select'
291
+ label_tag(label_name, field_label)
292
+ select_tag(resource_field_name, field.options_html, field.options)
293
+ elsif field_type == 'radio'
294
+ collection_radio_buttons(my[:id], field_name, field.collection,
295
+ field.value_method, field.text_method, field.options) do |b|
296
+ b.label(class: 'radio') { b.radio_button + b.text }
297
+ end
298
+ elsif field_type == 'checkbox'
299
+ collection_check_boxes(my[:id], field_name, field.collection,
300
+ field.value_method, field.text_method, field.options) do |b|
301
+ b.label(class: 'checkbox') { b.check_box + b.text }
302
+ end
303
+ elsif field_type == 'submit'
304
+ submit_tag(field_name, field.options)
305
+ else
306
+ label_tag(label_name, field_label) unless field_type == 'hidden'
307
+ args = [resource_field_name, "{{@#{resource_name.singularize}.#{field_name}}}", field.options]
308
+ tag_string = if field_type == 'text_area'
309
+ 'text_area_tag'
310
+ else
311
+ "#{field_type}_field_tag"
312
+ end
313
+
314
+ self.send(tag_string.to_sym, *args)
315
+ end
316
+ }
317
+ end
318
+ }
319
+ }
320
+
321
+ def wrap_for_stack_trace(body)
322
+ "ExpressTemplates::Components::FormFor.render_in(self) {\n#{body}\n}"
323
+ end
324
+
325
+ def compile
326
+ wrap_for_stack_trace(lookup(:markup))
327
+ end
328
+
329
+ class Field
330
+ attr :name, :options, :label, :type, :wrapper_class
331
+ def initialize(name, options = {}, type=:text)
332
+ @name = name
333
+ @options = options
334
+ @label = options[:label]
335
+ @wrapper_class = @options.delete(:wrapper_class)
336
+ @type = type
337
+ end
338
+ end
339
+
340
+ class TextArea < Field
341
+ def initialize(name, options={})
342
+ super(name, options, :text_area)
343
+ end
344
+ end
345
+
346
+ class Radio < Field
347
+ attr :collection, :value_method, :text_method
348
+ def initialize(name, options = {})
349
+ @collection = options.delete :collection
350
+ @value_method = options.delete :value_method
351
+ @text_method = options.delete :text_method
352
+ super(name, options, :radio)
353
+ end
354
+ end
355
+
356
+ class Checkbox < Radio
357
+ def initialize(name, options = {})
358
+ super(name, options)
359
+ @type = :checkbox
360
+ end
361
+ end
362
+
363
+ class Select < Field
364
+ attr :choices
365
+ def initialize(name, options = {})
366
+ @choices = options.delete :select_options
367
+ @selected = options.delete :selected
368
+ super(name, options, :select)
369
+ end
370
+
371
+ def options_html
372
+ choice_string = ""
373
+ if @choices.is_a?(String)
374
+ choice_string = @choices
375
+ else
376
+ @choices.map do |choice|
377
+ selected_string = (@selected != nil && choice == @selected) ? 'selected=selected' : nil
378
+ choice_string << "<option #{selected_string}>#{choice}</option>"
379
+ end
380
+ "{{'#{choice_string}'.html_safe}}"
381
+ end
382
+ end
383
+ end
17
384
  end
18
385
  end
19
- end
386
+ end