glimmer-dsl-web 0.0.2 → 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0da388a6300e3a65d87f0187665ef8e69b5d206d5e4b2f41a87c3bf02c75730f
4
- data.tar.gz: 576c7d4fd51df187e76bd3024418011c081e97f603bb773aaa52360c2bc3a65b
3
+ metadata.gz: 10f466c47fb515cb9cd28f7eb49920c1cb52ba68126ccd85a94c321f807b43a6
4
+ data.tar.gz: ac50a3b1d5f3d96b4b6770d5a725f900840aca77da0ccef7c7773a31fa43c4df
5
5
  SHA512:
6
- metadata.gz: 575f405b644a48498aa5bac901d7760a6a635e8d3a11668bb5beeee98e0023f1c92861f0393eca475d293fe411617881258f4decb5e6124288b5b74d3261b810
7
- data.tar.gz: 882a868936f8bca1794f5b302ed1b28d8adb6b330606008c2738822a4517d1bf9ba3ffa13b36abcb2a8f37a6349397c63e9619ce3562e9c5f93aa59b322f92d5
6
+ metadata.gz: e6fc2635c32e91c06843bb230b26e600eefda14c9e2b4b9ca22f4dcb26ebaf5e341dee90074fcedd356a506b2c9f169a5ff51104b3afba37bad6b80b9c570cad
7
+ data.tar.gz: cccb3243bd7351b40564aacabf1c866271b5d6a53ddd0ebda4441b8b90274370469bfa3ba3f38b68d16827850df94fa57dfe7408e2e5091097a4120c4577cc6d
data/CHANGELOG.md CHANGED
@@ -1,11 +1,19 @@
1
1
  # Change Log
2
2
 
3
+ ## 0.0.3
4
+
5
+ - Set Glimmer specific element attributes (e.g. `parent`) as data attributes on generated HTML elements
6
+ - Support setting text content by passing as first argument to an element as an alternative to block return value
7
+ - Proxy method/attribute invocations on an element to its HTML element (e.g. `input_element.check_validity` proxies to `checkValidity` JS function)
8
+ - Support JS listeners like `onclick` by nesting an `on_someeventname` block under an element (e.g. `on_click { ... }`)
9
+ - New Hello, Button! Sample: `require 'glimmer-dsl-web/samples/hello/hello_button'`
10
+
3
11
  ## 0.0.2
4
12
 
5
13
  - Rename element `:root` option to `:parent` option
6
14
  - Set `body` as parent by default if `:parent` option is not specified for a root element
7
- - `glimmer-dsl-web/samples/hello/hello_world.rb`
8
15
  - Set `class` instead of `id` on generated HTML elements to identify them (e.g. `element-2`).
16
+ - New Hello, World! Sample: `require 'glimmer-dsl-web/samples/hello/hello_world'`
9
17
 
10
18
  ## 0.0.1
11
19
 
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # [<img src="https://raw.githubusercontent.com/AndyObtiva/glimmer/master/images/glimmer-logo-hi-res.png" height=85 />](https://github.com/AndyObtiva/glimmer) Glimmer DSL for Web 0.0.2 (Early Alpha)
1
+ # [<img src="https://raw.githubusercontent.com/AndyObtiva/glimmer/master/images/glimmer-logo-hi-res.png" height=85 />](https://github.com/AndyObtiva/glimmer) Glimmer DSL for Web 0.0.3 (Early Alpha)
2
2
  ## Ruby in the Browser Web GUI Library
