glimmer-dsl-web 0.0.8 → 0.0.10
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +17 -0
- data/README.md +610 -175
- data/VERSION +1 -1
- data/glimmer-dsl-web.gemspec +10 -5
- data/lib/glimmer/dsl/web/component_expression.rb +30 -0
- data/lib/glimmer/dsl/web/dsl.rb +2 -0
- data/lib/glimmer/helpers/glimmer_helper.rb +32 -0
- data/lib/glimmer/web/component.rb +319 -0
- data/lib/glimmer/web/element_proxy.rb +13 -4
- data/lib/glimmer-dsl-web/samples/hello/hello_component.rb +223 -0
- data/lib/glimmer-dsl-web/samples/hello/hello_content_data_binding.rb +46 -42
- data/lib/glimmer-dsl-web/samples/hello/hello_glimmer_component_helper/address_form.rb +156 -0
- data/lib/glimmer-dsl-web.rb +1 -0
- metadata +11 -6
data/README.md
CHANGED
@@ -1,11 +1,9 @@
|
|
1
|
-
# [<img src="https://raw.githubusercontent.com/AndyObtiva/glimmer/master/images/glimmer-logo-hi-res.png" height=85 />](https://github.com/AndyObtiva/glimmer) Glimmer DSL for Web 0.0.
|
1
|
+
# [<img src="https://raw.githubusercontent.com/AndyObtiva/glimmer/master/images/glimmer-logo-hi-res.png" height=85 />](https://github.com/AndyObtiva/glimmer) Glimmer DSL for Web 0.0.10 (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, 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)
|
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](#hello-data-binding) (using `<=`) and Bidirectional (Two-Way) [Data-Binding](#hello-data-binding) (using `<=>`). Dynamic rendering (and re-rendering) of HTML content is also supported via [Content Data-Binding](#hello-content-data-binding). And, modular design is supported with [Glimmer Web Components](#hello-component). 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)!
|
9
7
|
|
10
8
|
**Hello, World! Sample**
|
11
9
|
|
@@ -416,141 +414,287 @@ end
|
|
416
414
|
|
417
415
|
@user = User.new
|
418
416
|
|
419
|
-
|
417
|
+
include Glimmer
|
418
|
+
|
419
|
+
Document.ready? do
|
420
420
|
div {
|
421
|
-
|
422
|
-
|
423
|
-
|
421
|
+
div {
|
422
|
+
label('Number of addresses: ', for: 'address-count-field')
|
423
|
+
input(id: 'address-count-field', type: 'number', min: 1, max: 3) {
|
424
|
+
value <=> [@user, :address_count]
|
425
|
+
}
|
424
426
|
}
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
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;
|
427
|
+
|
428
|
+
div {
|
429
|
+
# Content Data-Binding is used to dynamically (re)generate content of div
|
430
|
+
# based on changes to @user.addresses, replacing older content on every change
|
431
|
+
content(@user, :addresses) do
|
432
|
+
@user.addresses.each do |address|
|
433
|
+
div {
|
434
|
+
div(style: 'display: grid; grid-auto-columns: 80px 280px;') { |address_div|
|
435
|
+
[:name, :street, :city, :state, :zip].each do |attribute|
|
436
|
+
label(attribute.to_s.capitalize, for: "#{attribute}-field")
|
437
|
+
input(id: "#{attribute}-field", type: 'text') {
|
438
|
+
value <=> [address, attribute]
|
458
439
|
}
|
459
|
-
|
440
|
+
end
|
441
|
+
|
442
|
+
div(style: 'grid-column: 1 / span 2;') {
|
443
|
+
inner_text <= [address, :text]
|
444
|
+
}
|
445
|
+
|
446
|
+
style {
|
447
|
+
<<~CSS
|
448
|
+
#{address_div.selector} {
|
449
|
+
margin: 10px 0;
|
450
|
+
}
|
451
|
+
#{address_div.selector} * {
|
452
|
+
margin: 5px;
|
453
|
+
}
|
454
|
+
#{address_div.selector} label {
|
455
|
+
grid-column: 1;
|
456
|
+
}
|
457
|
+
#{address_div.selector} input, #{address_div.selector} select {
|
458
|
+
grid-column: 2;
|
459
|
+
}
|
460
|
+
CSS
|
461
|
+
}
|
460
462
|
}
|
461
463
|
}
|
462
|
-
|
464
|
+
end
|
463
465
|
end
|
464
|
-
|
465
|
-
}
|
466
|
-
|
466
|
+
}
|
467
|
+
}.render
|
468
|
+
end
|
467
469
|
```
|
468
470
|
|
469
471
|
Screenshot:
|
470
472
|
|
471
473
|
![Hello, Content Data-Binding!](/images/glimmer-dsl-web-samples-hello-hello-content-data-binding.gif)
|
472
474
|
|
473
|
-
**
|
475
|
+
**Hello, Component!**
|
474
476
|
|
475
|
-
|
477
|
+
You can define Glimmer web components (View components) to reuse visual concepts to your heart's content,
|
478
|
+
by simply defining a class with `include Glimmer::Web::Component` and encasing the reusable markup inside
|
479
|
+
a `markup {...}` block. Glimmer web components automatically extend the Glimmer GUI DSL with new keywords
|
480
|
+
that match the underscored versions of the component class names (e.g. a `OrderSummary` class yields
|
481
|
+
the `order_summary` keyword for reusing that component within the Glimmer GUI DSL).
|
482
|
+
You may also insert a Glimmer component anywhere into a Rails application View using
|
483
|
+
`glimmer_component(component_path, *args)` Rails helper. Add `include GlimmerHelper` to `ApplicationHelper`
|
484
|
+
or another Rails helper, and use `<%= glimmer_component("path/to/component", *args) %>` in Views.
|
485
|
+
Below, we define an `AddressForm` component that generates a `address_form` keyword, and then we
|
486
|
+
reuse it twice inside an `AddressPage` component displaying a Shipping Address and a Billing Address.
|
476
487
|
|
477
|
-
Glimmer GUI code
|
488
|
+
Glimmer GUI code:
|
478
489
|
|
479
490
|
```ruby
|
480
491
|
require 'glimmer-dsl-web'
|
481
492
|
|
482
|
-
|
483
|
-
|
484
|
-
|
485
|
-
|
486
|
-
|
493
|
+
Address = Struct.new(:full_name, :street, :street2, :city, :state, :zip_code, keyword_init: true) do
|
494
|
+
STATES = {
|
495
|
+
"AK"=>"Alaska",
|
496
|
+
"AL"=>"Alabama",
|
497
|
+
"AR"=>"Arkansas",
|
498
|
+
"AS"=>"American Samoa",
|
499
|
+
"AZ"=>"Arizona",
|
500
|
+
"CA"=>"California",
|
501
|
+
"CO"=>"Colorado",
|
502
|
+
"CT"=>"Connecticut",
|
503
|
+
"DC"=>"District of Columbia",
|
504
|
+
"DE"=>"Delaware",
|
505
|
+
"FL"=>"Florida",
|
506
|
+
"GA"=>"Georgia",
|
507
|
+
"GU"=>"Guam",
|
508
|
+
"HI"=>"Hawaii",
|
509
|
+
"IA"=>"Iowa",
|
510
|
+
"ID"=>"Idaho",
|
511
|
+
"IL"=>"Illinois",
|
512
|
+
"IN"=>"Indiana",
|
513
|
+
"KS"=>"Kansas",
|
514
|
+
"KY"=>"Kentucky",
|
515
|
+
"LA"=>"Louisiana",
|
516
|
+
"MA"=>"Massachusetts",
|
517
|
+
"MD"=>"Maryland",
|
518
|
+
"ME"=>"Maine",
|
519
|
+
"MI"=>"Michigan",
|
520
|
+
"MN"=>"Minnesota",
|
521
|
+
"MO"=>"Missouri",
|
522
|
+
"MS"=>"Mississippi",
|
523
|
+
"MT"=>"Montana",
|
524
|
+
"NC"=>"North Carolina",
|
525
|
+
"ND"=>"North Dakota",
|
526
|
+
"NE"=>"Nebraska",
|
527
|
+
"NH"=>"New Hampshire",
|
528
|
+
"NJ"=>"New Jersey",
|
529
|
+
"NM"=>"New Mexico",
|
530
|
+
"NV"=>"Nevada",
|
531
|
+
"NY"=>"New York",
|
532
|
+
"OH"=>"Ohio",
|
533
|
+
"OK"=>"Oklahoma",
|
534
|
+
"OR"=>"Oregon",
|
535
|
+
"PA"=>"Pennsylvania",
|
536
|
+
"PR"=>"Puerto Rico",
|
537
|
+
"RI"=>"Rhode Island",
|
538
|
+
"SC"=>"South Carolina",
|
539
|
+
"SD"=>"South Dakota",
|
540
|
+
"TN"=>"Tennessee",
|
541
|
+
"TX"=>"Texas",
|
542
|
+
"UT"=>"Utah",
|
543
|
+
"VA"=>"Virginia",
|
544
|
+
"VI"=>"Virgin Islands",
|
545
|
+
"VT"=>"Vermont",
|
546
|
+
"WA"=>"Washington",
|
547
|
+
"WI"=>"Wisconsin",
|
548
|
+
"WV"=>"West Virginia",
|
549
|
+
"WY"=>"Wyoming"
|
550
|
+
}
|
551
|
+
|
552
|
+
def state_code
|
553
|
+
STATES.invert[state]
|
554
|
+
end
|
555
|
+
|
556
|
+
def state_code=(value)
|
557
|
+
self.state = STATES[value]
|
487
558
|
end
|
488
559
|
|
489
|
-
def
|
490
|
-
|
560
|
+
def summary
|
561
|
+
to_h.values.map(&:to_s).reject(&:empty?).join(', ')
|
491
562
|
end
|
492
563
|
end
|
493
564
|
|
494
|
-
|
565
|
+
# AddressForm Glimmer Web Component (View component)
|
566
|
+
#
|
567
|
+
# Including Glimmer::Web::Component makes this class a View component and automatically
|
568
|
+
# generates a new Glimmer GUI DSL keyword that matches the lowercase underscored version
|
569
|
+
# of the name of the class. AddressForm generates address_form keyword, which can be used
|
570
|
+
# elsewhere in Glimmer GUI DSL code as done inside AddressPage below.
|
571
|
+
class AddressForm
|
495
572
|
include Glimmer::Web::Component
|
496
573
|
|
497
|
-
|
498
|
-
|
499
|
-
|
574
|
+
option :address
|
575
|
+
|
576
|
+
# Optionally, you can execute code before rendering markup.
|
577
|
+
# This is useful for pre-setup of variables (e.g. Models) that you would use in the markup.
|
578
|
+
#
|
579
|
+
# before_render do
|
580
|
+
# end
|
500
581
|
|
582
|
+
# Optionally, you can execute code after rendering markup.
|
583
|
+
# This is useful for post-setup of extra Model listeners that would interact with the
|
584
|
+
# markup elements and expect them to be rendered already.
|
585
|
+
#
|
586
|
+
# after_render do
|
587
|
+
# end
|
588
|
+
|
589
|
+
# markup block provides the content of the
|
501
590
|
markup {
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
# is read and converted to "Click To Increment: #{value} ", and then automatically
|
509
|
-
# copied to button innerText (content) to display to the user
|
510
|
-
inner_text <= [@counter, :count, on_read: ->(value) { "Click To Increment: #{value} " }]
|
591
|
+
div {
|
592
|
+
div(style: 'display: grid; grid-auto-columns: 80px 260px;') { |address_div|
|
593
|
+
label('Full Name: ', for: 'full-name-field')
|
594
|
+
input(id: 'full-name-field') {
|
595
|
+
value <=> [address, :full_name]
|
596
|
+
}
|
511
597
|
|
512
|
-
|
513
|
-
|
598
|
+
@somelabel = label('Street: ', for: 'street-field')
|
599
|
+
input(id: 'street-field') {
|
600
|
+
value <=> [address, :street]
|
601
|
+
}
|
602
|
+
|
603
|
+
label('Street 2: ', for: 'street2-field')
|
604
|
+
textarea(id: 'street2-field') {
|
605
|
+
value <=> [address, :street2]
|
606
|
+
}
|
607
|
+
|
608
|
+
label('City: ', for: 'city-field')
|
609
|
+
input(id: 'city-field') {
|
610
|
+
value <=> [address, :city]
|
611
|
+
}
|
612
|
+
|
613
|
+
label('State: ', for: 'state-field')
|
614
|
+
select(id: 'state-field') {
|
615
|
+
Address::STATES.each do |state_code, state|
|
616
|
+
option(value: state_code) { state }
|
617
|
+
end
|
618
|
+
|
619
|
+
value <=> [address, :state_code]
|
620
|
+
}
|
621
|
+
|
622
|
+
label('Zip Code: ', for: 'zip-code-field')
|
623
|
+
input(id: 'zip-code-field', type: 'number', min: '0', max: '99999') {
|
624
|
+
value <=> [address, :zip_code,
|
625
|
+
on_write: :to_s,
|
626
|
+
]
|
627
|
+
}
|
628
|
+
|
629
|
+
style {
|
630
|
+
<<~CSS
|
631
|
+
#{address_div.selector} * {
|
632
|
+
margin: 5px;
|
633
|
+
}
|
634
|
+
#{address_div.selector} input, #{address_div.selector} select {
|
635
|
+
grid-column: 2;
|
636
|
+
}
|
637
|
+
CSS
|
514
638
|
}
|
515
639
|
}
|
640
|
+
|
641
|
+
div(style: 'margin: 5px') {
|
642
|
+
inner_text <= [address, :summary,
|
643
|
+
computed_by: address.members + ['state_code'],
|
644
|
+
]
|
645
|
+
}
|
516
646
|
}
|
517
647
|
}
|
518
648
|
end
|
519
649
|
|
520
|
-
|
521
|
-
|
522
|
-
|
523
|
-
|
524
|
-
|
525
|
-
|
526
|
-
|
527
|
-
|
528
|
-
|
529
|
-
|
530
|
-
|
531
|
-
|
532
|
-
|
533
|
-
|
534
|
-
|
535
|
-
|
536
|
-
|
537
|
-
|
538
|
-
|
539
|
-
|
540
|
-
|
541
|
-
|
542
|
-
|
543
|
-
|
650
|
+
# AddressPage Glimmer Web Component (View component)
|
651
|
+
#
|
652
|
+
# This View component represents the main page being rendered,
|
653
|
+
# as done by its `render` class method below
|
654
|
+
class AddressPage
|
655
|
+
include Glimmer::Web::Component
|
656
|
+
|
657
|
+
before_render do
|
658
|
+
@shipping_address = Address.new(
|
659
|
+
full_name: 'Johnny Doe',
|
660
|
+
street: '3922 Park Ave',
|
661
|
+
street2: 'PO BOX 8382',
|
662
|
+
city: 'San Diego',
|
663
|
+
state: 'California',
|
664
|
+
zip_code: '91913',
|
665
|
+
)
|
666
|
+
@billing_address = Address.new(
|
667
|
+
full_name: 'John C Doe',
|
668
|
+
street: '123 Main St',
|
669
|
+
street2: 'Apartment 3C',
|
670
|
+
city: 'San Diego',
|
671
|
+
state: 'California',
|
672
|
+
zip_code: '91911',
|
673
|
+
)
|
674
|
+
end
|
675
|
+
|
676
|
+
markup {
|
677
|
+
div {
|
678
|
+
h1('Shipping Address')
|
679
|
+
|
680
|
+
address_form(address: @shipping_address)
|
681
|
+
|
682
|
+
h1('Billing Address')
|
683
|
+
|
684
|
+
address_form(address: @billing_address)
|
685
|
+
}
|
686
|
+
}
|
687
|
+
end
|
544
688
|
|
545
|
-
|
546
|
-
|
547
|
-
|
548
|
-
|
549
|
-
</button>
|
550
|
-
</div>
|
689
|
+
Document.ready? do
|
690
|
+
# renders a top-level (root) AddressPage component
|
691
|
+
AddressPage.render
|
692
|
+
end
|
551
693
|
```
|
552
694
|
|
695
|
+
Screenshot:
|
553
696
|
|
697
|
+
![Hello, Component!](/images/glimmer-dsl-web-samples-hello-hello-component.png)
|
554
698
|
|
555
699
|
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.
|
556
700
|
|
@@ -575,6 +719,7 @@ Learn more about the differences between various [Glimmer](https://github.com/An
|
|
575
719
|
- [Hello, Form!](#hello-form)
|
576
720
|
- [Hello, Data-Binding!](#hello-data-binding)
|
577
721
|
- [Hello, Content Data-Binding!](#hello-content-data-binding)
|
722
|
+
- [Hello, Component!](#hello-content-data-binding)
|
578
723
|
- [Hello, Input (Date/Time)!](#hello-input-datetime)
|
579
724
|
- [Button Counter](#button-counter)
|
580
725
|
- [Glimmer Process](#glimmer-process)
|
@@ -621,7 +766,7 @@ rails new glimmer_app_server
|
|
621
766
|
Add the following to `Gemfile`:
|
622
767
|
|
623
768
|
```
|
624
|
-
gem 'glimmer-dsl-web', '~> 0.0.
|
769
|
+
gem 'glimmer-dsl-web', '~> 0.0.10'
|
625
770
|
```
|
626
771
|
|
627
772
|
Run:
|
@@ -630,15 +775,25 @@ Run:
|
|
630
775
|
bundle
|
631
776
|
```
|
632
777
|
|
778
|
+
(run `rm -rf tmp/cache` from inside your Rails app if you upgrade your `glimmer-dsl-web` gem version from an older one to clear Opal-Rails's cache)
|
779
|
+
|
633
780
|
Follow [opal-rails](https://github.com/opal/opal-rails) instructions, basically running:
|
634
781
|
|
635
782
|
```
|
636
783
|
bin/rails g opal:install
|
637
784
|
```
|
638
785
|
|
639
|
-
|
640
|
-
|
786
|
+
To enable the `glimmer-dsl-web` library in the frontend, edit `config/initializers/assets.rb` and add the following at the bottom:
|
787
|
+
|
788
|
+
```ruby
|
641
789
|
Opal.use_gem 'glimmer-dsl-web'
|
790
|
+
Opal.append_path Rails.root.join('app', 'assets', 'opal')
|
791
|
+
```
|
792
|
+
|
793
|
+
To enable Opal Browser Debugging in Ruby with the [Source Maps](https://opalrb.com/docs/guides/v1.4.1/source_maps.html) feature, edit `config/initializers/opal.rb` and add the following inside the `Rails.application.configure do; end` block at the bottom of it:
|
794
|
+
|
795
|
+
```ruby
|
796
|
+
config.assets.debug = true
|
642
797
|
```
|
643
798
|
|
644
799
|
Run:
|
@@ -660,17 +815,22 @@ mount Glimmer::Engine => "/glimmer" # add on top
|
|
660
815
|
root to: 'welcomes#index'
|
661
816
|
```
|
662
817
|
|
663
|
-
Edit `app/views/layouts/application.html.erb` and add the following below other `stylesheet_link_tag` declarations:
|
664
|
-
|
665
|
-
```erb
|
666
|
-
<%= stylesheet_link_tag 'glimmer/glimmer', media: 'all', 'data-turbolinks-track': 'reload' %>
|
667
|
-
```
|
668
|
-
|
669
818
|
Clear the file `app/views/welcomes/index.html.erb` completely from all content.
|
670
819
|
|
671
820
|
Delete `app/javascript/application.js`
|
672
821
|
|
673
|
-
|
822
|
+
Rename `app/assets/javascript` directory to `app/assets/opal`.
|
823
|
+
|
824
|
+
Add the following lines to `app/assets/config/manifest.js` (and delete their `javascript` equivalents):
|
825
|
+
|
826
|
+
```js
|
827
|
+
//= link_tree ../../opal .js
|
828
|
+
//= link_directory ../opal .js
|
829
|
+
```
|
830
|
+
|
831
|
+
Rename `app/assets/opal/application.js.rb` to `app/assets/opal/application.rb`.
|
832
|
+
|
833
|
+
Edit and replace `app/assets/opal/application.rb` content with code below (optionally including a require statement for one of the [samples](#samples) below inside a `Document.ready? do; end` block):
|
674
834
|
|
675
835
|
```ruby
|
676
836
|
require 'glimmer-dsl-web' # brings opal and other dependencies automatically
|
@@ -680,15 +840,6 @@ require 'glimmer-dsl-web' # brings opal and other dependencies automatically
|
|
680
840
|
|
681
841
|
Example to confirm setup is working:
|
682
842
|
|
683
|
-
Initial HTML Markup:
|
684
|
-
|
685
|
-
```html
|
686
|
-
...
|
687
|
-
<div id="app-container">
|
688
|
-
</div>
|
689
|
-
...
|
690
|
-
```
|
691
|
-
|
692
843
|
Glimmer GUI code:
|
693
844
|
|
694
845
|
```ruby
|
@@ -710,13 +861,11 @@ That produces:
|
|
710
861
|
|
711
862
|
```html
|
712
863
|
...
|
713
|
-
<div
|
714
|
-
<div data-parent="#app-container" class="element element-1">
|
864
|
+
<div data-parent="body" class="element element-1">
|
715
865
|
<label class="greeting element element-2">
|
716
866
|
Hello, World!
|
717
867
|
</label>
|
718
868
|
</div>
|
719
|
-
</div>
|
720
869
|
...
|
721
870
|
```
|
722
871
|
|
@@ -731,6 +880,22 @@ You should see:
|
|
731
880
|
|
732
881
|
![setup is working](/images/glimmer-dsl-web-setup-example-working.png)
|
733
882
|
|
883
|
+
You may also insert a Glimmer component anywhere into a Rails application View using `glimmer_component(component_path, *args)` Rails helper. Add `include GlimmerHelper` to `ApplicationHelper` or another Rails helper, and use `<%= glimmer_component("path/to/component", *args) %>` in Views.
|
884
|
+
|
885
|
+
To use `glimmer_component`, edit `app/helpers/application_helper.rb` in your Rails application, add `require 'glimmer/helpers/glimmer_helper'` on top and `include GlimmerHelper` inside `module`.
|
886
|
+
|
887
|
+
`app/helpers/application_helper.rb` should look like this after the change:
|
888
|
+
|
889
|
+
```ruby
|
890
|
+
require 'glimmer/helpers/glimmer_helper'
|
891
|
+
|
892
|
+
module ApplicationHelper
|
893
|
+
# ...
|
894
|
+
include GlimmerHelper
|
895
|
+
# ...
|
896
|
+
end
|
897
|
+
```
|
898
|
+
|
734
899
|
If you run into any issues in setup, refer to the [Sample Glimmer DSL for Web Rails 7 App](https://github.com/AndyObtiva/sample-glimmer-dsl-web-rails7-app) project (in case I forgot to include some setup steps by mistake).
|
735
900
|
|
736
901
|
Otherwise, if you still cannot setup successfully (even with the help of the sample project, or if the sample project stops working), please do not hesitate to report an [Issue request](https://github.com/AndyObtiva/glimmer-dsl-web/issues) or fix and submit a [Pull Request](https://github.com/AndyObtiva/glimmer-dsl-web/pulls).
|
@@ -760,7 +925,7 @@ Disable the `webpacker` gem line in `Gemfile`:
|
|
760
925
|
Add the following to `Gemfile`:
|
761
926
|
|
762
927
|
```ruby
|
763
|
-
gem 'glimmer-dsl-web', '~> 0.0.
|
928
|
+
gem 'glimmer-dsl-web', '~> 0.0.10'
|
764
929
|
```
|
765
930
|
|
766
931
|
Run:
|
@@ -769,15 +934,25 @@ Run:
|
|
769
934
|
bundle
|
770
935
|
```
|
771
936
|
|
937
|
+
(run `rm -rf tmp/cache` from inside your Rails app if you upgrade your `glimmer-dsl-web` gem version from an older one to clear Opal-Rails's cache)
|
938
|
+
|
772
939
|
Follow [opal-rails](https://github.com/opal/opal-rails) instructions, basically running:
|
773
940
|
|
774
941
|
```
|
775
942
|
bin/rails g opal:install
|
776
943
|
```
|
777
944
|
|
778
|
-
|
779
|
-
|
945
|
+
To enable the `glimmer-dsl-web` library in the frontend, edit `config/initializers/assets.rb` and add the following at the bottom:
|
946
|
+
|
947
|
+
```ruby
|
780
948
|
Opal.use_gem 'glimmer-dsl-web'
|
949
|
+
Opal.append_path Rails.root.join('app', 'assets', 'opal')
|
950
|
+
```
|
951
|
+
|
952
|
+
To enable Opal Browser Debugging in Ruby with the [Source Maps](https://opalrb.com/docs/guides/v1.4.1/source_maps.html) feature, edit `config/initializers/opal.rb` and add the following inside the `Rails.application.configure do; end` block at the bottom of it:
|
953
|
+
|
954
|
+
```ruby
|
955
|
+
config.assets.debug = true
|
781
956
|
```
|
782
957
|
|
783
958
|
Run:
|
@@ -798,11 +973,6 @@ Add the following to `config/routes.rb` inside the `Rails.application.routes.dra
|
|
798
973
|
mount Glimmer::Engine => "/glimmer" # add on top
|
799
974
|
root to: 'welcomes#index'
|
800
975
|
```
|
801
|
-
|
802
|
-
Edit `app/views/layouts/application.html.erb` and add the following below other `stylesheet_link_tag` declarations:
|
803
|
-
|
804
|
-
```erb
|
805
|
-
<%= stylesheet_link_tag 'glimmer/glimmer', media: 'all', 'data-turbolinks-track': 'reload' %>
|
806
976
|
```
|
807
977
|
|
808
978
|
Also, delete the following line:
|
@@ -813,7 +983,18 @@ Also, delete the following line:
|
|
813
983
|
|
814
984
|
Clear the file `app/views/welcomes/index.html.erb` completely from all content.
|
815
985
|
|
816
|
-
|
986
|
+
Rename `app/assets/javascript` directory to `app/assets/opal`.
|
987
|
+
|
988
|
+
Add the following lines to `app/assets/config/manifest.js` (and delete their `javascript` equivalents):
|
989
|
+
|
990
|
+
```js
|
991
|
+
//= link_tree ../../opal .js
|
992
|
+
//= link_directory ../opal .js
|
993
|
+
```
|
994
|
+
|
995
|
+
Rename `app/assets/opal/application.js.rb` to `app/assets/opal/application.rb`.
|
996
|
+
|
997
|
+
Edit and replace `app/assets/opal/application.rb` content with code below (optionally including a require statement for one of the [samples](#samples) below inside a `Document.ready? do; end` block):
|
817
998
|
|
818
999
|
```ruby
|
819
1000
|
require 'glimmer-dsl-web' # brings opal and other dependencies automatically
|
@@ -874,6 +1055,22 @@ You should see:
|
|
874
1055
|
|
875
1056
|
![setup is working](/images/glimmer-dsl-web-setup-example-working.png)
|
876
1057
|
|
1058
|
+
You may also insert a Glimmer component anywhere into a Rails application View using `glimmer_component(component_path, *args)` Rails helper. Add `include GlimmerHelper` to `ApplicationHelper` or another Rails helper, and use `<%= glimmer_component("path/to/component", *args) %>` in Views.
|
1059
|
+
|
1060
|
+
To use `glimmer_component`, edit `app/helpers/application_helper.rb` in your Rails application, add `require 'glimmer/helpers/glimmer_helper'` on top and `include GlimmerHelper` inside `module`.
|
1061
|
+
|
1062
|
+
`app/helpers/application_helper.rb` should look like this after the change:
|
1063
|
+
|
1064
|
+
```ruby
|
1065
|
+
require 'glimmer/helpers/glimmer_helper'
|
1066
|
+
|
1067
|
+
module ApplicationHelper
|
1068
|
+
# ...
|
1069
|
+
include GlimmerHelper
|
1070
|
+
# ...
|
1071
|
+
end
|
1072
|
+
```
|
1073
|
+
|
877
1074
|
**NOT RELEASED OR SUPPORTED YET**
|
878
1075
|
|
879
1076
|
If you run into any issues in setup, refer to the [Sample Glimmer DSL for Web Rails 6 App](https://github.com/AndyObtiva/sample-glimmer-dsl-web-rails6-app) project (in case I forgot to include some setup steps by mistake).
|
@@ -886,7 +1083,7 @@ Glimmer DSL for Web offers a GUI DSL for building HTML Web User Interfaces decla
|
|
886
1083
|
|
887
1084
|
1- **Keywords (HTML Elements)**
|
888
1085
|
|
889
|
-
You can declare any HTML element by simply using the lowercase
|
1086
|
+
You can declare any HTML element by simply using the lowercase version of its name (Ruby convention for method names) like `div`, `span`, `form`, `input`, `button`, `table`, `tr`, `th`, and `td`.
|
890
1087
|
|
891
1088
|
Under the hood, HTML element DSL keywords are invoked as Ruby methods.
|
892
1089
|
|
@@ -1022,6 +1219,8 @@ That produces the following under `<body></body>`:
|
|
1022
1219
|
|
1023
1220
|
#### Hello, Button!
|
1024
1221
|
|
1222
|
+
Event listeners can be setup on any element using the same event names used in HTML (e.g. `onclick`) while passing in a standard Ruby block to handle behavior. `$$` gives access to `window` to invoke functions like `alert`.
|
1223
|
+
|
1025
1224
|
Glimmer GUI code:
|
1026
1225
|
|
1027
1226
|
```ruby
|
@@ -1054,6 +1253,8 @@ Screenshot:
|
|
1054
1253
|
|
1055
1254
|
#### Hello, Form!
|
1056
1255
|
|
1256
|
+
[Glimmer DSL for Web](https://rubygems.org/gems/glimmer-dsl-web) gives access to all Web Browser built-in features like HTML form validations, input focus, events, and element functions from a very terse and productive Ruby GUI DSL.
|
1257
|
+
|
1057
1258
|
Glimmer GUI code:
|
1058
1259
|
|
1059
1260
|
```ruby
|
@@ -1207,6 +1408,8 @@ Screenshot:
|
|
1207
1408
|
|
1208
1409
|
#### Hello, Data-Binding!
|
1209
1410
|
|
1411
|
+
[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.
|
1412
|
+
|
1210
1413
|
Glimmer GUI code:
|
1211
1414
|
|
1212
1415
|
```ruby
|
@@ -1378,6 +1581,10 @@ Screenshot:
|
|
1378
1581
|
|
1379
1582
|
#### Hello, Content Data-Binding!
|
1380
1583
|
|
1584
|
+
If you need to regenerate HTML element content dynamically, you can use Content Data-Binding to effortlessly
|
1585
|
+
rebuild HTML elements based on changes in a Model attribute that provides the source data.
|
1586
|
+
In this example, we generate multiple address forms based on the number of addresses the user has.
|
1587
|
+
|
1381
1588
|
Glimmer GUI code:
|
1382
1589
|
|
1383
1590
|
```ruby
|
@@ -1449,59 +1656,287 @@ end
|
|
1449
1656
|
|
1450
1657
|
@user = User.new
|
1451
1658
|
|
1452
|
-
|
1659
|
+
include Glimmer
|
1660
|
+
|
1661
|
+
Document.ready? do
|
1453
1662
|
div {
|
1454
|
-
|
1455
|
-
|
1456
|
-
|
1663
|
+
div {
|
1664
|
+
label('Number of addresses: ', for: 'address-count-field')
|
1665
|
+
input(id: 'address-count-field', type: 'number', min: 1, max: 3) {
|
1666
|
+
value <=> [@user, :address_count]
|
1667
|
+
}
|
1668
|
+
}
|
1669
|
+
|
1670
|
+
div {
|
1671
|
+
# Content Data-Binding is used to dynamically (re)generate content of div
|
1672
|
+
# based on changes to @user.addresses, replacing older content on every change
|
1673
|
+
content(@user, :addresses) do
|
1674
|
+
@user.addresses.each do |address|
|
1675
|
+
div {
|
1676
|
+
div(style: 'display: grid; grid-auto-columns: 80px 280px;') { |address_div|
|
1677
|
+
[:name, :street, :city, :state, :zip].each do |attribute|
|
1678
|
+
label(attribute.to_s.capitalize, for: "#{attribute}-field")
|
1679
|
+
input(id: "#{attribute}-field", type: 'text') {
|
1680
|
+
value <=> [address, attribute]
|
1681
|
+
}
|
1682
|
+
end
|
1683
|
+
|
1684
|
+
div(style: 'grid-column: 1 / span 2;') {
|
1685
|
+
inner_text <= [address, :text]
|
1686
|
+
}
|
1687
|
+
|
1688
|
+
style {
|
1689
|
+
<<~CSS
|
1690
|
+
#{address_div.selector} {
|
1691
|
+
margin: 10px 0;
|
1692
|
+
}
|
1693
|
+
#{address_div.selector} * {
|
1694
|
+
margin: 5px;
|
1695
|
+
}
|
1696
|
+
#{address_div.selector} label {
|
1697
|
+
grid-column: 1;
|
1698
|
+
}
|
1699
|
+
#{address_div.selector} input, #{address_div.selector} select {
|
1700
|
+
grid-column: 2;
|
1701
|
+
}
|
1702
|
+
CSS
|
1703
|
+
}
|
1704
|
+
}
|
1705
|
+
}
|
1706
|
+
end
|
1707
|
+
end
|
1457
1708
|
}
|
1709
|
+
}.render
|
1710
|
+
end
|
1711
|
+
```
|
1712
|
+
|
1713
|
+
Screenshot:
|
1714
|
+
|
1715
|
+
![Hello, Content Data-Binding!](/images/glimmer-dsl-web-samples-hello-hello-content-data-binding.gif)
|
1716
|
+
|
1717
|
+
#### Hello, Component!
|
1718
|
+
|
1719
|
+
You can define Glimmer web components (View components) to reuse visual concepts to your heart's content,
|
1720
|
+
by simply defining a class with `include Glimmer::Web::Component` and encasing the reusable markup inside
|
1721
|
+
a `markup {...}` block. Glimmer web components automatically extend the Glimmer GUI DSL with new keywords
|
1722
|
+
that match the underscored versions of the component class names (e.g. a `OrderSummary` class yields
|
1723
|
+
the `order_summary` keyword for reusing that component within the Glimmer GUI DSL).
|
1724
|
+
You may also insert a Glimmer component anywhere into a Rails application View using
|
1725
|
+
`glimmer_component(component_path, *args)` Rails helper. Add `include GlimmerHelper` to `ApplicationHelper`
|
1726
|
+
or another Rails helper, and use `<%= glimmer_component("path/to/component", *args) %>` in Views.
|
1727
|
+
Below, we define an `AddressForm` component that generates a `address_form` keyword, and then we
|
1728
|
+
reuse it twice inside an `AddressPage` component displaying a Shipping Address and a Billing Address.
|
1729
|
+
|
1730
|
+
Glimmer GUI code:
|
1731
|
+
|
1732
|
+
```ruby
|
1733
|
+
require 'glimmer-dsl-web'
|
1734
|
+
|
1735
|
+
Address = Struct.new(:full_name, :street, :street2, :city, :state, :zip_code, keyword_init: true) do
|
1736
|
+
STATES = {
|
1737
|
+
"AK"=>"Alaska",
|
1738
|
+
"AL"=>"Alabama",
|
1739
|
+
"AR"=>"Arkansas",
|
1740
|
+
"AS"=>"American Samoa",
|
1741
|
+
"AZ"=>"Arizona",
|
1742
|
+
"CA"=>"California",
|
1743
|
+
"CO"=>"Colorado",
|
1744
|
+
"CT"=>"Connecticut",
|
1745
|
+
"DC"=>"District of Columbia",
|
1746
|
+
"DE"=>"Delaware",
|
1747
|
+
"FL"=>"Florida",
|
1748
|
+
"GA"=>"Georgia",
|
1749
|
+
"GU"=>"Guam",
|
1750
|
+
"HI"=>"Hawaii",
|
1751
|
+
"IA"=>"Iowa",
|
1752
|
+
"ID"=>"Idaho",
|
1753
|
+
"IL"=>"Illinois",
|
1754
|
+
"IN"=>"Indiana",
|
1755
|
+
"KS"=>"Kansas",
|
1756
|
+
"KY"=>"Kentucky",
|
1757
|
+
"LA"=>"Louisiana",
|
1758
|
+
"MA"=>"Massachusetts",
|
1759
|
+
"MD"=>"Maryland",
|
1760
|
+
"ME"=>"Maine",
|
1761
|
+
"MI"=>"Michigan",
|
1762
|
+
"MN"=>"Minnesota",
|
1763
|
+
"MO"=>"Missouri",
|
1764
|
+
"MS"=>"Mississippi",
|
1765
|
+
"MT"=>"Montana",
|
1766
|
+
"NC"=>"North Carolina",
|
1767
|
+
"ND"=>"North Dakota",
|
1768
|
+
"NE"=>"Nebraska",
|
1769
|
+
"NH"=>"New Hampshire",
|
1770
|
+
"NJ"=>"New Jersey",
|
1771
|
+
"NM"=>"New Mexico",
|
1772
|
+
"NV"=>"Nevada",
|
1773
|
+
"NY"=>"New York",
|
1774
|
+
"OH"=>"Ohio",
|
1775
|
+
"OK"=>"Oklahoma",
|
1776
|
+
"OR"=>"Oregon",
|
1777
|
+
"PA"=>"Pennsylvania",
|
1778
|
+
"PR"=>"Puerto Rico",
|
1779
|
+
"RI"=>"Rhode Island",
|
1780
|
+
"SC"=>"South Carolina",
|
1781
|
+
"SD"=>"South Dakota",
|
1782
|
+
"TN"=>"Tennessee",
|
1783
|
+
"TX"=>"Texas",
|
1784
|
+
"UT"=>"Utah",
|
1785
|
+
"VA"=>"Virginia",
|
1786
|
+
"VI"=>"Virgin Islands",
|
1787
|
+
"VT"=>"Vermont",
|
1788
|
+
"WA"=>"Washington",
|
1789
|
+
"WI"=>"Wisconsin",
|
1790
|
+
"WV"=>"West Virginia",
|
1791
|
+
"WY"=>"Wyoming"
|
1458
1792
|
}
|
1459
1793
|
|
1460
|
-
|
1461
|
-
|
1462
|
-
|
1463
|
-
|
1464
|
-
|
1465
|
-
|
1466
|
-
|
1467
|
-
|
1468
|
-
|
1469
|
-
|
1470
|
-
|
1471
|
-
|
1472
|
-
|
1473
|
-
|
1474
|
-
|
1475
|
-
|
1794
|
+
def state_code
|
1795
|
+
STATES.invert[state]
|
1796
|
+
end
|
1797
|
+
|
1798
|
+
def state_code=(value)
|
1799
|
+
self.state = STATES[value]
|
1800
|
+
end
|
1801
|
+
|
1802
|
+
def summary
|
1803
|
+
to_h.values.map(&:to_s).reject(&:empty?).join(', ')
|
1804
|
+
end
|
1805
|
+
end
|
1806
|
+
|
1807
|
+
# AddressForm Glimmer Web Component (View component)
|
1808
|
+
#
|
1809
|
+
# Including Glimmer::Web::Component makes this class a View component and automatically
|
1810
|
+
# generates a new Glimmer GUI DSL keyword that matches the lowercase underscored version
|
1811
|
+
# of the name of the class. AddressForm generates address_form keyword, which can be used
|
1812
|
+
# elsewhere in Glimmer GUI DSL code as done inside AddressPage below.
|
1813
|
+
class AddressForm
|
1814
|
+
include Glimmer::Web::Component
|
1815
|
+
|
1816
|
+
option :address
|
1817
|
+
|
1818
|
+
# Optionally, you can execute code before rendering markup.
|
1819
|
+
# This is useful for pre-setup of variables (e.g. Models) that you would use in the markup.
|
1820
|
+
#
|
1821
|
+
# before_render do
|
1822
|
+
# end
|
1823
|
+
|
1824
|
+
# Optionally, you can execute code after rendering markup.
|
1825
|
+
# This is useful for post-setup of extra Model listeners that would interact with the
|
1826
|
+
# markup elements and expect them to be rendered already.
|
1827
|
+
#
|
1828
|
+
# after_render do
|
1829
|
+
# end
|
1830
|
+
|
1831
|
+
# markup block provides the content of the
|
1832
|
+
markup {
|
1833
|
+
div {
|
1834
|
+
div(style: 'display: grid; grid-auto-columns: 80px 260px;') { |address_div|
|
1835
|
+
label('Full Name: ', for: 'full-name-field')
|
1836
|
+
input(id: 'full-name-field') {
|
1837
|
+
value <=> [address, :full_name]
|
1838
|
+
}
|
1839
|
+
|
1840
|
+
@somelabel = label('Street: ', for: 'street-field')
|
1841
|
+
input(id: 'street-field') {
|
1842
|
+
value <=> [address, :street]
|
1843
|
+
}
|
1844
|
+
|
1845
|
+
label('Street 2: ', for: 'street2-field')
|
1846
|
+
textarea(id: 'street2-field') {
|
1847
|
+
value <=> [address, :street2]
|
1848
|
+
}
|
1849
|
+
|
1850
|
+
label('City: ', for: 'city-field')
|
1851
|
+
input(id: 'city-field') {
|
1852
|
+
value <=> [address, :city]
|
1853
|
+
}
|
1854
|
+
|
1855
|
+
label('State: ', for: 'state-field')
|
1856
|
+
select(id: 'state-field') {
|
1857
|
+
Address::STATES.each do |state_code, state|
|
1858
|
+
option(value: state_code) { state }
|
1859
|
+
end
|
1860
|
+
|
1861
|
+
value <=> [address, :state_code]
|
1862
|
+
}
|
1863
|
+
|
1864
|
+
label('Zip Code: ', for: 'zip-code-field')
|
1865
|
+
input(id: 'zip-code-field', type: 'number', min: '0', max: '99999') {
|
1866
|
+
value <=> [address, :zip_code,
|
1867
|
+
on_write: :to_s,
|
1868
|
+
]
|
1869
|
+
}
|
1870
|
+
|
1871
|
+
style {
|
1872
|
+
<<~CSS
|
1873
|
+
#{address_div.selector} * {
|
1874
|
+
margin: 5px;
|
1476
1875
|
}
|
1477
|
-
|
1478
|
-
|
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
|
1876
|
+
#{address_div.selector} input, #{address_div.selector} select {
|
1877
|
+
grid-column: 2;
|
1493
1878
|
}
|
1494
|
-
|
1879
|
+
CSS
|
1495
1880
|
}
|
1496
|
-
|
1497
|
-
|
1881
|
+
}
|
1882
|
+
|
1883
|
+
div(style: 'margin: 5px') {
|
1884
|
+
inner_text <= [address, :summary,
|
1885
|
+
computed_by: address.members + ['state_code'],
|
1886
|
+
]
|
1887
|
+
}
|
1888
|
+
}
|
1889
|
+
}
|
1890
|
+
end
|
1891
|
+
|
1892
|
+
# AddressPage Glimmer Web Component (View component)
|
1893
|
+
#
|
1894
|
+
# This View component represents the main page being rendered,
|
1895
|
+
# as done by its `render` class method below
|
1896
|
+
class AddressPage
|
1897
|
+
include Glimmer::Web::Component
|
1898
|
+
|
1899
|
+
before_render do
|
1900
|
+
@shipping_address = Address.new(
|
1901
|
+
full_name: 'Johnny Doe',
|
1902
|
+
street: '3922 Park Ave',
|
1903
|
+
street2: 'PO BOX 8382',
|
1904
|
+
city: 'San Diego',
|
1905
|
+
state: 'California',
|
1906
|
+
zip_code: '91913',
|
1907
|
+
)
|
1908
|
+
@billing_address = Address.new(
|
1909
|
+
full_name: 'John C Doe',
|
1910
|
+
street: '123 Main St',
|
1911
|
+
street2: 'Apartment 3C',
|
1912
|
+
city: 'San Diego',
|
1913
|
+
state: 'California',
|
1914
|
+
zip_code: '91911',
|
1915
|
+
)
|
1916
|
+
end
|
1917
|
+
|
1918
|
+
markup {
|
1919
|
+
div {
|
1920
|
+
h1('Shipping Address')
|
1921
|
+
|
1922
|
+
address_form(address: @shipping_address)
|
1923
|
+
|
1924
|
+
h1('Billing Address')
|
1925
|
+
|
1926
|
+
address_form(address: @billing_address)
|
1927
|
+
}
|
1498
1928
|
}
|
1499
|
-
|
1929
|
+
end
|
1930
|
+
|
1931
|
+
Document.ready? do
|
1932
|
+
# renders a top-level (root) AddressPage component
|
1933
|
+
AddressPage.render
|
1934
|
+
end
|
1500
1935
|
```
|
1501
1936
|
|
1502
1937
|
Screenshot:
|
1503
1938
|
|
1504
|
-
![Hello,
|
1939
|
+
![Hello, Component!](/images/glimmer-dsl-web-samples-hello-hello-component.png)
|
1505
1940
|
|
1506
1941
|
#### Hello, Input (Date/Time)!
|
1507
1942
|
|
@@ -1657,7 +2092,7 @@ class HelloButton
|
|
1657
2092
|
}
|
1658
2093
|
end
|
1659
2094
|
|
1660
|
-
HelloButton.render
|
2095
|
+
HelloButton.render
|
1661
2096
|
```
|
1662
2097
|
|
1663
2098
|
That produces:
|