glimmer-dsl-web 0.0.2 → 0.0.4

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.
@@ -2,17 +2,17 @@
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.2 ruby lib
5
+ # stub: glimmer-dsl-web 0.0.4 ruby lib
6
6
 
7
7
  Gem::Specification.new do |s|
8
8
  s.name = "glimmer-dsl-web".freeze
9
- s.version = "0.0.2".freeze
9
+ s.version = "0.0.4".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 = "2023-12-27"
15
- s.description = "Glimmer DSL for Web (Ruby in the Browser Web GUI 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
14
+ s.date = "2023-12-30"
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 = [
18
18
  "CHANGELOG.md",
@@ -30,6 +30,7 @@ Gem::Specification.new do |s|
30
30
  "lib/glimmer-dsl-web/ext/class.rb",
31
31
  "lib/glimmer-dsl-web/ext/date.rb",
32
32
  "lib/glimmer-dsl-web/ext/exception.rb",
33
+ "lib/glimmer-dsl-web/samples/hello/hello_button.rb",
33
34
  "lib/glimmer-dsl-web/samples/hello/hello_world.rb",
34
35
  "lib/glimmer-dsl-web/vendor/jquery.js",
35
36
  "lib/glimmer/config/opal_logger.rb",
@@ -37,15 +38,18 @@ Gem::Specification.new do |s|
37
38
  "lib/glimmer/data_binding/observable_element.rb",
38
39
  "lib/glimmer/dsl/web/dsl.rb",
39
40
  "lib/glimmer/dsl/web/element_expression.rb",
41
+ "lib/glimmer/dsl/web/listener_expression.rb",
42
+ "lib/glimmer/dsl/web/property_expression.rb",
40
43
  "lib/glimmer/util/proc_tracker.rb",
41
44
  "lib/glimmer/web.rb",
42
45
  "lib/glimmer/web/element_proxy.rb",
46
+ "lib/glimmer/web/listener_proxy.rb",
43
47
  "lib/glimmer/web/property_owner.rb"
44
48
  ]
45
49
  s.homepage = "http://github.com/AndyObtiva/glimmer-dsl-web".freeze
46
50
  s.licenses = ["MIT".freeze]
47
51
  s.rubygems_version = "3.5.3".freeze
48
- s.summary = "Glimmer DSL for Web".freeze
52
+ s.summary = "Glimmer DSL for Web (Ruby in the Browser Web GUI Frontend Library)".freeze
49
53
 
50
54
  s.specification_version = 4
51
55
 
@@ -1,21 +1,25 @@
1
1
  require 'glimmer/dsl/engine'
2
2
  # Dir[File.expand_path('../*_expression.rb', __FILE__)].each {|f| require f}
3
3
  require 'glimmer/dsl/web/element_expression'
4
+ require 'glimmer/dsl/web/listener_expression'
5
+ require 'glimmer/dsl/web/property_expression'
4
6
 
5
7
  module Glimmer
6
8
  module DSL
7
9
  module Web
8
10
  # TODO implement all those expressions
9
11
  # %w[
10
- # event_listener
12
+ # listener
11
13
  # data_binding
12
- # attribute
14
+ # property
13
15
  # shine_data_binding
14
16
  # element
15
17
  # ]
16
18
  Engine.add_dynamic_expressions(
17
19
  Web,
18
20
  %w[
21
+ listener
22
+ property
19
23
  element
20
24
  ]
21
25
  )
@@ -11,7 +11,7 @@ module Glimmer
11
11
  def can_interpret?(parent, keyword, *args, &block)
12
12
  # TODO automatically pass parent option as element if not passed instead of rejecting elements without a paraent nor root
13
13
  # TODO raise a proper error if root is an element that is not found (maybe do this in model)
14
- true
14
+ !keyword.to_s.start_with?('on_')
15
15
  end
16
16
 
17
17
  def interpret(parent, keyword, *args, &block)
