glimmer-dsl-web 0.0.2 → 0.0.4

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