glimmer-dsl-web 0.0.9 → 0.0.11

Sign up to get free protection for your applications and to get access to all the features.
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