glimmer-dsl-web 0.4.4 → 0.5.0

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: 968cea935d70d244e06ab3edce32c14c9483ac897e09d9ab5e578cfc0c19ce71
4
- data.tar.gz: 138548a00c394758b57a7848f9940748fc3df0dfd19a1638c79e26c5c7325424
3
+ metadata.gz: 702f2aed3db1d4fcb6fbda9e794a756a3cdb44dfa47db714bbd350a5dd5e1d81
4
+ data.tar.gz: 5c6b8c276bc246c023cb9419744bceea8021450943cf7e2da2dfb0f86cbb4b3c
5
5
  SHA512:
6
- metadata.gz: 5cbe3be5841ec5519f8fc23ae620e6b0545dd8c736a865bed49b161fdb2a4f07faecc3dcddec08a6066c64a30c39e1eb3b753ad74e50633a3b505193832151a5
7
- data.tar.gz: e2f6709c7356a0d32c3170789bf7f958db98906d7c7dbbfca1dec7483f43865c961e3facb0d3889ffd28fd4818b1e4850b3a8cbdfe231b4352825aacaabfd2ce
6
+ metadata.gz: 5e3dbff73a4ebb082523dbcf76facc65d27a93e69e2873c08f02bd5939b34a1e9292eadd7698963d5eb53cfa164fbf7b2b14a100213c5d0f3d8d5566d6dc08cd
7
+ data.tar.gz: dfcc0d083636b8d0943b1ff78215c73651bfe768b970ac9cf81852a9e29ab54bf18f94d49483dc4753154c5497cbeb0f99acfbf91d684eebb97d533be19ccb30
data/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # Change Log
2
2
 
3
+ ## 0.5.0
4
+
5
+ - Support Glimmer Web Component Slots (by adding `slot: :slot_name` to any parent element, like a `div`, inside a `Glimmer::Web::Component` `markup {...}` element)
6
+
3
7
  ## 0.4.4
4
8
 
5
9
  - Support setting element `style` property as String or Hash (like `background-color: yello; font-size: 12px;` or `{background_color: :yellow, font_size: 12}`)