@@ -21,7 +21,7 @@ module Glimmer
21
21
  def add_content(parent, keyword, *args, &block)
22
22
  if parent.rendered? || parent.skip_content_on_render_blocks?
23
23
  return_value = super(parent, keyword, *args, &block)
24
- if return_value.is_a?(String)
24
+ if return_value.is_a?(String) && parent.dom_element.text.to_s.empty?
25
25
  parent.add_text_content(return_value)
26
26
  end
27
27
  parent.post_add_content
@@ -0,0 +1,19 @@
1
+ require 'glimmer/dsl/expression'
2
+
3
+ module Glimmer
4
+ module DSL
5
+ module Web
6
+ class ListenerExpression < Expression
7
+ def can_interpret?(parent, keyword, *args, &block)
8
+ parent and
9
+ parent.respond_to?(:can_handle_observation_request?) and
10
+ parent.can_handle_observation_request?(keyword)
11
+ end
12
+
13
+ def interpret(parent, keyword, *args, &block)
14
+ parent.handle_observation_request(keyword, block)
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,23 @@
1
+ require 'glimmer/dsl/expression'
2
+
3
+ require 'glimmer/web/element_proxy'
4
+
5
+ module Glimmer
6
+ module DSL
7
+ module Web
8
+ class PropertyExpression < Expression
9
+ def can_interpret?(parent, keyword, *args, &block)
10
+ parent.is_a?(Glimmer::Web::ElementProxy) and
11
+ (!args.empty?) and
12
+ parent.respond_to?("#{keyword}=") and
13
+ block.nil?
14
+ end
15
+
16
+ def interpret(parent, keyword, *args, &block)
17
+ parent.send("#{keyword}=", *args)
18
+ args
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2020-2022 Andy Maleh
1
+ # Copyright (c) 2023 Andy Maleh
2
2
  #
3
3
  # Permission is hereby granted, free of charge, to any person obtaining
4
4
  # a copy of this software and associated documentation files (the
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2020-2022 Andy Maleh
1
+ # Copyright (c) 2023 Andy Maleh
2
2
  #
3
3
  # Permission is hereby granted, free of charge, to any person obtaining
4
4
  # a copy of this software and associated documentation files (the
@@ -21,6 +21,7 @@
21
21
 
22
22
  # require 'glimmer/web/event_listener_proxy'
23
23
  require 'glimmer/web/property_owner'
24
+ require 'glimmer/web/listener_proxy'
24
25
 
25
26
  # TODO implement menu (which delays building it till render using add_content_on_render)
26
27
 
@@ -68,9 +69,11 @@ module Glimmer
68
69
 
69
70
  include Glimmer
70
71
  include PropertyOwner
71
-
72
+
72
73
  Event = Struct.new(:widget, keyword_init: true)
73
-
74
+
75
+ GLIMMER_ATTRIBUTES = [:parent]
76
+
74
77
  attr_reader :keyword, :parent, :args, :options, :children, :enabled, :foreground, :background, :focus, :removed?, :rendered
75
78
  alias rendered? rendered
76
79
 
@@ -92,7 +95,7 @@ module Glimmer
92
95
 
93
96
  # Executes for the parent of a child that just got removed
94
97
  def post_remove_child(child)
95
- @children&.delete(child)
98
+ @children.delete(child)
96
99
  end
97
100
 
98
101
  # Executes at the closing of a parent widget curly braces after all children/properties have been added/set
@@ -105,26 +108,19 @@ module Glimmer
105
108
  end
106
109
 
107
110
  def remove
111
+ @children.dup.each do |child|
112
+ child.remove
113
+ end
108
114
  remove_all_listeners
109
115
  dom_element.remove
110
116
  parent&.post_remove_child(self)
111
- # children.each(:remove) # TODO enable this safely
112
117
  @removed = true
113
- listeners_for('widget_removed').each {|listener| listener.call(Event.new(widget: self))}
118
+ # listeners_for('widget_removed').each {|listener| listener.call(Event.new(widget: self))}
114
119
  end
