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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ff9463575ae6b368ccbde387628ed3267109d92ca7239eb893647b24d80fddd7
4
- data.tar.gz: 6896f526bef25233083ad18e5fb2a8c668e2c88dfd91fe671de2bc3ad9e6d45e
3
+ metadata.gz: eecb0321e7308184ed4fb10bb9811c76a5e118cd7157e9956c29427e15717573
4
+ data.tar.gz: 61561e5ca787eb1bda14e6d2cb62fcc0b312900b0ecd1b34fcdaf16d9b9b58da
5
5
  SHA512:
6
- metadata.gz: 204c573bae64566e947914a8862360eff803bd1be3b9f67a671c5053cf65cb07a880c0f176650db757ed9bb720cd9afeb4a1954eed865c9faa34e8540b21d4d0
7
- data.tar.gz: ec311e00c136cbdce076b798735c02118f2c3459d8a3af929093325e70b3c5fbec8696868a02046cab9154e7745bb3356a77d83b05876a03517c82293c751693
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 (Beta)
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
- ### Finally, Ruby Developer Productivity, Happiness, and Fun in the Frontend!!!
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.1'
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.1'
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
- For example, below is a sample that demonstrates how to contribute slot content to `address_header` and `address_footer` in an `address_form` component.
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
1
+ 0.6.2
@@ -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.1 ruby lib
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.1"
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-02"
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
- custom_widget_class = Glimmer::Web::Component.for(keyword)
16
- custom_widget_class.new(parent, args, {}, &block)
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
- super(parent, keyword, *args, &block)
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
- parent.respond_to?(:slot_elements) &&
34
+ !component.nil? &&
34
35
  (
35
- parent.slot_elements.keys.include?(slot) ||
36
- parent.slot_elements.keys.include?(slot.to_sym)
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
- slot_element = parent.slot_elements[slot] || parent.slot_elements[slot.to_sym]
43
- slot_element&.content(&block)
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
- # delegate to GUI DSL ContentExpression
478
- super
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