glimmer-dsl-web 0.0.6 → 0.0.8

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: 6d0e231339d0d3cd0a330fce53e852109df1b4b7b306194cff98e8e2eecbbd39
4
- data.tar.gz: 384766d64c13a9724402021ea1ce47fae393bf66ccb4c56011a6e14e4c3fdefd
3
+ metadata.gz: '028be0c05ee74f8e639b3b43adbdd52e7b90e7e5b52a0e7026fbf74e0d017300'
4
+ data.tar.gz: d2b8e3fc639dfa55655f05faf740fd36fcf518bc143eb8f08a874be3e34f72ec
5
5
  SHA512:
6
- metadata.gz: 1d127d5d4b7eed73f60940b8c669593fcace051c6eccd6a5e80bc9d759aedecec922ba2b701341b9fc043781d7d2e4d0e371cc967dd782f4c3ed228d7b68c748
7
- data.tar.gz: 9bb14fe78d3d4d6b7709429132c7b00bedb12d1183adcead609f88545b199174e7dda788241e41db63732551650064d77ec8794d3360c9c08c4c3719397a02a7
6
+ metadata.gz: e9a0add34e7db5cd7d0816e0006ed0426f9bc739402e4951f672195f6dcce472943b5f5c501213269b0ec84fb636aaea701fe35326639827bd580b94db370a5a
7
+ data.tar.gz: 19ccf1280735e472c6f643a21e105f0b1020fdacc53a66f63588be6f7b8faddb1e8391667094687a204c3d1363ee760c6b752c84866b86323437c17b8fdbe7d6
data/CHANGELOG.md CHANGED
@@ -1,5 +1,31 @@
1
1
  # Change Log
2
2
 
3
+ ## 0.0.8
4
+
5
+ - Validate that element keywords belong to the valid list of HTML element tag names to prevent rendering fake elements the keyword does not match a real HTML element tag
6
+ - Support inner_html/outer_html properties (it seems they did not convert to innerHTML/outerHTML correctly because of the all caps HTML)
7
+ - Support element properties via original JS names (e.g. innerHTML) to prevent logic from breaking if some do not convert correctly from underscored versions
8
+ - Package dependencies properly in the gem to avoid instructing users to add in Rails Gemfile manually
9
+ - Content Data-Binding support to regenerate element content based on changes to an observed model attribute.
10
+ Content Data-Binding Example:
11
+ content(*data_binding_options) { |data_binding_value|
12
+ li {
13
+ data_binding_value
14
+ }
15
+ }
16
+ - Ensuring removing data-binding model listeners when calling `element#remove` in addition to already removing standard HTML event listeners
17
+ - New Hello, Content Data-Binding! Sample: `require 'glimmer-dsl-web/samples/hello/hello_content_data_binding'`
18
+
19
+ ## 0.0.7
20
+
21
+ - Support input[type=number] value data-binding as a Ruby Numeric object (Integer or Float)
22
+ - Support input[type=range] value data-binding as a Ruby Numeric object (Integer or Float)
23
+ - Support input[type=datetime-local] value data-binding as a Ruby Time object
24
+ - Support input[type=date] value data-binding as a Ruby Date object
25
+ - Support input[type=time] value data-binding as a Ruby Time object
26
+ - Update Hello, Data-Binding! Sample to include a checkbox
27
+ - New Hello, Input (Date/Time)! Sample: `require 'glimmer-dsl-web/samples/hello/hello_input'`
28
+
3
29
  ## 0.0.6
4
30
 
5
31
  - Support attribute unidirectional/bidirectional data-binding
data/LICENSE.txt CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2023 Andy Maleh
1
+ Copyright (c) 2023-2024 Andy Maleh
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
data/README.md CHANGED
@@ -1,9 +1,11 @@
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)
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.8 (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. 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)!
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, most intuitive, most straight-forward, and most productive 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 `<=>`). Dynamic rendering (and re-rendering) of HTML content is also supported via Content Data-Binding. 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
+ (the project plans to add component support very soon, albeit components are already supported by creating your own Ruby classes and having them render part of the GUI hierarchy)
7
9
 