115
120
 
116
121
  def remove_all_listeners
117
- effective_observation_request_to_event_mapping.keys.each do |keyword|
118
- effective_observation_request_to_event_mapping[keyword].to_collection.each do |mapping|
119
- observation_requests[keyword].to_a.each do |event_listener|
120
- event = mapping[:event]
121
- event_handler = mapping[:event_handler]
122
- event_element_css_selector = mapping[:event_element_css_selector]
123
- the_listener_dom_element = event_element_css_selector ? Element[event_element_css_selector] : listener_dom_element
124
- the_listener_dom_element.off(event, event_listener)
125
- # TODO improve to precisely remove the listeners that were added, no more no less. (or use the event_listener_proxies method instead or in collaboration)
126
- end
127
- end
122
+ listeners.each do |event, event_listeners|
123
+ event_listeners.dup.each(&:unregister)
128
124
  end
129
125
  end
130
126
 
@@ -198,6 +194,7 @@ module Glimmer
198
194
  if parent_selector
199
195
  Document.find(parent_selector)
200
196
  else
197
+ # TODO consider moving this to initializer
201
198
  options[:parent] ||= 'body'
202
199
  Document.find(options[:parent])
203
200
  end
@@ -240,7 +237,7 @@ module Glimmer
240
237
  end
241
238
 
242
239
  def add_text_content(text)
243
- dom_element.append(text)
240
+ dom_element.append(text.to_s)
244
241
  end
245
242
 
246
243
  def content_on_render_blocks
@@ -263,33 +260,31 @@ module Glimmer
263
260
  # TODO consider passing parent element instead and having table item include a table cell widget only for opal
264
261
  @dom = nil
265
262
  @dom = dom # TODO unify how to build dom for most widgets based on element, id, and name (class)
266
- @dom = @parent.get_layout.dom(@dom) if @parent.respond_to?(:layout) && @parent.get_layout
267
263
  @dom
268
264
  end
269
265
 
270
266
  def dom
271
- body_class = ([name, element_id] + css_classes.to_a).join(' ')
272
267
  # TODO auto-convert known glimmer attributes like parent to data attributes like data-parent
273
- html_options = options.dup
274
- html_options[:class] ||= ''
275
- html_options[:class] = "#{html_options[:class]} #{body_class}".strip
276
268
  @dom ||= html {
277
269
  send(keyword, html_options) {
278
- # TODO consider supporting the idea of dynamic CSS building on close of shell that adds only as much CSS as needed for widgets that were mentioned
279
- # style(class: 'common-style') {
280
- # style_dom_css
281
- # }
282
- # [LayoutProxy, WidgetProxy].map(&:descendants).reduce(:+).each do |style_class|
283
- # if style_class.constants.include?('STYLE')
284
- # style(class: "#{style_class.name.split(':').last.underscore.gsub('_', '-').sub(/-proxy$/, '')}-style") {
285
- # style_class::STYLE
286
- # }
287
- # end
288
- # end
270
+ args.first if args.first.is_a?(String)
289
271
  }
290
272
  }.to_s
291
273
  end
292
274
 
275
+ def html_options
276
+ body_class = ([name, element_id] + css_classes.to_a).join(' ')
277
+ html_options = options.dup
278
+ GLIMMER_ATTRIBUTES.each do |attribute|
279
+ next unless html_options.include?(attribute)
280
+ data_normalized_attribute = attribute.split('_').join('-')
281
+ html_options["data-#{data_normalized_attribute}"] = html_options.delete(attribute)
282
+ end
283
+ html_options[:class] ||= ''
284
+ html_options[:class] = "#{html_options[:class]} #{body_class}".strip
285
+ html_options
286
+ end
287
+
293
288
  def content(&block)
294
289
  Glimmer::DSL::Engine.add_content(self, Glimmer::DSL::Web::ElementExpression.new, keyword, &block)
295
290
  end
