glimmer-dsl-web 0.4.3 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +10 -0
- data/README.md +251 -5
- data/VERSION +1 -1
- data/glimmer-dsl-web.gemspec +7 -5
- data/lib/glimmer/dsl/web/component_slot_content_expression.rb +48 -0
- data/lib/glimmer/dsl/web/dsl.rb +2 -0
- data/lib/glimmer/dsl/web/element_expression.rb +9 -1
- data/lib/glimmer/web/component.rb +2 -1
- data/lib/glimmer/web/element_proxy.rb +55 -5
- data/lib/glimmer-dsl-web/samples/hello/hello_component_slots.rb +228 -0
- data/lib/glimmer-dsl-web/samples/regular/todo_mvc/views/todo_list_item.rb +8 -6
- metadata +12 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 702f2aed3db1d4fcb6fbda9e794a756a3cdb44dfa47db714bbd350a5dd5e1d81
|
4
|
+
data.tar.gz: 5c6b8c276bc246c023cb9419744bceea8021450943cf7e2da2dfb0f86cbb4b3c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5e3dbff73a4ebb082523dbcf76facc65d27a93e69e2873c08f02bd5939b34a1e9292eadd7698963d5eb53cfa164fbf7b2b14a100213c5d0f3d8d5566d6dc08cd
|
7
|
+
data.tar.gz: dfcc0d083636b8d0943b1ff78215c73651bfe768b970ac9cf81852a9e29ab54bf18f94d49483dc4753154c5497cbeb0f99acfbf91d684eebb97d533be19ccb30
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,15 @@
|
|
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
|
+
|
7
|
+
## 0.4.4
|
8
|
+
|
9
|
+
- Support setting element `style` property as String or Hash (like `background-color: yello; font-size: 12px;` or `{background_color: :yellow, font_size: 12}`)
|
10
|
+
- Support setting element `classes` property as String or Array of Strings/Symbols (like `"pushed round"`, `['pushed', 'round']` or `[:pushed, :round]`)
|
11
|
+
- Upgrade opal-jquery to version 0.5.2
|
12
|
+
|
3
13
|
## 0.4.3
|
4
14
|
|
5
15
|
- Support element class name inclusion data-binding (e.g. `class_name('pushed') <= [@button_model, :pushed]`)
|
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.
|
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-
|
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.
|
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.
|
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.
|
1
|
+
0.5.0
|
data/glimmer-dsl-web.gemspec
CHANGED
@@ -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.
|
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.
|
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
|
-
s.date = "2024-
|
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
|
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 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",
|
@@ -104,7 +106,7 @@ Gem::Specification.new do |s|
|
|
104
106
|
s.add_runtime_dependency(%q<opal>.freeze, ["= 1.8.2"])
|
105
107
|
s.add_runtime_dependency(%q<opal-rails>.freeze, ["= 2.0.3"])
|
106
108
|
s.add_runtime_dependency(%q<opal-async>.freeze, ["~> 1.4.1"])
|
107
|
-
s.add_runtime_dependency(%q<opal-jquery>.freeze, ["~> 0.5.
|
109
|
+
s.add_runtime_dependency(%q<opal-jquery>.freeze, ["~> 0.5.2"])
|
108
110
|
s.add_runtime_dependency(%q<to_collection>.freeze, [">= 2.0.1", "< 3.0.0"])
|
109
111
|
s.add_development_dependency(%q<puts_debuggerer>.freeze, [">= 1.0.1"])
|
110
112
|
s.add_development_dependency(%q<rake>.freeze, [">= 10.1.0", "< 14.0.0"])
|
@@ -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
|
data/lib/glimmer/dsl/web/dsl.rb
CHANGED
@@ -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
|
-
|
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
|
@@ -328,6 +340,22 @@ module Glimmer
|
|
328
340
|
end
|
329
341
|
end
|
330
342
|
|
343
|
+
def style(value = null)
|
344
|
+
if rendered?
|
345
|
+
if value.nil?
|
346
|
+
dom_element.attr('style')
|
347
|
+
else
|
348
|
+
value = normalize_style(value)
|
349
|
+
dom_element.attr('style', value)
|
350
|
+
end
|
351
|
+
else
|
352
|
+
enqueue_args = ['style']
|
353
|
+
enqueue_args << value unless value.nil?
|
354
|
+
enqueue_post_render_method_call(*enqueue_args)
|
355
|
+
end
|
356
|
+
end
|
357
|
+
alias style= style
|
358
|
+
|
331
359
|
def style_property(property, value = nil)
|
332
360
|
if rendered?
|
333
361
|
property = property.to_s.gsub('_', '-')
|
@@ -451,7 +479,8 @@ module Glimmer
|
|
451
479
|
html_options["data-#{data_normalized_attribute}"] = html_options.delete(attribute)
|
452
480
|
end
|
453
481
|
html_options[:class] ||= ''
|
454
|
-
html_options[:class] = "#{html_options
|
482
|
+
html_options[:class] = "#{normalize_class_name(html_options.delete('class') || html_options.delete(:class))} #{body_class}".strip
|
483
|
+
html_options[:style] = normalize_style(html_options.delete('style') || html_options.delete(:style))
|
455
484
|
html_options['data-turbo'] = 'false' if parent.nil?
|
456
485
|
html_options
|
457
486
|
end
|
@@ -480,15 +509,16 @@ module Glimmer
|
|
480
509
|
@element_id ||= "element-#{ElementProxy.next_id_number_for(name)}"
|
481
510
|
end
|
482
511
|
|
483
|
-
def class_name=(
|
512
|
+
def class_name=(*values)
|
484
513
|
if rendered?
|
485
|
-
values =
|
514
|
+
values = normalize_class_name(values).split(' ')
|
486
515
|
new_class_name = (base_css_classes + values).uniq.compact.join(' ')
|
487
516
|
dom_element.prop('className', new_class_name)
|
488
517
|
else
|
489
|
-
enqueue_post_render_method_call('class_name=',
|
518
|
+
enqueue_post_render_method_call('class_name=', *values)
|
490
519
|
end
|
491
520
|
end
|
521
|
+
alias classes= class_name=
|
492
522
|
|
493
523
|
def add_css_class(css_class)
|
494
524
|
if rendered?
|
@@ -932,6 +962,26 @@ module Glimmer
|
|
932
962
|
SWT_CURSOR_TO_CSS_CURSOR_MAP[@cursor]
|
933
963
|
end
|
934
964
|
|
965
|
+
def normalize_class_name(class_name_value)
|
966
|
+
if class_name_value.is_a?(Array)
|
967
|
+
class_name_value.map(&:to_s).join(' ')
|
968
|
+
else
|
969
|
+
class_name_value.to_s
|
970
|
+
end
|
971
|
+
end
|
972
|
+
|
973
|
+
def normalize_style(style_value)
|
974
|
+
if style_value.is_a?(Hash)
|
975
|
+
style_value.reduce('') do |output, (key, value)|
|
976
|
+
key = key.to_s.gsub('_', '-')
|
977
|
+
value = value.px if value.is_a?(Numeric)
|
978
|
+
output += "#{key}: #{value}; "
|
979
|
+
end
|
980
|
+
else
|
981
|
+
style_value.to_s
|
982
|
+
end
|
983
|
+
end
|
984
|
+
|
935
985
|
end
|
936
986
|
end
|
937
987
|
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
|
@@ -14,17 +14,19 @@ class TodoListItem
|
|
14
14
|
end
|
15
15
|
|
16
16
|
markup {
|
17
|
+
|
18
|
+
|
17
19
|
li {
|
18
|
-
# Data-bind inclusion of `
|
19
|
-
# meaning inclusion of completed class
|
20
|
+
# Data-bind inclusion of `completed` in `li` `class` attribute unidirectionally to `todo` `completed` attribute,
|
21
|
+
# meaning inclusion/exclusion of `completed` class happens automatically when `todo.completed` boolean value changes.
|
20
22
|
class_name(:completed) <= [todo, :completed]
|
21
23
|
|
22
|
-
# Data-bind inclusion of `
|
23
|
-
# meaning inclusion of active class
|
24
|
+
# Data-bind inclusion of `active` in `li` `class` attribute unidirectionally to `todo` `completed` attribute, negated,
|
25
|
+
# meaning inclusion/exclusion of `active` class happens automatically when `todo.completed` negated boolean value changes.
|
24
26
|
class_name(:active) <= [todo, :completed, on_read: :!]
|
25
27
|
|
26
|
-
# Data-bind inclusion of `
|
27
|
-
# meaning inclusion of editing class
|
28
|
+
# Data-bind inclusion of `editing` in `li` `class` attribute unidirectionally to `todo` `editing` attribute,
|
29
|
+
# meaning inclusion/exclusion of `editing` class happens automatically when `todo.editing` boolean value changes.
|
28
30
|
class_name(:editing) <= [todo, :editing]
|
29
31
|
|
30
32
|
div(class: 'view') {
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: glimmer-dsl-web
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andy Maleh
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-
|
11
|
+
date: 2024-08-03 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: glimmer
|
@@ -100,14 +100,14 @@ dependencies:
|
|
100
100
|
requirements:
|
101
101
|
- - "~>"
|
102
102
|
- !ruby/object:Gem::Version
|
103
|
-
version: 0.5.
|
103
|
+
version: 0.5.2
|
104
104
|
type: :runtime
|
105
105
|
prerelease: false
|
106
106
|
version_requirements: !ruby/object:Gem::Requirement
|
107
107
|
requirements:
|
108
108
|
- - "~>"
|
109
109
|
- !ruby/object:Gem::Version
|
110
|
-
version: 0.5.
|
110
|
+
version: 0.5.2
|
111
111
|
- !ruby/object:Gem::Dependency
|
112
112
|
name: to_collection
|
113
113
|
requirement: !ruby/object:Gem::Requirement
|
@@ -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
|
244
|
-
a Ruby CSS DSL is supported with the included Glimmer DSL for CSS. Many
|
245
|
-
are demonstrated in the Rails sample app (there is a very minimal Standalone
|
246
|
-
Rails] sample app too). You can finally live in pure Rubyland on the Web in
|
247
|
-
the frontend and backend with Glimmer DSL for Web! This gem relies on Opal
|
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
|