glimmer-dsl-web 0.0.9 → 0.0.11
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 +19 -0
- data/README.md +712 -38
- data/VERSION +1 -1
- data/glimmer-dsl-web.gemspec +11 -6
- data/lib/glimmer/dsl/web/component_expression.rb +2 -1
- data/lib/glimmer/dsl/web/dsl.rb +22 -0
- data/lib/glimmer/dsl/web/observe_expression.rb +42 -0
- data/lib/glimmer/helpers/glimmer_helper.rb +30 -0
- data/lib/glimmer/web/component.rb +29 -5
- data/lib/glimmer/web/element_proxy.rb +15 -17
- data/lib/glimmer/web/event_proxy.rb +7 -4
- data/lib/glimmer/web/listener_proxy.rb +12 -11
- data/lib/glimmer-dsl-web/samples/hello/hello_glimmer_component_helper/address_form.rb +156 -0
- data/lib/glimmer-dsl-web/samples/hello/hello_observer.rb +88 -0
- data/lib/glimmer-dsl-web/samples/regular/button_counter.rb +36 -0
- data/lib/glimmer-dsl-web.rb +1 -0
- metadata +13 -8
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.0.
|
1
|
+
0.0.11
|
data/glimmer-dsl-web.gemspec
CHANGED
@@ -2,16 +2,16 @@
|
|
2
2
|
# DO NOT EDIT THIS FILE DIRECTLY
|
3
3
|
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
4
|
# -*- encoding: utf-8 -*-
|
5
|
-
# stub: glimmer-dsl-web 0.0.
|
5
|
+
# stub: glimmer-dsl-web 0.0.11 ruby lib
|
6
6
|
|
7
7
|
Gem::Specification.new do |s|
|
8
8
|
s.name = "glimmer-dsl-web".freeze
|
9
|
-
s.version = "0.0.
|
9
|
+
s.version = "0.0.11".freeze
|
10
10
|
|
11
11
|
s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version=
|
12
12
|
s.require_paths = ["lib".freeze]
|
13
13
|
s.authors = ["Andy Maleh".freeze]
|
14
|
-
s.date = "2024-01-
|
14
|
+
s.date = "2024-01-06"
|
15
15
|
s.description = "Glimmer DSL for Web (Ruby in the Browser Web GUI Frontend Library) - Enables frontend GUI development with Ruby by adopting a DSL that follows web-like HTML syntax, enabling the transfer of HTML/CSS/JS skills to Ruby frontend development. This library relies on Opal Ruby.".freeze
|
16
16
|
s.email = "andy.am@gmail.com".freeze
|
17
17
|
s.extra_rdoc_files = [
|
@@ -35,8 +35,11 @@ Gem::Specification.new do |s|
|
|
35
35
|
"lib/glimmer-dsl-web/samples/hello/hello_content_data_binding.rb",
|
36
36
|
"lib/glimmer-dsl-web/samples/hello/hello_data_binding.rb",
|
37
37
|
"lib/glimmer-dsl-web/samples/hello/hello_form.rb",
|
38
|
+
"lib/glimmer-dsl-web/samples/hello/hello_glimmer_component_helper/address_form.rb",
|
38
39
|
"lib/glimmer-dsl-web/samples/hello/hello_input_date_time.rb",
|
40
|
+
"lib/glimmer-dsl-web/samples/hello/hello_observer.rb",
|
39
41
|
"lib/glimmer-dsl-web/samples/hello/hello_world.rb",
|
42
|
+
"lib/glimmer-dsl-web/samples/regular/button_counter.rb",
|
40
43
|
"lib/glimmer-dsl-web/vendor/jquery.js",
|
41
44
|
"lib/glimmer/config/opal_logger.rb",
|
42
45
|
"lib/glimmer/data_binding/element_binding.rb",
|
@@ -48,10 +51,12 @@ Gem::Specification.new do |s|
|
|
48
51
|
"lib/glimmer/dsl/web/element_expression.rb",
|
49
52
|
"lib/glimmer/dsl/web/general_element_expression.rb",
|
50
53
|
"lib/glimmer/dsl/web/listener_expression.rb",
|
54
|
+
"lib/glimmer/dsl/web/observe_expression.rb",
|
51
55
|
"lib/glimmer/dsl/web/p_expression.rb",
|
52
56
|
"lib/glimmer/dsl/web/property_expression.rb",
|
53
57
|
"lib/glimmer/dsl/web/select_expression.rb",
|
54
58
|
"lib/glimmer/dsl/web/shine_data_binding_expression.rb",
|
59
|
+
"lib/glimmer/helpers/glimmer_helper.rb",
|
55
60
|
"lib/glimmer/util/proc_tracker.rb",
|
56
61
|
"lib/glimmer/web.rb",
|
57
62
|
"lib/glimmer/web/component.rb",
|
@@ -69,10 +74,10 @@ Gem::Specification.new do |s|
|
|
69
74
|
s.add_runtime_dependency(%q<glimmer>.freeze, ["~> 2.7.6".freeze])
|
70
75
|
s.add_runtime_dependency(%q<glimmer-dsl-xml>.freeze, ["~> 1.3.2".freeze])
|
71
76
|
s.add_runtime_dependency(%q<glimmer-dsl-css>.freeze, ["~> 1.2.2".freeze])
|
72
|
-
s.add_runtime_dependency(%q<opal>.freeze, ["= 1.
|
73
|
-
s.add_runtime_dependency(%q<opal-rails>.freeze, ["= 2.0.
|
77
|
+
s.add_runtime_dependency(%q<opal>.freeze, ["= 1.8.2".freeze])
|
78
|
+
s.add_runtime_dependency(%q<opal-rails>.freeze, ["= 2.0.3".freeze])
|
74
79
|
s.add_runtime_dependency(%q<opal-async>.freeze, ["~> 1.4.0".freeze])
|
75
|
-
s.add_runtime_dependency(%q<opal-jquery>.freeze, ["~> 0.
|
80
|
+
s.add_runtime_dependency(%q<opal-jquery>.freeze, ["~> 0.5.0".freeze])
|
76
81
|
s.add_runtime_dependency(%q<to_collection>.freeze, [">= 2.0.1".freeze, "< 3.0.0".freeze])
|
77
82
|
s.add_development_dependency(%q<puts_debuggerer>.freeze, [">= 0".freeze])
|
78
83
|
s.add_development_dependency(%q<rake>.freeze, [">= 10.1.0".freeze, "< 14.0.0".freeze])
|
@@ -21,8 +21,9 @@ module Glimmer
|
|
21
21
|
if block.source_location && (block.source_location == parent.content&.__getobj__&.source_location)
|
22
22
|
parent.content.call(parent) unless parent.content.called?
|
23
23
|
else
|
24
|
-
super
|
24
|
+
super(parent, keyword, *args, &block)
|
25
25
|
end
|
26
|
+
parent.post_add_content
|
26
27
|
end
|
27
28
|
end
|
28
29
|
end
|
data/lib/glimmer/dsl/web/dsl.rb
CHANGED
@@ -1,3 +1,24 @@
|
|
1
|
+
# Copyright (c) 2023-2024 Andy Maleh
|
2
|
+
#
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
# a copy of this software and associated documentation files (the
|
5
|
+
# "Software"), to deal in the Software without restriction, including
|
6
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
# the following conditions:
|
10
|
+
#
|
11
|
+
# The above copyright notice and this permission notice shall be
|
12
|
+
# included in all copies or substantial portions of the Software.
|
13
|
+
#
|
14
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
21
|
+
|
1
22
|
require 'glimmer/dsl/engine'
|
2
23
|
require 'glimmer/dsl/web/element_expression'
|
3
24
|
require 'glimmer/dsl/web/listener_expression'
|
@@ -9,6 +30,7 @@ require 'glimmer/dsl/web/data_binding_expression'
|
|
9
30
|
require 'glimmer/dsl/web/content_data_binding_expression'
|
10
31
|
require 'glimmer/dsl/web/shine_data_binding_expression'
|
11
32
|
require 'glimmer/dsl/web/component_expression'
|
33
|
+
require 'glimmer/dsl/web/observe_expression'
|
12
34
|
|
13
35
|
module Glimmer
|
14
36
|
module DSL
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# Copyright (c) 2023-2024 Andy Maleh
|
2
|
+
#
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
# a copy of this software and associated documentation files (the
|
5
|
+
# "Software"), to deal in the Software without restriction, including
|
6
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
# the following conditions:
|
10
|
+
#
|
11
|
+
# The above copyright notice and this permission notice shall be
|
12
|
+
# included in all copies or substantial portions of the Software.
|
13
|
+
#
|
14
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
21
|
+
|
22
|
+
require 'glimmer/dsl/static_expression'
|
23
|
+
require 'glimmer/dsl/top_level_expression'
|
24
|
+
require 'glimmer/dsl/observe_expression'
|
25
|
+
require 'glimmer/web/component'
|
26
|
+
|
27
|
+
module Glimmer
|
28
|
+
module DSL
|
29
|
+
module SWT
|
30
|
+
class ObserveExpression < StaticExpression
|
31
|
+
include TopLevelExpression
|
32
|
+
include Glimmer::DSL::ObserveExpression
|
33
|
+
|
34
|
+
def interpret(parent, keyword, *args, &block)
|
35
|
+
observer_registration = super(parent, keyword, *args, &block)
|
36
|
+
Glimmer::Web::Component.interpretation_stack.last&.observer_registrations&.push(observer_registration)
|
37
|
+
observer_registration
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module GlimmerHelper
|
2
|
+
class << self
|
3
|
+
def next_id_number
|
4
|
+
@next_id_number ||= 0
|
5
|
+
@next_id_number += 1
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
def glimmer_component(component_asset_path, *component_args)
|
10
|
+
component_file = component_asset_path.split('/').last
|
11
|
+
component_class_name = component_file.classify
|
12
|
+
next_id_number = GlimmerHelper.next_id_number
|
13
|
+
component_id = "glimmer_component_#{next_id_number}"
|
14
|
+
component_script_container_id = "glimmer_component_script_container_#{next_id_number}"
|
15
|
+
component_args_json = JSON.dump(component_args)
|
16
|
+
opal_script = <<~Opal
|
17
|
+
require 'glimmer-dsl-web'
|
18
|
+
component_args_json = '#{component_args_json}'
|
19
|
+
component_args = JSON.parse(component_args_json)
|
20
|
+
component_args << {} if !component_args.last.is_a?(Hash)
|
21
|
+
component_args.last[:parent] = "##{component_id}"
|
22
|
+
#{component_class_name}.render(*component_args)
|
23
|
+
Opal
|
24
|
+
content_tag(:div, id: component_script_container_id, class: ['glimmer_component_script_container', "#{component_file}_script_container"]) do
|
25
|
+
content_tag(:div, '', id: component_id, class: ['glimmer_component', component_file]) +
|
26
|
+
javascript_include_tag(component_asset_path, "data-turbolinks-track": "reload") +
|
27
|
+
content_tag(:script, raw(opal_script), type: 'text/ruby')
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -94,8 +94,9 @@ module Glimmer
|
|
94
94
|
end
|
95
95
|
|
96
96
|
def render(*args)
|
97
|
-
rendered_component = send(keyword)
|
98
|
-
|
97
|
+
rendered_component = send(keyword, *args)
|
98
|
+
options = args.last.is_a?(Hash) ? args.last.slice(:parent, :custom_parent_dom_element, :brand_new) : {}
|
99
|
+
rendered_component.render(**options)
|
99
100
|
rendered_component
|
100
101
|
end
|
101
102
|
end
|
@@ -175,14 +176,19 @@ module Glimmer
|
|
175
176
|
def reset_component_namespaces
|
176
177
|
@component_namespaces = Set[Object, Glimmer::Web]
|
177
178
|
end
|
179
|
+
|
180
|
+
def interpretation_stack
|
181
|
+
@interpretation_stack ||= []
|
182
|
+
end
|
178
183
|
end
|
179
184
|
# <- end of class methods
|
180
185
|
|
181
186
|
|
182
|
-
attr_reader :markup_root, :parent, :options
|
187
|
+
attr_reader :markup_root, :parent, :args, :options
|
183
188
|
alias parent_proxy parent
|
184
189
|
|
185
190
|
def initialize(parent, args, options, &content)
|
191
|
+
Component.interpretation_stack.push(self)
|
186
192
|
@parent = parent
|
187
193
|
options = args.delete_at(-1) if args.is_a?(Array) && args.last.is_a?(Hash)
|
188
194
|
if args.is_a?(Hash)
|
@@ -198,9 +204,18 @@ module Glimmer
|
|
198
204
|
markup_block = self.class.instance_variable_get("@markup_block")
|
199
205
|
raise Glimmer::Error, 'Invalid Glimmer web component for having no markup! Please define markup block!' if markup_block.nil?
|
200
206
|
@markup_root = instance_exec(&markup_block)
|
207
|
+
@markup_root.options[:parent] = options[:parent] if options[:parent]
|
201
208
|
@parent ||= @markup_root.parent
|
202
209
|
raise Glimmer::Error, 'Invalid Glimmer web component for having an empty markup! Please fill markup block!' if @markup_root.nil?
|
203
210
|
execute_hooks('after_render')
|
211
|
+
|
212
|
+
# TODO adapt for web
|
213
|
+
observer_registration_cleanup_listener = proc do
|
214
|
+
observer_registrations.compact.each(&:deregister)
|
215
|
+
observer_registrations.clear
|
216
|
+
end
|
217
|
+
@markup_root.handle_observation_request('on_remove', observer_registration_cleanup_listener)
|
218
|
+
post_add_content if content.nil?
|
204
219
|
end
|
205
220
|
|
206
221
|
# Subclasses may override to perform post initialization work on an added child
|
@@ -208,6 +223,15 @@ module Glimmer
|
|
208
223
|
# No Op by default
|
209
224
|
end
|
210
225
|
|
226
|
+
def post_add_content
|
227
|
+
Component.interpretation_stack.pop
|
228
|
+
end
|
229
|
+
|
230
|
+
# This stores observe keyword registrations of model/attribute observers
|
231
|
+
def observer_registrations
|
232
|
+
@observer_registrations ||= []
|
233
|
+
end
|
234
|
+
|
211
235
|
def can_handle_observation_request?(observation_request)
|
212
236
|
observation_request = observation_request.to_s
|
213
237
|
result = false
|
@@ -273,9 +297,9 @@ module Glimmer
|
|
273
297
|
"#{attribute_name}="
|
274
298
|
end
|
275
299
|
|
276
|
-
def render(
|
300
|
+
def render(parent: nil, custom_parent_dom_element: nil, brand_new: false)
|
277
301
|
# this method is defined to prevent displaying a harmless Glimmer no keyword error as an annoying useless warning
|
278
|
-
@markup_root&.render(
|
302
|
+
@markup_root&.render(parent: parent, custom_parent_dom_element: custom_parent_dom_element, brand_new: brand_new)
|
279
303
|
end
|
280
304
|
|
281
305
|
# Returns content block if used as an attribute reader (no args)
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# backtick_javascript: true
|
2
|
+
|
1
3
|
# Copyright (c) 2023-2024 Andy Maleh
|
2
4
|
#
|
3
5
|
# Permission is hereby granted, free of charge, to any person obtaining
|
@@ -137,11 +139,14 @@ module Glimmer
|
|
137
139
|
@children.dup.each do |child|
|
138
140
|
child.remove
|
139
141
|
end
|
142
|
+
on_remove_listeners = listeners_for('on_remove').dup
|
140
143
|
remove_all_listeners
|
141
144
|
dom_element.remove
|
142
145
|
parent&.post_remove_child(self)
|
143
146
|
@removed = true
|
144
|
-
|
147
|
+
on_remove_listeners.each do |listener|
|
148
|
+
listener.original_event_listener.call(EventProxy.new(listener: listener))
|
149
|
+
end
|
145
150
|
end
|
146
151
|
|
147
152
|
def remove_all_listeners
|
@@ -215,15 +220,18 @@ module Glimmer
|
|
215
220
|
if parent_selector
|
216
221
|
Document.find(parent_selector)
|
217
222
|
else
|
218
|
-
# TODO consider moving this to initializer
|
219
223
|
options[:parent] ||= 'body'
|
220
224
|
the_element = Document.find(options[:parent])
|
221
|
-
|
225
|
+
if the_element.length == 0
|
226
|
+
options[:parent] = 'body'
|
227
|
+
the_element = Document.find('body')
|
228
|
+
end
|
222
229
|
the_element
|
223
230
|
end
|
224
231
|
end
|
225
232
|
|
226
|
-
def render(
|
233
|
+
def render(parent: nil, custom_parent_dom_element: nil, brand_new: false)
|
234
|
+
parent_selector = parent
|
227
235
|
options[:parent] = parent_selector if !parent_selector.to_s.empty?
|
228
236
|
if !options[:parent].to_s.empty?
|
229
237
|
# ensure element is orphaned as it is becoming a top-level root element
|
@@ -430,22 +438,12 @@ module Glimmer
|
|
430
438
|
end
|
431
439
|
|
432
440
|
def handle_observation_request(keyword, original_event_listener)
|
433
|
-
# case keyword
|
434
|
-
# when 'on_widget_removed'
|
435
|
-
# listeners_for(keyword.sub(/^on_/, '')) << original_event_listener.to_proc
|
436
|
-
# else
|
437
|
-
handle_javascript_observation_request(keyword, original_event_listener)
|
438
|
-
# end
|
439
|
-
end
|
440
|
-
|
441
|
-
def handle_javascript_observation_request(keyword, original_event_listener)
|
442
441
|
listener = ListenerProxy.new(
|
443
442
|
element: self,
|
444
443
|
selector: selector,
|
445
444
|
dom_element: dom_element,
|
446
445
|
event_attribute: keyword,
|
447
|
-
|
448
|
-
original_event_listener: original_event_listener
|
446
|
+
original_event_listener: original_event_listener,
|
449
447
|
)
|
450
448
|
listener.register
|
451
449
|
listeners_for(keyword) << listener
|
@@ -499,7 +497,7 @@ module Glimmer
|
|
499
497
|
end
|
500
498
|
|
501
499
|
def respond_to_missing?(method_name, include_private = false)
|
502
|
-
# TODO consider doing more correct checking of availability of properties/methods using native
|
500
|
+
# TODO consider doing more correct checking of availability of properties/methods using native ticks
|
503
501
|
property_name = property_name_for(method_name)
|
504
502
|
unnormalized_property_name = unnormalized_property_name_for(method_name)
|
505
503
|
super(method_name, include_private) ||
|
@@ -512,7 +510,7 @@ module Glimmer
|
|
512
510
|
end
|
513
511
|
|
514
512
|
def method_missing(method_name, *args, &block)
|
515
|
-
# TODO consider doing more correct checking of availability of properties/methods using native
|
513
|
+
# TODO consider doing more correct checking of availability of properties/methods using native ticks
|
516
514
|
property_name = property_name_for(method_name)
|
517
515
|
unnormalized_property_name = unnormalized_property_name_for(method_name)
|
518
516
|
if method_name.to_s.start_with?('on_')
|
@@ -24,9 +24,11 @@ module Glimmer
|
|
24
24
|
class EventProxy
|
25
25
|
attr_reader :js_event, :listener
|
26
26
|
|
27
|
-
|
28
|
-
|
27
|
+
# Instantiates EventProxy
|
28
|
+
# When js_event is nil, it is a custom event
|
29
|
+
def initialize(listener:, js_event: nil)
|
29
30
|
@listener = listener
|
31
|
+
@js_event = js_event
|
30
32
|
end
|
31
33
|
|
32
34
|
def element = listener.element
|
@@ -34,6 +36,7 @@ module Glimmer
|
|
34
36
|
def event_attribute = listener.event_attribute
|
35
37
|
|
36
38
|
def original_event
|
39
|
+
return if js_event.nil?
|
37
40
|
Native(`#{js_event.to_n}.originalEvent`)
|
38
41
|
end
|
39
42
|
|
@@ -41,14 +44,14 @@ module Glimmer
|
|
41
44
|
property_name = method_name.to_s.camelcase
|
42
45
|
super(method_name, include_private) ||
|
43
46
|
js_event.respond_to?(method_name, include_private) ||
|
44
|
-
`#{property_name} in #{original_event.to_n}`
|
47
|
+
(original_event && `#{property_name} in #{original_event.to_n}`)
|
45
48
|
end
|
46
49
|
|
47
50
|
def method_missing(method_name, *args, &block)
|
48
51
|
property_name = method_name.to_s.camelcase
|
49
52
|
if js_event.respond_to?(method_name, true)
|
50
53
|
js_event.send(method_name, *args, &block)
|
51
|
-
elsif `#{property_name} in #{original_event.to_n}`
|
54
|
+
elsif (original_event && `#{property_name} in #{original_event.to_n}`)
|
52
55
|
original_event[property_name]
|
53
56
|
else
|
54
57
|
super(method_name, *args, &block)
|
@@ -24,33 +24,34 @@ require 'glimmer/web/event_proxy'
|
|
24
24
|
module Glimmer
|
25
25
|
module Web
|
26
26
|
class ListenerProxy
|
27
|
-
attr_reader :element, :event_attribute, :event_name, :dom_element, :selector, :
|
27
|
+
attr_reader :element, :event_attribute, :event_name, :dom_element, :selector, :js_listener, :original_event_listener
|
28
28
|
|
29
|
-
def initialize(element:, event_attribute:, dom_element:, selector:,
|
29
|
+
def initialize(element:, event_attribute:, dom_element:, selector:, original_event_listener:)
|
30
30
|
@element = element
|
31
31
|
@event_attribute = event_attribute
|
32
|
-
@event_name = event_attribute.sub(/^on/, '')
|
32
|
+
@event_name = event_attribute.sub(/^on_/, '').sub(/^on/, '')
|
33
33
|
@dom_element = dom_element
|
34
34
|
@selector = selector
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
35
|
+
if !event_attribute.start_with?('on_') # custom event
|
36
|
+
@js_listener = lambda do |js_event|
|
37
|
+
event = EventProxy.new(js_event: js_event, listener: self)
|
38
|
+
result = original_event_listener.call(event)
|
39
|
+
result = true if result.nil?
|
40
|
+
result
|
41
|
+
end
|
41
42
|
end
|
42
43
|
@original_event_listener = original_event_listener
|
43
44
|
end
|
44
45
|
|
45
46
|
def register
|
46
|
-
@dom_element.on(@event_name, &@js_listener)
|
47
|
+
@dom_element.on(@event_name, &@js_listener) unless @js_listener.nil?
|
47
48
|
end
|
48
49
|
alias observe register
|
49
50
|
alias reregister register
|
50
51
|
|
51
52
|
def unregister
|
52
53
|
# TODO contribute fix to opal to allow passing observer with & to off with selector not specified as nil
|
53
|
-
@dom_element.off(@event_name, @js_listener)
|
54
|
+
@dom_element.off(@event_name, @js_listener) unless @js_listener.nil?
|
54
55
|
@element.listeners_for(@event_attribute).delete(self)
|
55
56
|
end
|
56
57
|
alias unobserve unregister
|
@@ -0,0 +1,156 @@
|
|
1
|
+
require 'glimmer-dsl-web'
|
2
|
+
|
3
|
+
class AddressForm
|
4
|
+
Address = Struct.new(:full_name, :street, :street2, :city, :state, :zip_code, keyword_init: true) do
|
5
|
+
def state_code
|
6
|
+
STATES.invert[state]
|
7
|
+
end
|
8
|
+
|
9
|
+
def state_code=(value)
|
10
|
+
self.state = STATES[value]
|
11
|
+
end
|
12
|
+
|
13
|
+
def summary
|
14
|
+
to_h.values.map(&:to_s).reject(&:empty?).join(', ')
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
STATES = {
|
19
|
+
"AK"=>"Alaska",
|
20
|
+
"AL"=>"Alabama",
|
21
|
+
"AR"=>"Arkansas",
|
22
|
+
"AS"=>"American Samoa",
|
23
|
+
"AZ"=>"Arizona",
|
24
|
+
"CA"=>"California",
|
25
|
+
"CO"=>"Colorado",
|
26
|
+
"CT"=>"Connecticut",
|
27
|
+
"DC"=>"District of Columbia",
|
28
|
+
"DE"=>"Delaware",
|
29
|
+
"FL"=>"Florida",
|
30
|
+
"GA"=>"Georgia",
|
31
|
+
"GU"=>"Guam",
|
32
|
+
"HI"=>"Hawaii",
|
33
|
+
"IA"=>"Iowa",
|
34
|
+
"ID"=>"Idaho",
|
35
|
+
"IL"=>"Illinois",
|
36
|
+
"IN"=>"Indiana",
|
37
|
+
"KS"=>"Kansas",
|
38
|
+
"KY"=>"Kentucky",
|
39
|
+
"LA"=>"Louisiana",
|
40
|
+
"MA"=>"Massachusetts",
|
41
|
+
"MD"=>"Maryland",
|
42
|
+
"ME"=>"Maine",
|
43
|
+
"MI"=>"Michigan",
|
44
|
+
"MN"=>"Minnesota",
|
45
|
+
"MO"=>"Missouri",
|
46
|
+
"MS"=>"Mississippi",
|
47
|
+
"MT"=>"Montana",
|
48
|
+
"NC"=>"North Carolina",
|
49
|
+
"ND"=>"North Dakota",
|
50
|
+
"NE"=>"Nebraska",
|
51
|
+
"NH"=>"New Hampshire",
|
52
|
+
"NJ"=>"New Jersey",
|
53
|
+
"NM"=>"New Mexico",
|
54
|
+
"NV"=>"Nevada",
|
55
|
+
"NY"=>"New York",
|
56
|
+
"OH"=>"Ohio",
|
57
|
+
"OK"=>"Oklahoma",
|
58
|
+
"OR"=>"Oregon",
|
59
|
+
"PA"=>"Pennsylvania",
|
60
|
+
"PR"=>"Puerto Rico",
|
61
|
+
"RI"=>"Rhode Island",
|
62
|
+
"SC"=>"South Carolina",
|
63
|
+
"SD"=>"South Dakota",
|
64
|
+
"TN"=>"Tennessee",
|
65
|
+
"TX"=>"Texas",
|
66
|
+
"UT"=>"Utah",
|
67
|
+
"VA"=>"Virginia",
|
68
|
+
"VI"=>"Virgin Islands",
|
69
|
+
"VT"=>"Vermont",
|
70
|
+
"WA"=>"Washington",
|
71
|
+
"WI"=>"Wisconsin",
|
72
|
+
"WV"=>"West Virginia",
|
73
|
+
"WY"=>"Wyoming"
|
74
|
+
}
|
75
|
+
|
76
|
+
include Glimmer::Web::Component
|
77
|
+
|
78
|
+
option :full_name
|
79
|
+
option :street
|
80
|
+
option :street2
|
81
|
+
option :city
|
82
|
+
option :state
|
83
|
+
option :zip_code
|
84
|
+
|
85
|
+
attr_reader :address
|
86
|
+
|
87
|
+
before_render do
|
88
|
+
@address = Address.new(
|
89
|
+
full_name: full_name,
|
90
|
+
street: street,
|
91
|
+
street2: street2,
|
92
|
+
city: city,
|
93
|
+
state: state,
|
94
|
+
zip_code: zip_code,
|
95
|
+
)
|
96
|
+
end
|
97
|
+
|
98
|
+
markup {
|
99
|
+
div {
|
100
|
+
div(style: 'display: grid; grid-auto-columns: 80px 260px;') { |address_div|
|
101
|
+
label('Full Name: ', for: 'full-name-field')
|
102
|
+
input(id: 'full-name-field') {
|
103
|
+
value <=> [address, :full_name]
|
104
|
+
}
|
105
|
+
|
106
|
+
@somelabel = label('Street: ', for: 'street-field')
|
107
|
+
input(id: 'street-field') {
|
108
|
+
value <=> [address, :street]
|
109
|
+
}
|
110
|
+
|
111
|
+
label('Street 2: ', for: 'street2-field')
|
112
|
+
textarea(id: 'street2-field') {
|
113
|
+
value <=> [address, :street2]
|
114
|
+
}
|
115
|
+
|
116
|
+
label('City: ', for: 'city-field')
|
117
|
+
input(id: 'city-field') {
|
118
|
+
value <=> [address, :city]
|
119
|
+
}
|
120
|
+
|
121
|
+
label('State: ', for: 'state-field')
|
122
|
+
select(id: 'state-field') {
|
123
|
+
STATES.each do |state_code, state|
|
124
|
+
option(value: state_code) { state }
|
125
|
+
end
|
126
|
+
|
127
|
+
value <=> [address, :state_code]
|
128
|
+
}
|
129
|
+
|
130
|
+
label('Zip Code: ', for: 'zip-code-field')
|
131
|
+
input(id: 'zip-code-field', type: 'number', min: '0', max: '99999') {
|
132
|
+
value <=> [address, :zip_code,
|
133
|
+
on_write: :to_s,
|
134
|
+
]
|
135
|
+
}
|
136
|
+
|
137
|
+
style {
|
138
|
+
<<~CSS
|
139
|
+
#{address_div.selector} * {
|
140
|
+
margin: 5px;
|
141
|
+
}
|
142
|
+
#{address_div.selector} input, #{address_div.selector} select {
|
143
|
+
grid-column: 2;
|
144
|
+
}
|
145
|
+
CSS
|
146
|
+
}
|
147
|
+
}
|
148
|
+
|
149
|
+
div(style: 'margin: 5px') {
|
150
|
+
inner_text <= [address, :summary,
|
151
|
+
computed_by: address.members + ['state_code'],
|
152
|
+
]
|
153
|
+
}
|
154
|
+
}
|
155
|
+
}
|
156
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
# Copyright (c) 2023-2024 Andy Maleh
|
2
|
+
#
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
# a copy of this software and associated documentation files (the
|
5
|
+
# "Software"), to deal in the Software without restriction, including
|
6
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
# the following conditions:
|
10
|
+
#
|
11
|
+
# The above copyright notice and this permission notice shall be
|
12
|
+
# included in all copies or substantial portions of the Software.
|
13
|
+
#
|
14
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
21
|
+
|
22
|
+
require 'glimmer-dsl-web'
|
23
|
+
|
24
|
+
class NumberHolder
|
25
|
+
attr_accessor :number
|
26
|
+
|
27
|
+
def initialize
|
28
|
+
self.number = 50
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
class HelloObserver
|
33
|
+
include Glimmer::Web::Component
|
34
|
+
|
35
|
+
before_render do
|
36
|
+
@number_holder = NumberHolder.new
|
37
|
+
end
|
38
|
+
|
39
|
+
after_render do
|
40
|
+
# observe Model attribute @number_holder.number for changes and update View
|
41
|
+
observe(@number_holder, :number) do
|
42
|
+
number_string = @number_holder.number.to_s
|
43
|
+
@number_input.value = number_string unless @number_input.value == number_string
|
44
|
+
@range_input.value = number_string unless @range_input.value == number_string
|
45
|
+
end
|
46
|
+
# Bidirectional Data-Binding does the same thing automatically
|
47
|
+
# Just disable the observe block above as well as the oninput listeners below
|
48
|
+
# and enable the `value <=> [@number_holder, :number]` lines to try the data-binding version
|
49
|
+
# Learn more about Bidirectional and Unidirectional Data-Binding in hello_data_binding.rb
|
50
|
+
end
|
51
|
+
|
52
|
+
markup {
|
53
|
+
div {
|
54
|
+
div {
|
55
|
+
@number_input = input(type: 'number', value: @number_holder.number, min: 0, max: 100) {
|
56
|
+
# oninput listener updates Model attribute @number_holder.number
|
57
|
+
oninput do
|
58
|
+
@number_holder.number = @number_input.value.to_i
|
59
|
+
end
|
60
|
+
|
61
|
+
# Bidirectional Data-Binding simplifies the implementation significantly
|
62
|
+
# by enabling the following line and disabling oninput listeners as well
|
63
|
+
# as the after_body observe block observer
|
64
|
+
# Learn more about Bidirectional and Unidirectional Data-Binding in hello_data_binding.rb
|
65
|
+
# value <=> [@number_holder, :number]
|
66
|
+
}
|
67
|
+
}
|
68
|
+
div {
|
69
|
+
@range_input = input(type: 'range', value: @number_holder.number, min: 0, max: 100) {
|
70
|
+
# oninput listener updates Model attribute @number_holder.number
|
71
|
+
oninput do
|
72
|
+
@number_holder.number = @range_input.value.to_i
|
73
|
+
end
|
74
|
+
|
75
|
+
# Bidirectional Data-Binding simplifies the implementation significantly
|
76
|
+
# by enabling the following line and disabling oninput listeners as well
|
77
|
+
# as the after_body observe block observer
|
78
|
+
# Learn more about Bidirectional and Unidirectional Data-Binding in hello_data_binding.rb
|
79
|
+
# value <=> [@number_holder, :number]
|
80
|
+
}
|
81
|
+
}
|
82
|
+
}
|
83
|
+
}
|
84
|
+
end
|
85
|
+
|
86
|
+
Document.ready? do
|
87
|
+
HelloObserver.render
|
88
|
+
end
|