@@ -745,58 +740,70 @@ module Glimmer
745
740
  listeners[listener_event.to_s] ||= []
746
741
  end
747
742
 
748
- def can_handle_observation_request?(observation_request)
743
+ def can_handle_observation_request?(keyword)
749
744
  # TODO sort this out for Opal
750
- observation_request = observation_request.to_s
751
- if observation_request.start_with?('on_swt_')
752
- constant_name = observation_request.sub(/^on_swt_/, '')
753
- SWTProxy.has_constant?(constant_name)
754
- elsif observation_request.start_with?('on_')
755
- # event = observation_request.sub(/^on_/, '')
756
- # can_add_listener?(event) || can_handle_drag_observation_request?(observation_request) || can_handle_drop_observation_request?(observation_request)
757
- true # TODO filter by valid listeners only in the future
758
- end
745
+ keyword = keyword.to_s
746
+ keyword.start_with?('on_')
747
+ # if keyword.start_with?('on_swt_')
748
+ # constant_name = keyword.sub(/^on_swt_/, '')
749
+ # SWTProxy.has_constant?(constant_name)
750
+ # elsif keyword.start_with?('on_')
751
+ # # event = keyword.sub(/^on_/, '')
752
+ # # can_add_listener?(event) || can_handle_drag_observation_request?(keyword) || can_handle_drop_observation_request?(keyword)
753
+ # true # TODO filter by valid listeners only in the future
754
+ # end
759
755
  end
760
756
 
761
757
  def handle_observation_request(keyword, original_event_listener)
762
- case keyword
763
- when 'on_widget_removed'
764
- listeners_for(keyword.sub(/^on_/, '')) << original_event_listener.to_proc
765
- else
758
+ # case keyword
759
+ # when 'on_widget_removed'
760
+ # listeners_for(keyword.sub(/^on_/, '')) << original_event_listener.to_proc
761
+ # else
766
762
  handle_javascript_observation_request(keyword, original_event_listener)
767
- end
763
+ # end
768
764
  end
769
765
 
770
766
  def handle_javascript_observation_request(keyword, original_event_listener)
