glimmer-dsl-web 0.6.1 → 0.6.3
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/CHANGELOG.md +11 -0
- data/README.md +403 -7
- data/VERSION +1 -1
- data/glimmer-dsl-web.gemspec +4 -3
- data/lib/glimmer/dsl/web/component_expression.rb +21 -3
- data/lib/glimmer/dsl/web/component_slot_content_expression.rb +11 -5
- data/lib/glimmer/web/component.rb +19 -3
- data/lib/glimmer/web/element_proxy.rb +23 -11
- data/lib/glimmer-dsl-web/samples/hello/hello_component.rb +141 -177
- data/lib/glimmer-dsl-web/samples/hello/hello_component_listeners.rb +18 -57
- data/lib/glimmer-dsl-web/samples/hello/hello_component_listeners_default_slot.rb +349 -0
- data/lib/glimmer-dsl-web/samples/hello/hello_component_slots.rb +28 -68
- data/lib/glimmer-dsl-web/samples/hello/hello_content_data_binding.rb +3 -2
- data/lib/glimmer-dsl-web/samples/hello/hello_data_binding.rb +28 -71
- data/lib/glimmer-dsl-web/samples/regular/todo_mvc/views/todo_list.rb +1 -1
- metadata +3 -2
@@ -249,6 +249,10 @@ module Glimmer
|
|
249
249
|
element_binding.unregister_all_observables
|
250
250
|
end
|
251
251
|
data_bindings.clear
|
252
|
+
content_binding_observer_registrations.each do |observer_registration|
|
253
|
+
observer_registration.unregister
|
254
|
+
end
|
255
|
+
content_binding_observer_registrations.clear
|
252
256
|
end
|
253
257
|
|
254
258
|
# Subclasses can override with their own selector
|
@@ -604,16 +608,20 @@ module Glimmer
|
|
604
608
|
|
605
609
|
def handle_observation_request(keyword, original_event_listener)
|
606
610
|
if rendered?
|
607
|
-
|
608
|
-
|
609
|
-
|
610
|
-
|
611
|
-
|
612
|
-
|
613
|
-
|
614
|
-
|
615
|
-
|
616
|
-
|
611
|
+
if keyword.start_with?('on_') && !['on_render', 'on_remove'].include?(keyword.to_s)
|
612
|
+
(ancestor_component || component)&.handle_observation_request(keyword, original_event_listener)
|
613
|
+
else
|
614
|
+
listener = ListenerProxy.new(
|
615
|
+
element: self,
|
616
|
+
selector: selector,
|
617
|
+
dom_element: dom_element,
|
618
|
+
event_attribute: keyword,
|
619
|
+
original_event_listener: original_event_listener,
|
620
|
+
)
|
621
|
+
listener.register
|
622
|
+
listeners_for(keyword) << listener
|
623
|
+
listener
|
624
|
+
end
|
617
625
|
else
|
618
626
|
enqueue_post_render_method_call('handle_observation_request', keyword, original_event_listener)
|
619
627
|
end
|
@@ -691,10 +699,14 @@ module Glimmer
|
|
691
699
|
end
|
692
700
|
model_binding_observer = Glimmer::DataBinding::ModelBinding.new(*binding_args)
|
693
701
|
content_binding_observer = Glimmer::DataBinding::Observer.proc(&content_binding_work)
|
694
|
-
content_binding_observer.observe(model_binding_observer)
|
702
|
+
content_binding_observer_registrations << content_binding_observer.observe(model_binding_observer)
|
695
703
|
content_binding_work.call # TODO inspect if we need to pass args here (from observed attributes) [but it's simpler not to pass anything at first]
|
696
704
|
end
|
697
705
|
|
706
|
+
def content_binding_observer_registrations
|
707
|
+
@content_binding_observer_registrations ||= []
|
708
|
+
end
|
709
|
+
|
698
710
|
def respond_to_missing?(method_name, include_private = false)
|
699
711
|
# TODO consider doing more correct checking of availability of properties/methods using native ticks
|
700
712
|
property_name = property_name_for(method_name)
|
@@ -21,198 +21,162 @@
|
|
21
21
|
|
22
22
|
require 'glimmer-dsl-web'
|
23
23
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
"LA"=>"Louisiana",
|
47
|
-
"MA"=>"Massachusetts",
|
48
|
-
"MD"=>"Maryland",
|
49
|
-
"ME"=>"Maine",
|
50
|
-
"MI"=>"Michigan",
|
51
|
-
"MN"=>"Minnesota",
|
52
|
-
"MO"=>"Missouri",
|
53
|
-
"MS"=>"Mississippi",
|
54
|
-
"MT"=>"Montana",
|
55
|
-
"NC"=>"North Carolina",
|
56
|
-
"ND"=>"North Dakota",
|
57
|
-
"NE"=>"Nebraska",
|
58
|
-
"NH"=>"New Hampshire",
|
59
|
-
"NJ"=>"New Jersey",
|
60
|
-
"NM"=>"New Mexico",
|
61
|
-
"NV"=>"Nevada",
|
62
|
-
"NY"=>"New York",
|
63
|
-
"OH"=>"Ohio",
|
64
|
-
"OK"=>"Oklahoma",
|
65
|
-
"OR"=>"Oregon",
|
66
|
-
"PA"=>"Pennsylvania",
|
67
|
-
"PR"=>"Puerto Rico",
|
68
|
-
"RI"=>"Rhode Island",
|
69
|
-
"SC"=>"South Carolina",
|
70
|
-
"SD"=>"South Dakota",
|
71
|
-
"TN"=>"Tennessee",
|
72
|
-
"TX"=>"Texas",
|
73
|
-
"UT"=>"Utah",
|
74
|
-
"VA"=>"Virginia",
|
75
|
-
"VI"=>"Virgin Islands",
|
76
|
-
"VT"=>"Vermont",
|
77
|
-
"WA"=>"Washington",
|
78
|
-
"WI"=>"Wisconsin",
|
79
|
-
"WV"=>"West Virginia",
|
80
|
-
"WY"=>"Wyoming"
|
81
|
-
}
|
82
|
-
|
83
|
-
def state_code
|
84
|
-
STATES.invert[state]
|
85
|
-
end
|
24
|
+
unless Object.const_defined?(:Address)
|
25
|
+
Address = Struct.new(:full_name, :street, :street2, :city, :state, :zip_code, :billing_and_shipping, keyword_init: true) do
|
26
|
+
STATES = {
|
27
|
+
"AK"=>"Alaska", "AL"=>"Alabama", "AR"=>"Arkansas", "AS"=>"American Samoa", "AZ"=>"Arizona",
|
28
|
+
"CA"=>"California", "CO"=>"Colorado", "CT"=>"Connecticut", "DC"=>"District of Columbia", "DE"=>"Delaware",
|
29
|
+
"FL"=>"Florida", "GA"=>"Georgia", "GU"=>"Guam", "HI"=>"Hawaii", "IA"=>"Iowa", "ID"=>"Idaho", "IL"=>"Illinois",
|
30
|
+
"IN"=>"Indiana", "KS"=>"Kansas", "KY"=>"Kentucky", "LA"=>"Louisiana", "MA"=>"Massachusetts", "MD"=>"Maryland",
|
31
|
+
"ME"=>"Maine", "MI"=>"Michigan", "MN"=>"Minnesota", "MO"=>"Missouri", "MS"=>"Mississippi", "MT"=>"Montana",
|
32
|
+
"NC"=>"North Carolina", "ND"=>"North Dakota", "NE"=>"Nebraska", "NH"=>"New Hampshire", "NJ"=>"New Jersey",
|
33
|
+
"NM"=>"New Mexico", "NV"=>"Nevada", "NY"=>"New York", "OH"=>"Ohio", "OK"=>"Oklahoma", "OR"=>"Oregon",
|
34
|
+
"PA"=>"Pennsylvania", "PR"=>"Puerto Rico", "RI"=>"Rhode Island", "SC"=>"South Carolina", "SD"=>"South Dakota",
|
35
|
+
"TN"=>"Tennessee", "TX"=>"Texas", "UT"=>"Utah", "VA"=>"Virginia", "VI"=>"Virgin Islands", "VT"=>"Vermont",
|
36
|
+
"WA"=>"Washington", "WI"=>"Wisconsin", "WV"=>"West Virginia", "WY"=>"Wyoming"
|
37
|
+
}
|
38
|
+
|
39
|
+
def state_code
|
40
|
+
STATES.invert[state]
|
41
|
+
end
|
42
|
+
|
43
|
+
def state_code=(value)
|
44
|
+
self.state = STATES[value]
|
45
|
+
end
|
86
46
|
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
47
|
+
def summary
|
48
|
+
string_attributes = to_h.except(:billing_and_shipping)
|
49
|
+
summary = string_attributes.values.map(&:to_s).reject(&:empty?).join(', ')
|
50
|
+
summary += " (Billing & Shipping)" if billing_and_shipping
|
51
|
+
summary
|
52
|
+
end
|
93
53
|
end
|
94
54
|
end
|
95
55
|
|
96
|
-
|
97
|
-
#
|
98
|
-
# Including Glimmer::Web::Component makes this class a View component and automatically
|
99
|
-
# generates a new Glimmer HTML DSL keyword that matches the lowercase underscored version
|
100
|
-
# of the name of the class. AddressForm generates address_form keyword, which can be used
|
101
|
-
# elsewhere in Glimmer HTML DSL code as done inside AddressPage below.
|
102
|
-
class AddressForm
|
103
|
-
include Glimmer::Web::Component
|
104
|
-
|
105
|
-
option :address
|
106
|
-
|
107
|
-
# Optionally, you can execute code before rendering markup.
|
108
|
-
# This is useful for pre-setup of variables (e.g. Models) that you would use in the markup.
|
56
|
+
unless Object.const_defined?(:AddressForm)
|
57
|
+
# AddressForm Glimmer Web Component (View component)
|
109
58
|
#
|
110
|
-
#
|
111
|
-
#
|
112
|
-
|
113
|
-
#
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
59
|
+
# Including Glimmer::Web::Component makes this class a View component and automatically
|
60
|
+
# generates a new Glimmer HTML DSL keyword that matches the lowercase underscored version
|
61
|
+
# of the name of the class. AddressForm generates address_form keyword, which can be used
|
62
|
+
# elsewhere in Glimmer HTML DSL code as done inside AddressPage below.
|
63
|
+
class AddressForm
|
64
|
+
include Glimmer::Web::Component
|
65
|
+
|
66
|
+
option :address
|
67
|
+
|
68
|
+
# Optionally, you can execute code before rendering markup.
|
69
|
+
# This is useful for pre-setup of variables (e.g. Models) that you would use in the markup.
|
70
|
+
#
|
71
|
+
# before_render do
|
72
|
+
# end
|
73
|
+
|
74
|
+
# Optionally, you can execute code after rendering markup.
|
75
|
+
# This is useful for post-setup of extra Model listeners that would interact with the
|
76
|
+
# markup elements and expect them to be rendered already.
|
77
|
+
#
|
78
|
+
# after_render do
|
79
|
+
# end
|
80
|
+
|
81
|
+
# markup block provides the content of the
|
82
|
+
markup {
|
83
|
+
div {
|
84
|
+
div(style: 'display: grid; grid-auto-columns: 80px 260px;') { |address_div|
|
85
|
+
label('Full Name: ', for: 'full-name-field')
|
86
|
+
input(id: 'full-name-field') {
|
87
|
+
value <=> [address, :full_name]
|
88
|
+
}
|
89
|
+
|
90
|
+
label('Street: ', for: 'street-field')
|
91
|
+
input(id: 'street-field') {
|
92
|
+
value <=> [address, :street]
|
93
|
+
}
|
94
|
+
|
95
|
+
label('Street 2: ', for: 'street2-field')
|
96
|
+
textarea(id: 'street2-field') {
|
97
|
+
value <=> [address, :street2]
|
98
|
+
}
|
99
|
+
|
100
|
+
label('City: ', for: 'city-field')
|
101
|
+
input(id: 'city-field') {
|
102
|
+
value <=> [address, :city]
|
103
|
+
}
|
104
|
+
|
105
|
+
label('State: ', for: 'state-field')
|
106
|
+
select(id: 'state-field') {
|
107
|
+
Address::STATES.each do |state_code, state|
|
108
|
+
option(value: state_code) { state }
|
109
|
+
end
|
119
110
|
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
111
|
+
value <=> [address, :state_code]
|
112
|
+
}
|
113
|
+
|
114
|
+
label('Zip Code: ', for: 'zip-code-field')
|
115
|
+
input(id: 'zip-code-field', type: 'number', min: '0', max: '99999') {
|
116
|
+
value <=> [address, :zip_code,
|
117
|
+
on_write: :to_s,
|
118
|
+
]
|
119
|
+
}
|
120
|
+
|
121
|
+
style {
|
122
|
+
r("#{address_div.selector} *") {
|
123
|
+
margin '5px'
|
124
|
+
}
|
125
|
+
r("#{address_div.selector} input, #{address_div.selector} select") {
|
126
|
+
grid_column '2'
|
127
|
+
}
|
128
|
+
}
|
137
129
|
}
|
138
130
|
|
139
|
-
|
140
|
-
|
141
|
-
|
131
|
+
div(style: 'margin: 5px') {
|
132
|
+
inner_text <= [address, :summary,
|
133
|
+
computed_by: address.members + ['state_code'],
|
134
|
+
]
|
142
135
|
}
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
option(value: state_code) { state }
|
148
|
-
end
|
136
|
+
}
|
137
|
+
}
|
138
|
+
end
|
139
|
+
end
|
149
140
|
|
150
|
-
|
151
|
-
|
141
|
+
unless Object.const_defined?(:AddressPage)
|
142
|
+
# AddressPage Glimmer Web Component (View component)
|
143
|
+
#
|
144
|
+
# This View component represents the main page being rendered,
|
145
|
+
# as done by its `render` class method below
|
146
|
+
class AddressPage
|
147
|
+
include Glimmer::Web::Component
|
148
|
+
|
149
|
+
before_render do
|
150
|
+
@shipping_address = Address.new(
|
151
|
+
full_name: 'Johnny Doe',
|
152
|
+
street: '3922 Park Ave',
|
153
|
+
street2: 'PO BOX 8382',
|
154
|
+
city: 'San Diego',
|
155
|
+
state: 'California',
|
156
|
+
zip_code: '91913',
|
157
|
+
)
|
158
|
+
@billing_address = Address.new(
|
159
|
+
full_name: 'John C Doe',
|
160
|
+
street: '123 Main St',
|
161
|
+
street2: 'Apartment 3C',
|
162
|
+
city: 'San Diego',
|
163
|
+
state: 'California',
|
164
|
+
zip_code: '91911',
|
165
|
+
)
|
166
|
+
end
|
167
|
+
|
168
|
+
markup {
|
169
|
+
div {
|
170
|
+
h1('Shipping Address')
|
152
171
|
|
153
|
-
|
154
|
-
input(id: 'zip-code-field', type: 'number', min: '0', max: '99999') {
|
155
|
-
value <=> [address, :zip_code,
|
156
|
-
on_write: :to_s,
|
157
|
-
]
|
158
|
-
}
|
172
|
+
address_form(address: @shipping_address)
|
159
173
|
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
}
|
164
|
-
r("#{address_div.selector} input, #{address_div.selector} select") {
|
165
|
-
grid_column '2'
|
166
|
-
}
|
167
|
-
}
|
168
|
-
}
|
169
|
-
|
170
|
-
div(style: 'margin: 5px') {
|
171
|
-
inner_text <= [address, :summary,
|
172
|
-
computed_by: address.members + ['state_code'],
|
173
|
-
]
|
174
|
+
h1('Billing Address')
|
175
|
+
|
176
|
+
address_form(address: @billing_address)
|
174
177
|
}
|
175
178
|
}
|
176
|
-
}
|
177
|
-
end
|
178
|
-
|
179
|
-
# AddressPage Glimmer Web Component (View component)
|
180
|
-
#
|
181
|
-
# This View component represents the main page being rendered,
|
182
|
-
# as done by its `render` class method below
|
183
|
-
class AddressPage
|
184
|
-
include Glimmer::Web::Component
|
185
|
-
|
186
|
-
before_render do
|
187
|
-
@shipping_address = Address.new(
|
188
|
-
full_name: 'Johnny Doe',
|
189
|
-
street: '3922 Park Ave',
|
190
|
-
street2: 'PO BOX 8382',
|
191
|
-
city: 'San Diego',
|
192
|
-
state: 'California',
|
193
|
-
zip_code: '91913',
|
194
|
-
)
|
195
|
-
@billing_address = Address.new(
|
196
|
-
full_name: 'John C Doe',
|
197
|
-
street: '123 Main St',
|
198
|
-
street2: 'Apartment 3C',
|
199
|
-
city: 'San Diego',
|
200
|
-
state: 'California',
|
201
|
-
zip_code: '91911',
|
202
|
-
)
|
203
179
|
end
|
204
|
-
|
205
|
-
markup {
|
206
|
-
div {
|
207
|
-
h1('Shipping Address')
|
208
|
-
|
209
|
-
address_form(address: @shipping_address)
|
210
|
-
|
211
|
-
h1('Billing Address')
|
212
|
-
|
213
|
-
address_form(address: @billing_address)
|
214
|
-
}
|
215
|
-
}
|
216
180
|
end
|
217
181
|
|
218
182
|
Document.ready? do
|
@@ -22,63 +22,18 @@
|
|
22
22
|
require 'glimmer-dsl-web'
|
23
23
|
|
24
24
|
unless Object.const_defined?(:Address)
|
25
|
-
Address = Struct.new(:full_name, :street, :street2, :city, :state, :zip_code, keyword_init: true) do
|
25
|
+
Address = Struct.new(:full_name, :street, :street2, :city, :state, :zip_code, :billing_and_shipping, keyword_init: true) do
|
26
26
|
STATES = {
|
27
|
-
"AK"=>"Alaska",
|
28
|
-
"
|
29
|
-
"
|
30
|
-
"
|
31
|
-
"
|
32
|
-
"
|
33
|
-
"
|
34
|
-
"
|
35
|
-
"
|
36
|
-
"
|
37
|
-
"FL"=>"Florida",
|
38
|
-
"GA"=>"Georgia",
|
39
|
-
"GU"=>"Guam",
|
40
|
-
"HI"=>"Hawaii",
|
41
|
-
"IA"=>"Iowa",
|
42
|
-
"ID"=>"Idaho",
|
43
|
-
"IL"=>"Illinois",
|
44
|
-
"IN"=>"Indiana",
|
45
|
-
"KS"=>"Kansas",
|
46
|
-
"KY"=>"Kentucky",
|
47
|
-
"LA"=>"Louisiana",
|
48
|
-
"MA"=>"Massachusetts",
|
49
|
-
"MD"=>"Maryland",
|
50
|
-
"ME"=>"Maine",
|
51
|
-
"MI"=>"Michigan",
|
52
|
-
"MN"=>"Minnesota",
|
53
|
-
"MO"=>"Missouri",
|
54
|
-
"MS"=>"Mississippi",
|
55
|
-
"MT"=>"Montana",
|
56
|
-
"NC"=>"North Carolina",
|
57
|
-
"ND"=>"North Dakota",
|
58
|
-
"NE"=>"Nebraska",
|
59
|
-
"NH"=>"New Hampshire",
|
60
|
-
"NJ"=>"New Jersey",
|
61
|
-
"NM"=>"New Mexico",
|
62
|
-
"NV"=>"Nevada",
|
63
|
-
"NY"=>"New York",
|
64
|
-
"OH"=>"Ohio",
|
65
|
-
"OK"=>"Oklahoma",
|
66
|
-
"OR"=>"Oregon",
|
67
|
-
"PA"=>"Pennsylvania",
|
68
|
-
"PR"=>"Puerto Rico",
|
69
|
-
"RI"=>"Rhode Island",
|
70
|
-
"SC"=>"South Carolina",
|
71
|
-
"SD"=>"South Dakota",
|
72
|
-
"TN"=>"Tennessee",
|
73
|
-
"TX"=>"Texas",
|
74
|
-
"UT"=>"Utah",
|
75
|
-
"VA"=>"Virginia",
|
76
|
-
"VI"=>"Virgin Islands",
|
77
|
-
"VT"=>"Vermont",
|
78
|
-
"WA"=>"Washington",
|
79
|
-
"WI"=>"Wisconsin",
|
80
|
-
"WV"=>"West Virginia",
|
81
|
-
"WY"=>"Wyoming"
|
27
|
+
"AK"=>"Alaska", "AL"=>"Alabama", "AR"=>"Arkansas", "AS"=>"American Samoa", "AZ"=>"Arizona",
|
28
|
+
"CA"=>"California", "CO"=>"Colorado", "CT"=>"Connecticut", "DC"=>"District of Columbia", "DE"=>"Delaware",
|
29
|
+
"FL"=>"Florida", "GA"=>"Georgia", "GU"=>"Guam", "HI"=>"Hawaii", "IA"=>"Iowa", "ID"=>"Idaho", "IL"=>"Illinois",
|
30
|
+
"IN"=>"Indiana", "KS"=>"Kansas", "KY"=>"Kentucky", "LA"=>"Louisiana", "MA"=>"Massachusetts", "MD"=>"Maryland",
|
31
|
+
"ME"=>"Maine", "MI"=>"Michigan", "MN"=>"Minnesota", "MO"=>"Missouri", "MS"=>"Mississippi", "MT"=>"Montana",
|
32
|
+
"NC"=>"North Carolina", "ND"=>"North Dakota", "NE"=>"Nebraska", "NH"=>"New Hampshire", "NJ"=>"New Jersey",
|
33
|
+
"NM"=>"New Mexico", "NV"=>"Nevada", "NY"=>"New York", "OH"=>"Ohio", "OK"=>"Oklahoma", "OR"=>"Oregon",
|
34
|
+
"PA"=>"Pennsylvania", "PR"=>"Puerto Rico", "RI"=>"Rhode Island", "SC"=>"South Carolina", "SD"=>"South Dakota",
|
35
|
+
"TN"=>"Tennessee", "TX"=>"Texas", "UT"=>"Utah", "VA"=>"Virginia", "VI"=>"Virgin Islands", "VT"=>"Vermont",
|
36
|
+
"WA"=>"Washington", "WI"=>"Wisconsin", "WV"=>"West Virginia", "WY"=>"Wyoming"
|
82
37
|
}
|
83
38
|
|
84
39
|
def state_code
|
@@ -90,7 +45,10 @@ unless Object.const_defined?(:Address)
|
|
90
45
|
end
|
91
46
|
|
92
47
|
def summary
|
93
|
-
to_h.
|
48
|
+
string_attributes = to_h.except(:billing_and_shipping)
|
49
|
+
summary = string_attributes.values.map(&:to_s).reject(&:empty?).join(', ')
|
50
|
+
summary += " (Billing & Shipping)" if billing_and_shipping
|
51
|
+
summary
|
94
52
|
end
|
95
53
|
end
|
96
54
|
end
|
@@ -306,6 +264,8 @@ unless Object.const_defined?(:HelloComponentListeners)
|
|
306
264
|
#
|
307
265
|
# This View component represents the main page being rendered,
|
308
266
|
# as done by its `render` class method below
|
267
|
+
#
|
268
|
+
# Note: check out HelloComponentListenersDefaultSlot for a simpler version that leverages the default slot feature
|
309
269
|
class HelloComponentListeners
|
310
270
|
class Presenter
|
311
271
|
attr_accessor :status_message
|
@@ -386,5 +346,6 @@ end
|
|
386
346
|
|
387
347
|
Document.ready? do
|
388
348
|
# renders a top-level (root) HelloComponentListeners component
|
349
|
+
# Note: check out hello_component_listeners_default_slot.rb for a simpler version that leverages the default slot feature
|
389
350
|
HelloComponentListeners.render
|
390
351
|
end
|