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