glimmer-dsl-web 0.0.9 → 0.0.11
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +19 -0
- data/README.md +712 -38
- data/VERSION +1 -1
- data/glimmer-dsl-web.gemspec +11 -6
- data/lib/glimmer/dsl/web/component_expression.rb +2 -1
- data/lib/glimmer/dsl/web/dsl.rb +22 -0
- data/lib/glimmer/dsl/web/observe_expression.rb +42 -0
- data/lib/glimmer/helpers/glimmer_helper.rb +30 -0
- data/lib/glimmer/web/component.rb +29 -5
- data/lib/glimmer/web/element_proxy.rb +15 -17
- data/lib/glimmer/web/event_proxy.rb +7 -4
- data/lib/glimmer/web/listener_proxy.rb +12 -11
- data/lib/glimmer-dsl-web/samples/hello/hello_glimmer_component_helper/address_form.rb +156 -0
- data/lib/glimmer-dsl-web/samples/hello/hello_observer.rb +88 -0
- data/lib/glimmer-dsl-web/samples/regular/button_counter.rb +36 -0
- data/lib/glimmer-dsl-web.rb +1 -0
- metadata +13 -8
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.0.
|
1
|
+
# [<img src="https://raw.githubusercontent.com/AndyObtiva/glimmer/master/images/glimmer-logo-hi-res.png" height=85 />](https://github.com/AndyObtiva/glimmer) Glimmer DSL for Web 0.0.11 (Early Alpha)
|
2
2
|
## Ruby in the Browser Web GUI Frontend Library
|
3
3
|
[![Gem Version](https://badge.fury.io/rb/glimmer-dsl-web.svg)](http://badge.fury.io/rb/glimmer-dsl-web)
|
4
4
|
[![Join the chat at https://gitter.im/AndyObtiva/glimmer](https://badges.gitter.im/AndyObtiva/glimmer.svg)](https://gitter.im/AndyObtiva/glimmer?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
@@ -220,6 +220,86 @@ Screenshot:
|
|
220
220
|
|
221
221
|
![Hello, Form!](/images/glimmer-dsl-web-samples-hello-hello-form.gif)
|
222
222
|
|
223
|
+
**Hello, Observer!**
|
224
|
+
|
225
|
+
[Glimmer DSL for Web](https://rubygems.org/gems/glimmer-dsl-web) provides the `observe(model, attribute) { ... }` keyword to employ the [Observer Design Pattern](https://en.wikipedia.org/wiki/Observer_pattern) as per [MVC](https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller) (Model View Controller), enabling Views to observe Models and update themselves in response to changes. If the `observe` keyword is used from inside a [Component](#hello-component) (covered later), when the Component is removed or its top-level element is removed, the observer is automatically cleaned up. The need for such explicit observers is significantly diminished by the availablility of the more advanced Unidirectional [Data-Binding](#hello-data-binding) Support and Bidirectional [Data-Binding](#hello-data-binding) Support (covered later).
|
226
|
+
|
227
|
+
Glimmer GUI code:
|
228
|
+
|
229
|
+
```ruby
|
230
|
+
require 'glimmer-dsl-web'
|
231
|
+
|
232
|
+
class NumberHolder
|
233
|
+
attr_accessor :number
|
234
|
+
|
235
|
+
def initialize
|
236
|
+
self.number = 50
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
class HelloObserver
|
241
|
+
include Glimmer::Web::Component
|
242
|
+
|
243
|
+
before_render do
|
244
|
+
@number_holder = NumberHolder.new
|
245
|
+
end
|
246
|
+
|
247
|
+
after_render do
|
248
|
+
# observe Model attribute @number_holder.number for changes and update View
|
249
|
+
observe(@number_holder, :number) do
|
250
|
+
number_string = @number_holder.number.to_s
|
251
|
+
@number_input.value = number_string unless @number_input.value == number_string
|
252
|
+
@range_input.value = number_string unless @range_input.value == number_string
|
253
|
+
end
|
254
|
+
# Bidirectional Data-Binding does the same thing automatically
|
255
|
+
# Just disable the observe block above as well as the oninput listeners below
|
256
|
+
# and enable the `value <=> [@number_holder, :number]` lines to try the data-binding version
|
257
|
+
# Learn more about Bidirectional and Unidirectional Data-Binding in hello_data_binding.rb
|
258
|
+
end
|
259
|
+
|
260
|
+
markup {
|
261
|
+
div {
|
262
|
+
div {
|
263
|
+
@number_input = input(type: 'number', value: @number_holder.number, min: 0, max: 100) {
|
264
|
+
# oninput listener updates Model attribute @number_holder.number
|
265
|
+
oninput do
|
266
|
+
@number_holder.number = @number_input.value.to_i
|
267
|
+
end
|
268
|
+
|
269
|
+
# Bidirectional Data-Binding simplifies the implementation significantly
|
270
|
+
# by enabling the following line and disabling oninput listeners as well
|
271
|
+
# as the after_body observe block observer
|
272
|
+
# Learn more about Bidirectional and Unidirectional Data-Binding in hello_data_binding.rb
|
273
|
+
# value <=> [@number_holder, :number]
|
274
|
+
}
|
275
|
+
}
|
276
|
+
div {
|
277
|
+
@range_input = input(type: 'range', value: @number_holder.number, min: 0, max: 100) {
|
278
|
+
# oninput listener updates Model attribute @number_holder.number
|
279
|
+
oninput do
|
280
|
+
@number_holder.number = @range_input.value.to_i
|
281
|
+
end
|
282
|
+
|
283
|
+
# Bidirectional Data-Binding simplifies the implementation significantly
|
284
|
+
# by enabling the following line and disabling oninput listeners as well
|
285
|
+
# as the after_body observe block observer
|
286
|
+
# Learn more about Bidirectional and Unidirectional Data-Binding in hello_data_binding.rb
|
287
|
+
# value <=> [@number_holder, :number]
|
288
|
+
}
|
289
|
+
}
|
290
|
+
}
|
291
|
+
}
|
292
|
+
end
|
293
|
+
|
294
|
+
Document.ready? do
|
295
|
+
HelloObserver.render
|
296
|
+
end
|
297
|
+
```
|
298
|
+
|
299
|
+
Screenshot:
|
300
|
+
|
301
|
+
![Hello, Observer!](/images/glimmer-dsl-web-samples-hello-hello-observer.gif)
|
302
|
+
|
223
303
|
**Hello, Data-Binding!**
|
224
304
|
|
225
305
|
[Glimmer DSL for Web](https://rubygems.org/gems/glimmer-dsl-web) intuitively supports both Unidirectional (One-Way) Data-Binding via the `<=` operator and Bidirectional (Two-Way) Data-Binding via the `<=>` operator, incredibly simplifying how to sync View properties with Model attributes with the simplest code to reason about.
|
@@ -693,6 +773,211 @@ Screenshot:
|
|
693
773
|
|
694
774
|
![Hello, Component!](/images/glimmer-dsl-web-samples-hello-hello-component.png)
|
695
775
|
|
776
|
+
**Hello, glimmer_component Rails Helper!**
|
777
|
+
|
778
|
+
You may insert a Glimmer component anywhere into a Rails View using
|
779
|
+
`glimmer_component(component_path, *args)` Rails helper. Add `include GlimmerHelper` to `ApplicationHelper`
|
780
|
+
or another Rails helper, and use `<%= glimmer_component("path/to/component", *args) %>` in Views.
|
781
|
+
|
782
|
+
Rails `ApplicationHelper` setup code:
|
783
|
+
|
784
|
+
```ruby
|
785
|
+
require 'glimmer/helpers/glimmer_helper'
|
786
|
+
|
787
|
+
module ApplicationHelper
|
788
|
+
# ...
|
789
|
+
include GlimmerHelper
|
790
|
+
# ...
|
791
|
+
end
|
792
|
+
```
|
793
|
+
|
794
|
+
Rails View code:
|
795
|
+
|
796
|
+
```erb
|
797
|
+
<div id="address-container">
|
798
|
+
<h1>Shipping Address </h1>
|
799
|
+
<legend>Please enter your shipping address information (Zip Code must be a valid 5 digit number)</legend>
|
800
|
+
<!-- This sample demonstrates use of glimmer_component helper with arguments -->
|
801
|
+
<%= glimmer_component('address_form',
|
802
|
+
full_name: params[:full_name],
|
803
|
+
street: params[:street],
|
804
|
+
street2: params[:street2],
|
805
|
+
city: params[:city],
|
806
|
+
state: params[:state],
|
807
|
+
zip_code: params[:zip_code]
|
808
|
+
)
|
809
|
+
%>
|
810
|
+
<div>
|
811
|
+
<a href="/"><< Back Home</a>
|
812
|
+
</div>
|
813
|
+
</div>
|
814
|
+
```
|
815
|
+
|
816
|
+
Glimmer GUI code:
|
817
|
+
|
818
|
+
```ruby
|
819
|
+
require 'glimmer-dsl-web'
|
820
|
+
|
821
|
+
class AddressForm
|
822
|
+
Address = Struct.new(:full_name, :street, :street2, :city, :state, :zip_code, keyword_init: true) do
|
823
|
+
def state_code
|
824
|
+
STATES.invert[state]
|
825
|
+
end
|
826
|
+
|
827
|
+
def state_code=(value)
|
828
|
+
self.state = STATES[value]
|
829
|
+
end
|
830
|
+
|
831
|
+
def summary
|
832
|
+
to_h.values.map(&:to_s).reject(&:empty?).join(', ')
|
833
|
+
end
|
834
|
+
end
|
835
|
+
|
836
|
+
STATES = {
|
837
|
+
"AK"=>"Alaska",
|
838
|
+
"AL"=>"Alabama",
|
839
|
+
"AR"=>"Arkansas",
|
840
|
+
"AS"=>"American Samoa",
|
841
|
+
"AZ"=>"Arizona",
|
842
|
+
"CA"=>"California",
|
843
|
+
"CO"=>"Colorado",
|
844
|
+
"CT"=>"Connecticut",
|
845
|
+
"DC"=>"District of Columbia",
|
846
|
+
"DE"=>"Delaware",
|
847
|
+
"FL"=>"Florida",
|
848
|
+
"GA"=>"Georgia",
|
849
|
+
"GU"=>"Guam",
|
850
|
+
"HI"=>"Hawaii",
|
851
|
+
"IA"=>"Iowa",
|
852
|
+
"ID"=>"Idaho",
|
853
|
+
"IL"=>"Illinois",
|
854
|
+
"IN"=>"Indiana",
|
855
|
+
"KS"=>"Kansas",
|
856
|
+
"KY"=>"Kentucky",
|
857
|
+
"LA"=>"Louisiana",
|
858
|
+
"MA"=>"Massachusetts",
|
859
|
+
"MD"=>"Maryland",
|
860
|
+
"ME"=>"Maine",
|
861
|
+
"MI"=>"Michigan",
|
862
|
+
"MN"=>"Minnesota",
|
863
|
+
"MO"=>"Missouri",
|
864
|
+
"MS"=>"Mississippi",
|
865
|
+
"MT"=>"Montana",
|
866
|
+
"NC"=>"North Carolina",
|
867
|
+
"ND"=>"North Dakota",
|
868
|
+
"NE"=>"Nebraska",
|
869
|
+
"NH"=>"New Hampshire",
|
870
|
+
"NJ"=>"New Jersey",
|
871
|
+
"NM"=>"New Mexico",
|
872
|
+
"NV"=>"Nevada",
|
873
|
+
"NY"=>"New York",
|
874
|
+
"OH"=>"Ohio",
|
875
|
+
"OK"=>"Oklahoma",
|
876
|
+
"OR"=>"Oregon",
|
877
|
+
"PA"=>"Pennsylvania",
|
878
|
+
"PR"=>"Puerto Rico",
|
879
|
+
"RI"=>"Rhode Island",
|
880
|
+
"SC"=>"South Carolina",
|
881
|
+
"SD"=>"South Dakota",
|
882
|
+
"TN"=>"Tennessee",
|
883
|
+
"TX"=>"Texas",
|
884
|
+
"UT"=>"Utah",
|
885
|
+
"VA"=>"Virginia",
|
886
|
+
"VI"=>"Virgin Islands",
|
887
|
+
"VT"=>"Vermont",
|
888
|
+
"WA"=>"Washington",
|
889
|
+
"WI"=>"Wisconsin",
|
890
|
+
"WV"=>"West Virginia",
|
891
|
+
"WY"=>"Wyoming"
|
892
|
+
}
|
893
|
+
|
894
|
+
include Glimmer::Web::Component
|
895
|
+
|
896
|
+
option :full_name
|
897
|
+
option :street
|
898
|
+
option :street2
|
899
|
+
option :city
|
900
|
+
option :state
|
901
|
+
option :zip_code
|
902
|
+
|
903
|
+
attr_reader :address
|
904
|
+
|
905
|
+
before_render do
|
906
|
+
@address = Address.new(
|
907
|
+
full_name: full_name,
|
908
|
+
street: street,
|
909
|
+
street2: street2,
|
910
|
+
city: city,
|
911
|
+
state: state,
|
912
|
+
zip_code: zip_code,
|
913
|
+
)
|
914
|
+
end
|
915
|
+
|
916
|
+
markup {
|
917
|
+
div {
|
918
|
+
div(style: 'display: grid; grid-auto-columns: 80px 260px;') { |address_div|
|
919
|
+
label('Full Name: ', for: 'full-name-field')
|
920
|
+
input(id: 'full-name-field') {
|
921
|
+
value <=> [address, :full_name]
|
922
|
+
}
|
923
|
+
|
924
|
+
@somelabel = label('Street: ', for: 'street-field')
|
925
|
+
input(id: 'street-field') {
|
926
|
+
value <=> [address, :street]
|
927
|
+
}
|
928
|
+
|
929
|
+
label('Street 2: ', for: 'street2-field')
|
930
|
+
textarea(id: 'street2-field') {
|
931
|
+
value <=> [address, :street2]
|
932
|
+
}
|
933
|
+
|
934
|
+
label('City: ', for: 'city-field')
|
935
|
+
input(id: 'city-field') {
|
936
|
+
value <=> [address, :city]
|
937
|
+
}
|
938
|
+
|
939
|
+
label('State: ', for: 'state-field')
|
940
|
+
select(id: 'state-field') {
|
941
|
+
STATES.each do |state_code, state|
|
942
|
+
option(value: state_code) { state }
|
943
|
+
end
|
944
|
+
|
945
|
+
value <=> [address, :state_code]
|
946
|
+
}
|
947
|
+
|
948
|
+
label('Zip Code: ', for: 'zip-code-field')
|
949
|
+
input(id: 'zip-code-field', type: 'number', min: '0', max: '99999') {
|
950
|
+
value <=> [address, :zip_code,
|
951
|
+
on_write: :to_s,
|
952
|
+
]
|
953
|
+
}
|
954
|
+
|
955
|
+
style {
|
956
|
+
<<~CSS
|
957
|
+
#{address_div.selector} * {
|
958
|
+
margin: 5px;
|
959
|
+
}
|
960
|
+
#{address_div.selector} input, #{address_div.selector} select {
|
961
|
+
grid-column: 2;
|
962
|
+
}
|
963
|
+
CSS
|
964
|
+
}
|
965
|
+
}
|
966
|
+
|
967
|
+
div(style: 'margin: 5px') {
|
968
|
+
inner_text <= [address, :summary,
|
969
|
+
computed_by: address.members + ['state_code'],
|
970
|
+
]
|
971
|
+
}
|
972
|
+
}
|
973
|
+
}
|
974
|
+
end
|
975
|
+
```
|
976
|
+
|
977
|
+
Screenshot:
|
978
|
+
|
979
|
+
![Hello, glimmer_component Rails Helper!](/images/glimmer-dsl-web-samples-hello-hello-component.png)
|
980
|
+
|
696
981
|
NOTE: Glimmer DSL for Web is an Early Alpha project. If you want it developed faster, please [open an issue report](https://github.com/AndyObtiva/glimmer-dsl-web/issues/new). I have completed some GitHub project features much faster before due to [issue reports](https://github.com/AndyObtiva/glimmer-dsl-web/issues) and [pull requests](https://github.com/AndyObtiva/glimmer-dsl-web/pulls). Please help make better by contributing, adopting for small or low risk projects, and providing feedback. It is still an early alpha, so the more feedback and issues you report the better.
|
697
982
|
|
698
983
|
Learn more about the differences between various [Glimmer](https://github.com/AndyObtiva/glimmer) DSLs by looking at:
|
@@ -714,9 +999,11 @@ Learn more about the differences between various [Glimmer](https://github.com/An
|
|
714
999
|
- [Hello, World!](#hello-world)
|
715
1000
|
- [Hello, Button!](#hello-button)
|
716
1001
|
- [Hello, Form!](#hello-form)
|
1002
|
+
- [Hello, Observer!](#hello-observer)
|
717
1003
|
- [Hello, Data-Binding!](#hello-data-binding)
|
718
1004
|
- [Hello, Content Data-Binding!](#hello-content-data-binding)
|
719
1005
|
- [Hello, Component!](#hello-content-data-binding)
|
1006
|
+
- [Hello, glimmer_component Rails Helper!](#hello-glimmer_component-rails-helper)
|
720
1007
|
- [Hello, Input (Date/Time)!](#hello-input-datetime)
|
721
1008
|
- [Button Counter](#button-counter)
|
722
1009
|
- [Glimmer Process](#glimmer-process)
|
@@ -733,10 +1020,8 @@ Learn more about the differences between various [Glimmer](https://github.com/An
|
|
733
1020
|
|
734
1021
|
[Glimmer DSL for Web](https://rubygems.org/gems/glimmer-dsl-web) will begin by supporting [Opal Ruby](https://opalrb.com/) on [Rails](https://rubyonrails.org/). [Opal](https://opalrb.com/) is a lightweight Ruby to JavaScript transpiler that results in small downloadables compared to WASM. In the future, the project might grow to support [Ruby WASM](https://github.com/ruby/ruby.wasm) as an alternative to [Opal Ruby](https://opalrb.com/) that could be switched to with a simple configuration change.
|
735
1022
|
|
736
|
-
- Ruby 3.0
|
1023
|
+
- Ruby 3.0+
|
737
1024
|
- Rails 6-7: [https://github.com/rails/rails](https://github.com/rails/rails)
|
738
|
-
- Opal 1.4.1 for Rails 6-7: [https://github.com/opal/opal](https://github.com/opal/opal)
|
739
|
-
- Opal-Rails 2.0.2 for Rails 6-7: [https://github.com/opal/opal-rails](https://github.com/opal/opal-rails)
|
740
1025
|
|
741
1026
|
## Setup
|
742
1027
|
|
@@ -763,7 +1048,7 @@ rails new glimmer_app_server
|
|
763
1048
|
Add the following to `Gemfile`:
|
764
1049
|
|
765
1050
|
```
|
766
|
-
gem 'glimmer-dsl-web', '~> 0.0.
|
1051
|
+
gem 'glimmer-dsl-web', '~> 0.0.11'
|
767
1052
|
```
|
768
1053
|
|
769
1054
|
Run:
|
@@ -772,6 +1057,8 @@ Run:
|
|
772
1057
|
bundle
|
773
1058
|
```
|
774
1059
|
|
1060
|
+
(run `rm -rf tmp/cache` from inside your Rails app if you upgrade your `glimmer-dsl-web` gem version from an older one to clear Opal-Rails's cache)
|
1061
|
+
|
775
1062
|
Follow [opal-rails](https://github.com/opal/opal-rails) instructions, basically running:
|
776
1063
|
|
777
1064
|
```
|
@@ -782,6 +1069,7 @@ To enable the `glimmer-dsl-web` library in the frontend, edit `config/initialize
|
|
782
1069
|
|
783
1070
|
```ruby
|
784
1071
|
Opal.use_gem 'glimmer-dsl-web'
|
1072
|
+
Opal.append_path Rails.root.join('app', 'assets', 'opal')
|
785
1073
|
```
|
786
1074
|
|
787
1075
|
To enable Opal Browser Debugging in Ruby with the [Source Maps](https://opalrb.com/docs/guides/v1.4.1/source_maps.html) feature, edit `config/initializers/opal.rb` and add the following inside the `Rails.application.configure do; end` block at the bottom of it:
|
@@ -811,9 +1099,20 @@ root to: 'welcomes#index'
|
|
811
1099
|
|
812
1100
|
Clear the file `app/views/welcomes/index.html.erb` completely from all content.
|
813
1101
|
|
814
|
-
Delete `app/javascript
|
1102
|
+
Delete `app/javascript` directory
|
815
1103
|
|
816
|
-
|
1104
|
+
Rename `app/assets/javascript` directory to `app/assets/opal`.
|
1105
|
+
|
1106
|
+
Add the following lines to `app/assets/config/manifest.js` (and delete their `javascript` equivalents):
|
1107
|
+
|
1108
|
+
```js
|
1109
|
+
//= link_tree ../../opal .js
|
1110
|
+
//= link_directory ../opal .js
|
1111
|
+
```
|
1112
|
+
|
1113
|
+
Rename `app/assets/opal/application.js.rb` to `app/assets/opal/application.rb`.
|
1114
|
+
|
1115
|
+
Edit and replace `app/assets/opal/application.rb` content with code below (optionally including a require statement for one of the [samples](#samples) below):
|
817
1116
|
|
818
1117
|
```ruby
|
819
1118
|
require 'glimmer-dsl-web' # brings opal and other dependencies automatically
|
@@ -821,9 +1120,67 @@ require 'glimmer-dsl-web' # brings opal and other dependencies automatically
|
|
821
1120
|
# Add more require-statements or Glimmer GUI DSL code
|
822
1121
|
```
|
823
1122
|
|
1123
|
+
```ruby
|
1124
|
+
require 'glimmer-dsl-web'
|
1125
|
+
|
1126
|
+
require 'glimmer-dsl-web/samples/hello/hello_world.rb'
|
1127
|
+
```
|
1128
|
+
|
1129
|
+
If the `<body></body>` element (where the Glimmer GUI DSL adds elements by default) is not available when the JS file is loading, you need to put the code inside a `Document.ready? do; end` (but, it is recommended that you load the JS file after the parent element like `<body></body>` is in the page already for faster performance, which is guaranteed automatically by using `glimmer_component`, mentioned in details below):
|
1130
|
+
|
1131
|
+
```ruby
|
1132
|
+
require 'glimmer-dsl-web'
|
1133
|
+
|
1134
|
+
Document.ready? do
|
1135
|
+
require 'glimmer-dsl-web/samples/hello/hello_world.rb'
|
1136
|
+
end
|
1137
|
+
```
|
1138
|
+
|
824
1139
|
Example to confirm setup is working:
|
825
1140
|
|
826
|
-
|
1141
|
+
Glimmer GUI code:
|
1142
|
+
|
1143
|
+
```ruby
|
1144
|
+
require 'glimmer-dsl-web'
|
1145
|
+
|
1146
|
+
include Glimmer
|
1147
|
+
|
1148
|
+
Document.ready? do
|
1149
|
+
# This will hook into element #app-container and then build HTML inside it using Ruby DSL code
|
1150
|
+
div {
|
1151
|
+
label(class: 'greeting') {
|
1152
|
+
'Hello, World!'
|
1153
|
+
}
|
1154
|
+
}.render
|
1155
|
+
end
|
1156
|
+
```
|
1157
|
+
|
1158
|
+
That produces:
|
1159
|
+
|
1160
|
+
```html
|
1161
|
+
<body>
|
1162
|
+
<div data-parent="body" class="element element-1">
|
1163
|
+
<label class="greeting element element-2">
|
1164
|
+
Hello, World!
|
1165
|
+
</label>
|
1166
|
+
</div>
|
1167
|
+
</body>
|
1168
|
+
```
|
1169
|
+
|
1170
|
+
Start the Rails server:
|
1171
|
+
```
|
1172
|
+
rails s
|
1173
|
+
```
|
1174
|
+
|
1175
|
+
Visit `http://localhost:3000`
|
1176
|
+
|
1177
|
+
You should see:
|
1178
|
+
|
1179
|
+
![setup is working](/images/glimmer-dsl-web-setup-example-working.png)
|
1180
|
+
|
1181
|
+
If you want to customize where the top-level element is mounted, just pass a `parent: 'css_selector'` option.
|
1182
|
+
|
1183
|
+
HTML:
|
827
1184
|
|
828
1185
|
```html
|
829
1186
|
...
|
@@ -854,7 +1211,7 @@ That produces:
|
|
854
1211
|
```html
|
855
1212
|
...
|
856
1213
|
<div id="app-container">
|
857
|
-
<div data-parent="
|
1214
|
+
<div data-parent="app-container" class="element element-1">
|
858
1215
|
<label class="greeting element element-2">
|
859
1216
|
Hello, World!
|
860
1217
|
</label>
|
@@ -862,17 +1219,21 @@ That produces:
|
|
862
1219
|
</div>
|
863
1220
|
...
|
864
1221
|
```
|
1222
|
+
You may insert a Glimmer component anywhere into a Rails View using `glimmer_component(component_path, *args)` Rails helper. Add `include GlimmerHelper` to `ApplicationHelper` or another Rails helper, and use `<%= glimmer_component("path/to/component", *args) %>` in Views.
|
865
1223
|
|
866
|
-
|
867
|
-
```
|
868
|
-
rails s
|
869
|
-
```
|
1224
|
+
To use `glimmer_component`, edit `app/helpers/application_helper.rb` in your Rails application, add `require 'glimmer/helpers/glimmer_helper'` on top and `include GlimmerHelper` inside `module`.
|
870
1225
|
|
871
|
-
|
1226
|
+
`app/helpers/application_helper.rb` should look like this after the change:
|
872
1227
|
|
873
|
-
|
1228
|
+
```ruby
|
1229
|
+
require 'glimmer/helpers/glimmer_helper'
|
874
1230
|
|
875
|
-
|
1231
|
+
module ApplicationHelper
|
1232
|
+
# ...
|
1233
|
+
include GlimmerHelper
|
1234
|
+
# ...
|
1235
|
+
end
|
1236
|
+
```
|
876
1237
|
|
877
1238
|
If you run into any issues in setup, refer to the [Sample Glimmer DSL for Web Rails 7 App](https://github.com/AndyObtiva/sample-glimmer-dsl-web-rails7-app) project (in case I forgot to include some setup steps by mistake).
|
878
1239
|
|
@@ -903,7 +1264,7 @@ Disable the `webpacker` gem line in `Gemfile`:
|
|
903
1264
|
Add the following to `Gemfile`:
|
904
1265
|
|
905
1266
|
```ruby
|
906
|
-
gem 'glimmer-dsl-web', '~> 0.0.
|
1267
|
+
gem 'glimmer-dsl-web', '~> 0.0.11'
|
907
1268
|
```
|
908
1269
|
|
909
1270
|
Run:
|
@@ -912,6 +1273,8 @@ Run:
|
|
912
1273
|
bundle
|
913
1274
|
```
|
914
1275
|
|
1276
|
+
(run `rm -rf tmp/cache` from inside your Rails app if you upgrade your `glimmer-dsl-web` gem version from an older one to clear Opal-Rails's cache)
|
1277
|
+
|
915
1278
|
Follow [opal-rails](https://github.com/opal/opal-rails) instructions, basically running:
|
916
1279
|
|
917
1280
|
```
|
@@ -922,6 +1285,7 @@ To enable the `glimmer-dsl-web` library in the frontend, edit `config/initialize
|
|
922
1285
|
|
923
1286
|
```ruby
|
924
1287
|
Opal.use_gem 'glimmer-dsl-web'
|
1288
|
+
Opal.append_path Rails.root.join('app', 'assets', 'opal')
|
925
1289
|
```
|
926
1290
|
|
927
1291
|
To enable Opal Browser Debugging in Ruby with the [Source Maps](https://opalrb.com/docs/guides/v1.4.1/source_maps.html) feature, edit `config/initializers/opal.rb` and add the following inside the `Rails.application.configure do; end` block at the bottom of it:
|
@@ -958,7 +1322,18 @@ Also, delete the following line:
|
|
958
1322
|
|
959
1323
|
Clear the file `app/views/welcomes/index.html.erb` completely from all content.
|
960
1324
|
|
961
|
-
|
1325
|
+
Rename `app/assets/javascript` directory to `app/assets/opal`.
|
1326
|
+
|
1327
|
+
Add the following lines to `app/assets/config/manifest.js` (and delete their `javascript` equivalents):
|
1328
|
+
|
1329
|
+
```js
|
1330
|
+
//= link_tree ../../opal .js
|
1331
|
+
//= link_directory ../opal .js
|
1332
|
+
```
|
1333
|
+
|
1334
|
+
Rename `app/assets/opal/application.js.rb` to `app/assets/opal/application.rb`.
|
1335
|
+
|
1336
|
+
Edit and replace `app/assets/opal/application.rb` content with code below (optionally including a require statement for one of the [samples](#samples) below inside a `Document.ready? do; end` block):
|
962
1337
|
|
963
1338
|
```ruby
|
964
1339
|
require 'glimmer-dsl-web' # brings opal and other dependencies automatically
|
@@ -1019,6 +1394,22 @@ You should see:
|
|
1019
1394
|
|
1020
1395
|
![setup is working](/images/glimmer-dsl-web-setup-example-working.png)
|
1021
1396
|
|
1397
|
+
You may insert a Glimmer component anywhere into a Rails View using `glimmer_component(component_path, *args)` Rails helper. Add `include GlimmerHelper` to `ApplicationHelper` or another Rails helper, and use `<%= glimmer_component("path/to/component", *args) %>` in Views.
|
1398
|
+
|
1399
|
+
To use `glimmer_component`, edit `app/helpers/application_helper.rb` in your Rails application, add `require 'glimmer/helpers/glimmer_helper'` on top and `include GlimmerHelper` inside `module`.
|
1400
|
+
|
1401
|
+
`app/helpers/application_helper.rb` should look like this after the change:
|
1402
|
+
|
1403
|
+
```ruby
|
1404
|
+
require 'glimmer/helpers/glimmer_helper'
|
1405
|
+
|
1406
|
+
module ApplicationHelper
|
1407
|
+
# ...
|
1408
|
+
include GlimmerHelper
|
1409
|
+
# ...
|
1410
|
+
end
|
1411
|
+
```
|
1412
|
+
|
1022
1413
|
**NOT RELEASED OR SUPPORTED YET**
|
1023
1414
|
|
1024
1415
|
If you run into any issues in setup, refer to the [Sample Glimmer DSL for Web Rails 6 App](https://github.com/AndyObtiva/sample-glimmer-dsl-web-rails6-app) project (in case I forgot to include some setup steps by mistake).
|
@@ -1354,6 +1745,86 @@ Screenshot:
|
|
1354
1745
|
|
1355
1746
|
![Hello, Form!](/images/glimmer-dsl-web-samples-hello-hello-form.gif)
|
1356
1747
|
|
1748
|
+
#### Hello, Observer!
|
1749
|
+
|
1750
|
+
[Glimmer DSL for Web](https://rubygems.org/gems/glimmer-dsl-web) provides the `observe(model, attribute) { ... }` keyword to employ the [Observer Design Pattern](https://en.wikipedia.org/wiki/Observer_pattern) as per [MVC](https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller) (Model View Controller), enabling Views to observe Models and update themselves in response to changes. If the `observe` keyword is used from inside a [Component](#hello-component) (covered later), when the Component is removed or its top-level element is removed, the observer is automatically cleaned up. The need for such explicit observers is significantly diminished by the availablility of the more advanced Unidirectional [Data-Binding](#hello-data-binding) Support and Bidirectional [Data-Binding](#hello-data-binding) Support (covered later).
|
1751
|
+
|
1752
|
+
Glimmer GUI code:
|
1753
|
+
|
1754
|
+
```ruby
|
1755
|
+
require 'glimmer-dsl-web'
|
1756
|
+
|
1757
|
+
class NumberHolder
|
1758
|
+
attr_accessor :number
|
1759
|
+
|
1760
|
+
def initialize
|
1761
|
+
self.number = 50
|
1762
|
+
end
|
1763
|
+
end
|
1764
|
+
|
1765
|
+
class HelloObserver
|
1766
|
+
include Glimmer::Web::Component
|
1767
|
+
|
1768
|
+
before_render do
|
1769
|
+
@number_holder = NumberHolder.new
|
1770
|
+
end
|
1771
|
+
|
1772
|
+
after_render do
|
1773
|
+
# observe Model attribute @number_holder.number for changes and update View
|
1774
|
+
observe(@number_holder, :number) do
|
1775
|
+
number_string = @number_holder.number.to_s
|
1776
|
+
@number_input.value = number_string unless @number_input.value == number_string
|
1777
|
+
@range_input.value = number_string unless @range_input.value == number_string
|
1778
|
+
end
|
1779
|
+
# Bidirectional Data-Binding does the same thing automatically
|
1780
|
+
# Just disable the observe block above as well as the oninput listeners below
|
1781
|
+
# and enable the `value <=> [@number_holder, :number]` lines to try the data-binding version
|
1782
|
+
# Learn more about Bidirectional and Unidirectional Data-Binding in hello_data_binding.rb
|
1783
|
+
end
|
1784
|
+
|
1785
|
+
markup {
|
1786
|
+
div {
|
1787
|
+
div {
|
1788
|
+
@number_input = input(type: 'number', value: @number_holder.number, min: 0, max: 100) {
|
1789
|
+
# oninput listener updates Model attribute @number_holder.number
|
1790
|
+
oninput do
|
1791
|
+
@number_holder.number = @number_input.value.to_i
|
1792
|
+
end
|
1793
|
+
|
1794
|
+
# Bidirectional Data-Binding simplifies the implementation significantly
|
1795
|
+
# by enabling the following line and disabling oninput listeners as well
|
1796
|
+
# as the after_body observe block observer
|
1797
|
+
# Learn more about Bidirectional and Unidirectional Data-Binding in hello_data_binding.rb
|
1798
|
+
# value <=> [@number_holder, :number]
|
1799
|
+
}
|
1800
|
+
}
|
1801
|
+
div {
|
1802
|
+
@range_input = input(type: 'range', value: @number_holder.number, min: 0, max: 100) {
|
1803
|
+
# oninput listener updates Model attribute @number_holder.number
|
1804
|
+
oninput do
|
1805
|
+
@number_holder.number = @range_input.value.to_i
|
1806
|
+
end
|
1807
|
+
|
1808
|
+
# Bidirectional Data-Binding simplifies the implementation significantly
|
1809
|
+
# by enabling the following line and disabling oninput listeners as well
|
1810
|
+
# as the after_body observe block observer
|
1811
|
+
# Learn more about Bidirectional and Unidirectional Data-Binding in hello_data_binding.rb
|
1812
|
+
# value <=> [@number_holder, :number]
|
1813
|
+
}
|
1814
|
+
}
|
1815
|
+
}
|
1816
|
+
}
|
1817
|
+
end
|
1818
|
+
|
1819
|
+
Document.ready? do
|
1820
|
+
HelloObserver.render
|
1821
|
+
end
|
1822
|
+
```
|
1823
|
+
|
1824
|
+
Screenshot:
|
1825
|
+
|
1826
|
+
![Hello, Observer!](/images/glimmer-dsl-web-samples-hello-hello-observer.gif)
|
1827
|
+
|
1357
1828
|
#### Hello, Data-Binding!
|
1358
1829
|
|
1359
1830
|
[Glimmer DSL for Web](https://rubygems.org/gems/glimmer-dsl-web) intuitively supports both Unidirectional (One-Way) Data-Binding via the `<=` operator and Bidirectional (Two-Way) Data-Binding via the `<=>` operator, incredibly simplifying how to sync View properties with Model attributes with the simplest code to reason about.
|
@@ -1883,6 +2354,212 @@ Screenshot:
|
|
1883
2354
|
|
1884
2355
|
![Hello, Component!](/images/glimmer-dsl-web-samples-hello-hello-component.png)
|
1885
2356
|
|
2357
|
+
#### Hello, glimmer_component Rails Helper!
|
2358
|
+
|
2359
|
+
You may insert a Glimmer component anywhere into a Rails View using
|
2360
|
+
`glimmer_component(component_path, *args)` Rails helper. Add `include GlimmerHelper` to `ApplicationHelper`
|
2361
|
+
or another Rails helper, and use `<%= glimmer_component("path/to/component", *args) %>` in Views.
|
2362
|
+
|
2363
|
+
Rails `ApplicationHelper` setup code:
|
2364
|
+
|
2365
|
+
```ruby
|
2366
|
+
require 'glimmer/helpers/glimmer_helper'
|
2367
|
+
|
2368
|
+
module ApplicationHelper
|
2369
|
+
# ...
|
2370
|
+
include GlimmerHelper
|
2371
|
+
# ...
|
2372
|
+
end
|
2373
|
+
```
|
2374
|
+
|
2375
|
+
Rails View code:
|
2376
|
+
|
2377
|
+
```erb
|
2378
|
+
<div id="address-container">
|
2379
|
+
<h1>Shipping Address </h1>
|
2380
|
+
<legend>Please enter your shipping address information (Zip Code must be a valid 5 digit number)</legend>
|
2381
|
+
<!-- This sample demonstrates use of glimmer_component helper with arguments -->
|
2382
|
+
<%= glimmer_component('address_form',
|
2383
|
+
full_name: params[:full_name],
|
2384
|
+
street: params[:street],
|
2385
|
+
street2: params[:street2],
|
2386
|
+
city: params[:city],
|
2387
|
+
state: params[:state],
|
2388
|
+
zip_code: params[:zip_code]
|
2389
|
+
)
|
2390
|
+
%>
|
2391
|
+
<div>
|
2392
|
+
<a href="/"><< Back Home</a>
|
2393
|
+
</div>
|
2394
|
+
</div>
|
2395
|
+
```
|
2396
|
+
|
2397
|
+
Glimmer GUI code:
|
2398
|
+
|
2399
|
+
```ruby
|
2400
|
+
require 'glimmer-dsl-web'
|
2401
|
+
|
2402
|
+
class AddressForm
|
2403
|
+
Address = Struct.new(:full_name, :street, :street2, :city, :state, :zip_code, keyword_init: true) do
|
2404
|
+
def state_code
|
2405
|
+
STATES.invert[state]
|
2406
|
+
end
|
2407
|
+
|
2408
|
+
def state_code=(value)
|
2409
|
+
self.state = STATES[value]
|
2410
|
+
end
|
2411
|
+
|
2412
|
+
def summary
|
2413
|
+
to_h.values.map(&:to_s).reject(&:empty?).join(', ')
|
2414
|
+
end
|
2415
|
+
end
|
2416
|
+
|
2417
|
+
STATES = {
|
2418
|
+
"AK"=>"Alaska",
|
2419
|
+
"AL"=>"Alabama",
|
2420
|
+
"AR"=>"Arkansas",
|
2421
|
+
"AS"=>"American Samoa",
|
2422
|
+
"AZ"=>"Arizona",
|
2423
|
+
"CA"=>"California",
|
2424
|
+
"CO"=>"Colorado",
|
2425
|
+
"CT"=>"Connecticut",
|
2426
|
+
"DC"=>"District of Columbia",
|
2427
|
+
"DE"=>"Delaware",
|
2428
|
+
"FL"=>"Florida",
|
2429
|
+
"GA"=>"Georgia",
|
2430
|
+
"GU"=>"Guam",
|
2431
|
+
"HI"=>"Hawaii",
|
2432
|
+
"IA"=>"Iowa",
|
2433
|
+
"ID"=>"Idaho",
|
2434
|
+
"IL"=>"Illinois",
|
2435
|
+
"IN"=>"Indiana",
|
2436
|
+
"KS"=>"Kansas",
|
2437
|
+
"KY"=>"Kentucky",
|
2438
|
+
"LA"=>"Louisiana",
|
2439
|
+
"MA"=>"Massachusetts",
|
2440
|
+
"MD"=>"Maryland",
|
2441
|
+
"ME"=>"Maine",
|
2442
|
+
"MI"=>"Michigan",
|
2443
|
+
"MN"=>"Minnesota",
|
2444
|
+
"MO"=>"Missouri",
|
2445
|
+
"MS"=>"Mississippi",
|
2446
|
+
"MT"=>"Montana",
|
2447
|
+
"NC"=>"North Carolina",
|
2448
|
+
"ND"=>"North Dakota",
|
2449
|
+
"NE"=>"Nebraska",
|
2450
|
+
"NH"=>"New Hampshire",
|
2451
|
+
"NJ"=>"New Jersey",
|
2452
|
+
"NM"=>"New Mexico",
|
2453
|
+
"NV"=>"Nevada",
|
2454
|
+
"NY"=>"New York",
|
2455
|
+
"OH"=>"Ohio",
|
2456
|
+
"OK"=>"Oklahoma",
|
2457
|
+
"OR"=>"Oregon",
|
2458
|
+
"PA"=>"Pennsylvania",
|
2459
|
+
"PR"=>"Puerto Rico",
|
2460
|
+
"RI"=>"Rhode Island",
|
2461
|
+
"SC"=>"South Carolina",
|
2462
|
+
"SD"=>"South Dakota",
|
2463
|
+
"TN"=>"Tennessee",
|
2464
|
+
"TX"=>"Texas",
|
2465
|
+
"UT"=>"Utah",
|
2466
|
+
"VA"=>"Virginia",
|
2467
|
+
"VI"=>"Virgin Islands",
|
2468
|
+
"VT"=>"Vermont",
|
2469
|
+
"WA"=>"Washington",
|
2470
|
+
"WI"=>"Wisconsin",
|
2471
|
+
"WV"=>"West Virginia",
|
2472
|
+
"WY"=>"Wyoming"
|
2473
|
+
}
|
2474
|
+
|
2475
|
+
include Glimmer::Web::Component
|
2476
|
+
|
2477
|
+
option :full_name
|
2478
|
+
option :street
|
2479
|
+
option :street2
|
2480
|
+
option :city
|
2481
|
+
option :state
|
2482
|
+
option :zip_code
|
2483
|
+
|
2484
|
+
attr_reader :address
|
2485
|
+
|
2486
|
+
before_render do
|
2487
|
+
@address = Address.new(
|
2488
|
+
full_name: full_name,
|
2489
|
+
street: street,
|
2490
|
+
street2: street2,
|
2491
|
+
city: city,
|
2492
|
+
state: state,
|
2493
|
+
zip_code: zip_code,
|
2494
|
+
)
|
2495
|
+
end
|
2496
|
+
|
2497
|
+
markup {
|
2498
|
+
div {
|
2499
|
+
div(style: 'display: grid; grid-auto-columns: 80px 260px;') { |address_div|
|
2500
|
+
label('Full Name: ', for: 'full-name-field')
|
2501
|
+
input(id: 'full-name-field') {
|
2502
|
+
value <=> [address, :full_name]
|
2503
|
+
}
|
2504
|
+
|
2505
|
+
@somelabel = label('Street: ', for: 'street-field')
|
2506
|
+
input(id: 'street-field') {
|
2507
|
+
value <=> [address, :street]
|
2508
|
+
}
|
2509
|
+
|
2510
|
+
label('Street 2: ', for: 'street2-field')
|
2511
|
+
textarea(id: 'street2-field') {
|
2512
|
+
value <=> [address, :street2]
|
2513
|
+
}
|
2514
|
+
|
2515
|
+
label('City: ', for: 'city-field')
|
2516
|
+
input(id: 'city-field') {
|
2517
|
+
value <=> [address, :city]
|
2518
|
+
}
|
2519
|
+
|
2520
|
+
label('State: ', for: 'state-field')
|
2521
|
+
select(id: 'state-field') {
|
2522
|
+
STATES.each do |state_code, state|
|
2523
|
+
option(value: state_code) { state }
|
2524
|
+
end
|
2525
|
+
|
2526
|
+
value <=> [address, :state_code]
|
2527
|
+
}
|
2528
|
+
|
2529
|
+
label('Zip Code: ', for: 'zip-code-field')
|
2530
|
+
input(id: 'zip-code-field', type: 'number', min: '0', max: '99999') {
|
2531
|
+
value <=> [address, :zip_code,
|
2532
|
+
on_write: :to_s,
|
2533
|
+
]
|
2534
|
+
}
|
2535
|
+
|
2536
|
+
style {
|
2537
|
+
<<~CSS
|
2538
|
+
#{address_div.selector} * {
|
2539
|
+
margin: 5px;
|
2540
|
+
}
|
2541
|
+
#{address_div.selector} input, #{address_div.selector} select {
|
2542
|
+
grid-column: 2;
|
2543
|
+
}
|
2544
|
+
CSS
|
2545
|
+
}
|
2546
|
+
}
|
2547
|
+
|
2548
|
+
div(style: 'margin: 5px') {
|
2549
|
+
inner_text <= [address, :summary,
|
2550
|
+
computed_by: address.members + ['state_code'],
|
2551
|
+
]
|
2552
|
+
}
|
2553
|
+
}
|
2554
|
+
}
|
2555
|
+
end
|
2556
|
+
```
|
2557
|
+
|
2558
|
+
Screenshot:
|
2559
|
+
|
2560
|
+
![Hello, glimmer_component Rails Helper!](/images/glimmer-dsl-web-samples-hello-hello-component.png)
|
2561
|
+
|
2562
|
+
|
1886
2563
|
#### Hello, Input (Date/Time)!
|
1887
2564
|
|
1888
2565
|
Glimmer GUI code:
|
@@ -1992,8 +2669,6 @@ Screenshot:
|
|
1992
2669
|
|
1993
2670
|
#### Button Counter
|
1994
2671
|
|
1995
|
-
**UPCOMING (NOT RELEASED OR SUPPORTED YET)**
|
1996
|
-
|
1997
2672
|
Glimmer GUI code demonstrating MVC + Glimmer Web Components (Views) + Data-Binding:
|
1998
2673
|
|
1999
2674
|
```ruby
|
@@ -2005,13 +2680,9 @@ class Counter
|
|
2005
2680
|
def initialize
|
2006
2681
|
self.count = 0
|
2007
2682
|
end
|
2008
|
-
|
2009
|
-
def increment!
|
2010
|
-
self.count += 1
|
2011
|
-
end
|
2012
2683
|
end
|
2013
2684
|
|
2014
|
-
class
|
2685
|
+
class ButtonCounter
|
2015
2686
|
include Glimmer::Web::Component
|
2016
2687
|
|
2017
2688
|
before_render do
|
@@ -2019,32 +2690,31 @@ class HelloButton
|
|
2019
2690
|
end
|
2020
2691
|
|
2021
2692
|
markup {
|
2022
|
-
|
2023
|
-
div(parent: parent_selector) {
|
2024
|
-
text 'Button Counter'
|
2025
|
-
|
2693
|
+
div {
|
2026
2694
|
button {
|
2027
2695
|
# Unidirectional Data-Binding indicating that on every change to @counter.count, the value
|
2028
2696
|
# is read and converted to "Click To Increment: #{value} ", and then automatically
|
2029
2697
|
# copied to button innerText (content) to display to the user
|
2030
|
-
inner_text <= [@counter, :count,
|
2698
|
+
inner_text <= [@counter, :count,
|
2699
|
+
on_read: ->(value) { "Click To Increment: #{value} " }
|
2700
|
+
]
|
2031
2701
|
|
2032
2702
|
onclick {
|
2033
|
-
@counter.
|
2703
|
+
@counter.count += 1
|
2034
2704
|
}
|
2035
2705
|
}
|
2036
2706
|
}
|
2037
2707
|
}
|
2038
2708
|
end
|
2039
2709
|
|
2040
|
-
|
2710
|
+
ButtonCounter.render
|
2041
2711
|
```
|
2042
2712
|
|
2043
2713
|
That produces:
|
2044
2714
|
|
2045
2715
|
```html
|
2046
|
-
<div
|
2047
|
-
<button>
|
2716
|
+
<div data-parent="body" class="element element-1">
|
2717
|
+
<button class="element element-2">
|
2048
2718
|
Click To Increment: 0
|
2049
2719
|
</button>
|
2050
2720
|
</div>
|
@@ -2053,8 +2723,8 @@ That produces:
|
|
2053
2723
|
When clicked:
|
2054
2724
|
|
2055
2725
|
```html
|
2056
|
-
<div
|
2057
|
-
<button>
|
2726
|
+
<div data-parent="body" class="element element-1">
|
2727
|
+
<button class="element element-2">
|
2058
2728
|
Click To Increment: 1
|
2059
2729
|
</button>
|
2060
2730
|
</div>
|
@@ -2063,13 +2733,17 @@ When clicked:
|
|
2063
2733
|
When clicked 7 times:
|
2064
2734
|
|
2065
2735
|
```html
|
2066
|
-
<div
|
2067
|
-
<button>
|
2736
|
+
<div data-parent="body" class="element element-1">
|
2737
|
+
<button class="element element-2">
|
2068
2738
|
Click To Increment: 7
|
2069
2739
|
</button>
|
2070
2740
|
</div>
|
2071
2741
|
```
|
2072
2742
|
|
2743
|
+
Screenshot:
|
2744
|
+
|
2745
|
+
![Button Counter](/images/glimmer-dsl-web-samples-regular-button-counter.gif)
|
2746
|
+
|
2073
2747
|
## Glimmer Supporting Libraries
|
2074
2748
|
|
2075
2749
|
Here is a list of notable 3rd party gems used by Glimmer DSL for Web:
|