771
- return unless effective_observation_request_to_event_mapping.keys.include?(keyword)
772
- event = nil
773
- delegate = nil
774
- effective_observation_request_to_event_mapping[keyword].to_collection.each do |mapping|
775
- observation_requests[keyword] ||= Set.new
776
- observation_requests[keyword] << original_event_listener
777
- event = mapping[:event]
778
- event_handler = mapping[:event_handler]
779
- event_element_css_selector = mapping[:event_element_css_selector]
780
- potential_event_listener = event_handler&.call(original_event_listener)
781
- event_listener = potential_event_listener || original_event_listener
782
- async_event_listener = proc do |event|
783
- # TODO look into the issue with using async::task.new here. maybe put it in event listener (like not being able to call preventDefault or return false successfully )
784
- # maybe consider pushing inside the widget classes instead where needed only or implement universal doit support correctly to bypass this issue
785
- # Async::Task.new do
786
- @@widget_handling_listener = self
787
- # TODO also make sure to disable all widgets for suspension
788
- event_listener.call(event) unless dialog_ancestor&.event_handling_suspended?
789
- @widget_handling_listener = nil
790
- # end
791
- end
792
- the_listener_dom_element = event_element_css_selector ? Element[event_element_css_selector] : listener_dom_element
793
- unless the_listener_dom_element.empty?
794
- the_listener_dom_element.on(event, &async_event_listener)
795
- # TODO ensure uniqueness of insertion (perhaps adding equals/hash method to event listener proxy)
796
-
797
- event_listener_proxies << EventListenerProxy.new(element_proxy: self, selector: selector, dom_element: the_listener_dom_element, event: event, listener: async_event_listener, original_event_listener: original_event_listener)
798
- end
799
- end
767
+ listener = ListenerProxy.new(
768
+ element_proxy: self,
769
+ selector: selector,
770
+ dom_element: dom_element,
771
+ event: keyword.sub(/^on_/, ''),
772
+ listener: original_event_listener,
773
+ original_event_listener: original_event_listener
774
+ )
775
+ listener.register
776
+ listeners_for(keyword) << listener
777
+ listener
778
+ # return unless effective_observation_request_to_event_mapping.keys.include?(keyword)
779
+ # event = nil
780
+ # delegate = nil
781
+ # effective_observation_request_to_event_mapping[keyword].to_collection.each do |mapping|
782
+ # observation_requests[keyword] ||= Set.new
783
+ # observation_requests[keyword] << original_event_listener
784
+ # event = mapping[:event]
785
+ # event_handler = mapping[:event_handler]
786
+ # event_element_css_selector = mapping[:event_element_css_selector]
787
+ # potential_event_listener = event_handler&.call(original_event_listener)
788
+ # event_listener = potential_event_listener || original_event_listener
789
+ # async_event_listener = proc do |event|
790
+ ## TODO look into the issue with using async::task.new here. maybe put it in event listener (like not being able to call preventDefault or return false successfully )
791
+ ## maybe consider pushing inside the widget classes instead where needed only or implement universal doit support correctly to bypass this issue
792
+ ## Async::Task.new do
793
+ # @@widget_handling_listener = self
794
+ ## TODO also make sure to disable all widgets for suspension
795
+ # event_listener.call(event) unless dialog_ancestor&.event_handling_suspended?
796
+ # @widget_handling_listener = nil
797
+ ## end
798
+ # end
799
+ # the_listener_dom_element = event_element_css_selector ? Element[event_element_css_selector] : listener_dom_element
800
+ # unless the_listener_dom_element.empty?
801
+ # the_listener_dom_element.on(event, &async_event_listener)
802
+ ## TODO ensure uniqueness of insertion (perhaps adding equals/hash method to event listener proxy)
803
+ #
804
+ # event_listener_proxies << EventListenerProxy.new(element_proxy: self, selector: selector, dom_element: the_listener_dom_element, event: event, listener: async_event_listener, original_event_listener: original_event_listener)
805
+ # end
806
+ # end
800
807
  end
801
808
 
802
809
  def remove_event_listener_proxies
@@ -819,14 +826,43 @@ module Glimmer
819
826
  super(attribute_name, *args) # PropertyOwner
820
827
  end
821
828
 
822
- def method_missing(method, *args, &block)
823
- if method.to_s.start_with?('on_')
824
- handle_observation_request(method, block)
829
+ def respond_to_missing?(method_name, include_private = false)
830
+ property_name = property_name_for(method_name)
831
+ super(method_name, include_private) ||
832
+ (dom_element && dom_element.length > 0 && Native.call(dom_element, '0').respond_to?(method_name.to_s.camelcase, include_private)) ||
833
+ dom_element.respond_to?(method_name, include_private) ||
834
+ (!dom_element.prop(property_name).nil? && !dom_element.prop(property_name).is_a?(Proc)) ||
835
+ method_name.to_s.start_with?('on_')
836
+ end
837
+
838
+ def method_missing(method_name, *args, &block)
839
+ property_name = property_name_for(method_name)
840
+ if method_name.to_s.start_with?('on_')
841
+ handle_observation_request(method_name, block)
842
+ elsif dom_element.respond_to?(method_name)
843
+ dom_element.send(method_name, *args, &block)
844
+ elsif !dom_element.prop(property_name).nil? && !dom_element.prop(property_name).is_a?(Proc)
845
+ if method_name.end_with?('=')
846
+ dom_element.prop(property_name, *args)
847
+ else
848
+ dom_element.prop(property_name)
849
+ end
850
+ elsif dom_element && dom_element.length > 0
851
+ begin
852
+ js_args = block.nil? ? args : (args + [block])
853
+ Native.call(dom_element, '0').method_missing(method_name.to_s.camelcase, *js_args)
854
+ rescue Exception => e
855
+ super(method_name, *args, &block)
856
+ end
825
857
  else