3
3
  [![Gem Version](https://badge.fury.io/rb/glimmer-dsl-web.svg)](http://badge.fury.io/rb/glimmer-dsl-web)
4
4
  [![Join the chat at https://gitter.im/AndyObtiva/glimmer](https://badges.gitter.im/AndyObtiva/glimmer.svg)](https://gitter.im/AndyObtiva/glimmer?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
@@ -29,12 +29,14 @@ require 'glimmer-dsl-web'
29
29
 
30
30
  include Glimmer
31
31
 
32
- # This will hook into element #app-container and then build HTML inside it using Ruby DSL code
33
- div(parent: '#app-container') {
34
- label(class: 'greeting') {
35
- 'Hello, World!'
36
- }
37
- }
32
+ Document.ready? do
33
+ # This will hook into element #app-container and then build HTML inside it using Ruby DSL code
34
+ div(parent: '#app-container') {
35
+ label(class: 'greeting') {
36
+ 'Hello, World!'
37
+ }
38
+ }.render
39
+ end
38
40
  ```
39
41
 
40
42
  That produces:
@@ -51,6 +53,8 @@ That produces:
51
53
  ...
52
54
  ```
53
55
 
56
+ ![setup is working](/images/glimmer-dsl-web-setup-example-working.png)
57
+
54
58
  **Hello, World! Sample**
55
59
 
56
60
  Glimmer GUI code:
@@ -75,8 +79,178 @@ That produces the following under `<body></body>`:
75
79
  </div>
76
80
  ```
77
81
 
82
+ ![setup is working](/images/glimmer-dsl-web-setup-example-working.png)
83
+
78
84
  **Hello, Button! Sample**
79
85
 
86
+ Glimmer GUI code:
87
+
88
+ ```ruby
89
+ require 'glimmer-dsl-web'
90
+
91
+ include Glimmer
92
+
93
+ Document.ready? do
94
+ div {
95
+ h1('Contact Form')
96
+ form {
97
+ div(class: 'field-row') {
98
+ label('Name: ', for: 'name-field')
99
+ @name_input = input(id: 'name-field', class: 'field', type: 'text', required: true)
100
+ }
101
+ div(class: 'field-row') {
102
+ label('Email: ', for: 'email-field')
103
+ @email_input = input(id: 'email-field', class: 'field', type: 'email', required: true)
104
+ }
105
+ @add_button = button('Add Contact', class: 'submit-button') {
106
+ on_click do
107
+ if ([@name_input, @email_input].all? {|input| input.check_validity })
108
+ @table.content {
109
+ tr {
110
+ td { @name_input.value }
111
+ td { @email_input.value }
112
+ }
113
+ }
114
+ @email_input.value = @name_input.value = ''
115
+ else
116
+ error_messages = []
117
+ error_messages << "Name is not valid! Make sure it is filled." if !@name_input.check_validity
118
+ error_messages << "Email is not valid! Make sure it is filled and has a valid format." if !@email_input.check_validity
119
+ $$.alert(error_messages.join("\n"))
120
+ end
121
+ end
122
+ }
123
+ }
124
+ h1('Contacts Table')
125
+ @table = table {
126
+ tr {
127
+ th('Name')
128
+ th('Email')
129
+ }
130
+ tr {
131
+ td('John Doe')
132
+ td('johndoe@example.com')
133
+ }
134
+ tr {
135
+ td('Jane Doe')
136
+ td('janedoe@example.com')
137
+ }
138
+ }
139
+
140
+ # CSS Styles
141
+ style {
142
+ <<~CSS
143
+ .field-row {
144
+ margin: 10px 5px;
145
+ }
146
+ .field {
147
+ margin-left: 5px;
148
+ }
149
+ .submit-button {
150
+ display: block;
151
+ margin: 10px 5px;
152
+ }
153
+ table {
154
+ border:1px solid grey;
155
+ border-spacing: 0;
156
+ }
157
+ table tr td, table tr th {
158
+ padding: 5px;
159
+ }
160
+ table tr:nth-child(even) {
161
+ background: #ccc;
162
+ }
163
+ CSS
164
+ }
165
+ }.render
166
+ end
167
+ ```
168
+
169
+ That produces the following under `<body></body>`:
170
+
171
+ ```html
172
+ <div data-parent="body" class="element element-1">
173
+ <h1 class="element element-2">Contact Form</h1>
174
+ <form class="element element-3">
175
+ <div class="field-row element element-4">
176
+ <label for="name-field" class="element element-5">Name: </label>
177
+ <input id="name-field" class="field element element-6" type="text" required="true">
178
+ </div>
179
+ <div class="field-row element element-7">
180
+ <label for="email-field" class="element element-8">Email: </label>
181
+ <input id="email-field" class="field element element-9" type="email" required="true">
182
+ </div>
183
+ <button class="submit-button element element-10">Add Contact</button>
184
+ </form>
185
+ <h1 class="element element-11">Contacts Table</h1>
186
+ <table class="element element-12">
187
+ <tr class="element element-13">
188
+ <th class="element element-14">Name</th>
189
+ <th class="element element-15">Email</th>
190
+ </tr>
191
+ <tr class="element element-16">
192
+ <td class="element element-17">John Doe</td>
193
+ <td class="element element-18">johndoe@example.com</td>
194
+ </tr>
195
+ <tr class="element element-19">
196
+ <td class="element element-20">Jane Doe</td>
197
+ <td class="element element-21">janedoe@example.com</td>
198
+ </tr>
199
+ </table>
200
+ <style class="element element-22">.field-row {
201
+ margin: 10px 5px;
202
+ }
203
+ .field {
204
+ margin-left: 5px;
205
+ }
206
+ .submit-button {
207
+ display: block;
208
+ margin: 10px 5px;
209
+ }
210
+ table {
211
+ border:1px solid grey;
212
+ border-spacing: 0;
213
+ }
214
+ table tr td, table tr th {
215
+ padding: 5px;
216
+ }
217
+ table tr:nth-child(even) {
218
+ background: #ccc;
219
+ }
220
+ </style>
221
+ </div>
222
+ ```
223
+
224
+ Screenshots:
225
+
226
+ ---
227
+
228
+ ***Hello, Button!***
229
+
230
+ ![Hello, Button!](/images/glimmer-dsl-web-samples-hello-hello-button.png)
231
+
232
+ ---
233
+
234
+ ***Hello, Button! Submitted Invalid Data***
235
+
236
+ ![Hello, Button! Invalid Data](/images/glimmer-dsl-web-samples-hello-hello-button-invalid-data.png)
237
+
238
+ ---
239
+
240
+ ***Hello, Button! Filled Valid Name and Email***
241
+
242
+ ![Hello, Button! Filled](/images/glimmer-dsl-web-samples-hello-hello-button-filled-name-and-email.png)
243
+
244
+ ---
245
+
246
+ ***Hello, Button! Added Contact***
247
+
248
+ ![Hello, Button! Added Contact](/images/glimmer-dsl-web-samples-hello-hello-button-added-contact.png)
249
+
250
+ ---
251
+
252
+ **Button Counter Sample**
253
+
80
254
  **UPCOMING (NOT RELEASED OR SUPPORTED YET)**
81
255
 
82
256
  Glimmer GUI code demonstrating MVC + Glimmer Web Components (Views) + Data-Binding:
@@ -106,7 +280,7 @@ class HelloButton
106
280
  markup {
107
281
  # This will hook into element #app-container and then build HTML inside it using Ruby DSL code
108
282
  div(root_css_selector) {
109
- text 'Hello, Button!'
283
+ text 'Button Counter'
110
284
 
111
285
  button {
112
286
  # Unidirectional Data-Binding indicating that on every change to @counter.count, the value
@@ -175,6 +349,7 @@ Learn more about the differences between various [Glimmer](https://github.com/An
175
349
  - [Hello Samples](#hello-samples)
176
350
  - [Hello, World!](#hello-world)
177
351
  - [Hello, Button!](#hello-button)
352
+ - [Button Counter](#button-counter)
178
353
  - [Glimmer Process](#glimmer-process)
179
354
  - [Help](#help)
180
355
  - [Issues](#issues)
@@ -221,7 +396,7 @@ gem 'opal', '1.4.1'
221
396
  gem 'opal-rails', '2.0.2'
222
397
  gem 'opal-async', '~> 1.4.0'
223
398
  gem 'opal-jquery', '~> 0.4.6'
224
- gem 'glimmer-dsl-web', '~> 0.0.2'
399
+ gem 'glimmer-dsl-web', '~> 0.0.3'
225
400
  gem 'glimmer-dsl-xml', '~> 1.3.1', require: false
226
401
  gem 'glimmer-dsl-css', '~> 1.2.1', require: false
227
402
  ```
@@ -298,12 +473,14 @@ require 'glimmer-dsl-web'
298
473
 
299
474
  include Glimmer
300
475
 
301
- # This will hook into element #app-container and then build HTML inside it using Ruby DSL code
302
- div(parent: '#app-container') {
303
- label(class: 'greeting') {
304
- 'Hello, World!'
305
- }
306
- }
476
+ Document.ready? do
477
+ # This will hook into element #app-container and then build HTML inside it using Ruby DSL code
478
+ div(parent: '#app-container') {
479
+ label(class: 'greeting') {
480
+ 'Hello, World!'
481
+ }
482
+ }.render
483
+ end
307
484
  ```
308
485
 
309
486
  That produces:
@@ -364,7 +541,7 @@ gem 'opal', '1.4.1'
364
541
  gem 'opal-rails', '2.0.2'
365
542
  gem 'opal-async', '~> 1.4.0'
366
543
  gem 'opal-jquery', '~> 0.4.6'
367
- gem 'glimmer-dsl-web', '~> 0.0.2'
544
+ gem 'glimmer-dsl-web', '~> 0.0.3'
368
545
  gem 'glimmer-dsl-xml', '~> 1.3.1', require: false
369
546
  gem 'glimmer-dsl-css', '~> 1.2.1', require: false
370
547
  ```
@@ -445,12 +622,14 @@ require 'glimmer-dsl-web'
445
622
 
446
623
  include Glimmer
447
624
 
448
- # This will hook into element #app-container and then build HTML inside it using Ruby DSL code
449
- div(parent: '#app-container') {
450
- label(class: 'greeting') {
451
- 'Hello, World!'
452
- }
453
- }
625
+ Document.ready? do
626
+ # This will hook into element #app-container and then build HTML inside it using Ruby DSL code
627
+ div(parent: '#app-container') {
628
+ label(class: 'greeting') {
629
+ 'Hello, World!'
630
+ }
631
+ }.render
632
+ end
454
633
  ```
455
634
 
456
635
  That produces:
@@ -526,6 +705,174 @@ That produces the following under `<body></body>`:
526
705
 
527
706
  #### Hello, Button!
528
707
 
708
+ Glimmer GUI code:
709
+
710
+ ```ruby
711
+ require 'glimmer-dsl-web'
712
+
713
+ include Glimmer
714
+
715
+ Document.ready? do
716
+ div {
717
+ h1('Contact Form')
718
+ form {
719
+ div(class: 'field-row') {
720
+ label('Name: ', for: 'name-field')
721
+ @name_input = input(id: 'name-field', class: 'field', type: 'text', required: true)
722
+ }
723
+ div(class: 'field-row') {
724
+ label('Email: ', for: 'email-field')
725
+ @email_input = input(id: 'email-field', class: 'field', type: 'email', required: true)
726
+ }
727
+ @add_button = button('Add Contact', class: 'submit-button') {
728
+ on_click do
729
+ if ([@name_input, @email_input].all? {|input| input.check_validity })
730
+ @table.content {
731
+ tr {
732
+ td { @name_input.value }
733
+ td { @email_input.value }
734
+ }
735
+ }
736
+ @email_input.value = @name_input.value = ''
737
+ else
738
+ error_messages = []
739
+ error_messages << "Name is not valid! Make sure it is filled." if !@name_input.check_validity
740
+ error_messages << "Email is not valid! Make sure it is filled and has a valid format." if !@email_input.check_validity
741
+ $$.alert(error_messages.join("\n"))
742
+ end
743
+ end
744
+ }
745
+ }
746
+ h1('Contacts Table')
747
+ @table = table {
748
+ tr {
749
+ th('Name')
750
+ th('Email')
751
+ }
752
+ tr {
753
+ td('John Doe')
754
+ td('johndoe@example.com')
755
+ }
756
+ tr {
757
+ td('Jane Doe')
758
+ td('janedoe@example.com')
759
+ }
760
+ }
761
+
762
+ # CSS Styles
763
+ style {
764
+ <<~CSS
765
+ .field-row {
766
+ margin: 10px 5px;
767
+ }
768
+ .field {
769
+ margin-left: 5px;
770
+ }
771
+ .submit-button {
772
+ display: block;
773
+ margin: 10px 5px;
774
+ }
775
+ table {
776
+ border:1px solid grey;
777
+ border-spacing: 0;
778
+ }
779
+ table tr td, table tr th {
780
+ padding: 5px;
781
+ }
782
+ table tr:nth-child(even) {
783
+ background: #ccc;
784
+ }
785
+ CSS
786
+ }
787
+ }.render
788
+ end
789
+ ```
790
+
791
+ That produces the following under `<body></body>`:
792
+
793
+ ```html
794
+ <div data-parent="body" class="element element-1">
795
+ <h1 class="element element-2">Contact Form</h1>
796
+ <form class="element element-3">
797
+ <div class="field-row element element-4">
798
+ <label for="name-field" class="element element-5">Name: </label>
799
+ <input id="name-field" class="field element element-6" type="text" required="true">
800
+ </div>
801
+ <div class="field-row element element-7">
802
+ <label for="email-field" class="element element-8">Email: </label>
803
+ <input id="email-field" class="field element element-9" type="email" required="true">
804
+ </div>
805
+ <button class="submit-button element element-10">Add Contact</button>
806
+ </form>
807
+ <h1 class="element element-11">Contacts Table</h1>
808
+ <table class="element element-12">
809
+ <tr class="element element-13">
810
+ <th class="element element-14">Name</th>
811
+ <th class="element element-15">Email</th>
812
+ </tr>
813
+ <tr class="element element-16">
814
+ <td class="element element-17">John Doe</td>
815
+ <td class="element element-18">johndoe@example.com</td>
816
+ </tr>
817
+ <tr class="element element-19">
818
+ <td class="element element-20">Jane Doe</td>
819
+ <td class="element element-21">janedoe@example.com</td>
820
+ </tr>
821
+ </table>
822
+ <style class="element element-22">.field-row {
823
+ margin: 10px 5px;
824
+ }
825
+ .field {
826
+ margin-left: 5px;
827
+ }
828
+ .submit-button {
829
+ display: block;
830
+ margin: 10px 5px;
831
+ }
832
+ table {
833
+ border:1px solid grey;
834
+ border-spacing: 0;
835
+ }
836
+ table tr td, table tr th {
837
+ padding: 5px;
838
+ }
839
+ table tr:nth-child(even) {
840
+ background: #ccc;
841
+ }
842
+ </style>
843
+ </div>
844
+ ```
845
+
846
+ Screenshots:
847
+
848
+ ---
849
+
850
+ ***Hello, Button!***
851
+
852
+ ![Hello, Button!](/images/glimmer-dsl-web-samples-hello-hello-button.png)
853
+
854
+ ---
855
+
856
+ ***Hello, Button! Submitted Invalid Data***
857
+
858
+ ![Hello, Button! Invalid Data](/images/glimmer-dsl-web-samples-hello-hello-button-invalid-data.png)
859
+
860
+ ---
861
+
862
+ ***Hello, Button! Filled Valid Name and Email***
863
+
864
+ ![Hello, Button! Filled](/images/glimmer-dsl-web-samples-hello-hello-button-filled-name-and-email.png)
865
+
866
+ ---
867
+
868
+ ***Hello, Button! Added Contact***
869
+
870
+ ![Hello, Button! Added Contact](/images/glimmer-dsl-web-samples-hello-hello-button-added-contact.png)
871
+
872
+ ---
873
+
874
+ #### Button Counter
875
+
529
876
  **UPCOMING (NOT RELEASED OR SUPPORTED YET)**
530
877
 
531
878
  Glimmer GUI code demonstrating MVC + Glimmer Web Components (Views) + Data-Binding:
@@ -555,7 +902,7 @@ class HelloButton
555
902
  markup {
556
903
  # This will hook into element #app-container and then build HTML inside it using Ruby DSL code
557
904
  div(root_css_selector) {
558
- text 'Hello, Button!'
905
+ text 'Button Counter'
559
906
 
560
907
  button {
561
908
  # Unidirectional Data-Binding indicating that on every change to @counter.count, the value
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.2
1
+ 0.0.3
@@ -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.2 ruby lib
5
+ # stub: glimmer-dsl-web 0.0.3 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.3".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"
14
+ s.date = "2023-12-28"
15
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
16
16
  s.email = "andy.am@gmail.com".freeze
17
17
  s.extra_rdoc_files = [
@@ -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,9 +38,11 @@ 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",
40
42
  "lib/glimmer/util/proc_tracker.rb",
41
43
  "lib/glimmer/web.rb",
42
44
  "lib/glimmer/web/element_proxy.rb",
45
+ "lib/glimmer/web/listener_proxy.rb",
43
46
  "lib/glimmer/web/property_owner.rb"
44
47
  ]
45
48
  s.homepage = "http://github.com/AndyObtiva/glimmer-dsl-web".freeze
@@ -1,13 +1,14 @@
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'
4
5
 
5
6
  module Glimmer
6
7
  module DSL
7
8
  module Web
8
9
  # TODO implement all those expressions
9
10
  # %w[
10
- # event_listener
11
+ # listener
11
12
  # data_binding
12
13
  # attribute
13
14
  # shine_data_binding
@@ -16,6 +17,7 @@ module Glimmer
16
17
  Engine.add_dynamic_expressions(
17
18
  Web,
18
19
  %w[
20
+ listener
19
21
  element
20
22
  ]
21
23
  )
@@ -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
@@ -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
 
@@ -198,6 +201,7 @@ module Glimmer
198
201
  if parent_selector
199
202
  Document.find(parent_selector)
200
203
  else
204
+ # TODO consider moving this to initializer
201
205
  options[:parent] ||= 'body'
202
206
  Document.find(options[:parent])
203
207
  end
@@ -240,7 +244,7 @@ module Glimmer
240
244
  end
241
245
 
242
246
  def add_text_content(text)
243
- dom_element.append(text)
247
+ dom_element.append(text.to_s)
244
248
  end
245
249
 
246
250
  def content_on_render_blocks
@@ -263,16 +267,12 @@ module Glimmer
263
267
  # TODO consider passing parent element instead and having table item include a table cell widget only for opal
264
268
  @dom = nil
265
269
  @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
270
+ # @dom = @parent.get_layout.dom(@dom) if @parent.respond_to?(:layout) && @parent.get_layout
267
271
  @dom
268
272
  end
269
273
 
270
274
  def dom
271
- body_class = ([name, element_id] + css_classes.to_a).join(' ')
272
275
  # 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
276
  @dom ||= html {
277
277
  send(keyword, html_options) {
278
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
@@ -286,10 +286,24 @@ module Glimmer
286
286
  # }
287
287
  # end
288
288
  # end
289
+ args.first if args.first.is_a?(String)
289
290
  }
290
291
  }.to_s
291
292
  end
292
293
 
294
+ def html_options
295
+ body_class = ([name, element_id] + css_classes.to_a).join(' ')
296
+ html_options = options.dup
297
+ GLIMMER_ATTRIBUTES.each do |attribute|
298
+ next unless html_options.include?(attribute)
299
+ data_normalized_attribute = attribute.split('_').join('-')
300
+ html_options["data-#{data_normalized_attribute}"] = html_options.delete(attribute)
301
+ end
302
+ html_options[:class] ||= ''
303
+ html_options[:class] = "#{html_options[:class]} #{body_class}".strip
304
+ html_options
305
+ end
306
+
293
307
  def content(&block)
294
308
  Glimmer::DSL::Engine.add_content(self, Glimmer::DSL::Web::ElementExpression.new, keyword, &block)
295
309
  end
@@ -745,58 +759,69 @@ module Glimmer
745
759
  listeners[listener_event.to_s] ||= []
746
760
  end
747
761
 
748
- def can_handle_observation_request?(observation_request)
762
+ def can_handle_observation_request?(keyword)
749
763
  # 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
764
+ keyword = keyword.to_s
765
+ keyword.start_with?('on_')
766
+ # if keyword.start_with?('on_swt_')
767
+ # constant_name = keyword.sub(/^on_swt_/, '')
768
+ # SWTProxy.has_constant?(constant_name)
769
+ # elsif keyword.start_with?('on_')
770
+ # # event = keyword.sub(/^on_/, '')
771
+ # # can_add_listener?(event) || can_handle_drag_observation_request?(keyword) || can_handle_drop_observation_request?(keyword)
772
+ # true # TODO filter by valid listeners only in the future
773
+ # end
759
774
  end
760
775
 
761
776
  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
777
+ # case keyword
778
+ # when 'on_widget_removed'
779
+ # listeners_for(keyword.sub(/^on_/, '')) << original_event_listener.to_proc
780
+ # else
766
781
  handle_javascript_observation_request(keyword, original_event_listener)
767
- end
782
+ # end
768
783
  end
769
784
 
770
785
  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
786
+ listener = ListenerProxy.new(
787
+ element_proxy: self,
788
+ selector: selector,
789
+ dom_element: dom_element,
790
+ event: keyword.sub(/^on_/, ''),
791
+ listener: original_event_listener,
792
+ original_event_listener: original_event_listener
793
+ )
794
+ listener.register
795
+ listener
796
+ # return unless effective_observation_request_to_event_mapping.keys.include?(keyword)
797
+ # event = nil
798
+ # delegate = nil
799
+ # effective_observation_request_to_event_mapping[keyword].to_collection.each do |mapping|
800
+ # observation_requests[keyword] ||= Set.new
801
+ # observation_requests[keyword] << original_event_listener
802
+ # event = mapping[:event]
803
+ # event_handler = mapping[:event_handler]
804
+ # event_element_css_selector = mapping[:event_element_css_selector]
805
+ # potential_event_listener = event_handler&.call(original_event_listener)
806
+ # event_listener = potential_event_listener || original_event_listener
807
+ # async_event_listener = proc do |event|
808
+ ## 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 )
809
+ ## maybe consider pushing inside the widget classes instead where needed only or implement universal doit support correctly to bypass this issue
810
+ ## Async::Task.new do
811
+ # @@widget_handling_listener = self
812
+ ## TODO also make sure to disable all widgets for suspension
813
+ # event_listener.call(event) unless dialog_ancestor&.event_handling_suspended?
814
+ # @widget_handling_listener = nil
815
+ ## end
816
+ # end
817
+ # the_listener_dom_element = event_element_css_selector ? Element[event_element_css_selector] : listener_dom_element
818
+ # unless the_listener_dom_element.empty?
819
+ # the_listener_dom_element.on(event, &async_event_listener)
820
+ ## TODO ensure uniqueness of insertion (perhaps adding equals/hash method to event listener proxy)
821
+ #
822
+ # 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)
823
+ # end
824
+ # end
800
825
  end
801
826
 
802
827
  def remove_event_listener_proxies
@@ -819,11 +844,27 @@ module Glimmer
819
844
  super(attribute_name, *args) # PropertyOwner
820
845
  end
821
846
 
822
- def method_missing(method, *args, &block)
823
- if method.to_s.start_with?('on_')
824
- handle_observation_request(method, block)
847
+ def respond_to_missing?(method_name, include_private = false)
848
+ super(method_name, include_private) ||
849
+ (dom_element && dom_element.length > 0 && Native.call(dom_element, '0').respond_to?(method_name, include_private)) ||
850
+ dom_element.respond_to?(method_name, include_private) ||
851
+ method_name.to_s.start_with?('on_')
852
+ end
853
+
854
+ def method_missing(method_name, *args, &block)
855
+ if method_name.to_s.start_with?('on_')
856
+ handle_observation_request(method_name, block)
857
+ elsif dom_element.respond_to?(method_name)
858
+ dom_element.send(method_name, *args, &block)
859
+ elsif dom_element && dom_element.length > 0
860
+ begin
861
+ js_args = block.nil? ? args : (args + [block])
862
+ Native.call(dom_element, '0').method_missing(method_name.to_s.camelcase, *js_args)
863
+ rescue Exception
864
+ super(method_name, *args, &block)
865
+ end
825
866
  else
826
- super(method, *args, &block)
867
+ super(method_name, *args, &block)
827
868
  end
828
869
  end
829
870
 
@@ -0,0 +1,37 @@
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
+ @dom_element.off(@event, &@js_listener)
32
+ end
33
+ alias unobserve unregister
34
+ alias deregister unregister
35
+ end
36
+ end
37
+ 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
+ @add_button = 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.3
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-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: glimmer
@@ -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,11 @@ 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
268
270
  - lib/glimmer/util/proc_tracker.rb
269
271
  - lib/glimmer/web.rb
270
272
  - lib/glimmer/web/element_proxy.rb
273
+ - lib/glimmer/web/listener_proxy.rb
271
274
  - lib/glimmer/web/property_owner.rb
272
275
  homepage: http://github.com/AndyObtiva/glimmer-dsl-web
273
276
  licenses: