glimmer-dsl-web 0.0.5 → 0.0.6
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +8 -0
- data/README.md +274 -59
- data/VERSION +1 -1
- data/glimmer-dsl-web.gemspec +9 -4
- data/lib/glimmer/data_binding/element_binding.rb +3 -3
- data/lib/glimmer/dsl/web/bind_expression.rb +36 -0
- data/lib/glimmer/dsl/web/data_binding_expression.rb +30 -0
- data/lib/glimmer/dsl/web/dsl.rb +6 -0
- data/lib/glimmer/dsl/web/element_expression.rb +2 -20
- data/lib/glimmer/dsl/web/general_element_expression.rb +29 -0
- data/lib/glimmer/dsl/web/p_expression.rb +2 -21
- data/lib/glimmer/dsl/web/select_expression.rb +12 -0
- data/lib/glimmer/dsl/web/shine_data_binding_expression.rb +42 -0
- data/lib/glimmer/web/element_proxy.rb +24 -141
- data/lib/glimmer-dsl-web/samples/hello/hello_data_binding.rb +166 -0
- data/lib/glimmer-dsl-web.rb +0 -1
- metadata +8 -22
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6d0e231339d0d3cd0a330fce53e852109df1b4b7b306194cff98e8e2eecbbd39
|
4
|
+
data.tar.gz: 384766d64c13a9724402021ea1ce47fae393bf66ccb4c56011a6e14e4c3fdefd
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1d127d5d4b7eed73f60940b8c669593fcace051c6eccd6a5e80bc9d759aedecec922ba2b701341b9fc043781d7d2e4d0e371cc967dd782f4c3ed228d7b68c748
|
7
|
+
data.tar.gz: 9bb14fe78d3d4d6b7709429132c7b00bedb12d1183adcead609f88545b199174e7dda788241e41db63732551650064d77ec8794d3360c9c08c4c3719397a02a7
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,13 @@
|
|
1
1
|
# Change Log
|
2
2
|
|
3
|
+
## 0.0.6
|
4
|
+
|
5
|
+
- Support attribute unidirectional/bidirectional data-binding
|
6
|
+
- Support `select` element (it was blocked by a built-in Ruby method)
|
7
|
+
- Handle case of `:parent` selector being invalid, defaulting to `body`.
|
8
|
+
- Remove pure-struct gem dependency as the latest Opal fixed the implementation of Struct
|
9
|
+
- New Hello, Data-Binding! Sample: `require 'glimmer-dsl-web/samples/hello/hello_data_binding'`
|
10
|
+
|
3
11
|
## 0.0.5
|
4
12
|
|
5
13
|
- Support `p` element as it was overriden by Ruby's `p` method.
|
data/README.md
CHANGED
@@ -1,55 +1,9 @@
|
|
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.6 (Early Alpha)
|
2
2
|
## Ruby in the Browser Web GUI Frontend 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)
|
5
5
|
|
6
|
-
[Glimmer](https://github.com/AndyObtiva/glimmer) DSL for Web enables building Web GUI frontends using [Ruby in the Browser](https://www.youtube.com/watch?v=4AdcfbI6A4c), as per [Matz's recommendation in his RubyConf 2022 keynote speech to replace JavaScript with Ruby](https://youtu.be/knutsgHTrfQ?t=789). It aims at providing the simplest frontend library in existence. You can finally live in pure Rubyland on the Web in both the frontend and backend with [Glimmer DSL for Web](https://rubygems.org/gems/glimmer-dsl-web)!
|
7
|
-
|
8
|
-
This project is inspired by [Glimmer DSL for Opal](https://github.com/AndyObtiva/glimmer-dsl-opal) and is similar in enabling frontend GUI development with Ruby. [Glimmer DSL for Web](https://rubygems.org/gems/glimmer-dsl-web) mainly differs from Glimmer DSL for Opal by adopting a DSL that follows web-like HTML syntax in Ruby to facilitate leveraging existing HTML/CSS/JS skills instead of adopting a desktop GUI DSL that is webified.
|
9
|
-
|
10
|
-
**Sample**
|
11
|
-
|
12
|
-
Initial HTML Markup:
|
13
|
-
|
14
|
-
```html
|
15
|
-
...
|
16
|
-
<div id="app-container">
|
17
|
-
</div>
|
18
|
-
...
|
19
|
-
```
|
20
|
-
|
21
|
-
Glimmer GUI code:
|
22
|
-
|
23
|
-
```ruby
|
24
|
-
require 'glimmer-dsl-web'
|
25
|
-
|
26
|
-
include Glimmer
|
27
|
-
|
28
|
-
Document.ready? do
|
29
|
-
# This will hook into element #app-container and then build HTML inside it using Ruby DSL code
|
30
|
-
div(parent: '#app-container') {
|
31
|
-
label(class: 'greeting') {
|
32
|
-
'Hello, World!'
|
33
|
-
}
|
34
|
-
}.render
|
35
|
-
end
|
36
|
-
```
|
37
|
-
|
38
|
-
That produces:
|
39
|
-
|
40
|
-
```html
|
41
|
-
...
|
42
|
-
<div id="app-container">
|
43
|
-
<div data-parent="#app-container" class="element element-1">
|
44
|
-
<label class="greeting element element-2">
|
45
|
-
Hello, World!
|
46
|
-
</label>
|
47
|
-
</div>
|
48
|
-
</div>
|
49
|
-
...
|
50
|
-
```
|
51
|
-
|
52
|
-
![setup is working](/images/glimmer-dsl-web-setup-example-working.png)
|
6
|
+
[Glimmer](https://github.com/AndyObtiva/glimmer) DSL for Web enables building Web GUI frontends using [Ruby in the Browser](https://www.youtube.com/watch?v=4AdcfbI6A4c), as per [Matz's recommendation in his RubyConf 2022 keynote speech to replace JavaScript with Ruby](https://youtu.be/knutsgHTrfQ?t=789). It aims at providing the simplest frontend library in existence. The library follows the Ruby way (with [DSLs](https://martinfowler.com/books/dsl.html) and [TIMTOWTDI](https://en.wiktionary.org/wiki/TMTOWTDI#English)) and the Rails way ([Convention over Configuration](https://rubyonrails.org/doctrine)) while supporting both Unidirectional (One-Way) Data-Binding (using `<=`) and Bidirectional (Two-Way) Data-Binding (using `<=>`). You can finally live in pure Rubyland on the Web in both the frontend and backend with [Glimmer DSL for Web](https://rubygems.org/gems/glimmer-dsl-web)!
|
53
7
|
|
54
8
|
**Hello, World! Sample**
|
55
9
|
|
@@ -266,6 +220,106 @@ Screenshot:
|
|
266
220
|
|
267
221
|
![Hello, Form!](/images/glimmer-dsl-web-samples-hello-hello-form.gif)
|
268
222
|
|
223
|
+
**Hello, Data-Binding!**
|
224
|
+
|
225
|
+
Glimmer GUI code:
|
226
|
+
|
227
|
+
```ruby
|
228
|
+
require 'glimmer-dsl-web'
|
229
|
+
|
230
|
+
Address = Struct.new(:street, :street2, :city, :state, :zip_code, keyword_init: true) do
|
231
|
+
STATES = {...} # contains US States
|
232
|
+
|
233
|
+
def state_code
|
234
|
+
STATES.invert[state]
|
235
|
+
end
|
236
|
+
|
237
|
+
def state_code=(value)
|
238
|
+
self.state = STATES[value]
|
239
|
+
end
|
240
|
+
|
241
|
+
def summary
|
242
|
+
values.map(&:to_s).reject(&:empty?).join(', ')
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
@address = Address.new(
|
247
|
+
street: '123 Main St',
|
248
|
+
street2: 'Apartment 3C, 2nd door to the right',
|
249
|
+
city: 'San Diego',
|
250
|
+
state: 'California',
|
251
|
+
zip_code: '91911'
|
252
|
+
)
|
253
|
+
|
254
|
+
include Glimmer
|
255
|
+
|
256
|
+
Document.ready? do
|
257
|
+
div {
|
258
|
+
form(style: 'display: grid; grid-auto-columns: 80px 200px;') { |address_form|
|
259
|
+
label('Street: ', for: 'street-field')
|
260
|
+
input(id: 'street-field') {
|
261
|
+
# Bidirectional Data-Binding with <=> ensures input.value and @address.street
|
262
|
+
# automatically stay in sync when either side changes
|
263
|
+
value <=> [@address, :street]
|
264
|
+
}
|
265
|
+
|
266
|
+
label('Street 2: ', for: 'street2-field')
|
267
|
+
textarea(id: 'street2-field') {
|
268
|
+
value <=> [@address, :street2]
|
269
|
+
}
|
270
|
+
|
271
|
+
label('City: ', for: 'city-field')
|
272
|
+
input(id: 'city-field') {
|
273
|
+
value <=> [@address, :city]
|
274
|
+
}
|
275
|
+
|
276
|
+
label('State: ', for: 'state-field')
|
277
|
+
select(id: 'state-field') {
|
278
|
+
Address::STATES.each do |state_code, state|
|
279
|
+
option(value: state_code) { state }
|
280
|
+
end
|
281
|
+
|
282
|
+
value <=> [@address, :state_code]
|
283
|
+
}
|
284
|
+
|
285
|
+
label('Zip Code: ', for: 'zip-code-field')
|
286
|
+
input(id: 'zip-code-field', type: 'number', min: '0', max: '99999') {
|
287
|
+
# Bidirectional Data-Binding with <=> ensures input.value and @address.zip_code
|
288
|
+
# automatically stay in sync when either side changes
|
289
|
+
# on_write option specifies :to_s method to invoke on value before writing to model attribute
|
290
|
+
# to ensure the numeric zip code value is stored as a String
|
291
|
+
value <=> [@address, :zip_code,
|
292
|
+
on_write: :to_s
|
293
|
+
]
|
294
|
+
}
|
295
|
+
|
296
|
+
style {
|
297
|
+
<<~CSS
|
298
|
+
.#{address_form.element_id} * {
|
299
|
+
margin: 5px;
|
300
|
+
}
|
301
|
+
.#{address_form.element_id} input, .#{address_form.element_id} select {
|
302
|
+
grid-column: 2;
|
303
|
+
}
|
304
|
+
CSS
|
305
|
+
}
|
306
|
+
}
|
307
|
+
|
308
|
+
div(style: 'margin: 5px') {
|
309
|
+
# Unidirectional Data-Binding is done with <= to ensure @address.summary changes update div.inner_text
|
310
|
+
# as computed by changes to the address member attributes + state_code address custom attribute
|
311
|
+
inner_text <= [@address, :summary,
|
312
|
+
computed_by: @address.members + ['state_code']
|
313
|
+
]
|
314
|
+
}
|
315
|
+
}.render
|
316
|
+
end
|
317
|
+
```
|
318
|
+
|
319
|
+
Screenshot:
|
320
|
+
|
321
|
+
![Hello, Data-Binding!](/images/glimmer-dsl-web-samples-hello-hello-data-binding.gif)
|
322
|
+
|
269
323
|
**Button Counter Sample**
|
270
324
|
|
271
325
|
**UPCOMING (NOT RELEASED OR SUPPORTED YET)**
|
@@ -348,7 +402,7 @@ When clicked 7 times:
|
|
348
402
|
|
349
403
|
|
350
404
|
|
351
|
-
NOTE: Glimmer DSL for Web is
|
405
|
+
NOTE: Glimmer DSL for Web is an Early Alpha project. If you want it developed faster, please [open an issue report](https://github.com/AndyObtiva/glimmer-dsl-web/issues/new). I have completed some GitHub project features much faster before due to [issue reports](https://github.com/AndyObtiva/glimmer-dsl-web/issues) and [pull requests](https://github.com/AndyObtiva/glimmer-dsl-web/pulls). Please help make better by contributing, adopting for small or low risk projects, and providing feedback. It is still an early alpha, so the more feedback and issues you report the better.
|
352
406
|
|
353
407
|
Learn more about the differences between various [Glimmer](https://github.com/AndyObtiva/glimmer) DSLs by looking at:
|
354
408
|
|
@@ -363,10 +417,13 @@ Learn more about the differences between various [Glimmer](https://github.com/An
|
|
363
417
|
- [Setup](#setup)
|
364
418
|
- [Usage](#usage)
|
365
419
|
- [Supported Glimmer DSL Keywords](#supported-glimmer-dsl-keywords)
|
420
|
+
- [Coming from Glimmer DSL for Opal](#coming-from-glimmer-dsl-for-opal)
|
366
421
|
- [Samples](#samples)
|
367
422
|
- [Hello Samples](#hello-samples)
|
368
423
|
- [Hello, World!](#hello-world)
|
424
|
+
- [Hello, Button!](#hello-button)
|
369
425
|
- [Hello, Form!](#hello-form)
|
426
|
+
- [Hello, Data-Binding!](#hello-data-binding)
|
370
427
|
- [Button Counter](#button-counter)
|
371
428
|
- [Glimmer Process](#glimmer-process)
|
372
429
|
- [Help](#help)
|
@@ -416,7 +473,7 @@ gem 'opal', '1.4.1'
|
|
416
473
|
gem 'opal-rails', '2.0.2'
|
417
474
|
gem 'opal-async', '~> 1.4.0'
|
418
475
|
gem 'opal-jquery', '~> 0.4.6'
|
419
|
-
gem 'glimmer-dsl-web', '~> 0.0.
|
476
|
+
gem 'glimmer-dsl-web', '~> 0.0.6'
|
420
477
|
gem 'glimmer-dsl-xml', '~> 1.3.1', require: false
|
421
478
|
gem 'glimmer-dsl-css', '~> 1.2.1', require: false
|
422
479
|
```
|
@@ -561,7 +618,7 @@ gem 'opal', '1.4.1'
|
|
561
618
|
gem 'opal-rails', '2.0.2'
|
562
619
|
gem 'opal-async', '~> 1.4.0'
|
563
620
|
gem 'opal-jquery', '~> 0.4.6'
|
564
|
-
gem 'glimmer-dsl-web', '~> 0.0.
|
621
|
+
gem 'glimmer-dsl-web', '~> 0.0.6'
|
565
622
|
gem 'glimmer-dsl-xml', '~> 1.3.1', require: false
|
566
623
|
gem 'glimmer-dsl-css', '~> 1.2.1', require: false
|
567
624
|
```
|
@@ -701,7 +758,7 @@ Also, if the element has a little bit of text content that can fit in one line,
|
|
701
758
|
|
702
759
|
3- **Content Block (Properties + Listeners + Nested Elements + Text Content)**
|
703
760
|
|
704
|
-
Element methods can accept a Ruby content block. It intentionally has a `{...}` style even as a multi-line block to indicate that the code is declarative GUI structure code.
|
761
|
+
Element methods can accept a Ruby content block. It intentionally has a `{...}` style even as a multi-line block to indicate that the code is declarative GUI structure code (intentionally breaking away from Ruby imperative code conventions given this is a declarative GUI DSL, meaning a different language that has its own conventions, embedded within Ruby).
|
705
762
|
|
706
763
|
You can nest HTML element properties under an element like:
|
707
764
|
|
@@ -743,7 +800,7 @@ form {
|
|
743
800
|
}
|
744
801
|
```
|
745
802
|
|
746
|
-
You can nest text content underneath an element's Ruby block, like:
|
803
|
+
You can nest text content underneath an element's Ruby block provided it is the return value of the block (last declared value), like:
|
747
804
|
|
748
805
|
```ruby
|
749
806
|
p(class: 'summary') {
|
@@ -757,11 +814,15 @@ You can get/set any element property or invoke any element function by simply ca
|
|
757
814
|
|
758
815
|
## Supported Glimmer DSL Keywords
|
759
816
|
|
760
|
-
[All HTML elements](https://developer.mozilla.org/en-US/docs/Web/HTML/Element).
|
817
|
+
[All HTML elements](https://developer.mozilla.org/en-US/docs/Web/HTML/Element), following the Ruby method name standard of lowercase and underscored names.
|
818
|
+
|
819
|
+
[All HTML attributes](https://www.w3schools.com/html/html_attributes.asp), following the Ruby method name standard of lowercase and underscored names.
|
761
820
|
|
762
|
-
[All HTML
|
821
|
+
[All HTML events](https://www.w3schools.com/tags/ref_eventattributes.asp), same event attribute names as in HTML.
|
763
822
|
|
764
|
-
|
823
|
+
## Coming from Glimmer DSL for Opal
|
824
|
+
|
825
|
+
This project is inspired by [Glimmer DSL for Opal](https://github.com/AndyObtiva/glimmer-dsl-opal) and is similar in enabling frontend GUI development with Ruby. [Glimmer DSL for Web](https://rubygems.org/gems/glimmer-dsl-web) mainly differs from Glimmer DSL for Opal by adopting a DSL that follows web-like HTML syntax in Ruby to facilitate leveraging existing HTML/CSS/JS skills instead of adopting a desktop GUI DSL that is webified. As a result, applications written in Glimmer DSL for Opal are not compatible with Glimmer DSL for Web.
|
765
826
|
|
766
827
|
## Samples
|
767
828
|
|
@@ -769,8 +830,6 @@ This external sample app contains all the samples mentioned below configured ins
|
|
769
830
|
|
770
831
|
https://github.com/AndyObtiva/sample-glimmer-dsl-web-rails7-app
|
771
832
|
|
772
|
-
**[NOT RELEASED OR SUPPORTED YET]** https://github.com/AndyObtiva/sample-glimmer-dsl-web-rails-app
|
773
|
-
|
774
833
|
### Hello Samples
|
775
834
|
|
776
835
|
#### Hello, World!
|
@@ -799,7 +858,7 @@ That produces the following under `<body></body>`:
|
|
799
858
|
|
800
859
|
![setup is working](/images/glimmer-dsl-web-setup-example-working.png)
|
801
860
|
|
802
|
-
Alternative syntax when an element
|
861
|
+
Alternative syntax (useful when an element has text content that fits in one line):
|
803
862
|
|
804
863
|
```ruby
|
805
864
|
require 'glimmer-dsl-web'
|
@@ -1006,6 +1065,162 @@ Screenshot:
|
|
1006
1065
|
|
1007
1066
|
![Hello, Form!](/images/glimmer-dsl-web-samples-hello-hello-form.gif)
|
1008
1067
|
|
1068
|
+
#### Hello, Data-Binding!
|
1069
|
+
|
1070
|
+
Glimmer GUI code:
|
1071
|
+
|
1072
|
+
```ruby
|
1073
|
+
require 'glimmer-dsl-web'
|
1074
|
+
|
1075
|
+
Address = Struct.new(:street, :street2, :city, :state, :zip_code, keyword_init: true) do
|
1076
|
+
STATES = {
|
1077
|
+
"AK"=>"Alaska",
|
1078
|
+
"AL"=>"Alabama",
|
1079
|
+
"AR"=>"Arkansas",
|
1080
|
+
"AS"=>"American Samoa",
|
1081
|
+
"AZ"=>"Arizona",
|
1082
|
+
"CA"=>"California",
|
1083
|
+
"CO"=>"Colorado",
|
1084
|
+
"CT"=>"Connecticut",
|
1085
|
+
"DC"=>"District of Columbia",
|
1086
|
+
"DE"=>"Delaware",
|
1087
|
+
"FL"=>"Florida",
|
1088
|
+
"GA"=>"Georgia",
|
1089
|
+
"GU"=>"Guam",
|
1090
|
+
"HI"=>"Hawaii",
|
1091
|
+
"IA"=>"Iowa",
|
1092
|
+
"ID"=>"Idaho",
|
1093
|
+
"IL"=>"Illinois",
|
1094
|
+
"IN"=>"Indiana",
|
1095
|
+
"KS"=>"Kansas",
|
1096
|
+
"KY"=>"Kentucky",
|
1097
|
+
"LA"=>"Louisiana",
|
1098
|
+
"MA"=>"Massachusetts",
|
1099
|
+
"MD"=>"Maryland",
|
1100
|
+
"ME"=>"Maine",
|
1101
|
+
"MI"=>"Michigan",
|
1102
|
+
"MN"=>"Minnesota",
|
1103
|
+
"MO"=>"Missouri",
|
1104
|
+
"MS"=>"Mississippi",
|
1105
|
+
"MT"=>"Montana",
|
1106
|
+
"NC"=>"North Carolina",
|
1107
|
+
"ND"=>"North Dakota",
|
1108
|
+
"NE"=>"Nebraska",
|
1109
|
+
"NH"=>"New Hampshire",
|
1110
|
+
"NJ"=>"New Jersey",
|
1111
|
+
"NM"=>"New Mexico",
|
1112
|
+
"NV"=>"Nevada",
|
1113
|
+
"NY"=>"New York",
|
1114
|
+
"OH"=>"Ohio",
|
1115
|
+
"OK"=>"Oklahoma",
|
1116
|
+
"OR"=>"Oregon",
|
1117
|
+
"PA"=>"Pennsylvania",
|
1118
|
+
"PR"=>"Puerto Rico",
|
1119
|
+
"RI"=>"Rhode Island",
|
1120
|
+
"SC"=>"South Carolina",
|
1121
|
+
"SD"=>"South Dakota",
|
1122
|
+
"TN"=>"Tennessee",
|
1123
|
+
"TX"=>"Texas",
|
1124
|
+
"UT"=>"Utah",
|
1125
|
+
"VA"=>"Virginia",
|
1126
|
+
"VI"=>"Virgin Islands",
|
1127
|
+
"VT"=>"Vermont",
|
1128
|
+
"WA"=>"Washington",
|
1129
|
+
"WI"=>"Wisconsin",
|
1130
|
+
"WV"=>"West Virginia",
|
1131
|
+
"WY"=>"Wyoming"
|
1132
|
+
}
|
1133
|
+
|
1134
|
+
def state_code
|
1135
|
+
STATES.invert[state]
|
1136
|
+
end
|
1137
|
+
|
1138
|
+
def state_code=(value)
|
1139
|
+
self.state = STATES[value]
|
1140
|
+
end
|
1141
|
+
|
1142
|
+
def summary
|
1143
|
+
values.map(&:to_s).reject(&:empty?).join(', ')
|
1144
|
+
end
|
1145
|
+
end
|
1146
|
+
|
1147
|
+
@address = Address.new(
|
1148
|
+
street: '123 Main St',
|
1149
|
+
street2: 'Apartment 3C, 2nd door to the right',
|
1150
|
+
city: 'San Diego',
|
1151
|
+
state: 'California',
|
1152
|
+
zip_code: '91911'
|
1153
|
+
)
|
1154
|
+
|
1155
|
+
include Glimmer
|
1156
|
+
|
1157
|
+
Document.ready? do
|
1158
|
+
div {
|
1159
|
+
form(style: 'display: grid; grid-auto-columns: 80px 200px;') { |address_form|
|
1160
|
+
label('Street: ', for: 'street-field')
|
1161
|
+
input(id: 'street-field') {
|
1162
|
+
# Bidirectional Data-Binding with <=> ensures input.value and @address.street
|
1163
|
+
# automatically stay in sync when either side changes
|
1164
|
+
value <=> [@address, :street]
|
1165
|
+
}
|
1166
|
+
|
1167
|
+
label('Street 2: ', for: 'street2-field')
|
1168
|
+
textarea(id: 'street2-field') {
|
1169
|
+
value <=> [@address, :street2]
|
1170
|
+
}
|
1171
|
+
|
1172
|
+
label('City: ', for: 'city-field')
|
1173
|
+
input(id: 'city-field') {
|
1174
|
+
value <=> [@address, :city]
|
1175
|
+
}
|
1176
|
+
|
1177
|
+
label('State: ', for: 'state-field')
|
1178
|
+
select(id: 'state-field') {
|
1179
|
+
Address::STATES.each do |state_code, state|
|
1180
|
+
option(value: state_code) { state }
|
1181
|
+
end
|
1182
|
+
|
1183
|
+
value <=> [@address, :state_code]
|
1184
|
+
}
|
1185
|
+
|
1186
|
+
label('Zip Code: ', for: 'zip-code-field')
|
1187
|
+
input(id: 'zip-code-field', type: 'number', min: '0', max: '99999') {
|
1188
|
+
# Bidirectional Data-Binding with <=> ensures input.value and @address.zip_code
|
1189
|
+
# automatically stay in sync when either side changes
|
1190
|
+
# on_write option specifies :to_s method to invoke on value before writing to model attribute
|
1191
|
+
# to ensure the numeric zip code value is stored as a String
|
1192
|
+
value <=> [@address, :zip_code,
|
1193
|
+
on_write: :to_s
|
1194
|
+
]
|
1195
|
+
}
|
1196
|
+
|
1197
|
+
style {
|
1198
|
+
<<~CSS
|
1199
|
+
.#{address_form.element_id} * {
|
1200
|
+
margin: 5px;
|
1201
|
+
}
|
1202
|
+
.#{address_form.element_id} input, .#{address_form.element_id} select {
|
1203
|
+
grid-column: 2;
|
1204
|
+
}
|
1205
|
+
CSS
|
1206
|
+
}
|
1207
|
+
}
|
1208
|
+
|
1209
|
+
div(style: 'margin: 5px') {
|
1210
|
+
# Unidirectional Data-Binding is done with <= to ensure @address.summary changes update div.inner_text
|
1211
|
+
# as computed by changes to the address member attributes + state_code address custom attribute
|
1212
|
+
inner_text <= [@address, :summary,
|
1213
|
+
computed_by: @address.members + ['state_code']
|
1214
|
+
]
|
1215
|
+
}
|
1216
|
+
}.render
|
1217
|
+
end
|
1218
|
+
```
|
1219
|
+
|
1220
|
+
Screenshot:
|
1221
|
+
|
1222
|
+
![Hello, Data-Binding!](/images/glimmer-dsl-web-samples-hello-hello-data-binding.gif)
|
1223
|
+
|
1009
1224
|
#### Button Counter
|
1010
1225
|
|
1011
1226
|
**UPCOMING (NOT RELEASED OR SUPPORTED YET)**
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.0.
|
1
|
+
0.0.6
|
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.6 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.6".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 = "
|
14
|
+
s.date = "2024-01-01"
|
15
15
|
s.description = "Glimmer DSL for Web (Ruby in the Browser Web GUI Frontend Library) - Enables frontend GUI development with Ruby by adopting a DSL that follows web-like HTML syntax, enabling the transfer of HTML/CSS/JS skills to Ruby frontend development. This library relies on Opal Ruby.".freeze
|
16
16
|
s.email = "andy.am@gmail.com".freeze
|
17
17
|
s.extra_rdoc_files = [
|
@@ -31,16 +31,22 @@ Gem::Specification.new do |s|
|
|
31
31
|
"lib/glimmer-dsl-web/ext/date.rb",
|
32
32
|
"lib/glimmer-dsl-web/ext/exception.rb",
|
33
33
|
"lib/glimmer-dsl-web/samples/hello/hello_button.rb",
|
34
|
+
"lib/glimmer-dsl-web/samples/hello/hello_data_binding.rb",
|
34
35
|
"lib/glimmer-dsl-web/samples/hello/hello_form.rb",
|
35
36
|
"lib/glimmer-dsl-web/samples/hello/hello_world.rb",
|
36
37
|
"lib/glimmer-dsl-web/vendor/jquery.js",
|
37
38
|
"lib/glimmer/config/opal_logger.rb",
|
38
39
|
"lib/glimmer/data_binding/element_binding.rb",
|
40
|
+
"lib/glimmer/dsl/web/bind_expression.rb",
|
41
|
+
"lib/glimmer/dsl/web/data_binding_expression.rb",
|
39
42
|
"lib/glimmer/dsl/web/dsl.rb",
|
40
43
|
"lib/glimmer/dsl/web/element_expression.rb",
|
44
|
+
"lib/glimmer/dsl/web/general_element_expression.rb",
|
41
45
|
"lib/glimmer/dsl/web/listener_expression.rb",
|
42
46
|
"lib/glimmer/dsl/web/p_expression.rb",
|
43
47
|
"lib/glimmer/dsl/web/property_expression.rb",
|
48
|
+
"lib/glimmer/dsl/web/select_expression.rb",
|
49
|
+
"lib/glimmer/dsl/web/shine_data_binding_expression.rb",
|
44
50
|
"lib/glimmer/util/proc_tracker.rb",
|
45
51
|
"lib/glimmer/web.rb",
|
46
52
|
"lib/glimmer/web/element_proxy.rb",
|
@@ -60,7 +66,6 @@ Gem::Specification.new do |s|
|
|
60
66
|
s.add_runtime_dependency(%q<glimmer-dsl-css>.freeze, ["~> 1.2.2".freeze])
|
61
67
|
s.add_runtime_dependency(%q<opal-async>.freeze, ["~> 1.4.0".freeze])
|
62
68
|
s.add_runtime_dependency(%q<to_collection>.freeze, [">= 2.0.1".freeze, "< 3.0.0".freeze])
|
63
|
-
s.add_runtime_dependency(%q<pure-struct>.freeze, [">= 1.0.2".freeze, "< 2.0.0".freeze])
|
64
69
|
s.add_development_dependency(%q<puts_debuggerer>.freeze, [">= 0".freeze])
|
65
70
|
s.add_development_dependency(%q<rake>.freeze, [">= 10.1.0".freeze, "< 14.0.0".freeze])
|
66
71
|
s.add_development_dependency(%q<rake-tui>.freeze, [">= 0".freeze])
|
@@ -4,7 +4,6 @@ require 'glimmer/data_binding/observer'
|
|
4
4
|
module Glimmer
|
5
5
|
module DataBinding
|
6
6
|
class ElementBinding
|
7
|
-
# TODO consider renaming to WidgetBinding since it's no longer dealing with elements directly yet widgets instead
|
8
7
|
include Glimmer
|
9
8
|
include Observable
|
10
9
|
include Observer
|
@@ -15,7 +14,8 @@ module Glimmer
|
|
15
14
|
@property = property
|
16
15
|
@translator = translator || proc {|value| value}
|
17
16
|
|
18
|
-
# TODO
|
17
|
+
# TODO implement automatic cleanup upon calling element.remove
|
18
|
+
# Alternatively, have this be built into ElementProxy and remove this code
|
19
19
|
# if @element.respond_to?(:dispose)
|
20
20
|
# @element.on_widget_disposed do |dispose_event|
|
21
21
|
# unregister_all_observables
|
@@ -25,7 +25,7 @@ module Glimmer
|
|
25
25
|
|
26
26
|
def call(value)
|
27
27
|
converted_value = translated_value = @translator.call(value)
|
28
|
-
@element.
|
28
|
+
@element.send("#{@property}=", converted_value) unless evaluate_property == converted_value
|
29
29
|
end
|
30
30
|
|
31
31
|
def evaluate_property
|
@@ -0,0 +1,36 @@
|
|
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/static_expression'
|
23
|
+
require 'glimmer/dsl/bind_expression'
|
24
|
+
require 'glimmer/data_binding/model_binding'
|
25
|
+
|
26
|
+
module Glimmer
|
27
|
+
module DSL
|
28
|
+
module Web
|
29
|
+
# Responsible for setting up the return value of the bind keyword (command symbol)
|
30
|
+
# as a ModelBinding. It is then used by other data-binding expressions
|
31
|
+
class BindExpression < StaticExpression
|
32
|
+
include Glimmer::DSL::BindExpression
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'glimmer/dsl/expression'
|
2
|
+
require 'glimmer/data_binding/model_binding'
|
3
|
+
require 'glimmer/data_binding/element_binding'
|
4
|
+
|
5
|
+
module Glimmer
|
6
|
+
module DSL
|
7
|
+
module Web
|
8
|
+
# Responsible for wiring two-way data-binding for text and selection properties
|
9
|
+
# on Text, Button, and Spinner elements.
|
10
|
+
# Does so by using the output of the bind(model, property) command in the form
|
11
|
+
# of a ModelBinding, which is then connected to an anonymous element observer
|
12
|
+
# (aka element_data_binder as per element_data_binders array)
|
13
|
+
#
|
14
|
+
# Depends on BindCommandHandler
|
15
|
+
class DataBindingExpression < Expression
|
16
|
+
def can_interpret?(parent, keyword, *args, &block)
|
17
|
+
args.size == 1 and
|
18
|
+
args[0].is_a?(DataBinding::ModelBinding) and
|
19
|
+
parent.respond_to?(:data_bind)
|
20
|
+
end
|
21
|
+
|
22
|
+
def interpret(parent, keyword, *args, &block)
|
23
|
+
model_binding = args[0]
|
24
|
+
property = keyword
|
25
|
+
parent.data_bind(property, model_binding)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
data/lib/glimmer/dsl/web/dsl.rb
CHANGED
@@ -4,6 +4,10 @@ require 'glimmer/dsl/web/element_expression'
|
|
4
4
|
require 'glimmer/dsl/web/listener_expression'
|
5
5
|
require 'glimmer/dsl/web/property_expression'
|
6
6
|
require 'glimmer/dsl/web/p_expression'
|
7
|
+
require 'glimmer/dsl/web/select_expression'
|
8
|
+
require 'glimmer/dsl/web/bind_expression'
|
9
|
+
require 'glimmer/dsl/web/data_binding_expression'
|
10
|
+
require 'glimmer/dsl/web/shine_data_binding_expression'
|
7
11
|
|
8
12
|
module Glimmer
|
9
13
|
module DSL
|
@@ -20,7 +24,9 @@ module Glimmer
|
|
20
24
|
Web,
|
21
25
|
%w[
|
22
26
|
listener
|
27
|
+
data_binding
|
23
28
|
property
|
29
|
+
shine_data_binding
|
24
30
|
element
|
25
31
|
]
|
26
32
|
)
|
@@ -1,35 +1,17 @@
|
|
1
1
|
require 'glimmer/dsl/expression'
|
2
|
-
require 'glimmer/dsl/
|
3
|
-
require 'glimmer/web/element_proxy'
|
2
|
+
require 'glimmer/dsl/web/general_element_expression'
|
4
3
|
|
5
4
|
module Glimmer
|
6
5
|
module DSL
|
7
6
|
module Web
|
8
7
|
class ElementExpression < Expression
|
9
|
-
include
|
8
|
+
include GeneralElementExpression
|
10
9
|
|
11
10
|
def can_interpret?(parent, keyword, *args, &block)
|
12
11
|
# TODO automatically pass parent option as element if not passed instead of rejecting elements without a paraent nor root
|
13
12
|
# TODO raise a proper error if root is an element that is not found (maybe do this in model)
|
14
13
|
!keyword.to_s.start_with?('on')
|
15
14
|
end
|
16
|
-
|
17
|
-
def interpret(parent, keyword, *args, &block)
|
18
|
-
Glimmer::Web::ElementProxy.for(keyword, parent, args, block)
|
19
|
-
end
|
20
|
-
|
21
|
-
def add_content(parent, keyword, *args, &block)
|
22
|
-
if parent.rendered? || parent.skip_content_on_render_blocks?
|
23
|
-
return_value = super(parent, keyword, *args, &block)
|
24
|
-
if return_value.is_a?(String) && parent.dom_element.text.to_s.empty?
|
25
|
-
parent.add_text_content(return_value)
|
26
|
-
end
|
27
|
-
parent.post_add_content
|
28
|
-
return_value
|
29
|
-
else
|
30
|
-
parent.add_content_on_render(&block)
|
31
|
-
end
|
32
|
-
end
|
33
15
|
end
|
34
16
|
end
|
35
17
|
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'glimmer/dsl/parent_expression'
|
2
|
+
require 'glimmer/web/element_proxy'
|
3
|
+
|
4
|
+
module Glimmer
|
5
|
+
module DSL
|
6
|
+
module Web
|
7
|
+
module GeneralElementExpression
|
8
|
+
include ParentExpression
|
9
|
+
|
10
|
+
def interpret(parent, keyword, *args, &block)
|
11
|
+
Glimmer::Web::ElementProxy.for(keyword, parent, args, block)
|
12
|
+
end
|
13
|
+
|
14
|
+
def add_content(parent, keyword, *args, &block)
|
15
|
+
if parent.rendered? || parent.skip_content_on_render_blocks?
|
16
|
+
return_value = super(parent, keyword, *args, &block)
|
17
|
+
if return_value.is_a?(String) && parent.dom_element.text.to_s.empty?
|
18
|
+
parent.add_text_content(return_value)
|
19
|
+
end
|
20
|
+
parent.post_add_content
|
21
|
+
return_value
|
22
|
+
else
|
23
|
+
parent.add_content_on_render(&block)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -1,30 +1,11 @@
|
|
1
1
|
require 'glimmer/dsl/static_expression'
|
2
|
-
require 'glimmer/dsl/
|
3
|
-
|
4
|
-
require 'glimmer/web/element_proxy'
|
2
|
+
require 'glimmer/dsl/web/general_element_expression'
|
5
3
|
|
6
4
|
module Glimmer
|
7
5
|
module DSL
|
8
6
|
module Web
|
9
7
|
class PExpression < StaticExpression
|
10
|
-
include
|
11
|
-
|
12
|
-
def interpret(parent, keyword, *args, &block)
|
13
|
-
Glimmer::Web::ElementProxy.for(keyword, parent, args, block)
|
14
|
-
end
|
15
|
-
|
16
|
-
def add_content(parent, keyword, *args, &block)
|
17
|
-
if parent.rendered? || parent.skip_content_on_render_blocks?
|
18
|
-
return_value = super(parent, keyword, *args, &block)
|
19
|
-
if return_value.is_a?(String) && parent.dom_element.text.to_s.empty?
|
20
|
-
parent.add_text_content(return_value)
|
21
|
-
end
|
22
|
-
parent.post_add_content
|
23
|
-
return_value
|
24
|
-
else
|
25
|
-
parent.add_content_on_render(&block)
|
26
|
-
end
|
27
|
-
end
|
8
|
+
include GeneralElementExpression
|
28
9
|
end
|
29
10
|
end
|
30
11
|
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# Copyright (c) 2020-2022 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/expression'
|
23
|
+
require 'glimmer/data_binding/model_binding'
|
24
|
+
require 'glimmer/data_binding/shine'
|
25
|
+
|
26
|
+
module Glimmer
|
27
|
+
module DSL
|
28
|
+
module Web
|
29
|
+
class ShineDataBindingExpression < Expression
|
30
|
+
def can_interpret?(parent, keyword, *args, &block)
|
31
|
+
args.size == 0 and
|
32
|
+
block.nil? and
|
33
|
+
(parent.respond_to?("#{keyword}=") and parent.respond_to?(keyword))
|
34
|
+
end
|
35
|
+
|
36
|
+
def interpret(parent, keyword, *args, &block)
|
37
|
+
Glimmer::DataBinding::Shine.new(parent, keyword)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -186,7 +186,9 @@ module Glimmer
|
|
186
186
|
else
|
187
187
|
# TODO consider moving this to initializer
|
188
188
|
options[:parent] ||= 'body'
|
189
|
-
Document.find(options[:parent])
|
189
|
+
the_element = Document.find(options[:parent])
|
190
|
+
the_element = Document.find('body') if the_element.length == 0
|
191
|
+
the_element
|
190
192
|
end
|
191
193
|
end
|
192
194
|
|
@@ -803,11 +805,19 @@ module Glimmer
|
|
803
805
|
event_listener_proxies.clear
|
804
806
|
end
|
805
807
|
|
806
|
-
def
|
807
|
-
|
808
|
-
|
809
|
-
|
810
|
-
|
808
|
+
def data_bind(property, model_binding)
|
809
|
+
element_binding_parameters = [self, property]
|
810
|
+
element_binding = DataBinding::ElementBinding.new(*element_binding_parameters)
|
811
|
+
element_binding.call(model_binding.evaluate_property)
|
812
|
+
#TODO make this options observer dependent and all similar observers in element specific data binding handlers
|
813
|
+
element_binding.observe(model_binding)
|
814
|
+
unless model_binding.binding_options[:read_only]
|
815
|
+
# TODO add guards against nil cases for hash below
|
816
|
+
listener_keyword = data_binding_element_keyword_to_property_listener_map[keyword][property]
|
817
|
+
data_binding_read_listener = lambda do |event|
|
818
|
+
model_binding.call(send(property))
|
819
|
+
end
|
820
|
+
handle_observation_request(listener_keyword, data_binding_read_listener)
|
811
821
|
end
|
812
822
|
end
|
813
823
|
|
@@ -923,144 +933,17 @@ module Glimmer
|
|
923
933
|
}
|
924
934
|
end
|
925
935
|
|
926
|
-
def
|
927
|
-
@
|
928
|
-
|
929
|
-
|
930
|
-
# on_focus_gained { |focus_event|
|
931
|
-
# observer.call(true)
|
932
|
-
# }
|
933
|
-
# on_focus_lost { |focus_event|
|
934
|
-
# observer.call(false)
|
935
|
-
# }
|
936
|
-
# end,
|
937
|
-
# },
|
938
|
-
MenuItemProxy => {
|
939
|
-
:selection => proc do |observer|
|
940
|
-
on_widget_selected { |selection_event|
|
941
|
-
# TODO look into validity of this and perhaps move toggle logic to MenuItemProxy
|
942
|
-
if check?
|
943
|
-
observer.call(!selection)
|
944
|
-
else
|
945
|
-
observer.call(selection)
|
946
|
-
end
|
947
|
-
}
|
948
|
-
end
|
949
|
-
},
|
950
|
-
ScaleProxy => {
|
951
|
-
:selection => proc do |observer|
|
952
|
-
on_widget_selected { |selection_event|
|
953
|
-
observer.call(selection)
|
954
|
-
}
|
955
|
-
end
|
956
|
-
},
|
957
|
-
SliderProxy => {
|
958
|
-
:selection => proc do |observer|
|
959
|
-
on_widget_selected { |selection_event|
|
960
|
-
observer.call(selection)
|
961
|
-
}
|
962
|
-
end
|
963
|
-
},
|
964
|
-
SpinnerProxy => {
|
965
|
-
:selection => proc do |observer|
|
966
|
-
on_widget_selected { |selection_event|
|
967
|
-
observer.call(selection)
|
968
|
-
}
|
969
|
-
end
|
936
|
+
def data_binding_element_keyword_to_property_listener_map
|
937
|
+
@data_binding_element_keyword_to_property_listener_map ||= {
|
938
|
+
'input' => {
|
939
|
+
'value' => 'oninput',
|
970
940
|
},
|
971
|
-
|
972
|
-
|
973
|
-
on_modify_text { |modify_event|
|
974
|
-
observer.call(text)
|
975
|
-
}
|
976
|
-
end,
|
977
|
-
# :caret_position => proc do |observer|
|
978
|
-
# on_event_keydown { |event|
|
979
|
-
# observer.call(getCaretPosition)
|
980
|
-
# }
|
981
|
-
# on_event_keyup { |event|
|
982
|
-
# observer.call(getCaretPosition)
|
983
|
-
# }
|
984
|
-
# on_event_mousedown { |event|
|
985
|
-
# observer.call(getCaretPosition)
|
986
|
-
# }
|
987
|
-
# on_event_mouseup { |event|
|
988
|
-
# observer.call(getCaretPosition)
|
989
|
-
# }
|
990
|
-
# end,
|
991
|
-
# :selection => proc do |observer|
|
992
|
-
# on_event_keydown { |event|
|
993
|
-
# observer.call(getSelection)
|
994
|
-
# }
|
995
|
-
# on_event_keyup { |event|
|
996
|
-
# observer.call(getSelection)
|
997
|
-
# }
|
998
|
-
# on_event_mousedown { |event|
|
999
|
-
# observer.call(getSelection)
|
1000
|
-
# }
|
1001
|
-
# on_event_mouseup { |event|
|
1002
|
-
# observer.call(getSelection)
|
1003
|
-
# }
|
1004
|
-
# end,
|
1005
|
-
# :selection_count => proc do |observer|
|
1006
|
-
# on_event_keydown { |event|
|
1007
|
-
# observer.call(getSelectionCount)
|
1008
|
-
# }
|
1009
|
-
# on_event_keyup { |event|
|
1010
|
-
# observer.call(getSelectionCount)
|
1011
|
-
# }
|
1012
|
-
# on_event_mousedown { |event|
|
1013
|
-
# observer.call(getSelectionCount)
|
1014
|
-
# }
|
1015
|
-
# on_event_mouseup { |event|
|
1016
|
-
# observer.call(getSelectionCount)
|
1017
|
-
# }
|
1018
|
-
# end,
|
1019
|
-
# :top_index => proc do |observer|
|
1020
|
-
# @last_top_index = getTopIndex
|
1021
|
-
# on_paint_control { |event|
|
1022
|
-
# if getTopIndex != @last_top_index
|
1023
|
-
# @last_top_index = getTopIndex
|
1024
|
-
# observer.call(@last_top_index)
|
1025
|
-
# end
|
1026
|
-
# }
|
1027
|
-
# end,
|
941
|
+
'select' => {
|
942
|
+
'value' => 'onchange',
|
1028
943
|
},
|
1029
|
-
|
1030
|
-
|
1031
|
-
# on_modify_text { |modify_event|
|
1032
|
-
# observer.call(getText)
|
1033
|
-
# }
|
1034
|
-
# end,
|
1035
|
-
# },
|
1036
|
-
DateTimeProxy => {
|
1037
|
-
:date_time => proc do |observer|
|
1038
|
-
on_widget_selected { |selection_event|
|
1039
|
-
observer.call(date_time)
|
1040
|
-
}
|
1041
|
-
end
|
944
|
+
'textarea' => {
|
945
|
+
'value' => 'oninput',
|
1042
946
|
},
|
1043
|
-
RadioProxy => { #radio?
|
1044
|
-
:selection => proc do |observer|
|
1045
|
-
on_widget_selected { |selection_event|
|
1046
|
-
observer.call(selection)
|
1047
|
-
}
|
1048
|
-
end
|
1049
|
-
},
|
1050
|
-
TableProxy => {
|
1051
|
-
:selection => proc do |observer|
|
1052
|
-
on_widget_selected { |selection_event|
|
1053
|
-
observer.call(selection_event.table_item.get_data) # TODO ensure selection doesn't conflict with editing
|
1054
|
-
}
|
1055
|
-
end,
|
1056
|
-
},
|
1057
|
-
# Java::OrgEclipseSwtWidgets::MenuItem => {
|
1058
|
-
# :selection => proc do |observer|
|
1059
|
-
# on_widget_selected { |selection_event|
|
1060
|
-
# observer.call(getSelection)
|
1061
|
-
# }
|
1062
|
-
# end
|
1063
|
-
# },
|
1064
947
|
}
|
1065
948
|
end
|
1066
949
|
|
@@ -0,0 +1,166 @@
|
|
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
|
+
Address = Struct.new(:street, :street2, :city, :state, :zip_code, keyword_init: true) do
|
25
|
+
STATES = {
|
26
|
+
"AK"=>"Alaska",
|
27
|
+
"AL"=>"Alabama",
|
28
|
+
"AR"=>"Arkansas",
|
29
|
+
"AS"=>"American Samoa",
|
30
|
+
"AZ"=>"Arizona",
|
31
|
+
"CA"=>"California",
|
32
|
+
"CO"=>"Colorado",
|
33
|
+
"CT"=>"Connecticut",
|
34
|
+
"DC"=>"District of Columbia",
|
35
|
+
"DE"=>"Delaware",
|
36
|
+
"FL"=>"Florida",
|
37
|
+
"GA"=>"Georgia",
|
38
|
+
"GU"=>"Guam",
|
39
|
+
"HI"=>"Hawaii",
|
40
|
+
"IA"=>"Iowa",
|
41
|
+
"ID"=>"Idaho",
|
42
|
+
"IL"=>"Illinois",
|
43
|
+
"IN"=>"Indiana",
|
44
|
+
"KS"=>"Kansas",
|
45
|
+
"KY"=>"Kentucky",
|
46
|
+
"LA"=>"Louisiana",
|
47
|
+
"MA"=>"Massachusetts",
|
48
|
+
"MD"=>"Maryland",
|
49
|
+
"ME"=>"Maine",
|
50
|
+
"MI"=>"Michigan",
|
51
|
+
"MN"=>"Minnesota",
|
52
|
+
"MO"=>"Missouri",
|
53
|
+
"MS"=>"Mississippi",
|
54
|
+
"MT"=>"Montana",
|
55
|
+
"NC"=>"North Carolina",
|
56
|
+
"ND"=>"North Dakota",
|
57
|
+
"NE"=>"Nebraska",
|
58
|
+
"NH"=>"New Hampshire",
|
59
|
+
"NJ"=>"New Jersey",
|
60
|
+
"NM"=>"New Mexico",
|
61
|
+
"NV"=>"Nevada",
|
62
|
+
"NY"=>"New York",
|
63
|
+
"OH"=>"Ohio",
|
64
|
+
"OK"=>"Oklahoma",
|
65
|
+
"OR"=>"Oregon",
|
66
|
+
"PA"=>"Pennsylvania",
|
67
|
+
"PR"=>"Puerto Rico",
|
68
|
+
"RI"=>"Rhode Island",
|
69
|
+
"SC"=>"South Carolina",
|
70
|
+
"SD"=>"South Dakota",
|
71
|
+
"TN"=>"Tennessee",
|
72
|
+
"TX"=>"Texas",
|
73
|
+
"UT"=>"Utah",
|
74
|
+
"VA"=>"Virginia",
|
75
|
+
"VI"=>"Virgin Islands",
|
76
|
+
"VT"=>"Vermont",
|
77
|
+
"WA"=>"Washington",
|
78
|
+
"WI"=>"Wisconsin",
|
79
|
+
"WV"=>"West Virginia",
|
80
|
+
"WY"=>"Wyoming"
|
81
|
+
}
|
82
|
+
|
83
|
+
def state_code
|
84
|
+
STATES.invert[state]
|
85
|
+
end
|
86
|
+
|
87
|
+
def state_code=(value)
|
88
|
+
self.state = STATES[value]
|
89
|
+
end
|
90
|
+
|
91
|
+
def summary
|
92
|
+
values.map(&:to_s).reject(&:empty?).join(', ')
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
@address = Address.new(
|
97
|
+
street: '123 Main St',
|
98
|
+
street2: 'Apartment 3C, 2nd door to the right',
|
99
|
+
city: 'San Diego',
|
100
|
+
state: 'California',
|
101
|
+
zip_code: '91911'
|
102
|
+
)
|
103
|
+
|
104
|
+
include Glimmer
|
105
|
+
|
106
|
+
Document.ready? do
|
107
|
+
div {
|
108
|
+
form(style: 'display: grid; grid-auto-columns: 80px 200px;') { |address_form|
|
109
|
+
label('Street: ', for: 'street-field')
|
110
|
+
input(id: 'street-field') {
|
111
|
+
# Bidirectional Data-Binding with <=> ensures input.value and @address.street
|
112
|
+
# automatically stay in sync when either side changes
|
113
|
+
value <=> [@address, :street]
|
114
|
+
}
|
115
|
+
|
116
|
+
label('Street 2: ', for: 'street2-field')
|
117
|
+
textarea(id: 'street2-field') {
|
118
|
+
value <=> [@address, :street2]
|
119
|
+
}
|
120
|
+
|
121
|
+
label('City: ', for: 'city-field')
|
122
|
+
input(id: 'city-field') {
|
123
|
+
value <=> [@address, :city]
|
124
|
+
}
|
125
|
+
|
126
|
+
label('State: ', for: 'state-field')
|
127
|
+
select(id: 'state-field') {
|
128
|
+
Address::STATES.each do |state_code, state|
|
129
|
+
option(value: state_code) { state }
|
130
|
+
end
|
131
|
+
|
132
|
+
value <=> [@address, :state_code]
|
133
|
+
}
|
134
|
+
|
135
|
+
label('Zip Code: ', for: 'zip-code-field')
|
136
|
+
input(id: 'zip-code-field', type: 'number', min: '0', max: '99999') {
|
137
|
+
# Bidirectional Data-Binding with <=> ensures input.value and @address.zip_code
|
138
|
+
# automatically stay in sync when either side changes
|
139
|
+
# on_write option specifies :to_s method to invoke on value before writing to model attribute
|
140
|
+
# to ensure the numeric zip code value is stored as a String
|
141
|
+
value <=> [@address, :zip_code,
|
142
|
+
on_write: :to_s
|
143
|
+
]
|
144
|
+
}
|
145
|
+
|
146
|
+
style {
|
147
|
+
<<~CSS
|
148
|
+
.#{address_form.element_id} * {
|
149
|
+
margin: 5px;
|
150
|
+
}
|
151
|
+
.#{address_form.element_id} input, .#{address_form.element_id} select {
|
152
|
+
grid-column: 2;
|
153
|
+
}
|
154
|
+
CSS
|
155
|
+
}
|
156
|
+
}
|
157
|
+
|
158
|
+
div(style: 'margin: 5px') {
|
159
|
+
# Unidirectional Data-Binding is done with <= to ensure @address.summary changes update div.inner_text
|
160
|
+
# as computed by changes to the address member attributes + state_code address custom attribute
|
161
|
+
inner_text <= [@address, :summary,
|
162
|
+
computed_by: @address.members + ['state_code']
|
163
|
+
]
|
164
|
+
}
|
165
|
+
}.render
|
166
|
+
end
|
data/lib/glimmer-dsl-web.rb
CHANGED
@@ -48,7 +48,6 @@ if RUBY_ENGINE == 'opal'
|
|
48
48
|
require 'opal-async'
|
49
49
|
require 'async/ext'
|
50
50
|
require 'to_collection'
|
51
|
-
require 'pure-struct' # TODO double check if the latest Opal implemented everything below already
|
52
51
|
require 'glimmer-dsl-web/vendor/jquery'
|
53
52
|
require 'opal-jquery'
|
54
53
|
require 'opal/jquery/local_storage'
|
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.6
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andy Maleh
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2024-01-01 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: glimmer
|
@@ -86,26 +86,6 @@ dependencies:
|
|
86
86
|
- - "<"
|
87
87
|
- !ruby/object:Gem::Version
|
88
88
|
version: 3.0.0
|
89
|
-
- !ruby/object:Gem::Dependency
|
90
|
-
name: pure-struct
|
91
|
-
requirement: !ruby/object:Gem::Requirement
|
92
|
-
requirements:
|
93
|
-
- - ">="
|
94
|
-
- !ruby/object:Gem::Version
|
95
|
-
version: 1.0.2
|
96
|
-
- - "<"
|
97
|
-
- !ruby/object:Gem::Version
|
98
|
-
version: 2.0.0
|
99
|
-
type: :runtime
|
100
|
-
prerelease: false
|
101
|
-
version_requirements: !ruby/object:Gem::Requirement
|
102
|
-
requirements:
|
103
|
-
- - ">="
|
104
|
-
- !ruby/object:Gem::Version
|
105
|
-
version: 1.0.2
|
106
|
-
- - "<"
|
107
|
-
- !ruby/object:Gem::Version
|
108
|
-
version: 2.0.0
|
109
89
|
- !ruby/object:Gem::Dependency
|
110
90
|
name: puts_debuggerer
|
111
91
|
requirement: !ruby/object:Gem::Requirement
|
@@ -259,16 +239,22 @@ files:
|
|
259
239
|
- lib/glimmer-dsl-web/ext/date.rb
|
260
240
|
- lib/glimmer-dsl-web/ext/exception.rb
|
261
241
|
- lib/glimmer-dsl-web/samples/hello/hello_button.rb
|
242
|
+
- lib/glimmer-dsl-web/samples/hello/hello_data_binding.rb
|
262
243
|
- lib/glimmer-dsl-web/samples/hello/hello_form.rb
|
263
244
|
- lib/glimmer-dsl-web/samples/hello/hello_world.rb
|
264
245
|
- lib/glimmer-dsl-web/vendor/jquery.js
|
265
246
|
- lib/glimmer/config/opal_logger.rb
|
266
247
|
- lib/glimmer/data_binding/element_binding.rb
|
248
|
+
- lib/glimmer/dsl/web/bind_expression.rb
|
249
|
+
- lib/glimmer/dsl/web/data_binding_expression.rb
|
267
250
|
- lib/glimmer/dsl/web/dsl.rb
|
268
251
|
- lib/glimmer/dsl/web/element_expression.rb
|
252
|
+
- lib/glimmer/dsl/web/general_element_expression.rb
|
269
253
|
- lib/glimmer/dsl/web/listener_expression.rb
|
270
254
|
- lib/glimmer/dsl/web/p_expression.rb
|
271
255
|
- lib/glimmer/dsl/web/property_expression.rb
|
256
|
+
- lib/glimmer/dsl/web/select_expression.rb
|
257
|
+
- lib/glimmer/dsl/web/shine_data_binding_expression.rb
|
272
258
|
- lib/glimmer/util/proc_tracker.rb
|
273
259
|
- lib/glimmer/web.rb
|
274
260
|
- lib/glimmer/web/element_proxy.rb
|