826
- super(method, *args, &block)
858
+ super(method_name, *args, &block)
827
859
  end
828
860
  end
829
861
 
862
+ def property_name_for(method_name)
863
+ method_name.end_with?('=') ? method_name.to_s[0...-1].camelcase : method_name.to_s.camelcase
864
+ end
865
+
830
866
  def swt_widget
831
867
  # only added for compatibility/adaptibility with Glimmer DSL for SWT
832
868
  self
@@ -0,0 +1,39 @@
1
+ module Glimmer
2
+ module Web
3
+ class ListenerProxy
4
+ attr_reader :element_proxy, :event, :dom_element, :selector, :listener, :original_event_listener
5
+
6
+ def initialize(element_proxy:, event:, dom_element:, selector:, listener:)
7
+ @element_proxy = element_proxy
8
+ @event = event
9
+ @dom_element = dom_element
10
+ @selector = selector
11
+ @listener = listener
12
+ @js_listener = lambda do |event|
13
+ event.prevent
14
+ event.prevent_default
15
+ event.stop_propagation
16
+ event.stop_immediate_propagation
17
+ # TODO wrap event with a Ruby Event object before passing to listener
18
+ listener.call(event)
19
+ false
20
+ end
21
+ @original_event_listener = original_event_listener
22
+ end
23
+
24
+ def register
25
+ @dom_element.on(@event, &@js_listener)
26
+ end
27
+ alias observe register
28
+ alias reregister register
29
+
30
+ def unregister
31
+ # TODO contribute fix to opal to allow passing observer with & to off with selector not specified as nil
32
+ @dom_element.off(@event, @js_listener)
33
+ @element_proxy.listeners_for(@event).delete(self)
34
+ end
35
+ alias unobserve unregister
36
+ alias deregister unregister
37
+ end
38
+ end
39
+ end
data/lib/glimmer/web.rb CHANGED
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2020-2022 Andy Maleh
1
+ # Copyright (c) 2023 Andy Maleh
2
2
  #
3
3
  # Permission is hereby granted, free of charge, to any person obtaining
4
4
  # a copy of this software and associated documentation files (the
@@ -0,0 +1,99 @@
1
+ # Copyright (c) 2023 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
+ include Glimmer
25
+
26
+ Document.ready? do
27
+ div {
28
+ h1('Contact Form')
29
+ form {
30
+ div(class: 'field-row') {
31
+ label('Name: ', for: 'name-field')
32
+ @name_input = input(id: 'name-field', class: 'field', type: 'text', required: true)
33
+ }
34
+ div(class: 'field-row') {
35
+ label('Email: ', for: 'email-field')
36
+ @email_input = input(id: 'email-field', class: 'field', type: 'email', required: true)
37
+ }
38
+ button('Add Contact', class: 'submit-button') {
39
+ on_click do
40
+ if ([@name_input, @email_input].all? {|input| input.check_validity })
41
+ @table.content {
42
+ tr {
43
+ td { @name_input.value }
44
+ td { @email_input.value }
45
+ }
46
+ }
47
+ @email_input.value = @name_input.value = ''
48
+ else
49
+ error_messages = []
50
+ error_messages << "Name is not valid! Make sure it is filled." if !@name_input.check_validity
51
+ error_messages << "Email is not valid! Make sure it is filled and has a valid format." if !@email_input.check_validity
52
+ $$.alert(error_messages.join("\n"))
53
+ end
54
+ end
55
+ }
56
+ }
57
+ h1('Contacts Table')
58
+ @table = table {
59
+ tr {
60
+ th('Name')
61
+ th('Email')
62
+ }
63
+ tr {
64
+ td('John Doe')
65
+ td('johndoe@example.com')
66
+ }
67
+ tr {
68
+ td('Jane Doe')
69
+ td('janedoe@example.com')
70
+ }
71
+ }
72
+
73
+ # CSS Styles
74
+ style {
75
+ <<~CSS
76
+ .field-row {
77
+ margin: 10px 5px;
78
+ }
79
+ .field {
80
+ margin-left: 5px;
81
+ }
82
+ .submit-button {
83
+ display: block;
84
+ margin: 10px 5px;
85
+ }
86
+ table {
87
+ border:1px solid grey;
88
+ border-spacing: 0;
89
+ }
90
+ table tr td, table tr th {
91
+ padding: 5px;
92
+ }
93
+ table tr:nth-child(even) {
94
+ background: #ccc;
95
+ }
96
+ CSS
97
+ }
98
+ }.render
99
+ end
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2020-2022 Andy Maleh
1
+ # Copyright (c) 2023 Andy Maleh
2
2
  #
