pakyow-presenter 0.11.3 → 1.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (100) hide show
  1. checksums.yaml +5 -5
  2. data/{pakyow-presenter/CHANGELOG.md → CHANGELOG.md} +0 -5
  3. data/LICENSE +4 -0
  4. data/{pakyow-presenter/README.md → README.md} +1 -2
  5. data/lib/pakyow/generators/presenter/presenter_generator.rb +28 -0
  6. data/lib/pakyow/generators/presenter/templates/presenter.rb.tt +2 -0
  7. data/lib/pakyow/plugin/helpers/rendering.rb +19 -0
  8. data/lib/pakyow/presenter/actions/auto_render.rb +15 -0
  9. data/lib/pakyow/presenter/attributes/attribute.rb +22 -0
  10. data/lib/pakyow/presenter/attributes/boolean.rb +24 -0
  11. data/lib/pakyow/presenter/attributes/hash.rb +89 -0
  12. data/lib/pakyow/presenter/attributes/set.rb +61 -0
  13. data/lib/pakyow/presenter/attributes/string.rb +38 -0
  14. data/lib/pakyow/presenter/attributes.rb +126 -0
  15. data/lib/pakyow/presenter/behavior/config.rb +29 -0
  16. data/lib/pakyow/presenter/behavior/error_rendering.rb +124 -0
  17. data/lib/pakyow/presenter/behavior/exposures.rb +29 -0
  18. data/lib/pakyow/presenter/behavior/implicit_rendering.rb +21 -0
  19. data/lib/pakyow/presenter/behavior/initializing.rb +37 -0
  20. data/lib/pakyow/presenter/behavior/modes.rb +117 -0
  21. data/lib/pakyow/presenter/behavior/versions.rb +71 -0
  22. data/lib/pakyow/presenter/behavior/watching.rb +21 -0
  23. data/lib/pakyow/presenter/binder.rb +115 -0
  24. data/lib/pakyow/presenter/binding_parts.rb +64 -0
  25. data/lib/pakyow/presenter/component.rb +74 -0
  26. data/lib/pakyow/presenter/composers/component.rb +78 -0
  27. data/lib/pakyow/presenter/composers/view.rb +73 -0
  28. data/lib/pakyow/presenter/errors.rb +63 -0
  29. data/lib/pakyow/presenter/framework.rb +203 -0
  30. data/lib/pakyow/presenter/front_matter_parser.rb +42 -0
  31. data/lib/pakyow/presenter/helpers/rendering.rb +18 -0
  32. data/lib/pakyow/presenter/presentable.rb +28 -0
  33. data/lib/pakyow/presenter/presentable_error.rb +19 -0
  34. data/lib/pakyow/presenter/presenter.rb +766 -0
  35. data/lib/pakyow/presenter/presenters/endpoint.rb +158 -0
  36. data/lib/pakyow/presenter/presenters/form.rb +522 -0
  37. data/lib/pakyow/presenter/processor.rb +61 -0
  38. data/lib/pakyow/presenter/renderable.rb +28 -0
  39. data/lib/pakyow/presenter/renderer.rb +269 -0
  40. data/lib/pakyow/presenter/rendering/actions/cleanup_prototype_nodes.rb +21 -0
  41. data/lib/pakyow/presenter/rendering/actions/cleanup_unbound_bindings.rb +35 -0
  42. data/lib/pakyow/presenter/rendering/actions/create_template_nodes.rb +27 -0
  43. data/lib/pakyow/presenter/rendering/actions/insert_prototype_bar.rb +101 -0
  44. data/lib/pakyow/presenter/rendering/actions/install_authenticity.rb +42 -0
  45. data/lib/pakyow/presenter/rendering/actions/place_in_mode.rb +56 -0
  46. data/lib/pakyow/presenter/rendering/actions/render_components.rb +279 -0
  47. data/lib/pakyow/presenter/rendering/actions/set_page_title.rb +35 -0
  48. data/lib/pakyow/presenter/rendering/actions/setup_endpoints.rb +62 -0
  49. data/lib/pakyow/presenter/rendering/actions/setup_forms.rb +174 -0
  50. data/lib/pakyow/presenter/significant_nodes.rb +309 -0
  51. data/lib/pakyow/presenter/templates.rb +229 -0
  52. data/lib/pakyow/presenter/versioned_view.rb +209 -0
  53. data/lib/pakyow/presenter/view.rb +586 -0
  54. data/lib/pakyow/presenter/views/form.rb +58 -0
  55. data/lib/pakyow/presenter/views/layout.rb +32 -0
  56. data/lib/pakyow/presenter/views/page.rb +72 -0
  57. data/lib/pakyow/presenter/views/partial.rb +28 -0
  58. data/lib/pakyow/presenter.rb +33 -0
  59. data/lib/pakyow/views/errors/layouts/development_error.html +102 -0
  60. data/lib/pakyow/views/errors/layouts/production_error.html +27 -0
  61. data/lib/pakyow/views/errors/pages/404.html +12 -0
  62. data/lib/pakyow/views/errors/pages/500.html +12 -0
  63. data/lib/pakyow/views/errors/pages/development/500.html +39 -0
  64. data/lib/string_doc/attributes.rb +109 -0
  65. data/lib/string_doc/meta_attributes.rb +48 -0
  66. data/lib/string_doc/meta_node.rb +328 -0
  67. data/lib/string_doc/node.rb +377 -0
  68. data/lib/string_doc.rb +642 -0
  69. metadata +95 -75
  70. data/pakyow-presenter/LICENSE +0 -20
  71. data/pakyow-presenter/lib/pakyow/presenter/attributes.rb +0 -228
  72. data/pakyow-presenter/lib/pakyow/presenter/base.rb +0 -38
  73. data/pakyow-presenter/lib/pakyow/presenter/binder.rb +0 -116
  74. data/pakyow-presenter/lib/pakyow/presenter/binder_set.rb +0 -94
  75. data/pakyow-presenter/lib/pakyow/presenter/binding_eval.rb +0 -37
  76. data/pakyow-presenter/lib/pakyow/presenter/config/presenter.rb +0 -42
  77. data/pakyow-presenter/lib/pakyow/presenter/container.rb +0 -6
  78. data/pakyow-presenter/lib/pakyow/presenter/doc_helpers.rb +0 -17
  79. data/pakyow-presenter/lib/pakyow/presenter/exceptions.rb +0 -11
  80. data/pakyow-presenter/lib/pakyow/presenter/ext/app.rb +0 -33
  81. data/pakyow-presenter/lib/pakyow/presenter/ext/call_context.rb +0 -28
  82. data/pakyow-presenter/lib/pakyow/presenter/helpers.rb +0 -46
  83. data/pakyow-presenter/lib/pakyow/presenter/page.rb +0 -110
  84. data/pakyow-presenter/lib/pakyow/presenter/partial.rb +0 -6
  85. data/pakyow-presenter/lib/pakyow/presenter/presenter.rb +0 -232
  86. data/pakyow-presenter/lib/pakyow/presenter/string_doc.rb +0 -380
  87. data/pakyow-presenter/lib/pakyow/presenter/string_doc_parser.rb +0 -144
  88. data/pakyow-presenter/lib/pakyow/presenter/string_doc_renderer.rb +0 -18
  89. data/pakyow-presenter/lib/pakyow/presenter/template.rb +0 -51
  90. data/pakyow-presenter/lib/pakyow/presenter/view.rb +0 -541
  91. data/pakyow-presenter/lib/pakyow/presenter/view_collection.rb +0 -330
  92. data/pakyow-presenter/lib/pakyow/presenter/view_composer.rb +0 -237
  93. data/pakyow-presenter/lib/pakyow/presenter/view_context.rb +0 -111
  94. data/pakyow-presenter/lib/pakyow/presenter/view_store.rb +0 -262
  95. data/pakyow-presenter/lib/pakyow/presenter/view_store_loader.rb +0 -43
  96. data/pakyow-presenter/lib/pakyow/presenter/view_version.rb +0 -113
  97. data/pakyow-presenter/lib/pakyow/presenter.rb +0 -8
  98. data/pakyow-presenter/lib/pakyow/views/errors/404.erb +0 -26
  99. data/pakyow-presenter/lib/pakyow/views/errors/500.erb +0 -23
  100. data/pakyow-presenter/lib/pakyow-presenter.rb +0 -1
