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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a9250414d9d8804f3af1ddf5cc083ce9e06ff87e3a57e1686c1e9bb7cb1ee847
4
- data.tar.gz: f9312dfb0e0a87a047b4b25216031587f965b70d8059a7de6d670105b2ce41ca
3
+ metadata.gz: 6d0e231339d0d3cd0a330fce53e852109df1b4b7b306194cff98e8e2eecbbd39
4
+ data.tar.gz: 384766d64c13a9724402021ea1ce47fae393bf66ccb4c56011a6e14e4c3fdefd
5
5
  SHA512:
6
- metadata.gz: 05be5bd7202f612582819cf970eb8185e530c2dff83e90caae1e3d5b09ad18d1aad44cd622e5b1b6d17f5afd83eb61762af2a941ab7bc77ea39b409e81ce4e6c
7
- data.tar.gz: 6a979d023e5faf0b78656ca332bf6593ea33ed224ab8f1f06490767466dd62861e6a560fec63be25352500e0e2fde92cfa9d31e47089a6f5a629f7546816c283
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.5 (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.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 a pre-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.
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.5'
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.5'
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 attributes](https://www.w3schools.com/html/html_attributes.asp).
821
+ [All HTML events](https://www.w3schools.com/tags/ref_eventattributes.asp), same event attribute names as in HTML.
763
822
 
764
- [All HTML events](https://www.w3schools.com/tags/ref_eventattributes.asp).
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 only has text content:
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.5
1
+ 0.0.6
@@ -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 ruby lib
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.5".freeze
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 = "2023-12-30"
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 see if this is needed in Opal
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.set_attribute(@property, converted_value) unless evaluate_property == converted_value
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
@@ -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/parent_expression'
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 ParentExpression
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/parent_expression'
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 ParentExpression
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,12 @@
1
+ require 'glimmer/dsl/static_expression'
2
+ require 'glimmer/dsl/web/general_element_expression'
3
+
4
+ module Glimmer
5
+ module DSL
6
+ module Web
7
+ class SelectExpression < StaticExpression
8
+ include GeneralElementExpression
9
+ end
10
+ end
11
+ end
12
+ 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 add_observer(observer, property_name)
807
- property_listener_installers = self.class&.ancestors&.to_a.map {|ancestor| widget_property_listener_installers[ancestor]}.compact
808
- widget_listener_installers = property_listener_installers.map{|installer| installer[property_name.to_s.to_sym]}.compact if !property_listener_installers.empty?
809
- widget_listener_installers.to_a.each do |widget_listener_installer|
810
- widget_listener_installer.call(observer)
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 widget_property_listener_installers
927
- @swt_widget_property_listener_installers ||= {
928
- # WidgetProxy => {
929
- # :focus => proc do |observer|
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
- TextProxy => {
972
- :text => proc do |observer|
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
- # Java::OrgEclipseSwtCustom::StyledText => {
1030
- # :text => proc do |observer|
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
@@ -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.5
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: 2023-12-30 00:00:00.000000000 Z
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