glimmer-dsl-web 0.0.2 → 0.0.3

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