@@ -0,0 +1,586 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "forwardable"
4
+
5
+ require "pakyow/support/core_refinements/array/ensurable"
6
+ require "pakyow/support/indifferentize"
7
+ require "pakyow/support/inflector"
8
+ require "pakyow/support/safe_string"
9
+
10
+ require "string_doc"
11
+
12
+ module Pakyow
13
+ module Presenter
14
+ # Provides an interface for manipulating view templates.
15
+ #
16
+ class View
17
+ class << self
18
+ # Creates a view from a file.
19
+ #
20
+ def load(path, content: nil)
21
+ new(content || File.read(path))
22
+ end
23
+
24
+ # Creates a view wrapping an object.
25
+ #
26
+ def from_object(object)
27
+ instance = if object.is_a?(StringDoc::Node) && object.labeled?(:view_type)
28
+ object.label(:view_type).allocate
29
+ else
30
+ allocate
31
+ end
32
+
33
+ instance.instance_variable_set(:@object, object)
34
+ instance.instance_variable_set(:@info, {})
35
+ instance.instance_variable_set(:@logical_path, nil)
36
+
37
+ if object.respond_to?(:attributes)
38
+ instance.attributes = object.attributes
39
+ else
40
+ instance.instance_variable_set(:@attributes, nil)
41
+ end
42
+
43
+ instance
44
+ end
45
+
46
+ def from_view_or_string(view_or_string)
47
+ case view_or_string
48
+ when View, VersionedView
49
+ view_or_string
50
+ else
51
+ View.new(Support::SafeStringHelpers.ensure_html_safety(view_or_string.to_s))
52
+ end
53
+ end
54
+ end
55
+
56
+ include Support::SafeStringHelpers
57
+
58
+ using Support::Indifferentize
59
+ using Support::Refinements::Array::Ensurable
60
+
61
+ extend Forwardable
62
+
63
+ def_delegators :@object, :type, :text, :html, :label, :labeled?
64
+
65
+ # The object responsible for parsing, manipulating, and rendering
66
+ # the underlying HTML document for the view.
67
+ #
68
+ attr_reader :object
69
+
70
+ # The logical path to the view template.
71
+ #
72
+ attr_reader :logical_path
73
+
74
+ # @api private
75
+ attr_writer :object
76
+
77
+ # Creates a view with +html+.
78
+ #
79
+ def initialize(html, info: {}, logical_path: nil)
80
+ @object = StringDoc.new(html)
81
+ @info, @logical_path = Support::IndifferentHash.deep(info), logical_path
82
+
83
+ if @object.respond_to?(:attributes)
84
+ self.attributes = @object.attributes
85
+ else
86
+ @attributes = nil
87
+ end
88
+ end
89
+
90
+ def initialize_copy(_)
91
+ super
92
+
93
+ @info = @info.dup
94
+ @object = @object.dup
95
+
96
+ if @object.respond_to?(:attributes)
97
+ self.attributes = @object.attributes
98
+ else
99
+ @attributes = nil
100
+ end
101
+ end
102
+
103
+ def soft_copy
104
+ instance = self.class.allocate
105
+
106
+ instance.instance_variable_set(:@info, @info.dup)
107
+
108
+ new_object = @object.soft_copy
109
+ instance.instance_variable_set(:@object, new_object)
110
+
111
+ if new_object.respond_to?(:attributes)
112
+ instance.attributes = new_object.attributes
113
+ else
114
+ instance.instance_variable_set(:@attributes, nil)
115
+ end
116
+
117
+ instance
118
+ end
119
+
120
+ # Finds a view binding by name. When passed more than one value, the view will
121
+ # be traversed through each name. Returns a {VersionedView}.
122
+ #
123
+ def find(*names)
124
+ if names.any?
125
+ named = names.shift.to_sym
126
+
127
+ found = each_binding(named).map { |node|
128
+ View.from_object(node)
129
+ }
130
+
131
+ result = if names.empty? && !found.empty? # found everything; wrap it up
132
+ VersionedView.new(found)
133
+ elsif !found.empty? && names.count > 0 # descend further
134
+ found.first.find(*names)
135
+ else
136
+ nil
137
+ end
138
+
139
+ if result && block_given?
140
+ yield result
141
+ end
142
+
143
+ result
144
+ else
145
+ nil
146
+ end
147
+ end
148
+
149
+ # Finds all view bindings by name, returning an array of {View} objects.
150
+ #
151
+ def find_all(named)
152
+ each_binding(named).map { |node|
153
+ View.from_object(node)
154
+ }
155
+ end
156
+
157
+ # Finds a form with a binding matching +name+.
158
+ #
159
+ def form(name)
160
+ @object.each_significant_node(:form) do |form_node|
161
+ return Form.from_object(form_node) if form_node.label(:binding) == name
162
+ end
163
+
164
+ nil
165
+ end
166
+
167
+ # Returns all forms.
168
+ #
169
+ def forms
170
+ @object.each_significant_node(:form, descend: true).map { |node|
171
+ Form.from_object(node)
172
+ }
173
+ end
174
+
175
+ # Finds a component matching +name+.
176
+ #
177
+ def component(name, renderable: false)
178
+ name = name.to_sym
179
+ components(renderable: renderable).find { |component|
180
+ component.object.label(:components).any? { |possible_component|
181
+ possible_component[:name] == name
182
+ }
183
+ }
184
+ end
185
+
186
+ # Returns all components.
187
+ #
188
+ def components(renderable: false)
189
+ @object.each_significant_node_without_descending_into_type(:component, descend: true).select { |node|
190
+ !renderable || node.label(:components).any? { |component| component[:renderable] }
191
+ }.map { |node|
192
+ View.from_object(node)
193
+ }
194
+ end
195
+
196
+ # Returns all view info when +key+ is +nil+, otherwise returns the value for +key+.
197
+ #
198
+ def info(key = nil)
199
+ if key.nil?
200
+ @info
201
+ else
202
+ @info.fetch(key, nil)
203
+ end
204
+ end
205
+
206
+ # Returns a view for the +<head>+ node.
207
+ #
208
+ def head
209
+ if head_node = @object.find_first_significant_node(:head)
210
+ View.from_object(head_node)
211
+ else
212
+ nil
213
+ end
214
+ end
215
+
216
+ # Returns a view for the +<body>+ node.
217
+ #
218
+ def body
219
+ if body_node = @object.find_first_significant_node(:body)
220
+ View.from_object(body_node)
221
+ else
222
+ nil
223
+ end
224
+ end
225
+
226
+ # Returns a view for the +<title>+ node.
227
+ #
228
+ def title
229
+ if title_node = @object.find_first_significant_node(:title)
230
+ View.from_object(title_node)
231
+ else
232
+ nil
233
+ end
234
+ end
235
+
236
+ # Yields +self+.
237
+ #
238
+ def with
239
+ tap do
240
+ yield self
241
+ end
242
+ end
243
+
244
+ # Transforms +self+ to match structure of +object+.
245
+ #
246
+ def transform(object)
247
+ tap do
248
+ if object.nil? || (object.respond_to?(:empty?) && object.empty?)
249
+ remove
250
+ else
251
+ removals = []
252
+ each_binding_prop(descend: false) do |binding|
253
+ binding_name = if binding.significant?(:multipart_binding)
254
+ binding.label(:binding_prop)
255
+ else
256
+ binding.label(:binding)
257
+ end
258
+
259
+ unless object.present?(binding_name)
260
+ removals << binding
261
+ end
262
+ end
263
+
264
+ removals.each(&:remove)
265
+ end
266
+
267
+ yield self, object if block_given?
268
+ end
269
+ end
270
+
271
+ # Binds a single object.
272
+ #
273
+ def bind(object)
274
+ tap do
275
+ unless object.nil?
276
+ each_binding_prop do |binding|
277
+ binding_name = if binding.significant?(:multipart_binding)
278
+ binding.label(:binding_prop)
279
+ else
280
+ binding.label(:binding)
281
+ end
282
+
283
+ if object.include?(binding_name)
284
+ value = if object.is_a?(Binder)
285
+ object.__content(binding_name, binding)
286
+ else
287
+ object[binding_name]
288
+ end
289
+
290
+ bind_value_to_node(value, binding)
291
+ binding.set_label(:bound, true)
292
+ end
293
+ end
294
+
295
+ attributes[:"data-id"] = object[:id]
296
+ self.object.set_label(:bound, true)
297
+ end
298
+ end
299
+ end
300
+
301
+ # Appends a view or string to +self+.
302
+ #
303
+ def append(view_or_string)
304
+ tap do
305
+ @object.append(self.class.from_view_or_string(view_or_string).object)
306
+ end
307
+ end
308
+
309
+ # Prepends a view or string to +self+.
310
+ #
311
+ def prepend(view_or_string)
312
+ tap do
313
+ @object.prepend(self.class.from_view_or_string(view_or_string).object)
314
+ end
315
+ end
316
+
317
+ # Inserts a view or string after +self+.
318
+ #
319
+ def after(view_or_string)
320
+ tap do
321
+ @object.after(self.class.from_view_or_string(view_or_string).object)
322
+ end
323
+ end
324
+
325
+ # Inserts a view or string before +self+.
326
+ #
327
+ def before(view_or_string)
328
+ tap do
329
+ @object.before(self.class.from_view_or_string(view_or_string).object)
330
+ end
331
+ end
332
+
333
+ # Replaces +self+ with a view or string.
334
+ #
335
+ def replace(view_or_string)
336
+ tap do
337
+ @object.replace(self.class.from_view_or_string(view_or_string).object)
338
+ end
339
+ end
340
+
341
+ # Removes +self+.
342
+ #
343
+ def remove
344
+ tap do
345
+ @object.remove
346
+ end
347
+ end
348
+
349
+ # Removes +self+'s children.
350
+ #
351
+ def clear
352
+ tap do
353
+ @object.clear
354
+ end
355
+ end
356
+
357
+ # Safely sets the html value of +self+.
358
+ #
359
+ def html=(html)
360
+ @object.html = ensure_html_safety(html.to_s)
361
+ end
362
+
363
+ # Returns true if +self+ is a binding.
364
+ #
365
+ def binding?
366
+ @object.significant?(:binding)
367
+ end
368
+
369
+ # Returns true if +self+ is a container.
370
+ #
371
+ def container?
372
+ @object.significant?(:container)
373
+ end
374
+
375
+ # Returns true if +self+ is a partial.
376
+ #
377
+ def partial?
378
+ @object.significant?(:partial)
379
+ end
380
+
381
+ # Returns true if +self+ is a form.
382
+ #
383
+ def form?
384
+ @object.significant?(:form)
385
+ end
386
+
387
+ # Returns true if +self+ equals +other+.
388
+ #
389
+ def ==(other)
390
+ other.is_a?(self.class) && @object == other.object
391
+ end
392
+
393
+ # Returns attributes object for +self+.
394
+ #
395
+ def attributes
396
+ @attributes
397
+ end
398
+ alias attrs attributes
399
+
400
+ # Wraps +attributes+ in a {Attributes} instance.
401
+ #
402
+ def attributes=(attributes)
403
+ @attributes = Attributes.new(attributes)
404
+ end
405
+ alias attrs= attributes=
406
+
407
+ # Returns the version name for +self+.
408
+ #
409
+ def version
410
+ (label(:version) || VersionedView::DEFAULT_VERSION).to_sym
411
+ end
412
+
413
+ # Converts +self+ to html, rendering the view.
414
+ #
415
+ def to_html
416
+ @object.to_html
417
+ end
418
+ alias :to_s :to_html
419
+
420
+ # @api private
421
+ def binding_name
422
+ label(:binding)
423
+ end
424
+
425
+ # @api private
426
+ def singular_binding_name
427
+ label(:singular_binding)
428
+ end
429
+
430
+ # @api private
431
+ def plural_binding_name
432
+ label(:plural_binding)
433
+ end
434
+
435
+ # @api private
436
+ def channeled_binding_name
437
+ label(:channeled_binding)
438
+ end
439
+
440
+ # @api private
441
+ def plural_channeled_binding_name
442
+ label(:plural_channeled_binding)
443
+ end
444
+
445
+ # @api private
446
+ def singular_channeled_binding_name
447
+ label(:singular_channeled_binding)
448
+ end
449
+
450
+ # @api private
451
+ def each_binding_scope(descend: false)
452
+ return enum_for(:each_binding_scope, descend: descend) unless block_given?
453
+
454
+ method = if descend
455
+ :each_significant_node
456
+ else
457
+ :each_significant_node_without_descending_into_type
458
+ end
459
+
460
+ @object.send(method, :binding, descend: descend) do |node|
461
+ if binding_scope?(node)
462
+ yield node
463
+ end
464
+ end
465
+ end
466
+
467
+ # @api private
468
+ def each_binding_prop(descend: false)
469
+ return enum_for(:each_binding_prop, descend: descend) unless block_given?
470
+
471
+ if (@object.is_a?(StringDoc::Node) || @object.is_a?(StringDoc::MetaNode)) && @object.significant?(:multipart_binding)
472
+ yield @object
473
+ else
474
+ method = if descend
475
+ :each_significant_node
476
+ else
477
+ :each_significant_node_without_descending_into_type
478
+ end
479
+
480
+ @object.send(method, :binding, descend: descend) do |node|
481
+ if binding_prop?(node)
482
+ yield node
483
+ end
484
+ end
485
+ end
486
+ end
487
+
488
+ # @api private
489
+ def each_binding(name)
490
+ return enum_for(:each_binding, name) unless block_given?
491
+
492
+ each_binding_scope do |node|
493
+ if node.label(:channeled_binding) == name
494
+ yield node
495
+ end
496
+ end
497
+
498
+ each_binding_prop do |node|
499
+ if (node.significant?(:multipart_binding) && node.label(:binding_prop) == name) || (!node.significant?(:multipart_binding) && node.label(:binding) == name)
500
+ yield node
501
+ end
502
+ end
503
+ end
504
+
505
+ # @api private
506
+ def binding_scopes(descend: false)
507
+ each_binding_scope(descend: descend).map(&:itself)
508
+ end
509
+
510
+ # @api private
511
+ def binding_props(descend: false)
512
+ each_binding_prop(descend: descend).map(&:itself)
513
+ end
514
+
515
+ # @api private
516
+ def binding_scope?(node)
517
+ node.significant?(:binding) && (node.significant?(:binding_within) || node.significant?(:multipart_binding) || node.label(:version) == :empty)
518
+ end
519
+
520
+ # @api private
521
+ def binding_prop?(node)
522
+ node.significant?(:binding) && node.label(:version) != :empty && (!node.significant?(:binding_within) || node.significant?(:multipart_binding))
523
+ end
524
+
525
+ # @api private
526
+ def find_partials(partials, found = [])
527
+ found.tap do
528
+ @object.each_significant_node(:partial, descend: true) do |node|
529
+ if replacement = partials[node.label(:partial)]
530
+ found << node.label(:partial)
531
+ replacement.find_partials(partials, found)
532
+ end
533
+ end
534
+ end
535
+ end
536
+
537
+ # @api private
538
+ def mixin(partials)
539
+ tap do
540
+ @object.each_significant_node(:partial, descend: true) do |partial_node|
541
+ if replacement = partials[partial_node.label(:partial)]
542
+ partial_node.replace(replacement.mixin(partials).object)
543
+ end
544
+ end
545
+ end
546
+ end
547
+
548
+ # Thanks Dan! https://stackoverflow.com/a/30225093
549
+ # @api private
550
+ INFO_MERGER = proc { |_, v1, v2| Support::IndifferentHash === v1 && Support::IndifferentHash === v2 ? v1.merge(v2, &merger) : Array === v1 && Array === v2 ? v1 | v2 : [:undefined, nil, :nil].include?(v2) ? v1 : v2 }
551
+
552
+ # @api private
553
+ def add_info(*infos)
554
+ tap do
555
+ infos.each do |info|
556
+ @info.merge!(Support::IndifferentHash.deep(info), &INFO_MERGER)
557
+ end
558
+ end
559
+ end
560
+
561
+ # @api private
562
+ def channeled_binding_scope?(scope)
563
+ binding_scopes.select { |node|
564
+ node.label(:binding) == scope
565
+ }.any? { |node|
566
+ node.label(:channel).any?
567
+ }
568
+ end
569
+
570
+ protected
571
+
572
+ def bind_value_to_node(value, node)
573
+ tag = node.tagname
574
+ unless StringDoc::Node.without_value?(tag)
575
+ value = String(value)
576
+
577
+ if StringDoc::Node.self_closing?(tag)
578
+ node.attributes[:value] = ensure_html_safety(value) if node.attributes[:value].nil?
579
+ else
580
+ node.html = ensure_html_safety(value)
581
+ end
582
+ end
583
+ end
584
+ end
585
+ end
586
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "pakyow/support/core_refinements/array/ensurable"
4
+
5
+ module Pakyow
6
+ module Presenter
7
+ class Form < View
8
+ using Support::Refinements::Array::Ensurable
9
+
10
+ INPUT_TAG = "input"
11
+ SELECT_TAG = "select"
12
+ TEXTAREA_TAG = "textarea"
13
+
14
+ FIELD_TAGS = [
15
+ INPUT_TAG,
16
+ SELECT_TAG,
17
+ TEXTAREA_TAG
18
+ ].freeze
19
+
20
+ CHECKBOX_TYPE = "checkbox"
21
+ RADIO_TYPE = "radio"
22
+
23
+ CHECKED_TYPES = [
24
+ CHECKBOX_TYPE, RADIO_TYPE
25
+ ].freeze
26
+
27
+ private
28
+
29
+ def bind_value_to_node(value, node)
30
+ super
31
+
32
+ if node.tagname == SELECT_TAG
33
+ select_option_with_value(value, View.from_object(node))
34
+ end
35
+
36
+ if CHECKED_TYPES.include?(node.attributes[:type])
37
+ check_or_uncheck_value(value, View.from_object(node))
38
+ end
39
+ end
40
+
41
+ def check_or_uncheck_value(value, view)
42
+ if view.attributes[:type] == "checkbox"
43
+ # There could be multiple values checked, so check for inclusion.
44
+ #
45
+ view.attributes[:checked] = Array.ensure(value).map(&:to_s).include?(view.attributes[:value])
46
+ else
47
+ view.attributes[:checked] = view.attributes[:value] == value.to_s
48
+ end
49
+ end
50
+
51
+ def select_option_with_value(value, view)
52
+ view.object.each_significant_node(:option) do |option|
53
+ View.from_object(option).attributes[:selected] = option.attributes[:value] == value
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pakyow
4
+ module Presenter
5
+ class Layout < View
6
+ attr_accessor :name
7
+
8
+ class << self
9
+ def load(path, content: nil, **args)
10
+ self.new(File.basename(path, ".*").to_sym, content || File.read(path), **args)
11
+ end
12
+ end
13
+
14
+ def initialize(name, html = "", **args)
15
+ @name = name
16
+ super(html, **args)
17
+ end
18
+
19
+ def container(name = Page::DEFAULT_CONTAINER)
20
+ @object.container(name.to_sym)
21
+ end
22
+
23
+ def build(page)
24
+ @object.each_significant_node(:container) do |container_node|
25
+ container_node.replace(page.content(container_node.label(:container)))
26
+ end
27
+
28
+ View.from_object(@object).add_info(info, page.info)
29
+ end
30
+ end
31
+ end
32
+ end