data/README.md CHANGED
@@ -1,4 +1,4 @@
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.4.4 (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.5.0 (Beta)
2
2
  ## Ruby-in-the-Browser Web Frontend Framework
3
3
  ### Finally, Ruby Developer Productivity, Happiness, and Fun in the Frontend!!!
4
4
  [![Gem Version](https://badge.fury.io/rb/glimmer-dsl-web.svg)](http://badge.fury.io/rb/glimmer-dsl-web)
@@ -12,7 +12,7 @@
12
12
 
13
13
  You can finally have Ruby developer happiness and productivity in the Frontend! No more wasting time splitting your resources across multiple languages, using badly engineered, over-engineered, or premature-optimization-obsessed JavaScript libraries, fighting JavaScript build issues (e.g. webpack), or rewriting Ruby Backend code in Frontend JavaScript. With [Ruby in the Browser](https://www.youtube.com/watch?v=4AdcfbI6A4c), you can have an exponential jump in development productivity (2x or higher), time-to-release (1/2 or less time), cost (1/2 or cheaper), and maintainability (~50% the code that is simpler and more readable) over JavaScript libraries like React, Angular, Ember, Vue, and Svelte, while being able to reuse Backend Ruby code as is in the Frontend for faster interactions when needed. Also, with Frontend Ruby, companies can cut their hiring budget in half by having Backend Ruby Software Engineers do Frontend Development in Ruby! [Ruby in the Browser](https://www.youtube.com/watch?v=4AdcfbI6A4c) finally fulfills every smart highly-productive Rubyist's dream by bringing Ruby productivity fun to Frontend Development, the same productivity fun you had for years and decades in Backend Development.
14
14
 
15
- [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). 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)!
15
+ [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) and [Slots](#hello-component-slots). 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
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
18
 
@@ -578,6 +578,15 @@ Rails helper (more about it in [Hello, glimmer_component Rails Helper!](#hello-g
578
578
  Below, we define an `AddressForm` component that generates an `address_form` keyword, and then we
579
579
  reuse it twice inside an `AddressPage` component displaying a Shipping Address and a Billing Address.
580
580
 
581
+ You can specify CSS styles that apply to all instances of a component by opening a `style {...}` block, which is
582
+ evaluated against the component class given that it applies to all instances.
583
+ That would automatically generate one `<style>` element for a component inside `<head>` if at least one instance
584
+ of it exists. Every Glimmer web component's markup root element will automatically get a CSS class matching its Ruby
585
+ class name underscored, which can be used to scope styles for all component instances inside its `style {...}` block.
586
+ For example, `AddressForm` gets the CSS class `address-form` on its top-level `div`. You can find out the default CSS class
587
+ that is attached to every component by calling `ComponentClass.component_element_class`. You can get a CSS selector version of
588
+ it by calling `ComponentClass.component_element_selector`.
589
+
581
590
  [lib/glimmer-dsl-web/samples/hello/hello_component.rb](/lib/glimmer-dsl-web/samples/hello/hello_component.rb)
582
591
 
583
592
  Glimmer HTML DSL Ruby code in the frontend:
@@ -1333,7 +1342,8 @@ Learn more about the differences between various [Glimmer](https://github.com/An
1333
1342
  - [Hello, Observer (Data-Binding)!](#hello-observer)
1334
1343
  - [Hello, Data-Binding!](#hello-data-binding)
1335
1344
  - [Hello, Content Data-Binding!](#hello-content-data-binding)
1336
- - [Hello, Component!](#hello-content-data-binding)
1345
+ - [Hello, Component!](#hello-compoent)
1346
+ - [Hello, Component Slots!](#hello-component-slots)
1337
1347
  - [Hello, glimmer_component Rails Helper!](#hello-glimmer_component-rails-helper)
1338
1348
  - [Hello, Paragraph!](#hello-paragraph)
1339
1349
  - [Hello, Style!](#hello-style)
@@ -1390,7 +1400,7 @@ rails new glimmer_app_server
1390
1400
  Add the following to `Gemfile`:
1391
1401
 
1392
1402
  ```
1393
- gem 'glimmer-dsl-web', '~> 0.4.4'
1403
+ gem 'glimmer-dsl-web', '~> 0.5.0'
1394
1404
  ```
1395
1405
 
1396
1406
  Run:
@@ -1619,7 +1629,7 @@ Disable the `webpacker` gem line in `Gemfile`:
1619
1629
  Add the following to `Gemfile`:
1620
1630
 
1621
1631
  ```ruby
1622
- gem 'glimmer-dsl-web', '~> 0.4.4'
1632
+ gem 'glimmer-dsl-web', '~> 0.5.0'
1623
1633
  ```
1624
1634
 
1625
1635
  Run:
@@ -2630,6 +2640,15 @@ Rails helper (more about it in [Hello, glimmer_component Rails Helper!](#hello-g
2630
2640
  Below, we define an `AddressForm` component that generates an `address_form` keyword, and then we
2631
2641
  reuse it twice inside an `AddressPage` component displaying a Shipping Address and a Billing Address.
2632
2642
 
2643
+ You can specify CSS styles that apply to all instances of a component by opening a `style {...}` block, which is
2644
+ evaluated against the component class given that it applies to all instances.
2645
+ That would automatically generate one `<style>` element for a component inside `<head>` if at least one instance
2646
+ of it exists. Every Glimmer web component's markup root element will automatically get a CSS class matching its Ruby
2647
+ class name underscored, which can be used to scope styles for all component instances inside its `style {...}` block.
2648
+ For example, `AddressForm` gets the CSS class `address-form` on its top-level `div`. You can find out the default CSS class
2649
+ that is attached to every component by calling `ComponentClass.component_element_class`. You can get a CSS selector version of
2650
+ it by calling `ComponentClass.component_element_selector`.
2651
+
2633
2652
  [lib/glimmer-dsl-web/samples/hello/hello_component.rb](/lib/glimmer-dsl-web/samples/hello/hello_component.rb)
2634
2653
 
2635
2654
  Glimmer HTML DSL Ruby code in the frontend:
@@ -2841,6 +2860,233 @@ Screenshot:
2841
2860
 
2842
2861
  ![Hello, Component!](/images/glimmer-dsl-web-samples-hello-hello-component.png)
2843
2862
 
2863
+ #### Hello, Component Slots!
2864
+
2865
+ Component slots enables consumers of components to contribute content in designated areas inside them called slots.
2866
+
2867
+ Inside a Glimmer web component, you can designate an element (e.g. `div`) as a component slot by passing the `slot: :slotname` option,
2868
+ which enables consumers to contribute elements inside the component slot by opening a `slotname {...}` inside the component content block.
2869
+
2870
+ For example, below is a sample that demonstrates how to contribute slot content to `address_header` and `address_footer` in an `address_form` component.
2871
+
2872
+ [lib/glimmer-dsl-web/samples/hello/hello_component_slots.rb](/lib/glimmer-dsl-web/samples/hello/hello_component_slots.rb)
2873
+
2874
+ Glimmer HTML DSL Ruby code in the frontend:
2875
+
2876
+ ```ruby
2877
+ require 'glimmer-dsl-web'
2878
+
2879
+ Address = Struct.new(:full_name, :street, :street2, :city, :state, :zip_code, keyword_init: true) do
2880
+ STATES = {
2881
+ "AK"=>"Alaska",
2882
+ "AL"=>"Alabama",
2883
+ "AR"=>"Arkansas",
2884
+ "AS"=>"American Samoa",
2885
+ "AZ"=>"Arizona",
2886
+ "CA"=>"California",
2887
+ "CO"=>"Colorado",
2888
+ "CT"=>"Connecticut",
2889
+ "DC"=>"District of Columbia",
2890
+ "DE"=>"Delaware",
2891
+ "FL"=>"Florida",
2892
+ "GA"=>"Georgia",
2893
+ "GU"=>"Guam",
2894
+ "HI"=>"Hawaii",
2895
+ "IA"=>"Iowa",
2896
+ "ID"=>"Idaho",
2897
+ "IL"=>"Illinois",
2898
+ "IN"=>"Indiana",
2899
+ "KS"=>"Kansas",
2900
+ "KY"=>"Kentucky",
2901
+ "LA"=>"Louisiana",
2902
+ "MA"=>"Massachusetts",
2903
+ "MD"=>"Maryland",
2904
+ "ME"=>"Maine",
2905
+ "MI"=>"Michigan",
2906
+ "MN"=>"Minnesota",
2907
+ "MO"=>"Missouri",
2908
+ "MS"=>"Mississippi",
2909
+ "MT"=>"Montana",
2910
+ "NC"=>"North Carolina",
2911
+ "ND"=>"North Dakota",
2912
+ "NE"=>"Nebraska",
2913
+ "NH"=>"New Hampshire",
2914
+ "NJ"=>"New Jersey",
2915
+ "NM"=>"New Mexico",
2916
+ "NV"=>"Nevada",
2917
+ "NY"=>"New York",
2918
+ "OH"=>"Ohio",
2919
+ "OK"=>"Oklahoma",
2920
+ "OR"=>"Oregon",
2921
+ "PA"=>"Pennsylvania",
2922
+ "PR"=>"Puerto Rico",
2923
+ "RI"=>"Rhode Island",
2924
+ "SC"=>"South Carolina",
2925
+ "SD"=>"South Dakota",
2926
+ "TN"=>"Tennessee",
2927
+ "TX"=>"Texas",
2928
+ "UT"=>"Utah",
2929
+ "VA"=>"Virginia",
2930
+ "VI"=>"Virgin Islands",
2931
+ "VT"=>"Vermont",
2932
+ "WA"=>"Washington",
2933
+ "WI"=>"Wisconsin",
2934
+ "WV"=>"West Virginia",
2935
+ "WY"=>"Wyoming"
2936
+ }
2937
+
2938
+ def state_code
2939
+ STATES.invert[state]
2940
+ end
2941
+
2942
+ def state_code=(value)
2943
+ self.state = STATES[value]
2944
+ end
2945
+
2946
+ def summary
2947
+ to_h.values.map(&:to_s).reject(&:empty?).join(', ')
2948
+ end
2949
+ end
2950
+
2951
+ # AddressForm Glimmer Web Component (View component)
2952
+ #
2953
+ # Including Glimmer::Web::Component makes this class a View component and automatically
2954
+ # generates a new Glimmer HTML DSL keyword that matches the lowercase underscored version
2955
+ # of the name of the class. AddressForm generates address_form keyword, which can be used
2956
+ # elsewhere in Glimmer HTML DSL code as done inside HelloComponentSlots below.
2957
+ class AddressForm
2958
+ include Glimmer::Web::Component
2959
+
2960
+ option :address
2961
+
2962
+ # markup block provides the content of the
2963
+ markup {
2964
+ div {
2965
+ # designate this div as a slot with the slot name :address_header to enable
2966
+ # consumers to contribute elements to `address_header {...}` slot
2967
+ div(slot: :address_header, class: 'address-form-header')
2968
+
2969
+ div(class: 'address-field-container', style: {display: :grid, grid_auto_columns: '80px 260px'}) {
2970
+ label('Full Name: ', for: 'full-name-field')
2971
+ input(id: 'full-name-field') {
2972
+ value <=> [address, :full_name]
2973
+ }
2974
+
2975
+ @somelabel = label('Street: ', for: 'street-field')
2976
+ input(id: 'street-field') {
2977
+ value <=> [address, :street]
2978
+ }
2979
+
2980
+ label('Street 2: ', for: 'street2-field')
2981
+ textarea(id: 'street2-field') {
2982
+ value <=> [address, :street2]
2983
+ }
2984
+
2985
+ label('City: ', for: 'city-field')
2986
+ input(id: 'city-field') {
2987
+ value <=> [address, :city]
2988
+ }
2989
+
2990
+ label('State: ', for: 'state-field')
2991
+ select(id: 'state-field') {
2992
+ Address::STATES.each do |state_code, state|
2993
+ option(value: state_code) { state }
2994
+ end
2995
+
2996
+ value <=> [address, :state_code]
2997
+ }
2998
+
2999
+ label('Zip Code: ', for: 'zip-code-field')
3000
+ input(id: 'zip-code-field', type: 'number', min: '0', max: '99999') {
3001
+ value <=> [address, :zip_code,
3002
+ on_write: :to_s,
3003
+ ]
3004
+ }
3005
+ }
3006
+
3007
+ div(style: 'margin: 5px') {
3008
+ inner_text <= [address, :summary,
3009
+ computed_by: address.members + ['state_code'],
3010
+ ]
3011
+ }
3012
+
3013
+ # designate this div as a slot with the slot name :address_footer to enable
3014
+ # consumers to contribute elements to `address_footer {...}` slot
3015
+ div(slot: :address_footer, class: 'address-form-footer')
3016
+ }
3017
+ }
3018
+
3019
+ style {
3020
+ r('.address-field-container *') {
3021
+ margin 5
3022
+ }
3023
+ r('.address-field-container input, .address-field-container select') {
3024
+ grid_column '2'
3025
+ }
3026
+ }
3027
+ end
3028
+
3029
+ # HelloComponentSlots Glimmer Web Component (View component)
3030
+ #
3031
+ # This View component represents the main page being rendered,
3032
+ # as done by its `render` class method below
3033
+ class HelloComponentSlots
3034
+ include Glimmer::Web::Component
3035
+
3036
+ before_render do
3037
+ @shipping_address = Address.new(
3038
+ full_name: 'Johnny Doe',
3039
+ street: '3922 Park Ave',
3040
+ street2: 'PO BOX 8382',
3041
+ city: 'San Diego',
3042
+ state: 'California',
3043
+ zip_code: '91913',
3044
+ )
3045
+ @billing_address = Address.new(
3046
+ full_name: 'John C Doe',
3047
+ street: '123 Main St',
3048
+ street2: 'Apartment 3C',
3049
+ city: 'San Diego',
3050
+ state: 'California',
3051
+ zip_code: '91911',
3052
+ )
3053
+ end
3054
+
3055
+ markup {
3056
+ div {
3057
+ address_form(address: @shipping_address) {
3058
+ address_header { # contribute elements to the address_header component slot
3059
+ h1('Shipping Address')
3060
+ legend('This is the address that is used for shipping your purchase.', style: {margin_bottom: 10})
3061
+ }
3062
+ address_footer { # contribute elements to the address_header component slot
3063
+ p(sub("#{strong('Note:')} #{em('Purchase will be returned if the Shipping Address does not accept it in one week.')}"))
3064
+ }
3065
+ }
3066
+
3067
+ address_form(address: @billing_address) {
3068
+ address_header { # contribute elements to the address_header component slot
3069
+ h1('Billing Address')
3070
+ legend('This is the address that is used for your billing method (e.g. credit card).', style: {margin_bottom: 10})
3071
+ }
3072
+ address_footer { # contribute elements to the address_header component slot
3073
+ p(sub("#{strong('Note:')} #{em('Payment will fail if payment method does not match the Billing Address.')}"))
3074
+ }
3075
+ }
3076
+ }
3077
+ }
3078
+ end
3079
+
3080
+ Document.ready? do
3081
+ # renders a top-level (root) HelloComponentSlots component
3082
+ HelloComponentSlots.render
3083
+ end
3084
+ ```
3085
+
3086
+ Screenshot:
3087
+
3088
+ ![Hello, Component Slots!](/images/glimmer-dsl-web-samples-hello-hello-component-slots.png)
3089
+
2844
3090
  #### Hello, glimmer_component Rails Helper!
2845
3091
 
2846
3092
  You may insert a Glimmer component anywhere into a Rails View using
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.4.4
1
+ 0.5.0
@@ -2,17 +2,17 @@
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.4.4 ruby lib
5
+ # stub: glimmer-dsl-web 0.5.0 ruby lib
6
6
 
7
7
  Gem::Specification.new do |s|
8
8
  s.name = "glimmer-dsl-web".freeze
9
- s.version = "0.4.4"
9
+ s.version = "0.5.0"
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
14
  s.date = "2024-08-03"
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. 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
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 and Slots. 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 = [
18
18
  "CHANGELOG.md",
@@ -33,6 +33,7 @@ Gem::Specification.new do |s|
33
33
  "lib/glimmer-dsl-web/ext/kernel.rb",
34
34
  "lib/glimmer-dsl-web/samples/hello/hello_button.rb",
35
35
  "lib/glimmer-dsl-web/samples/hello/hello_component.rb",
36
+ "lib/glimmer-dsl-web/samples/hello/hello_component_slots.rb",
36
37
  "lib/glimmer-dsl-web/samples/hello/hello_content_data_binding.rb",
37
38
  "lib/glimmer-dsl-web/samples/hello/hello_data_binding.rb",
38
39
  "lib/glimmer-dsl-web/samples/hello/hello_form.rb",
@@ -68,6 +69,7 @@ Gem::Specification.new do |s|
68
69
  "lib/glimmer/dsl/web/bind_expression.rb",
69
70
  "lib/glimmer/dsl/web/class_name_inclusion_data_binding.rb",
70
71
  "lib/glimmer/dsl/web/component_expression.rb",
72
+ "lib/glimmer/dsl/web/component_slot_content_expression.rb",
71
73
  "lib/glimmer/dsl/web/content_data_binding_expression.rb",
72
74
  "lib/glimmer/dsl/web/data_binding_expression.rb",
73
75
  "lib/glimmer/dsl/web/dsl.rb",
@@ -0,0 +1,48 @@
1
+ # Copyright (c) 2023-2024 Andy Maleh
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining
4
+ # a copy of this software and associated documentation files (the
5
+ # "Software"), to deal in the Software without restriction, including
6
+ # without limitation the rights to use, copy, modify, merge, publish,
7
+ # distribute, sublicense, and/or sell copies of the Software, and to
8
+ # permit persons to whom the Software is furnished to do so, subject to
9
+ # the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be
12
+ # included in all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
+
22
+ require 'glimmer'
23
+ require 'glimmer/dsl/expression'
24
+ require 'glimmer/web/component'
25
+
26
+ module Glimmer
27
+ module DSL
28
+ module Web
29
+ class ComponentSlotContentExpression < Expression
30
+ def can_interpret?(parent, keyword, *args, &block)
31
+ slot = keyword.to_s
32
+ block_given? &&
33
+ parent.respond_to?(:slot_elements) &&
34
+ (
35
+ parent.slot_elements.keys.include?(slot) ||
36
+ parent.slot_elements.keys.include?(slot.to_sym)
37
+ )
38
+ end
39
+
40
+ def interpret(parent, keyword, *args, &block)
41
+ slot = keyword.to_s
42
+ slot_element = parent.slot_elements[slot] || parent.slot_elements[slot.to_sym]
43
+ slot_element&.content(&block)
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -34,6 +34,7 @@ require 'glimmer/dsl/web/data_binding_expression'
34
34
  require 'glimmer/dsl/web/content_data_binding_expression'
35
35
  require 'glimmer/dsl/web/shine_data_binding_expression'
36
36
  require 'glimmer/dsl/web/component_expression'
37
+ require 'glimmer/dsl/web/component_slot_content_expression'
37
38
  require 'glimmer/dsl/web/observe_expression'
38
39
 
39
40
  module Glimmer
@@ -47,6 +48,7 @@ module Glimmer
47
48
  content_data_binding
48
49
  inline_style_data_binding
49
50
  class_name_inclusion_data_binding
51
+ component_slot_content
50
52
  component
51
53
  formatting_element
52
54
  data_binding
@@ -10,9 +10,17 @@ module Glimmer
10
10
  include GeneralElementExpression
11
11
 
12
12
  def can_interpret?(parent, keyword, *args, &block)
13
+ slot = keyword.to_s.to_sym
13
14
  Glimmer::Web::ElementProxy.keyword_supported?(keyword) &&
14
15
  (
15
- args.empty? ||
16
+ (
17
+ args.empty? &&
18
+ (
19
+ parent.nil? ||
20
+ !parent.respond_to?(:slot_elements) ||
21
+ !(parent.slot_elements.keys.include?(slot) || parent.slot_elements.keys.include?(slot.to_s))
22
+ )
23
+ ) ||
16
24
  args.size == 1 && args.first.is_a?(String) ||
17
25
  args.size == 1 && args.first.is_a?(Hash) ||
18
26
  args.size == 2 && args.first.is_a?(String) && args.last.is_a?(Hash)
@@ -255,7 +255,7 @@ module Glimmer
255
255
  end
256
256
  # <- end of class methods
257
257
 
258
- attr_reader :markup_root, :parent, :args, :options, :style_block, :component_style
258
+ attr_reader :markup_root, :parent, :args, :options, :style_block, :component_style, :slot_elements
259
259
  alias parent_proxy parent
260
260
 
261
261
  def initialize(parent, args, options, &content)
@@ -268,6 +268,7 @@ module Glimmer
268
268
  args = []
269
269
  end
270
270
  options ||= {}
271
+ @slot_elements = {}
271
272
  @args = args
272
273
  options ||= {}
273
274
  @options = self.class.options.merge(options)
@@ -146,17 +146,22 @@ module Glimmer
146
146
  REGEX_STYLE_SUB_PROPERTY = /^(style)_(.*)$/
147
147
  REGEX_CLASS_NAME_SUB_PROPERTY = /^(class_name)_(.*)$/
148
148
 
149
- attr_reader :keyword, :parent, :parent_component, :component, :args, :options, :children, :enabled, :foreground, :background, :removed, :rendered
149
+ attr_reader :keyword, :parent, :parent_component, :ancestor_component, :component, :args, :options, :slot, :children, :enabled, :foreground, :background, :removed, :rendered
150
150
  alias rendered? rendered
151
151
  alias removed? removed
152
152
 
153
153
  def initialize(keyword, parent, args, block)
154
154
  @keyword = keyword
155
155
  @parent = parent.is_a?(Glimmer::Web::Component) ? parent.markup_root : parent
156
+ # @parent_component is the component that is the parent of an external element (not in markup {...} block) that is nested underneath (e.g. address_form { this_element })
156
157
  @parent_component = parent if parent.is_a?(Glimmer::Web::Component)
157
158
  if Component.interpretation_stack.last&.markup_root.nil?
159
+ # @component is the one tied to this component-internal element as the markup root
158
160
  @component = Component.interpretation_stack.last
159
161
  @component&.instance_variable_set("@markup_root", self)
162
+ else
163
+ # @ancestor_component is the one tied to this component-internal element as an ancestor
164
+ @ancestor_component = Component.interpretation_stack.last
160
165
  end
161
166
  @options = args.last.is_a?(Hash) ? args.last.symbolize_keys : {}
162
167
  if parent.nil?
@@ -164,6 +169,9 @@ module Glimmer
164
169
  options[:render] ||= Component.interpretation_stack.last&.options&.[](:render)
165
170
  options[:bulk_render] ||= Component.interpretation_stack.last&.options&.[](:bulk_render)
166
171
  end
172
+ @slot = options['slot'] || options[:slot]
173
+ @slot = @slot.to_sym if @slot
174
+ ancestor_component.slot_elements[@slot] = self if @slot && ancestor_component
167
175
  @args = args
168
176
  @block = block
169
177
  @children = []
@@ -171,6 +179,10 @@ module Glimmer
171
179
  render if !bulk_render? && !@rendered && render_after_create?
172
180
  end
173
181
 
182
+ def ancestor_component
183
+ @ancestor_component || @component
184
+ end
185
+
174
186
  def bulk_render?
175
187
  options[:bulk_render] != false && (@parent.nil? || @parent.bulk_render?)
176
188
  end
@@ -0,0 +1,228 @@
1
+ # Copyright (c) 2023-2024 Andy Maleh
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining
4
+ # a copy of this software and associated documentation files (the
5
+ # "Software"), to deal in the Software without restriction, including
6
+ # without limitation the rights to use, copy, modify, merge, publish,
7
+ # distribute, sublicense, and/or sell copies of the Software, and to
8
+ # permit persons to whom the Software is furnished to do so, subject to
9
+ # the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be
12
+ # included in all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
+
22
+ require 'glimmer-dsl-web'
23
+
24
+ Address = Struct.new(:full_name, :street, :street2, :city, :state, :zip_code, keyword_init: true) do
25
+ STATES = {
26
+ "AK"=>"Alaska",
27
+ "AL"=>"Alabama",
28
+ "AR"=>"Arkansas",
29
+ "AS"=>"American Samoa",
30
+ "AZ"=>"Arizona",
31
+ "CA"=>"California",
32
+ "CO"=>"Colorado",
33
+ "CT"=>"Connecticut",
34
+ "DC"=>"District of Columbia",
35
+ "DE"=>"Delaware",
36
+ "FL"=>"Florida",
37
+ "GA"=>"Georgia",
38
+ "GU"=>"Guam",
39
+ "HI"=>"Hawaii",
40
+ "IA"=>"Iowa",
41
+ "ID"=>"Idaho",
42
+ "IL"=>"Illinois",
43
+ "IN"=>"Indiana",
44
+ "KS"=>"Kansas",
45
+ "KY"=>"Kentucky",
46
+ "LA"=>"Louisiana",
47
+ "MA"=>"Massachusetts",
48
+ "MD"=>"Maryland",
49
+ "ME"=>"Maine",
50
+ "MI"=>"Michigan",
51
+ "MN"=>"Minnesota",
52
+ "MO"=>"Missouri",
53
+ "MS"=>"Mississippi",
54
+ "MT"=>"Montana",
55
+ "NC"=>"North Carolina",
56
+ "ND"=>"North Dakota",
57
+ "NE"=>"Nebraska",
58
+ "NH"=>"New Hampshire",
59
+ "NJ"=>"New Jersey",
60
+ "NM"=>"New Mexico",
61
+ "NV"=>"Nevada",
62
+ "NY"=>"New York",
63
+ "OH"=>"Ohio",
64
+ "OK"=>"Oklahoma",
65
+ "OR"=>"Oregon",
66
+ "PA"=>"Pennsylvania",
67
+ "PR"=>"Puerto Rico",
68
+ "RI"=>"Rhode Island",
69
+ "SC"=>"South Carolina",
70
+ "SD"=>"South Dakota",
71
+ "TN"=>"Tennessee",
72
+ "TX"=>"Texas",
73
+ "UT"=>"Utah",
74
+ "VA"=>"Virginia",
75
+ "VI"=>"Virgin Islands",
76
+ "VT"=>"Vermont",
77
+ "WA"=>"Washington",
78
+ "WI"=>"Wisconsin",
79
+ "WV"=>"West Virginia",
80
+ "WY"=>"Wyoming"
81
+ }
82
+
83
+ def state_code
84
+ STATES.invert[state]
85
+ end
86
+
87
+ def state_code=(value)
88
+ self.state = STATES[value]
89
+ end
90
+
91
+ def summary
92
+ to_h.values.map(&:to_s).reject(&:empty?).join(', ')
93
+ end
94
+ end
95
+
96
+ # AddressForm Glimmer Web Component (View component)
97
+ #
98
+ # Including Glimmer::Web::Component makes this class a View component and automatically
99
+ # generates a new Glimmer HTML DSL keyword that matches the lowercase underscored version
100
+ # of the name of the class. AddressForm generates address_form keyword, which can be used
101
+ # elsewhere in Glimmer HTML DSL code as done inside HelloComponentSlots below.
102
+ class AddressForm
103
+ include Glimmer::Web::Component
104
+
105
+ option :address
106
+
107
+ # markup block provides the content of the
108
+ markup {
109
+ div {
110
+ # designate this div as a slot with the slot name :address_header to enable
111
+ # consumers to contribute elements to `address_header {...}` slot
112
+ div(slot: :address_header, class: 'address-form-header')
113
+
114
+ div(class: 'address-field-container', style: {display: :grid, grid_auto_columns: '80px 260px'}) {
115
+ label('Full Name: ', for: 'full-name-field')
116
+ input(id: 'full-name-field') {
117
+ value <=> [address, :full_name]
118
+ }
119
+
120
+ @somelabel = label('Street: ', for: 'street-field')
121
+ input(id: 'street-field') {
122
+ value <=> [address, :street]
123
+ }
124
+
125
+ label('Street 2: ', for: 'street2-field')
126
+ textarea(id: 'street2-field') {
127
+ value <=> [address, :street2]
128
+ }
129
+
130
+ label('City: ', for: 'city-field')
131
+ input(id: 'city-field') {
132
+ value <=> [address, :city]
133
+ }
134
+
135
+ label('State: ', for: 'state-field')
136
+ select(id: 'state-field') {
137
+ Address::STATES.each do |state_code, state|
138
+ option(value: state_code) { state }
139
+ end
140
+
141
+ value <=> [address, :state_code]
142
+ }
143
+
144
+ label('Zip Code: ', for: 'zip-code-field')
145
+ input(id: 'zip-code-field', type: 'number', min: '0', max: '99999') {
146
+ value <=> [address, :zip_code,
147
+ on_write: :to_s,
148
+ ]
149
+ }
150
+ }
151
+
152
+ div(style: 'margin: 5px') {
153
+ inner_text <= [address, :summary,
154
+ computed_by: address.members + ['state_code'],
155
+ ]
156
+ }
157
+
158
+ # designate this div as a slot with the slot name :address_footer to enable
159
+ # consumers to contribute elements to `address_footer {...}` slot
160
+ div(slot: :address_footer, class: 'address-form-footer')
161
+ }
162
+ }
163
+
164
+ style {
165
+ r('.address-field-container *') {
166
+ margin 5
167
+ }
168
+ r('.address-field-container input, .address-field-container select') {
169
+ grid_column '2'
170
+ }
171
+ }
172
+ end
173
+
174
+ # HelloComponentSlots Glimmer Web Component (View component)
175
+ #
176
+ # This View component represents the main page being rendered,
177
+ # as done by its `render` class method below
178
+ class HelloComponentSlots
179
+ include Glimmer::Web::Component
180
+
181
+ before_render do
182
+ @shipping_address = Address.new(
183
+ full_name: 'Johnny Doe',
184
+ street: '3922 Park Ave',
185
+ street2: 'PO BOX 8382',
186
+ city: 'San Diego',
187
+ state: 'California',
188
+ zip_code: '91913',
189
+ )
190
+ @billing_address = Address.new(
191
+ full_name: 'John C Doe',
192
+ street: '123 Main St',
193
+ street2: 'Apartment 3C',
194
+ city: 'San Diego',
195
+ state: 'California',
196
+ zip_code: '91911',
197
+ )
198
+ end
199
+
200
+ markup {
201
+ div {
202
+ address_form(address: @shipping_address) {
203
+ address_header { # contribute elements to the address_header component slot
204
+ h1('Shipping Address')
205
+ legend('This is the address that is used for shipping your purchase.', style: {margin_bottom: 10})
206
+ }
207
+ address_footer { # contribute elements to the address_header component slot
208
+ p(sub("#{strong('Note:')} #{em('Purchase will be returned if the Shipping Address does not accept it in one week.')}"))
209
+ }
210
+ }
211
+
212
+ address_form(address: @billing_address) {
213
+ address_header { # contribute elements to the address_header component slot
214
+ h1('Billing Address')
215
+ legend('This is the address that is used for your billing method (e.g. credit card).', style: {margin_bottom: 10})
216
+ }
217
+ address_footer { # contribute elements to the address_header component slot
218
+ p(sub("#{strong('Note:')} #{em('Payment will fail if payment method does not match the Billing Address.')}"))
219
+ }
220
+ }
221
+ }
222
+ }
223
+ end
224
+
225
+ Document.ready? do
226
+ # renders a top-level (root) HelloComponentSlots component
227
+ HelloComponentSlots.render
228
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: glimmer-dsl-web
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.4
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andy Maleh
@@ -240,11 +240,12 @@ description: Glimmer DSL for Web (Ruby in the Browser Web Frontend Framework) en
240
240
  both structure code and logic code in one language. It supports both Unidirectional
241
241
  (One-Way) Data-Binding (using <=) and Bidirectional (Two-Way) Data-Binding (using
242
242
  <=>). Dynamic rendering (and re-rendering) of HTML content is also supported via
243
- Content Data-Binding. Modular design is supported with Glimmer Web Components. And,
244
- a Ruby CSS DSL is supported with the included Glimmer DSL for CSS. Many samples
245
- are demonstrated in the Rails sample app (there is a very minimal Standalone [No
246
- Rails] sample app too). You can finally live in pure Rubyland on the Web in both
247
- the frontend and backend with Glimmer DSL for Web! This gem relies on Opal Ruby.
243
+ Content Data-Binding. Modular design is supported with Glimmer Web Components and
244
+ Slots. And, a Ruby CSS DSL is supported with the included Glimmer DSL for CSS. Many
245
+ samples are demonstrated in the Rails sample app (there is a very minimal Standalone
246
+ [No Rails] sample app too). You can finally live in pure Rubyland on the Web in
247
+ both the frontend and backend with Glimmer DSL for Web! This gem relies on Opal
248
+ Ruby.
248
249
  email: andy.am@gmail.com
249
250
  executables: []
250
251
  extensions: []
@@ -266,6 +267,7 @@ files:
266
267
  - lib/glimmer-dsl-web/ext/kernel.rb
267
268
  - lib/glimmer-dsl-web/samples/hello/hello_button.rb
268
269
  - lib/glimmer-dsl-web/samples/hello/hello_component.rb
270
+ - lib/glimmer-dsl-web/samples/hello/hello_component_slots.rb
269
271
  - lib/glimmer-dsl-web/samples/hello/hello_content_data_binding.rb
270
272
  - lib/glimmer-dsl-web/samples/hello/hello_data_binding.rb
271
273
  - lib/glimmer-dsl-web/samples/hello/hello_form.rb
@@ -301,6 +303,7 @@ files:
301
303
  - lib/glimmer/dsl/web/bind_expression.rb
302
304
  - lib/glimmer/dsl/web/class_name_inclusion_data_binding.rb
303
305
  - lib/glimmer/dsl/web/component_expression.rb
306
+ - lib/glimmer/dsl/web/component_slot_content_expression.rb
304
307
  - lib/glimmer/dsl/web/content_data_binding_expression.rb
305
308
  - lib/glimmer/dsl/web/data_binding_expression.rb
306
309
  - lib/glimmer/dsl/web/dsl.rb