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 +4 -4
- data/CHANGELOG.md +9 -1
- data/README.md +370 -23
- data/VERSION +1 -1
- data/glimmer-dsl-web.gemspec +6 -3
- data/lib/glimmer/dsl/web/dsl.rb +3 -1
- data/lib/glimmer/dsl/web/element_expression.rb +2 -2
- data/lib/glimmer/dsl/web/listener_expression.rb +19 -0
- data/lib/glimmer/util/proc_tracker.rb +1 -1
- data/lib/glimmer/web/element_proxy.rb +98 -57
- data/lib/glimmer/web/listener_proxy.rb +37 -0
- data/lib/glimmer/web.rb +1 -1
- data/lib/glimmer-dsl-web/samples/hello/hello_button.rb +99 -0
- data/lib/glimmer-dsl-web/samples/hello/hello_world.rb +1 -1
- data/lib/glimmer-dsl-web.rb +1 -1
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 10f466c47fb515cb9cd28f7eb49920c1cb52ba68126ccd85a94c321f807b43a6
|
4
|
+
data.tar.gz: ac50a3b1d5f3d96b4b6770d5a725f900840aca77da0ccef7c7773a31fa43c4df
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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.
|
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
|
[](http://badge.fury.io/rb/glimmer-dsl-web)
|
4
4
|
[](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
|
-
|
33
|
-
|
34
|
-
|
35
|
-
'
|
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
|
+

|
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
|
+

|
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
|
+

|
231
|
+
|
232
|
+
---
|
233
|
+
|
234
|
+
***Hello, Button! Submitted Invalid Data***
|
235
|
+
|
236
|
+

|
237
|
+
|
238
|
+
---
|
239
|
+
|
240
|
+
***Hello, Button! Filled Valid Name and Email***
|
241
|
+
|
242
|
+

|
243
|
+
|
244
|
+
---
|
245
|
+
|
246
|
+
***Hello, Button! Added Contact***
|
247
|
+
|
248
|
+

|
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 '
|
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.
|
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
|
-
|
302
|
-
|
303
|
-
|
304
|
-
'
|
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.
|
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
|
-
|
449
|
-
|
450
|
-
|
451
|
-
'
|
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
|
+

|
853
|
+
|
854
|
+
---
|
855
|
+
|
856
|
+
***Hello, Button! Submitted Invalid Data***
|
857
|
+
|
858
|
+

|
859
|
+
|
860
|
+
---
|
861
|
+
|
862
|
+
***Hello, Button! Filled Valid Name and Email***
|
863
|
+
|
864
|
+

|
865
|
+
|
866
|
+
---
|
867
|
+
|
868
|
+
***Hello, Button! Added Contact***
|
869
|
+
|
870
|
+

|
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 '
|
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.
|
1
|
+
0.0.3
|
data/glimmer-dsl-web.gemspec
CHANGED
@@ -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.
|
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.
|
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-
|
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
|
data/lib/glimmer/dsl/web/dsl.rb
CHANGED
@@ -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
|
-
#
|
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
|
-
|
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)
|
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
|
-
|
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?(
|
762
|
+
def can_handle_observation_request?(keyword)
|
749
763
|
# TODO sort this out for Opal
|
750
|
-
|
751
|
-
|
752
|
-
|
753
|
-
|
754
|
-
|
755
|
-
#
|
756
|
-
#
|
757
|
-
|
758
|
-
|
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
|
-
|
763
|
-
|
764
|
-
|
765
|
-
|
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
|
-
|
782
|
+
# end
|
768
783
|
end
|
769
784
|
|
770
785
|
def handle_javascript_observation_request(keyword, original_event_listener)
|
771
|
-
|
772
|
-
|
773
|
-
|
774
|
-
|
775
|
-
|
776
|
-
|
777
|
-
|
778
|
-
|
779
|
-
|
780
|
-
|
781
|
-
|
782
|
-
|
783
|
-
|
784
|
-
|
785
|
-
#
|
786
|
-
|
787
|
-
|
788
|
-
|
789
|
-
|
790
|
-
#
|
791
|
-
|
792
|
-
|
793
|
-
|
794
|
-
|
795
|
-
|
796
|
-
|
797
|
-
|
798
|
-
|
799
|
-
|
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
|
823
|
-
|
824
|
-
|
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(
|
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
@@ -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
|
data/lib/glimmer-dsl-web.rb
CHANGED
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.
|
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-
|
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:
|