8
10
  **Hello, World! Sample**
9
11
 
@@ -222,6 +224,8 @@ Screenshot:
222
224
 
223
225
  **Hello, Data-Binding!**
224
226
 
227
+ [Glimmer DSL for Web](https://rubygems.org/gems/glimmer-dsl-web) intuitively supports both Unidirectional (One-Way) Data-Binding via the `<=` operator and Bidirectional (Two-Way) Data-Binding via the `<=>` operator, incredibly simplifying how to sync View properties with Model attributes with the simplest code to reason about.
228
+
225
229
  Glimmer GUI code:
226
230
 
227
231
  ```ruby
@@ -239,7 +243,10 @@ Address = Struct.new(:street, :street2, :city, :state, :zip_code, keyword_init:
239
243
  end
240
244
 
241
245
  def summary
242
- values.map(&:to_s).reject(&:empty?).join(', ')
246
+ string_attributes = to_h.except(:billing_and_shipping)
247
+ summary = string_attributes.values.map(&:to_s).reject(&:empty?).join(', ')
248
+ summary += " (Billing & Shipping)" if billing_and_shipping
249
+ summary
243
250
  end
244
251
  end
245
252
 
@@ -248,14 +255,15 @@ end
248
255
  street2: 'Apartment 3C, 2nd door to the right',
249
256
  city: 'San Diego',
250
257
  state: 'California',
251
- zip_code: '91911'
258
+ zip_code: '91911',
259
+ billing_and_shipping: true,
252
260
  )
253
261
 
254
262
  include Glimmer
255
263
 
256
264
  Document.ready? do
257
265
  div {
258
- form(style: 'display: grid; grid-auto-columns: 80px 200px;') { |address_form|
266
+ div(style: 'display: grid; grid-auto-columns: 80px 260px;') { |address_div|
259
267
  label('Street: ', for: 'street-field')
260
268
  input(id: 'street-field') {
261
269
  # Bidirectional Data-Binding with <=> ensures input.value and @address.street
@@ -289,16 +297,25 @@ Document.ready? do
289
297
  # on_write option specifies :to_s method to invoke on value before writing to model attribute
290
298
  # to ensure the numeric zip code value is stored as a String
291
299
  value <=> [@address, :zip_code,
292
- on_write: :to_s
300
+ on_write: :to_s,
293
301
  ]
294
302
  }
295
303
 
304
+ div(style: 'grid-column: 1 / span 2') {
305
+ input(id: 'billing-and-shipping-field', type: 'checkbox') {
306
+ checked <=> [@address, :billing_and_shipping]
307
+ }
308
+ label(for: 'billing-and-shipping-field') {
309
+ 'Use this address for both Billing & Shipping'
310
+ }
311
+ }
312
+
296
313
  style {
297
314
  <<~CSS
298
- .#{address_form.element_id} * {
315
+ #{address_div.selector} * {
299
316
  margin: 5px;
300
317
  }
301
- .#{address_form.element_id} input, .#{address_form.element_id} select {
318
+ #{address_div.selector} input, #{address_div.selector} select {
302
319
  grid-column: 2;
303
320
  }
304
321
  CSS
@@ -306,10 +323,12 @@ Document.ready? do
306
323
  }
307
324
 
308
325
  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
326
+ # Unidirectional Data-Binding is done with <= to ensure @address.summary changes
327
+ # automatically update div.inner_text
328
+ # (computed by changes to address attributes, meaning if street changes,
329
+ # @address.summary is automatically recomputed.)
311
330
  inner_text <= [@address, :summary,
312
- computed_by: @address.members + ['state_code']
331
+ computed_by: @address.members + ['state_code'],
313
332
  ]
314
333
  }
315
334
  }.render
@@ -320,6 +339,137 @@ Screenshot:
320
339
 
321
340
  ![Hello, Data-Binding!](/images/glimmer-dsl-web-samples-hello-hello-data-binding.gif)
322
341
 
342
+ **Hello, Content Data-Binding!**
343
+
344
+ If you need to regenerate HTML element content dynamically, you can use Content Data-Binding to effortlessly
345
+ rebuild HTML elements based on changes in a Model attribute that provides the source data.
346
+ In this example, we generate multiple address forms based on the number of addresses the user has.
347
+
348
+ Glimmer GUI code:
349
+
350
+ ```ruby
351
+ require 'glimmer-dsl-web'
352
+
353
+ class Address
354
+ attr_accessor :text
355
+ attr_reader :name, :street, :city, :state, :zip
356
+
357
+ def name=(value)
358
+ @name = value
359
+ update_text
360
+ end
361
+
362
+ def street=(value)
363
+ @street = value
364
+ update_text
365
+ end
366
+
367
+ def city=(value)
368
+ @city = value
369
+ update_text
370
+ end
371
+
372
+ def state=(value)
373
+ @state = value
374
+ update_text
375
+ end
376
+
377
+ def zip=(value)
378
+ @zip = value
379
+ update_text
380
+ end
381
+
382
+ private
383
+
384
+ def update_text
385
+ self.text = [name, street, city, state, zip].compact.reject(&:empty?).join(', ')
386
+ end
387
+ end
388
+
389
+ class User
390
+ attr_accessor :addresses
391
+ attr_reader :address_count
392
+
393
+ def initialize
394
+ @address_count = 1
395
+ @addresses = []
396
+ update_addresses
397
+ end
398
+
399
+ def address_count=(value)
400
+ value = [[1, value.to_i].max, 3].min
401
+ @address_count = value
402
+ update_addresses
403
+ end
404
+
405
+ private
406
+
407
+ def update_addresses
408
+ address_count_change = address_count - addresses.size
409
+ if address_count_change > 0
410
+ address_count_change.times { addresses << Address.new }
411
+ else
412
+ address_count_change.abs.times { addresses.pop }
413
+ end
414
+ end
415
+ end
416
+
417
+ @user = User.new
418
+
419
+ div {
420
+ div {
421
+ label('Number of addresses: ', for: 'address-count-field')
422
+ input(id: 'address-count-field', type: 'number', min: 1, max: 3) {
423
+ value <=> [@user, :address_count]
424
+ }
425
+ }
426
+
427
+ div {
428
+ # Content Data-Binding is used to dynamically (re)generate content of div
429
+ # based on changes to @user.addresses, replacing older content on every change
430
+ content(@user, :addresses) do
431
+ @user.addresses.each do |address|
432
+ div {
433
+ div(style: 'display: grid; grid-auto-columns: 80px 280px;') { |address_div|
434
+ [:name, :street, :city, :state, :zip].each do |attribute|
435
+ label(attribute.to_s.capitalize, for: "#{attribute}-field")
436
+ input(id: "#{attribute}-field", type: 'text') {
437
+ value <=> [address, attribute]
438
+ }
439
+ end
440
+
441
+ div(style: 'grid-column: 1 / span 2;') {
442
+ inner_text <= [address, :text]
443
+ }
444
+
445
+ style {
446
+ <<~CSS
447
+ #{address_div.selector} {
448
+ margin: 10px 0;
449
+ }
450
+ #{address_div.selector} * {
451
+ margin: 5px;
452
+ }
453
+ #{address_div.selector} label {
454
+ grid-column: 1;
455
+ }
456
+ #{address_div.selector} input, #{address_div.selector} select {
457
+ grid-column: 2;
458
+ }
459
+ CSS
460
+ }
461
+ }
462
+ }
463
+ end
464
+ end
465
+ }
466
+ }.render
467
+ ```
468
+
469
+ Screenshot:
470
+
471
+ ![Hello, Content Data-Binding!](/images/glimmer-dsl-web-samples-hello-hello-content-data-binding.gif)
472
+
323
473
  **Button Counter Sample**
324
474
 
325
475
  **UPCOMING (NOT RELEASED OR SUPPORTED YET)**
@@ -424,6 +574,8 @@ Learn more about the differences between various [Glimmer](https://github.com/An
424
574
  - [Hello, Button!](#hello-button)
425
575
  - [Hello, Form!](#hello-form)
426
576
  - [Hello, Data-Binding!](#hello-data-binding)
577
+ - [Hello, Content Data-Binding!](#hello-content-data-binding)
578
+ - [Hello, Input (Date/Time)!](#hello-input-datetime)
427
579
  - [Button Counter](#button-counter)
428
580
  - [Glimmer Process](#glimmer-process)
429
581
  - [Help](#help)
@@ -446,7 +598,7 @@ Learn more about the differences between various [Glimmer](https://github.com/An
446
598
 
447
599
  ## Setup
448
600
 
449
- (NOTE: Keep in mind this is a very early experimental and incomplete **alpha**. If you run into issues, try to go back to a [previous revision](https://rubygems.org/gems/glimmer-dsl-web/versions). Also, there is a slight chance any issues you encounter are fixed in master or some other branch that you could check out instead)
601
+ (NOTE: Keep in mind this is an Early Alpha. If you run into issues, try to go back to a [previous revision](https://rubygems.org/gems/glimmer-dsl-web/versions). Also, there is a slight chance any issues you encounter are fixed in master or some other branch that you could check out instead)
450
602
 
451
603
  The [glimmer-dsl-web](https://rubygems.org/gems/glimmer-dsl-web) gem is a [Rails Engine](https://guides.rubyonrails.org/engines.html) gem that includes assets.
452
604
 
@@ -469,13 +621,7 @@ rails new glimmer_app_server
469
621
  Add the following to `Gemfile`:
470
622
 
471
623
  ```
