glimmer-dsl-web 0.0.6 → 0.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +26 -0
- data/LICENSE.txt +1 -1
- data/README.md +425 -36
- data/VERSION +1 -1
- data/glimmer-dsl-web.gemspec +11 -8
- data/lib/glimmer/data_binding/element_binding.rb +1 -1
- data/lib/glimmer/dsl/web/bind_expression.rb +1 -1
- data/lib/glimmer/dsl/web/content_data_binding_expression.rb +41 -0
- data/lib/glimmer/dsl/web/dsl.rb +2 -9
- data/lib/glimmer/dsl/web/element_expression.rb +3 -3
- data/lib/glimmer/util/proc_tracker.rb +1 -1
- data/lib/glimmer/web/element_proxy.rb +188 -459
- data/lib/glimmer/web/event_proxy.rb +1 -1
- data/lib/glimmer/web/listener_proxy.rb +1 -2
- data/lib/glimmer/web.rb +1 -1
- data/lib/glimmer-dsl-web/ext/date.rb +4 -1
- data/lib/glimmer-dsl-web/samples/hello/hello_button.rb +1 -1
- data/lib/glimmer-dsl-web/samples/hello/hello_content_data_binding.rb +137 -0
- data/lib/glimmer-dsl-web/samples/hello/hello_data_binding.rb +26 -11
- data/lib/glimmer-dsl-web/samples/hello/hello_form.rb +1 -1
- data/lib/glimmer-dsl-web/samples/hello/hello_input_date_time.rb +117 -0
- data/lib/glimmer-dsl-web/samples/hello/hello_world.rb +1 -1
- data/lib/glimmer-dsl-web.rb +7 -3
- metadata +49 -33
- data/lib/glimmer/web/property_owner.rb +0 -24
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: '028be0c05ee74f8e639b3b43adbdd52e7b90e7e5b52a0e7026fbf74e0d017300'
|
4
|
+
data.tar.gz: d2b8e3fc639dfa55655f05faf740fd36fcf518bc143eb8f08a874be3e34f72ec
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
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.
|
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
|
[](http://badge.fury.io/rb/glimmer-dsl-web)
|
4
4
|
[](https://gitter.im/AndyObtiva/glimmer?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
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
|
-
|
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
|
-
|
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
|
-
|
315
|
+
#{address_div.selector} * {
|
299
316
|
margin: 5px;
|
300
317
|
}
|
301
|
-
|
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
|
310
|
-
#
|
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
|

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

|
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
|
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 '
|
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 '
|
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
|
-
|
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
|
-
|
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
|
-
|
1352
|
+
#{address_div.selector} * {
|
1200
1353
|
margin: 5px;
|
1201
1354
|
}
|
1202
|
-
|
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
|
1211
|
-
#
|
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
|

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

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

|
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.
|
1
|
+
0.0.8
|