glimmer-dsl-web 0.6.1 → 0.6.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +7 -0
- data/README.md +403 -7
- data/VERSION +1 -1
- data/glimmer-dsl-web.gemspec +4 -3
- data/lib/glimmer/dsl/web/component_expression.rb +21 -3
- data/lib/glimmer/dsl/web/component_slot_content_expression.rb +11 -5
- data/lib/glimmer/web/component.rb +19 -3
- data/lib/glimmer/web/element_proxy.rb +14 -10
- data/lib/glimmer-dsl-web/samples/hello/hello_component.rb +141 -177
- data/lib/glimmer-dsl-web/samples/hello/hello_component_listeners.rb +18 -57
- data/lib/glimmer-dsl-web/samples/hello/hello_component_listeners_default_slot.rb +349 -0
- data/lib/glimmer-dsl-web/samples/hello/hello_component_slots.rb +28 -68
- data/lib/glimmer-dsl-web/samples/hello/hello_content_data_binding.rb +3 -2
- data/lib/glimmer-dsl-web/samples/hello/hello_data_binding.rb +28 -71
- data/lib/glimmer-dsl-web/samples/regular/todo_mvc/views/todo_list.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: eecb0321e7308184ed4fb10bb9811c76a5e118cd7157e9956c29427e15717573
|
4
|
+
data.tar.gz: 61561e5ca787eb1bda14e6d2cb62fcc0b312900b0ecd1b34fcdaf16d9b9b58da
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1a19b667b26371cce71670e8cb081130802712f125cab5be871f0b28bc6e8f4ee5ec391ce6b67203d52efe5f81da928f21ac0831a6484d2617c64e5ad9d0da1c
|
7
|
+
data.tar.gz: 751743fa39296af6e62c0af64b3a54a9b40ecb74cabc8192f77f539d3e196112522e5efd8a7fa5eb61a9fe86894d0c82128294bf6ab37a4f2efb39e2d364b255
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,12 @@
|
|
1
1
|
# Change Log
|
2
2
|
|
3
|
+
## 0.6.2
|
4
|
+
|
5
|
+
- Support specifying default slot in a Component with slots (e.g. `default_slot :some_slot`) to allow consumers to insert content into default slot by adding content directly under consumed component instead of opening a block with slot name explicitly
|
6
|
+
- Support `:markup_root_slot` as a default slot that is always available to insert content into the component markup root element in case another element is specified as the default slot.
|
7
|
+
- Hello, Component Listeners (Default Slot)! Sample (has simpler code than Hello, Component Listeners! due to specifying a default slot): `require 'glimmer-dsl-web/samples/hello/hello_component_listeners_default_slot'`
|
8
|
+
- Fix opacity value for the Todo MVC sample's toggle all checkbox (make it `0` instead of `0px`)
|
9
|
+
|
3
10
|
## 0.6.1
|
4
11
|
|
5
12
|
- Hello, Component Listeners! Sample: `require 'glimmer-dsl-web/samples/hello/hello_component_listeners'`
|
data/README.md
CHANGED
@@ -1,6 +1,7 @@
|
|
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.6.
|
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.6.2 (Beta)
|
2
2
|
## Ruby-in-the-Browser Web Frontend Framework
|
3
|
-
###
|
3
|
+
### The "Rails" of Frontend Frameworks!!!
|
4
|
+
#### Finally, Ruby Developer Productivity, Happiness, and Fun in the Frontend!!!
|
4
5
|
[![Gem Version](https://badge.fury.io/rb/glimmer-dsl-web.svg)](http://badge.fury.io/rb/glimmer-dsl-web)
|
5
6
|
[![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)
|
6
7
|
|
@@ -14,7 +15,7 @@ You can finally have Ruby developer happiness and productivity in the Frontend!
|
|
14
15
|
|
15
16
|
[Glimmer](https://github.com/AndyObtiva/glimmer) DSL for Web enables building Web 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 supports Rails' principle of the One Person Framework by not requiring any extra developers with JavaScript expertise, yet enabling Ruby (Backend) Software Engineers to develop the Frontend with Ruby code that is better than any JavaScript code produced by JS developers. It aims at providing the simplest, most intuitive, most straight-forward, and most productive frontend framework in existence. The framework 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)) in building Isomorphic Ruby on Rails Applications. It provides a Ruby [HTML DSL](#usage) (including full support for [SVG](#hello-svg)), which uniquely enables writing both structure code and logic code in one language. It supports 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). Modular design is supported with [Glimmer Web Components](#hello-component), [Component Slots](#hello-component-slots), and [Component Custom Event Listeners](#hello-component-listeners). And, a Ruby CSS DSL is supported with the included [Glimmer DSL for CSS](https://github.com/AndyObtiva/glimmer-dsl-css). To automatically convert legacy HTML & CSS code to Glimmer DSL Ruby code, Software Engineers could use the included [`html_to_glimmer`](https://github.com/AndyObtiva/glimmer-dsl-xml#html-to-glimmer-converter) and [`css_to_glimmer`](https://github.com/AndyObtiva/glimmer-dsl-css#css-to-glimmer-converter) commands. Many [samples](#samples) are demonstrated in the [Rails sample app](https://github.com/AndyObtiva/sample-glimmer-dsl-web-rails7-app) (there is a very minimal [Standalone [No Rails] static site sample app](https://github.com/Largo/glimmer-dsl-web-standalone-demo) too). 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)!
|
16
17
|
|
17
|
-
[Glimmer DSL for Web](https://rubygems.org/gems/glimmer-dsl-web) aims to be a very simple Ruby-based drop-in replacement for your existing JavaScript Frontend library (e.g. React, Angular, Vue, Ember, Svelte) or your JavaScript Frontend layer in general. It does not change how your Frontend interacts with the Backend, meaning you can continue to write Rails Backend API endpoints as needed and make HTTP/Ajax requests or read data embedded in elements, but from [Ruby in the Browser](https://www.youtube.com/watch?v=4AdcfbI6A4c). Whatever is possible in JavaScript is possible when using Glimmer DSL for Web as it integrates with any existing JavaScript library. The [Rails sample app](https://github.com/AndyObtiva/sample-glimmer-dsl-web-rails7-app) demonstrates how to [make HTTP calls](https://github.com/AndyObtiva/sample-glimmer-dsl-web-rails7-app/blob/master/app/assets/opal/sample_selector/models/sample_api.rb) and how to [integrate with a JavaScript library](https://github.com/AndyObtiva/sample-glimmer-dsl-web-rails7-app/blob/master/app/views/layouts/application.html.erb) (highlightjs) that performs [code syntax highlighting](https://github.com/AndyObtiva/sample-glimmer-dsl-web-rails7-app/blob/master/app/assets/opal/sample_selector.rb).
|
18
|
+
[Glimmer DSL for Web](https://rubygems.org/gems/glimmer-dsl-web) aims to be a very simple Ruby-based drop-in replacement for your existing JavaScript Frontend library (e.g. React, Angular, Vue, Ember, Svelte) or your JavaScript Frontend layer in general. It does not change how your Frontend interacts with the Backend, meaning you can continue to write Rails Backend API endpoints as needed and make HTTP/Ajax requests or read data embedded in elements, but from [Ruby in the Browser](https://www.youtube.com/watch?v=4AdcfbI6A4c). Whatever is possible in JavaScript is possible when using Glimmer DSL for Web as it integrates with any existing JavaScript library. The [Rails sample app](https://github.com/AndyObtiva/sample-glimmer-dsl-web-rails7-app) demonstrates how to [make HTTP calls](https://github.com/AndyObtiva/sample-glimmer-dsl-web-rails7-app/blob/master/app/assets/opal/sample_selector/models/sample_api.rb) and how to [integrate with a JavaScript library](https://github.com/AndyObtiva/sample-glimmer-dsl-web-rails7-app/blob/master/app/views/layouts/application.html.erb) (highlightjs) that performs [code syntax highlighting](https://github.com/AndyObtiva/sample-glimmer-dsl-web-rails7-app/blob/master/app/assets/opal/sample_selector.rb). [Glimmer DSL for Web](https://rubygems.org/gems/glimmer-dsl-web) currently runs on [Opal](https://opalrb.com/) ([Fukuoka Ruby 2023 Award Winner](https://www.digitalfukuoka.jp/topics/228?locale=ja)), a Ruby-to-JavaScript transpiler. In the future, it might support other Frontend Ruby environments, such as [ruby.wasm](https://github.com/ruby/ruby.wasm).
|
18
19
|
|
19
20
|
After looking through the [samples](#samples) below, read the [FAQ (Frequently Asked Questions)](#faq) to learn more about how Glimmer DSL for Web compares to other approaches/libraries like Hotwire (Turbo), Phlex, ViewComponent, Angular, Vue, React, Svelte, and other JS frameworks.
|
20
21
|
|
@@ -1345,6 +1346,7 @@ Learn more about the differences between various [Glimmer](https://github.com/An
|
|
1345
1346
|
- [Hello, Component!](#hello-compoent)
|
1346
1347
|
- [Hello, Component Slots!](#hello-component-slots)
|
1347
1348
|
- [Hello, Component Listeners!](#hello-compoent-listeners)
|
1349
|
+
- [Hello, Component Listeners (Default Slot)!](#hello-compoent-listeners-default-slot)
|
1348
1350
|
- [Hello, glimmer_component Rails Helper!](#hello-glimmer_component-rails-helper)
|
1349
1351
|
- [Hello, Paragraph!](#hello-paragraph)
|
1350
1352
|
- [Hello, Style!](#hello-style)
|
@@ -1367,7 +1369,7 @@ Learn more about the differences between various [Glimmer](https://github.com/An
|
|
1367
1369
|
|
1368
1370
|
## Prerequisites
|
1369
1371
|
|
1370
|
-
[Glimmer DSL for Web](https://rubygems.org/gems/glimmer-dsl-web) will begin by supporting [Opal Ruby](https://opalrb.com/) on [Rails](https://rubyonrails.org/). [Opal](https://opalrb.com/) is a lightweight Ruby to JavaScript transpiler that results in small downloadables compared to WASM. In the future, the project might grow to support [Ruby WASM](https://github.com/ruby/ruby.wasm) as an alternative to [Opal Ruby](https://opalrb.com/) that could be switched to with a simple configuration change.
|
1372
|
+
[Glimmer DSL for Web](https://rubygems.org/gems/glimmer-dsl-web) will begin by supporting [Opal Ruby](https://opalrb.com/) on [Rails](https://rubyonrails.org/). [Opal](https://opalrb.com/) ([Fukuoka Ruby 2023 Award Winner](https://www.digitalfukuoka.jp/topics/228?locale=ja)) is a lightweight Ruby to JavaScript transpiler that results in small downloadables compared to WASM. In the future, the project might grow to support [Ruby WASM](https://github.com/ruby/ruby.wasm) as an alternative to [Opal Ruby](https://opalrb.com/) that could be switched to with a simple configuration change.
|
1371
1373
|
|
1372
1374
|
- Ruby 3.0+
|
1373
1375
|
- Rails 7: [https://github.com/rails/rails](https://github.com/rails/rails)
|
@@ -1401,7 +1403,7 @@ rails new glimmer_app_server
|
|
1401
1403
|
Add the following to `Gemfile`:
|
1402
1404
|
|
1403
1405
|
```
|
1404
|
-
gem 'glimmer-dsl-web', '~> 0.6.
|
1406
|
+
gem 'glimmer-dsl-web', '~> 0.6.2'
|
1405
1407
|
```
|
1406
1408
|
|
1407
1409
|
Run:
|
@@ -1630,7 +1632,7 @@ Disable the `webpacker` gem line in `Gemfile`:
|
|
1630
1632
|
Add the following to `Gemfile`:
|
1631
1633
|
|
1632
1634
|
```ruby
|
1633
|
-
gem 'glimmer-dsl-web', '~> 0.6.
|
1635
|
+
gem 'glimmer-dsl-web', '~> 0.6.2'
|
1634
1636
|
```
|
1635
1637
|
|
1636
1638
|
Run:
|
@@ -2868,7 +2870,11 @@ Component slots enables consumers of components to contribute content in designa
|
|
2868
2870
|
Inside a Glimmer web component, you can designate an element (e.g. `div`) as a component slot by passing the `slot: :slotname` option,
|
2869
2871
|
which enables consumers to contribute elements inside the component slot by opening a `slotname {...}` inside the component content block.
|
2870
2872
|
|
2871
|
-
|
2873
|
+
If you want content that is added to a component block direclty added to a specific slot by default, you can specify a `default_slot :slot_name`.
|
2874
|
+
In that case, if you ever want to add content to the component markup root element, there is a slot that is always available for that, called `:markup_root_slot`.
|
2875
|
+
The default slot feature is demonstrated in the [Hello, Component Listeners (Default Slot)!](#hello-component-listeners-default-slot) sample.
|
2876
|
+
|
2877
|
+
For an example of using component slots, below is a sample that demonstrates how to contribute slot content to `address_header` and `address_footer` in an `address_form` component.
|
2872
2878
|
|
2873
2879
|
[lib/glimmer-dsl-web/samples/hello/hello_component_slots.rb](/lib/glimmer-dsl-web/samples/hello/hello_component_slots.rb)
|
2874
2880
|
|
@@ -3474,6 +3480,396 @@ Screenshot:
|
|
3474
3480
|
|
3475
3481
|
![Hello, Component Listeners!](/images/glimmer-dsl-web-samples-hello-hello-component-listeners.gif)
|
3476
3482
|
|
3483
|
+
#### Hello, Component Listeners (Default Slot)!
|
3484
|
+
|
3485
|
+
This is a modified simpler version of Hello, Component Listeners! that takes advantage of the Component Default Slot feature.
|
3486
|
+
|
3487
|
+
If you want content that is added to a component block direclty added to a specific slot by default, you can specify a `default_slot :slot_name`.
|
3488
|
+
In that case, if you ever want to add content to the component markup root element, there is a slot that is always available for that, called `:markup_root_slot`.
|
3489
|
+
The default slot feature is demonstrated in the [Hello, Component Listeners (Default Slot)!](#hello-component-listeners-default-slot) sample.
|
3490
|
+
|
3491
|
+
For example, an `AccordionSection` component specifies `default_slot :section_content` to simplify inserting content for consumers of the component.
|
3492
|
+
|
3493
|
+
[lib/glimmer-dsl-web/samples/hello/hello_component_listeners_default_slot.rb](/lib/glimmer-dsl-web/samples/hello/hello_component_listeners_default_slot.rb)
|
3494
|
+
|
3495
|
+
Glimmer HTML DSL Ruby code in the frontend:
|
3496
|
+
|
3497
|
+
```ruby
|
3498
|
+
require 'glimmer-dsl-web'
|
3499
|
+
|
3500
|
+
unless Object.const_defined?(:Address)
|
3501
|
+
Address = Struct.new(:full_name, :street, :street2, :city, :state, :zip_code, keyword_init: true) do
|
3502
|
+
STATES = {
|
3503
|
+
"AK"=>"Alaska",
|
3504
|
+
"AL"=>"Alabama",
|
3505
|
+
"AR"=>"Arkansas",
|
3506
|
+
"AS"=>"American Samoa",
|
3507
|
+
"AZ"=>"Arizona",
|
3508
|
+
"CA"=>"California",
|
3509
|
+
"CO"=>"Colorado",
|
3510
|
+
"CT"=>"Connecticut",
|
3511
|
+
"DC"=>"District of Columbia",
|
3512
|
+
"DE"=>"Delaware",
|
3513
|
+
"FL"=>"Florida",
|
3514
|
+
"GA"=>"Georgia",
|
3515
|
+
"GU"=>"Guam",
|
3516
|
+
"HI"=>"Hawaii",
|
3517
|
+
"IA"=>"Iowa",
|
3518
|
+
"ID"=>"Idaho",
|
3519
|
+
"IL"=>"Illinois",
|
3520
|
+
"IN"=>"Indiana",
|
3521
|
+
"KS"=>"Kansas",
|
3522
|
+
"KY"=>"Kentucky",
|
3523
|
+
"LA"=>"Louisiana",
|
3524
|
+
"MA"=>"Massachusetts",
|
3525
|
+
"MD"=>"Maryland",
|
3526
|
+
"ME"=>"Maine",
|
3527
|
+
"MI"=>"Michigan",
|
3528
|
+
"MN"=>"Minnesota",
|
3529
|
+
"MO"=>"Missouri",
|
3530
|
+
"MS"=>"Mississippi",
|
3531
|
+
"MT"=>"Montana",
|
3532
|
+
"NC"=>"North Carolina",
|
3533
|
+
"ND"=>"North Dakota",
|
3534
|
+
"NE"=>"Nebraska",
|
3535
|
+
"NH"=>"New Hampshire",
|
3536
|
+
"NJ"=>"New Jersey",
|
3537
|
+
"NM"=>"New Mexico",
|
3538
|
+
"NV"=>"Nevada",
|
3539
|
+
"NY"=>"New York",
|
3540
|
+
"OH"=>"Ohio",
|
3541
|
+
"OK"=>"Oklahoma",
|
3542
|
+
"OR"=>"Oregon",
|
3543
|
+
"PA"=>"Pennsylvania",
|
3544
|
+
"PR"=>"Puerto Rico",
|
3545
|
+
"RI"=>"Rhode Island",
|
3546
|
+
"SC"=>"South Carolina",
|
3547
|
+
"SD"=>"South Dakota",
|
3548
|
+
"TN"=>"Tennessee",
|
3549
|
+
"TX"=>"Texas",
|
3550
|
+
"UT"=>"Utah",
|
3551
|
+
"VA"=>"Virginia",
|
3552
|
+
"VI"=>"Virgin Islands",
|
3553
|
+
"VT"=>"Vermont",
|
3554
|
+
"WA"=>"Washington",
|
3555
|
+
"WI"=>"Wisconsin",
|
3556
|
+
"WV"=>"West Virginia",
|
3557
|
+
"WY"=>"Wyoming"
|
3558
|
+
}
|
3559
|
+
|
3560
|
+
def state_code
|
3561
|
+
STATES.invert[state]
|
3562
|
+
end
|
3563
|
+
|
3564
|
+
def state_code=(value)
|
3565
|
+
self.state = STATES[value]
|
3566
|
+
end
|
3567
|
+
|
3568
|
+
def summary
|
3569
|
+
to_h.values.map(&:to_s).reject(&:empty?).join(', ')
|
3570
|
+
end
|
3571
|
+
end
|
3572
|
+
end
|
3573
|
+
|
3574
|
+
unless Object.const_defined?(:AddressForm)
|
3575
|
+
# AddressForm Glimmer Web Component (View component)
|
3576
|
+
#
|
3577
|
+
# Including Glimmer::Web::Component makes this class a View component and automatically
|
3578
|
+
# generates a new Glimmer HTML DSL keyword that matches the lowercase underscored version
|
3579
|
+
# of the name of the class. AddressForm generates address_form keyword, which can be used
|
3580
|
+
# elsewhere in Glimmer HTML DSL code as done inside HelloComponentListenersDefaultSlot below.
|
3581
|
+
class AddressForm
|
3582
|
+
include Glimmer::Web::Component
|
3583
|
+
|
3584
|
+
option :address
|
3585
|
+
|
3586
|
+
markup {
|
3587
|
+
div {
|
3588
|
+
div(style: {display: :grid, grid_auto_columns: '80px 260px'}) { |address_div|
|
3589
|
+
label('Full Name: ', for: 'full-name-field')
|
3590
|
+
input(id: 'full-name-field') {
|
3591
|
+
value <=> [address, :full_name]
|
3592
|
+
}
|
3593
|
+
|
3594
|
+
label('Street: ', for: 'street-field')
|
3595
|
+
input(id: 'street-field') {
|
3596
|
+
value <=> [address, :street]
|
3597
|
+
}
|
3598
|
+
|
3599
|
+
label('Street 2: ', for: 'street2-field')
|
3600
|
+
textarea(id: 'street2-field') {
|
3601
|
+
value <=> [address, :street2]
|
3602
|
+
}
|
3603
|
+
|
3604
|
+
label('City: ', for: 'city-field')
|
3605
|
+
input(id: 'city-field') {
|
3606
|
+
value <=> [address, :city]
|
3607
|
+
}
|
3608
|
+
|
3609
|
+
label('State: ', for: 'state-field')
|
3610
|
+
select(id: 'state-field') {
|
3611
|
+
Address::STATES.each do |state_code, state|
|
3612
|
+
option(value: state_code) { state }
|
3613
|
+
end
|
3614
|
+
|
3615
|
+
value <=> [address, :state_code]
|
3616
|
+
}
|
3617
|
+
|
3618
|
+
label('Zip Code: ', for: 'zip-code-field')
|
3619
|
+
input(id: 'zip-code-field', type: 'number', min: '0', max: '99999') {
|
3620
|
+
value <=> [address, :zip_code,
|
3621
|
+
on_write: :to_s,
|
3622
|
+
]
|
3623
|
+
}
|
3624
|
+
|
3625
|
+
style {
|
3626
|
+
r("#{address_div.selector} *") {
|
3627
|
+
margin '5px'
|
3628
|
+
}
|
3629
|
+
r("#{address_div.selector} input, #{address_div.selector} select") {
|
3630
|
+
grid_column '2'
|
3631
|
+
}
|
3632
|
+
}
|
3633
|
+
}
|
3634
|
+
|
3635
|
+
div(style: {margin: 5}) {
|
3636
|
+
inner_text <= [address, :summary,
|
3637
|
+
computed_by: address.members + ['state_code'],
|
3638
|
+
]
|
3639
|
+
}
|
3640
|
+
}
|
3641
|
+
}
|
3642
|
+
end
|
3643
|
+
end
|
3644
|
+
|
3645
|
+
unless Object.const_defined?(:AccordionSection2)
|
3646
|
+
# Note: this is similar to AccordionSection in HelloComponentSlots but specifies default_slot for simpler consumption
|
3647
|
+
class AccordionSection2
|
3648
|
+
class Presenter
|
3649
|
+
attr_accessor :collapsed, :instant_transition
|
3650
|
+
|
3651
|
+
def toggle_collapsed(instant: false)
|
3652
|
+
self.instant_transition = instant
|
3653
|
+
self.collapsed = !collapsed
|
3654
|
+
end
|
3655
|
+
|
3656
|
+
def expand(instant: false)
|
3657
|
+
self.instant_transition = instant
|
3658
|
+
self.collapsed = false
|
3659
|
+
end
|
3660
|
+
|
3661
|
+
def collapse(instant: false)
|
3662
|
+
self.instant_transition = instant
|
3663
|
+
self.collapsed = true
|
3664
|
+
end
|
3665
|
+
end
|
3666
|
+
|
3667
|
+
include Glimmer::Web::Component
|
3668
|
+
|
3669
|
+
events :expanded, :collapsed
|
3670
|
+
|
3671
|
+
default_slot :section_content # automatically insert content in this element slot inside markup
|
3672
|
+
|
3673
|
+
option :title
|
3674
|
+
|
3675
|
+
attr_reader :presenter
|
3676
|
+
|
3677
|
+
before_render do
|
3678
|
+
@presenter = Presenter.new
|
3679
|
+
end
|
3680
|
+
|
3681
|
+
markup {
|
3682
|
+
section { # represents the :markup_root_slot to allow inserting content here instead of in default_slot
|
3683
|
+
# Unidirectionally data-bind the class inclusion of 'collapsed' to the @presenter.collapsed boolean attribute,
|
3684
|
+
# meaning if @presenter.collapsed changes to true, the CSS class 'collapsed' is included on the element,
|
3685
|
+
# and if it changes to false, the CSS class 'collapsed' is removed from the element.
|
3686
|
+
class_name(:collapsed) <= [@presenter, :collapsed]
|
3687
|
+
class_name(:instant_transition) <= [@presenter, :instant_transition]
|
3688
|
+
|
3689
|
+
header(title, class: 'accordion-section-title') {
|
3690
|
+
onclick do |event|
|
3691
|
+
@presenter.toggle_collapsed
|
3692
|
+
if @presenter.collapsed
|
3693
|
+
notify_listeners(:collapsed)
|
3694
|
+
else
|
3695
|
+
notify_listeners(:expanded)
|
3696
|
+
end
|
3697
|
+
end
|
3698
|
+
}
|
3699
|
+
|
3700
|
+
div(slot: :section_content, class: 'accordion-section-content')
|
3701
|
+
}
|
3702
|
+
}
|
3703
|
+
|
3704
|
+
style {
|
3705
|
+
r('.accordion-section-title') {
|
3706
|
+
font_size 2.em
|
3707
|
+
font_weight :bold
|
3708
|
+
cursor :pointer
|
3709
|
+
padding_left 20
|
3710
|
+
position :relative
|
3711
|
+
margin_block_start 0.33.em
|
3712
|
+
margin_block_end 0.33.em
|
3713
|
+
}
|
3714
|
+
|
3715
|
+
r('.accordion-section-title::before') {
|
3716
|
+
content '"▼"'
|
3717
|
+
position :absolute
|
3718
|
+
font_size 0.5.em
|
3719
|
+
top 10
|
3720
|
+
left 0
|
3721
|
+
}
|
3722
|
+
|
3723
|
+
r('.accordion-section-content') {
|
3724
|
+
height 246
|
3725
|
+
overflow :hidden
|
3726
|
+
transition 'height 0.5s linear'
|
3727
|
+
}
|
3728
|
+
|
3729
|
+
r("#{component_element_selector}.instant_transition .accordion-section-content") {
|
3730
|
+
transition 'initial'
|
3731
|
+
}
|
3732
|
+
|
3733
|
+
r("#{component_element_selector}.collapsed .accordion-section-title::before") {
|
3734
|
+
content '"►"'
|
3735
|
+
}
|
3736
|
+
|
3737
|
+
r("#{component_element_selector}.collapsed .accordion-section-content") {
|
3738
|
+
height 0
|
3739
|
+
}
|
3740
|
+
}
|
3741
|
+
end
|
3742
|
+
end
|
3743
|
+
|
3744
|
+
unless Object.const_defined?(:Accordion)
|
3745
|
+
class Accordion
|
3746
|
+
include Glimmer::Web::Component
|
3747
|
+
|
3748
|
+
events :accordion_section_expanded, :accordion_section_collapsed
|
3749
|
+
|
3750
|
+
markup {
|
3751
|
+
# given that no slots are specified, nesting content under the accordion component
|
3752
|
+
# in consumer code adds content directly inside the markup root div.
|
3753
|
+
div { |accordion| # represents the :markup_root_slot (top-level element)
|
3754
|
+
# on render, all accordion sections would have been added by consumers already, so we can
|
3755
|
+
# attach listeners to all of them by re-opening their content with `.content { ... }` block
|
3756
|
+
on_render do
|
3757
|
+
accordion_section_elements = accordion.children
|
3758
|
+
accordion_sections = accordion_section_elements.map(&:component)
|
3759
|
+
accordion_sections.each_with_index do |accordion_section, index|
|
3760
|
+
accordion_section_number = index + 1
|
3761
|
+
|
3762
|
+
# ensure only the first section is expanded
|
3763
|
+
accordion_section.presenter.collapse(instant: true) if accordion_section_number != 1
|
3764
|
+
|
3765
|
+
accordion_section.content { # re-open content and add component custom event listeners
|
3766
|
+
on_expanded do
|
3767
|
+
other_accordion_sections = accordion_sections.reject {|other_accordion_section| other_accordion_section == accordion_section }
|
3768
|
+
other_accordion_sections.each { |other_accordion_section| other_accordion_section.presenter.collapse }
|
3769
|
+
notify_listeners(:accordion_section_expanded, accordion_section_number)
|
3770
|
+
end
|
3771
|
+
|
3772
|
+
on_collapsed do
|
3773
|
+
notify_listeners(:accordion_section_collapsed, accordion_section_number)
|
3774
|
+
end
|
3775
|
+
}
|
3776
|
+
end
|
3777
|
+
end
|
3778
|
+
}
|
3779
|
+
}
|
3780
|
+
end
|
3781
|
+
end
|
3782
|
+
|
3783
|
+
unless Object.const_defined?(:HelloComponentListenersDefaultSlot)
|
3784
|
+
# HelloComponentListenersDefaultSlot Glimmer Web Component (View component)
|
3785
|
+
#
|
3786
|
+
# This View component represents the main page being rendered,
|
3787
|
+
# as done by its `render` class method below
|
3788
|
+
#
|
3789
|
+
# Note: this is a simpler version of HelloComponentSlots as it leverages the default slot feature
|
3790
|
+
class HelloComponentListenersDefaultSlot
|
3791
|
+
class Presenter
|
3792
|
+
attr_accessor :status_message
|
3793
|
+
|
3794
|
+
def initialize
|
3795
|
+
@status_message = "Accordion section 1 is expanded!"
|
3796
|
+
end
|
3797
|
+
end
|
3798
|
+
|
3799
|
+
include Glimmer::Web::Component
|
3800
|
+
|
3801
|
+
before_render do
|
3802
|
+
@presenter = Presenter.new
|
3803
|
+
@shipping_address = Address.new(
|
3804
|
+
full_name: 'Johnny Doe',
|
3805
|
+
street: '3922 Park Ave',
|
3806
|
+
street2: 'PO BOX 8382',
|
3807
|
+
city: 'San Diego',
|
3808
|
+
state: 'California',
|
3809
|
+
zip_code: '91913',
|
3810
|
+
)
|
3811
|
+
@billing_address = Address.new(
|
3812
|
+
full_name: 'John C Doe',
|
3813
|
+
street: '123 Main St',
|
3814
|
+
street2: 'Apartment 3C',
|
3815
|
+
city: 'San Diego',
|
3816
|
+
state: 'California',
|
3817
|
+
zip_code: '91911',
|
3818
|
+
)
|
3819
|
+
@emergency_address = Address.new(
|
3820
|
+
full_name: 'Mary Doe',
|
3821
|
+
street: '2038 Ipswitch St',
|
3822
|
+
street2: 'Suite 300',
|
3823
|
+
city: 'San Diego',
|
3824
|
+
state: 'California',
|
3825
|
+
zip_code: '91912',
|
3826
|
+
)
|
3827
|
+
end
|
3828
|
+
|
3829
|
+
markup {
|
3830
|
+
div {
|
3831
|
+
h1(style: {font_style: :italic}) {
|
3832
|
+
inner_html <= [@presenter, :status_message]
|
3833
|
+
}
|
3834
|
+
|
3835
|
+
accordion {
|
3836
|
+
# any content nested under component directly is added to its markup_root_slot element if no default_slot is specified
|
3837
|
+
accordion_section2(title: 'Shipping Address') {
|
3838
|
+
address_form(address: @shipping_address) # automatically inserts content in default_slot :section_content
|
3839
|
+
}
|
3840
|
+
|
3841
|
+
accordion_section2(title: 'Billing Address') {
|
3842
|
+
address_form(address: @billing_address) # automatically inserts content in default_slot :section_content
|
3843
|
+
}
|
3844
|
+
|
3845
|
+
accordion_section2(title: 'Emergency Address') {
|
3846
|
+
address_form(address: @emergency_address) # automatically inserts content in default_slot :section_content
|
3847
|
+
}
|
3848
|
+
|
3849
|
+
# on_accordion_section_expanded listener matches event :accordion_section_expanded declared in Accordion component
|
3850
|
+
on_accordion_section_expanded { |accordion_section_number|
|
3851
|
+
@presenter.status_message = "Accordion section #{accordion_section_number} is expanded!"
|
3852
|
+
}
|
3853
|
+
|
3854
|
+
on_accordion_section_collapsed { |accordion_section_number|
|
3855
|
+
@presenter.status_message = "Accordion section #{accordion_section_number} is collapsed!"
|
3856
|
+
}
|
3857
|
+
}
|
3858
|
+
}
|
3859
|
+
}
|
3860
|
+
end
|
3861
|
+
end
|
3862
|
+
|
3863
|
+
Document.ready? do
|
3864
|
+
# renders a top-level (root) HelloComponentListenersDefaultSlot component
|
3865
|
+
# Note: this is a simpler version of hello_component_slots.rb as it leverages the default slot feature
|
3866
|
+
HelloComponentListenersDefaultSlot.render
|
3867
|
+
end
|
3868
|
+
```
|
3869
|
+
|
3870
|
+
Screenshot:
|
3871
|
+
|
3872
|
+
![Hello, Component Listeners!](/images/glimmer-dsl-web-samples-hello-hello-component-listeners.gif)
|
3477
3873
|
|
3478
3874
|
#### Hello, glimmer_component Rails Helper!
|
3479
3875
|
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.6.
|
1
|
+
0.6.2
|
data/glimmer-dsl-web.gemspec
CHANGED
@@ -2,16 +2,16 @@
|
|
2
2
|
# DO NOT EDIT THIS FILE DIRECTLY
|
3
3
|
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
4
|
# -*- encoding: utf-8 -*-
|
5
|
-
# stub: glimmer-dsl-web 0.6.
|
5
|
+
# stub: glimmer-dsl-web 0.6.2 ruby lib
|
6
6
|
|
7
7
|
Gem::Specification.new do |s|
|
8
8
|
s.name = "glimmer-dsl-web".freeze
|
9
|
-
s.version = "0.6.
|
9
|
+
s.version = "0.6.2"
|
10
10
|
|
11
11
|
s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version=
|
12
12
|
s.require_paths = ["lib".freeze]
|
13
13
|
s.authors = ["Andy Maleh".freeze]
|
14
|
-
s.date = "2024-09-
|
14
|
+
s.date = "2024-09-30"
|
15
15
|
s.description = "Glimmer DSL for Web (Ruby in the Browser Web Frontend Framework) enables building Web Frontends using Ruby in the Browser, as per Matz's recommendation in his RubyConf 2022 keynote speech to replace JavaScript with Ruby. It aims at providing the simplest, most intuitive, most straight-forward, and most productive frontend framework in existence. The framework follows the Ruby way (with DSLs and TIMTOWTDI) and the Rails way (Convention over Configuration) in building Isomorphic Ruby on Rails Applications. It provides a Ruby HTML DSL, which uniquely enables writing both structure code and logic code in one language. It supports 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. Modular design is supported with Glimmer Web Components, Component Slots, and Component Custom Event Listeners. And, a Ruby CSS DSL is supported with the included Glimmer DSL for CSS. Many samples are demonstrated in the Rails sample app (there is a very minimal Standalone [No Rails] sample app too). You can finally live in pure Rubyland on the Web in both the frontend and backend with Glimmer DSL for Web! This gem relies on Opal Ruby.".freeze
|
16
16
|
s.email = "andy.am@gmail.com".freeze
|
17
17
|
s.extra_rdoc_files = [
|
@@ -34,6 +34,7 @@ Gem::Specification.new do |s|
|
|
34
34
|
"lib/glimmer-dsl-web/samples/hello/hello_button.rb",
|
35
35
|
"lib/glimmer-dsl-web/samples/hello/hello_component.rb",
|
36
36
|
"lib/glimmer-dsl-web/samples/hello/hello_component_listeners.rb",
|
37
|
+
"lib/glimmer-dsl-web/samples/hello/hello_component_listeners_default_slot.rb",
|
37
38
|
"lib/glimmer-dsl-web/samples/hello/hello_component_slots.rb",
|
38
39
|
"lib/glimmer-dsl-web/samples/hello/hello_content_data_binding.rb",
|
39
40
|
"lib/glimmer-dsl-web/samples/hello/hello_data_binding.rb",
|
@@ -12,16 +12,34 @@ module Glimmer
|
|
12
12
|
end
|
13
13
|
|
14
14
|
def interpret(parent, keyword, *args, &block)
|
15
|
-
|
16
|
-
|
15
|
+
component_class = Glimmer::Web::Component.for(keyword)
|
16
|
+
component_class.new(parent, args, {}, &block)
|
17
17
|
end
|
18
18
|
|
19
19
|
def add_content(parent, keyword, *args, &block)
|
20
|
+
options = args.last.is_a?(Hash) ? args.last : {}
|
21
|
+
slot = options[:slot] || options['slot']
|
22
|
+
slot = slot.to_s unless slot.nil?
|
20
23
|
# TODO consider avoiding source_location since it does not work in Opal
|
21
24
|
if block.source_location && (block.source_location == parent.content&.__getobj__&.source_location)
|
22
25
|
parent.content.call(parent) unless parent.content.called?
|
23
26
|
else
|
24
|
-
|
27
|
+
if slot
|
28
|
+
if slot == 'markup_root_slot'
|
29
|
+
super(parent, keyword, *args, &block)
|
30
|
+
else
|
31
|
+
slot_element = parent.slot_elements[slot]
|
32
|
+
slot_element&.content(&block)
|
33
|
+
end
|
34
|
+
else
|
35
|
+
if parent.default_slot
|
36
|
+
slot = parent.default_slot
|
37
|
+
slot_element = parent.slot_elements[slot]
|
38
|
+
slot_element&.content(&block)
|
39
|
+
else
|
40
|
+
super(parent, keyword, *args, &block)
|
41
|
+
end
|
42
|
+
end
|
25
43
|
end
|
26
44
|
parent.post_add_content
|
27
45
|
end
|
@@ -28,19 +28,25 @@ module Glimmer
|
|
28
28
|
module Web
|
29
29
|
class ComponentSlotContentExpression < Expression
|
30
30
|
def can_interpret?(parent, keyword, *args, &block)
|
31
|
+
component = parent.is_a?(Glimmer::Web::Component) ? parent : parent&.ancestor_component
|
31
32
|
slot = keyword.to_s
|
32
33
|
block_given? &&
|
33
|
-
|
34
|
+
!component.nil? &&
|
34
35
|
(
|
35
|
-
|
36
|
-
|
36
|
+
component.slot_elements.keys.include?(slot) ||
|
37
|
+
component.slot_elements.keys.include?(slot.to_sym)
|
37
38
|
)
|
38
39
|
end
|
39
40
|
|
40
41
|
def interpret(parent, keyword, *args, &block)
|
41
42
|
slot = keyword.to_s
|
42
|
-
|
43
|
-
|
43
|
+
component = parent.is_a?(Glimmer::Web::Component) ? parent : parent.ancestor_component
|
44
|
+
if slot == 'markup_root_slot'
|
45
|
+
component.content(slot: slot.to_sym, &block)
|
46
|
+
else
|
47
|
+
slot_element = component.slot_elements[slot] || component.slot_elements[slot.to_sym]
|
48
|
+
slot_element.content(&block)
|
49
|
+
end
|
44
50
|
end
|
45
51
|
end
|
46
52
|
end
|
@@ -104,6 +104,14 @@ module Glimmer
|
|
104
104
|
end
|
105
105
|
end
|
106
106
|
|
107
|
+
def default_slot(slot_name = nil)
|
108
|
+
if slot_name.nil?
|
109
|
+
@default_slot
|
110
|
+
else
|
111
|
+
@default_slot = slot_name.to_s.to_sym
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
107
115
|
def keyword
|
108
116
|
self.name.underscore.gsub('::', '__')
|
109
117
|
end
|
@@ -270,7 +278,7 @@ module Glimmer
|
|
270
278
|
end
|
271
279
|
# <- end of class methods
|
272
280
|
|
273
|
-
attr_reader :markup_root, :parent, :args, :options, :style_block, :component_style, :slot_elements, :events
|
281
|
+
attr_reader :markup_root, :parent, :args, :options, :style_block, :component_style, :slot_elements, :events, :default_slot
|
274
282
|
alias parent_proxy parent
|
275
283
|
|
276
284
|
def initialize(parent, args, options, &content)
|
@@ -288,6 +296,7 @@ module Glimmer
|
|
288
296
|
options ||= {}
|
289
297
|
@options = self.class.options.merge(options)
|
290
298
|
@events = self.class.instance_variable_get("@events") || []
|
299
|
+
@default_slot = self.class.instance_variable_get("@default_slot")
|
291
300
|
@content = Util::ProcTracker.new(content) if content
|
292
301
|
# @style_blocks = {} # TODO enable when doing bulk head rendering in the future
|
293
302
|
execute_hooks('before_render')
|
@@ -474,8 +483,15 @@ module Glimmer
|
|
474
483
|
@content
|
475
484
|
end
|
476
485
|
else
|
477
|
-
|
478
|
-
|
486
|
+
options = args.last.is_a?(Hash) ? args.last : {}
|
487
|
+
slot = options[:slot] || options['slot']
|
488
|
+
slot = slot.to_sym unless slot.nil?
|
489
|
+
if slot
|
490
|
+
Glimmer::DSL::Engine.add_content(self, Glimmer::DSL::Web::ComponentExpression.new, self.class.keyword, slot: slot, &block)
|
491
|
+
else
|
492
|
+
# delegate to GUI DSL ContentExpression
|
493
|
+
super
|
494
|
+
end
|
479
495
|
end
|
480
496
|
end
|
481
497
|
|