472
- gem 'opal', '1.4.1'
473
- gem 'opal-rails', '2.0.2'
474
- gem 'opal-async', '~> 1.4.0'
475
- gem 'opal-jquery', '~> 0.4.6'
476
- gem 'glimmer-dsl-web', '~> 0.0.6'
477
- gem 'glimmer-dsl-xml', '~> 1.3.1', require: false
478
- gem 'glimmer-dsl-css', '~> 1.2.1', require: false
624
+ gem 'glimmer-dsl-web', '~> 0.0.8'
479
625
  ```
480
626
 
481
627
  Run:
@@ -614,13 +760,7 @@ Disable the `webpacker` gem line in `Gemfile`:
614
760
  Add the following to `Gemfile`:
615
761
 
616
762
  ```ruby
617
- gem 'opal', '1.4.1'
618
- gem 'opal-rails', '2.0.2'
619
- gem 'opal-async', '~> 1.4.0'
620
- gem 'opal-jquery', '~> 0.4.6'
621
- gem 'glimmer-dsl-web', '~> 0.0.6'
622
- gem 'glimmer-dsl-xml', '~> 1.3.1', require: false
623
- gem 'glimmer-dsl-css', '~> 1.2.1', require: false
763
+ gem 'glimmer-dsl-web', '~> 0.0.8'
624
764
  ```
625
765
 
626
766
  Run:
@@ -1140,7 +1280,10 @@ Address = Struct.new(:street, :street2, :city, :state, :zip_code, keyword_init:
1140
1280
  end
1141
1281
 
1142
1282
  def summary
1143
- values.map(&:to_s).reject(&:empty?).join(', ')
1283
+ string_attributes = to_h.except(:billing_and_shipping)
1284
+ summary = string_attributes.values.map(&:to_s).reject(&:empty?).join(', ')
1285
+ summary += " (Billing & Shipping)" if billing_and_shipping
1286
+ summary
1144
1287
  end
1145
1288
  end
1146
1289
 
@@ -1149,14 +1292,15 @@ end
1149
1292
  street2: 'Apartment 3C, 2nd door to the right',
1150
1293
  city: 'San Diego',
1151
1294
  state: 'California',
1152
- zip_code: '91911'
1295
+ zip_code: '91911',
1296
+ billing_and_shipping: true,
1153
1297
  )
1154
1298
 
1155
1299
  include Glimmer
1156
1300
 
1157
1301
  Document.ready? do
1158
1302
  div {
1159
- form(style: 'display: grid; grid-auto-columns: 80px 200px;') { |address_form|
1303
+ div(style: 'display: grid; grid-auto-columns: 80px 260px;') { |address_div|
1160
1304
  label('Street: ', for: 'street-field')
1161
1305
  input(id: 'street-field') {
1162
1306
  # Bidirectional Data-Binding with <=> ensures input.value and @address.street
@@ -1190,16 +1334,25 @@ Document.ready? do
1190
1334
  # on_write option specifies :to_s method to invoke on value before writing to model attribute
1191
1335
  # to ensure the numeric zip code value is stored as a String
1192
1336
  value <=> [@address, :zip_code,
1193
- on_write: :to_s
1337
+ on_write: :to_s,
1194
1338
  ]
1195
1339
  }
1196
1340
 
1341
+ div(style: 'grid-column: 1 / span 2') {
1342
+ input(id: 'billing-and-shipping-field', type: 'checkbox') {
1343
+ checked <=> [@address, :billing_and_shipping]
1344
+ }
1345
+ label(for: 'billing-and-shipping-field') {
1346
+ 'Use this address for both Billing & Shipping'
1347
+ }
1348
+ }
1349
+
1197
1350
  style {
1198
1351
  <<~CSS
1199
- .#{address_form.element_id} * {
1352
+ #{address_div.selector} * {
1200
1353
  margin: 5px;
1201
1354
  }
1202
- .#{address_form.element_id} input, .#{address_form.element_id} select {
1355
+ #{address_div.selector} input, #{address_div.selector} select {
1203
1356
  grid-column: 2;
1204
1357
  }
1205
1358
  CSS
@@ -1207,10 +1360,12 @@ Document.ready? do
1207
1360
  }
1208
1361
 
1209
1362
  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
1363
+ # Unidirectional Data-Binding is done with <= to ensure @address.summary changes
1364
+ # automatically update div.inner_text
1365
+ # (computed by changes to address attributes, meaning if street changes,
1366
+ # @address.summary is automatically recomputed.)
1212
1367
  inner_text <= [@address, :summary,
1213
- computed_by: @address.members + ['state_code']
1368
+ computed_by: @address.members + ['state_code'],
1214
1369
  ]
1215
1370
  }
1216
1371
  }.render
@@ -1221,6 +1376,240 @@ Screenshot:
1221
1376
 
1222
1377
  ![Hello, Data-Binding!](/images/glimmer-dsl-web-samples-hello-hello-data-binding.gif)
1223
1378
 
1379
+ #### Hello, Content Data-Binding!
1380
+
1381
+ Glimmer GUI code:
1382
+
1383
+ ```ruby
1384
+ require 'glimmer-dsl-web'
1385
+
1386
+ class Address
1387
+ attr_accessor :text
1388
+ attr_reader :name, :street, :city, :state, :zip
1389
+
1390
+ def name=(value)
1391
+ @name = value
1392
+ update_text
1393
+ end
1394
+
1395
+ def street=(value)
1396
+ @street = value
1397
+ update_text
1398
+ end
1399
+
1400
+ def city=(value)
1401
+ @city = value
1402
+ update_text
1403
+ end
1404
+
1405
+ def state=(value)
1406
+ @state = value
1407
+ update_text
1408
+ end
1409
+
1410
+ def zip=(value)
1411
+ @zip = value
1412
+ update_text
1413
+ end
1414
+
1415
+ private
1416
+
1417
+ def update_text
1418
+ self.text = [name, street, city, state, zip].compact.reject(&:empty?).join(', ')
1419
+ end
1420
+ end
1421
+
1422
+ class User
1423
+ attr_accessor :addresses
1424
+ attr_reader :address_count
1425
+
1426
+ def initialize
1427
+ @address_count = 1
1428
+ @addresses = []
1429
+ update_addresses
1430
+ end
1431
+
1432
+ def address_count=(value)
1433
+ value = [[1, value.to_i].max, 3].min
1434
+ @address_count = value
1435
+ update_addresses
1436
+ end
1437
+
1438
+ private
1439
+
1440
+ def update_addresses
1441
+ address_count_change = address_count - addresses.size
1442
+ if address_count_change > 0
1443
+ address_count_change.times { addresses << Address.new }
1444
+ else
1445
+ address_count_change.abs.times { addresses.pop }
1446
+ end
1447
+ end
1448
+ end
1449
+
1450
+ @user = User.new
1451
+
1452
+ div {
1453
+ div {
1454
+ label('Number of addresses: ', for: 'address-count-field')
1455
+ input(id: 'address-count-field', type: 'number', min: 1, max: 3) {
1456
+ value <=> [@user, :address_count]
1457
+ }
1458
+ }
1459
+
1460
+ div {
1461
+ # Content Data-Binding is used to dynamically (re)generate content of div
1462
+ # based on changes to @user.addresses, replacing older content on every change
1463
+ content(@user, :addresses) do
1464
+ @user.addresses.each do |address|
1465
+ div {
1466
+ div(style: 'display: grid; grid-auto-columns: 80px 280px;') { |address_div|
1467
+ [:name, :street, :city, :state, :zip].each do |attribute|
1468
+ label(attribute.to_s.capitalize, for: "#{attribute}-field")
1469
+ input(id: "#{attribute}-field", type: 'text') {
1470
+ value <=> [address, attribute]
1471
+ }
1472
+ end
1473
+
1474
+ div(style: 'grid-column: 1 / span 2;') {
1475
+ inner_text <= [address, :text]
1476
+ }
1477
+
1478
+ style {
1479
+ <<~CSS
1480
+ #{address_div.selector} {
1481
+ margin: 10px 0;
1482
+ }
1483
+ #{address_div.selector} * {
1484
+ margin: 5px;
1485
+ }
1486
+ #{address_div.selector} label {
1487
+ grid-column: 1;
1488
+ }
1489
+ #{address_div.selector} input, #{address_div.selector} select {
1490
+ grid-column: 2;
1491
+ }
1492
+ CSS
1493
+ }
1494
+ }
1495
+ }
1496
+ end
1497
+ end
1498
+ }
1499
+ }.render
1500
+ ```
1501
+
1502
+ Screenshot:
1503
+
1504
+ ![Hello, Content Data-Binding!](/images/glimmer-dsl-web-samples-hello-hello-content-data-binding.gif)
1505
+
1506
+ #### Hello, Input (Date/Time)!
1507
+
1508
+ Glimmer GUI code:
1509
+
1510
+ ```ruby
1511
+ require 'glimmer-dsl-web'
1512
+
1513
+ class TimePresenter
1514
+ attr_accessor :date_time, :month_string, :week_string
1515
+
1516
+ def initialize
1517
+ @date_time = Time.now
1518
+ end
1519
+
1520
+ def month_string
1521
+ @date_time&.strftime('%Y-%m')
1522
+ end
1523
+
1524
+ def month_string=(value)
1525
+ if value.match(/^\d{4}-\d{2}$/)
1526
+ year, month = value.split('-')
1527
+ self.date_time = Time.new(year, month, date_time.day, date_time.hour, date_time.min)
1528
+ end
1529
+ end
1530
+
1531
+ def week_string
1532
+ return nil if @date_time.nil?
1533
+ year = @date_time.year
1534
+ week = ((@date_time.yday / 7).to_i + 1).to_s.rjust(2, '0')
1535
+ "#{year}-W#{week}"
1536
+ end
1537
+
1538
+ def date_time_string
1539
+ @date_time&.strftime('%Y-%m-%dT%H:%M')
1540
+ end
1541
+
1542
+ def date_time_string=(value)
1543
+ if value.match(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}$/)
1544
+ date_time_parts = value.split('T')
1545
+ date_parts = date_time_parts.first.split('-')
1546
+ time_parts = date_time_parts.last.split(':')
1547
+ self.date_time = Time.new(*date_parts, *time_parts)
1548
+ end
1549
+ end
1550
+ end
1551
+
1552
+ @time_presenter = TimePresenter.new
1553
+
1554
+ include Glimmer
1555
+
1556
+ Document.ready? do
1557
+ div {
1558
+ div(style: 'display: grid; grid-auto-columns: 130px 260px;') { |container_div|
1559
+ label('Date Time: ', for: 'date-time-field')
1560
+ input(id: 'date-time-field', type: 'datetime-local') {
1561
+ # Bidirectional Data-Binding with <=> ensures input.value and @time_presenter.date_time
1562
+ # automatically stay in sync when either side changes
1563
+ value <=> [@time_presenter, :date_time]
1564
+ }
1565
+
1566
+ label('Date: ', for: 'date-field')
1567
+ input(id: 'date-field', type: 'date') {
1568
+ value <=> [@time_presenter, :date_time]
1569
+ }
1570
+
1571
+ label('Time: ', for: 'time-field')
1572
+ input(id: 'time-field', type: 'time') {
1573
+ value <=> [@time_presenter, :date_time]
1574
+ }
1575
+
1576
+ label('Month: ', for: 'month-field')
1577
+ input(id: 'month-field', type: 'month') {
1578
+ value <=> [@time_presenter, :month_string, computed_by: :date_time]
1579
+ }
1580
+
1581
+ label('Week: ', for: 'week-field')
1582
+ input(id: 'week-field', type: 'week', disabled: true) {
1583
+ value <=> [@time_presenter, :week_string, computed_by: :date_time]
1584
+ }
1585
+
1586
+ label('Time String: ', for: 'time-string-field')
1587
+ input(id: 'time-string-field', type: 'text') {
1588
+ value <=> [@time_presenter, :date_time_string, computed_by: :date_time]
1589
+ }
1590
+
1591
+ style {
1592
+ <<~CSS
1593
+ #{container_div.selector} * {
1594
+ margin: 5px;
1595
+ }
1596
+ #{container_div.selector} label {
1597
+ grid-column: 1;
1598
+ }
1599
+ #{container_div.selector} input {
1600
+ grid-column: 2;
1601
+ }
1602
+ CSS
1603
+ }
1604
+ }
1605
+ }.render
1606
+ end
1607
+ ```
1608
+
1609
+ Screenshot:
1610
+
1611
+ ![Hello, Input (Date/Time)!](/images/glimmer-dsl-web-samples-hello-hello-input-date-time.gif)
1612
+
1224
1613
  #### Button Counter
1225
1614
 
1226
1615
  **UPCOMING (NOT RELEASED OR SUPPORTED YET)**
@@ -1351,7 +1740,7 @@ These features have been suggested. You might see them in a future version of Gl
1351
1740
 
1352
1741
  [MIT](https://opensource.org/licenses/MIT)
1353
1742
 
1354
- Copyright (c) 2023 - Andy Maleh.
1743
+ Copyright (c) 2023-2024 - Andy Maleh.
1355
1744
  See [LICENSE.txt](LICENSE.txt) for further details.
1356
1745
 
1357
1746
  --
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.6
1
+ 0.0.8