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.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.9
1
+ 0.0.11
@@ -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.9 ruby lib
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".freeze
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-05"
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.4.1".freeze])
73
- s.add_runtime_dependency(%q<opal-rails>.freeze, ["= 2.0.2".freeze])
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.4.6".freeze])
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
@@ -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
- rendered_component.render(*args)
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(*args)
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(*args)
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
- # listeners_for('widget_removed').each {|listener| listener.call(Event.new(widget: self))}
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
- the_element = Document.find('body') if the_element.length == 0
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(parent_selector = nil, custom_parent_dom_element: nil, brand_new: false)
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
- listener: original_event_listener,
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 `` ticks
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 `` ticks
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
- def initialize(js_event:, listener:)
28
- @js_event = js_event
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, :listener, :js_listener, :original_event_listener
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:, listener:)
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
- @listener = listener
36
- @js_listener = lambda do |js_event|
37
- event = EventProxy.new(js_event: js_event, listener: self)
38
- result = listener.call(event)
39
- result = true if result.nil?
40
- result
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