3
3
  # Permission is hereby granted, free of charge, to any person obtaining
4
4
  # a copy of this software and associated documentation files (the
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2020-2022 Andy Maleh
1
+ # Copyright (c) 2023 Andy Maleh
2
2
  #
3
3
  # Permission is hereby granted, free of charge, to any person obtaining
4
4
  # a copy of this software and associated documentation files (the
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: glimmer-dsl-web
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.0.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andy Maleh
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-12-27 00:00:00.000000000 Z
11
+ date: 2023-12-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: glimmer
@@ -236,10 +236,10 @@ dependencies:
236
236
  - - "~>"
237
237
  - !ruby/object:Gem::Version
238
238
  version: 0.4.4
239
- description: Glimmer DSL for Web (Ruby in the Browser Web GUI Library) - Enables frontend
240
- GUI development with Ruby by adopting a DSL that follows web-like HTML syntax, enabling
241
- the transfer of HTML/CSS/JS skills to Ruby frontend development. This library relies
242
- on Opal Ruby.
239
+ description: Glimmer DSL for Web (Ruby in the Browser Web GUI Frontend Library) -
240
+ Enables frontend GUI development with Ruby by adopting a DSL that follows web-like
241
+ HTML syntax, enabling the transfer of HTML/CSS/JS skills to Ruby frontend development.
242
+ This library relies on Opal Ruby.
243
243
  email: andy.am@gmail.com
244
244
  executables: []
245
245
  extensions: []
@@ -258,6 +258,7 @@ files:
258
258
  - lib/glimmer-dsl-web/ext/class.rb
259
259
  - lib/glimmer-dsl-web/ext/date.rb
260
260
  - lib/glimmer-dsl-web/ext/exception.rb
261
+ - lib/glimmer-dsl-web/samples/hello/hello_button.rb
261
262
  - lib/glimmer-dsl-web/samples/hello/hello_world.rb
262
263
  - lib/glimmer-dsl-web/vendor/jquery.js
263
264
  - lib/glimmer/config/opal_logger.rb
@@ -265,9 +266,12 @@ files:
265
266
  - lib/glimmer/data_binding/observable_element.rb
266
267
  - lib/glimmer/dsl/web/dsl.rb
267
268
  - lib/glimmer/dsl/web/element_expression.rb
269
+ - lib/glimmer/dsl/web/listener_expression.rb
270
+ - lib/glimmer/dsl/web/property_expression.rb
268
271
  - lib/glimmer/util/proc_tracker.rb
269
272
  - lib/glimmer/web.rb
270
273
  - lib/glimmer/web/element_proxy.rb
274
+ - lib/glimmer/web/listener_proxy.rb
271
275
  - lib/glimmer/web/property_owner.rb
272
276
  homepage: http://github.com/AndyObtiva/glimmer-dsl-web
273
277
  licenses:
@@ -291,5 +295,5 @@ requirements: []
291
295
  rubygems_version: 3.5.3
292
296
  signing_key:
293
297
  specification_version: 4
294
- summary: Glimmer DSL for Web
298
+ summary: Glimmer DSL for Web (Ruby in the Browser Web GUI Frontend Library)
295
299
  test_files: []