glimmer-dsl-web 0.0.10 → 0.0.11

Sign up to get free protection for your applications and to get access to all the features.
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.10 (Early Alpha)
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.
@@ -479,9 +559,6 @@ by simply defining a class with `include Glimmer::Web::Component` and encasing t
479
559
  a `markup {...}` block. Glimmer web components automatically extend the Glimmer GUI DSL with new keywords
480
560
  that match the underscored versions of the component class names (e.g. a `OrderSummary` class yields
481
561
  the `order_summary` keyword for reusing that component within the Glimmer GUI DSL).
482
- You may also insert a Glimmer component anywhere into a Rails application View using
483
- `glimmer_component(component_path, *args)` Rails helper. Add `include GlimmerHelper` to `ApplicationHelper`
484
- or another Rails helper, and use `<%= glimmer_component("path/to/component", *args) %>` in Views.
485
562
  Below, we define an `AddressForm` component that generates a `address_form` keyword, and then we
486
563
  reuse it twice inside an `AddressPage` component displaying a Shipping Address and a Billing Address.
487
564
 
@@ -696,6 +773,211 @@ Screenshot:
696
773
 
697
774
  ![Hello, Component!](/images/glimmer-dsl-web-samples-hello-hello-component.png)
698
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="/">&lt;&lt; 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
+
699
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.
700
982
 
701
983
  Learn more about the differences between various [Glimmer](https://github.com/AndyObtiva/glimmer) DSLs by looking at:
@@ -717,9 +999,11 @@ Learn more about the differences between various [Glimmer](https://github.com/An
717
999
  - [Hello, World!](#hello-world)
718
1000
  - [Hello, Button!](#hello-button)
719
1001
  - [Hello, Form!](#hello-form)
1002
+ - [Hello, Observer!](#hello-observer)
720
1003
  - [Hello, Data-Binding!](#hello-data-binding)
721
1004
  - [Hello, Content Data-Binding!](#hello-content-data-binding)
722
1005
  - [Hello, Component!](#hello-content-data-binding)
1006
+ - [Hello, glimmer_component Rails Helper!](#hello-glimmer_component-rails-helper)
723
1007
  - [Hello, Input (Date/Time)!](#hello-input-datetime)
724
1008
  - [Button Counter](#button-counter)
725
1009
  - [Glimmer Process](#glimmer-process)
@@ -736,10 +1020,8 @@ Learn more about the differences between various [Glimmer](https://github.com/An
736
1020
 
737
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.
738
1022
 
739
- - Ruby 3.0 (newer Ruby versions are not supported at this time)
1023
+ - Ruby 3.0+
740
1024
  - Rails 6-7: [https://github.com/rails/rails](https://github.com/rails/rails)
741
- - Opal 1.4.1 for Rails 6-7: [https://github.com/opal/opal](https://github.com/opal/opal)
742
- - Opal-Rails 2.0.2 for Rails 6-7: [https://github.com/opal/opal-rails](https://github.com/opal/opal-rails)
743
1025
 
744
1026
  ## Setup
745
1027
 
@@ -766,7 +1048,7 @@ rails new glimmer_app_server
766
1048
  Add the following to `Gemfile`:
767
1049
 
768
1050
  ```
769
- gem 'glimmer-dsl-web', '~> 0.0.10'
1051
+ gem 'glimmer-dsl-web', '~> 0.0.11'
770
1052
  ```
771
1053
 
772
1054
  Run:
@@ -817,7 +1099,7 @@ root to: 'welcomes#index'
817
1099
 
818
1100
  Clear the file `app/views/welcomes/index.html.erb` completely from all content.
819
1101
 
820
- Delete `app/javascript/application.js`
1102
+ Delete `app/javascript` directory
821
1103
 
822
1104
  Rename `app/assets/javascript` directory to `app/assets/opal`.
823
1105
 
@@ -830,7 +1112,7 @@ Add the following lines to `app/assets/config/manifest.js` (and delete their `ja
830
1112
 
831
1113
  Rename `app/assets/opal/application.js.rb` to `app/assets/opal/application.rb`.
832
1114
 
833
- 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):
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):
834
1116
 
835
1117
  ```ruby
836
1118
  require 'glimmer-dsl-web' # brings opal and other dependencies automatically
@@ -838,6 +1120,22 @@ require 'glimmer-dsl-web' # brings opal and other dependencies automatically
838
1120
  # Add more require-statements or Glimmer GUI DSL code
839
1121
  ```
840
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
+
841
1139
  Example to confirm setup is working:
842
1140
 
843
1141
  Glimmer GUI code:
@@ -849,7 +1147,7 @@ include Glimmer
849
1147
 
850
1148
  Document.ready? do
851
1149
  # This will hook into element #app-container and then build HTML inside it using Ruby DSL code
852
- div(parent: '#app-container') {
1150
+ div {
853
1151
  label(class: 'greeting') {
854
1152
  'Hello, World!'
855
1153
  }
@@ -860,13 +1158,13 @@ end
860
1158
  That produces:
861
1159
 
862
1160
  ```html
863
- ...
1161
+ <body>
864
1162
  <div data-parent="body" class="element element-1">
865
1163
  <label class="greeting element element-2">
866
1164
  Hello, World!
867
1165
  </label>
868
1166
  </div>
869
- ...
1167
+ </body>
870
1168
  ```
871
1169
 
872
1170
  Start the Rails server:
@@ -880,7 +1178,48 @@ You should see:
880
1178
 
881
1179
  ![setup is working](/images/glimmer-dsl-web-setup-example-working.png)
882
1180
 
883
- You may also insert a Glimmer component anywhere into a Rails application 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.
1181
+ If you want to customize where the top-level element is mounted, just pass a `parent: 'css_selector'` option.
1182
+
1183
+ HTML:
1184
+
1185
+ ```html
1186
+ ...
1187
+ <div id="app-container">
1188
+ </div>
1189
+ ...
1190
+ ```
1191
+
1192
+ Glimmer GUI code:
1193
+
1194
+ ```ruby
1195
+ require 'glimmer-dsl-web'
1196
+
1197
+ include Glimmer
1198
+
1199
+ Document.ready? do
1200
+ # This will hook into element #app-container and then build HTML inside it using Ruby DSL code
1201
+ div(parent: '#app-container') {
1202
+ label(class: 'greeting') {
1203
+ 'Hello, World!'
1204
+ }
1205
+ }.render
1206
+ end
1207
+ ```
1208
+
1209
+ That produces:
1210
+
1211
+ ```html
1212
+ ...
1213
+ <div id="app-container">
1214
+ <div data-parent="app-container" class="element element-1">
1215
+ <label class="greeting element element-2">
1216
+ Hello, World!
1217
+ </label>
1218
+ </div>
1219
+ </div>
1220
+ ...
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.
884
1223
 
885
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`.
886
1225
 
@@ -925,7 +1264,7 @@ Disable the `webpacker` gem line in `Gemfile`:
925
1264
  Add the following to `Gemfile`:
926
1265
 
927
1266
  ```ruby
928
- gem 'glimmer-dsl-web', '~> 0.0.10'
1267
+ gem 'glimmer-dsl-web', '~> 0.0.11'
929
1268
  ```
930
1269
 
931
1270
  Run:
@@ -1055,7 +1394,7 @@ You should see:
1055
1394
 
1056
1395
  ![setup is working](/images/glimmer-dsl-web-setup-example-working.png)
1057
1396
 
1058
- You may also insert a Glimmer component anywhere into a Rails application 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.
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.
1059
1398
 
1060
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`.
1061
1400
 
@@ -1406,6 +1745,86 @@ Screenshot:
1406
1745
 
1407
1746
  ![Hello, Form!](/images/glimmer-dsl-web-samples-hello-hello-form.gif)
1408
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
+
1409
1828
  #### Hello, Data-Binding!
1410
1829
 
1411
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.
@@ -1721,9 +2140,6 @@ by simply defining a class with `include Glimmer::Web::Component` and encasing t
1721
2140
  a `markup {...}` block. Glimmer web components automatically extend the Glimmer GUI DSL with new keywords
1722
2141
  that match the underscored versions of the component class names (e.g. a `OrderSummary` class yields
1723
2142
  the `order_summary` keyword for reusing that component within the Glimmer GUI DSL).
1724
- You may also insert a Glimmer component anywhere into a Rails application View using
1725
- `glimmer_component(component_path, *args)` Rails helper. Add `include GlimmerHelper` to `ApplicationHelper`
1726
- or another Rails helper, and use `<%= glimmer_component("path/to/component", *args) %>` in Views.
1727
2143
  Below, we define an `AddressForm` component that generates a `address_form` keyword, and then we
1728
2144
  reuse it twice inside an `AddressPage` component displaying a Shipping Address and a Billing Address.
1729
2145
 
@@ -1938,6 +2354,212 @@ Screenshot:
1938
2354
 
1939
2355
  ![Hello, Component!](/images/glimmer-dsl-web-samples-hello-hello-component.png)
1940
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="/">&lt;&lt; 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
+
1941
2563
  #### Hello, Input (Date/Time)!
1942
2564
 
1943
2565
  Glimmer GUI code:
@@ -2047,8 +2669,6 @@ Screenshot:
2047
2669
 
2048
2670
  #### Button Counter
2049
2671
 
2050
- **UPCOMING (NOT RELEASED OR SUPPORTED YET)**
2051
-
2052
2672
  Glimmer GUI code demonstrating MVC + Glimmer Web Components (Views) + Data-Binding:
2053
2673
 
2054
2674
  ```ruby
@@ -2060,13 +2680,9 @@ class Counter
2060
2680
  def initialize
2061
2681
  self.count = 0
2062
2682
  end
2063
-
2064
- def increment!
2065
- self.count += 1
2066
- end
2067
2683
  end
2068
2684
 
2069
- class HelloButton
2685
+ class ButtonCounter
2070
2686
  include Glimmer::Web::Component
2071
2687
 
2072
2688
  before_render do
@@ -2074,32 +2690,31 @@ class HelloButton
2074
2690
  end
2075
2691
 
2076
2692
  markup {
2077
- # This will hook into element #app-container and then build HTML inside it using Ruby DSL code
2078
- div(parent: parent_selector) {
2079
- text 'Button Counter'
2080
-
2693
+ div {
2081
2694
  button {
2082
2695
  # Unidirectional Data-Binding indicating that on every change to @counter.count, the value
2083
2696
  # is read and converted to "Click To Increment: #{value} ", and then automatically
2084
2697
  # copied to button innerText (content) to display to the user
2085
- inner_text <= [@counter, :count, on_read: ->(value) { "Click To Increment: #{value} " }]
2698
+ inner_text <= [@counter, :count,
2699
+ on_read: ->(value) { "Click To Increment: #{value} " }
2700
+ ]
2086
2701
 
2087
2702
  onclick {
2088
- @counter.increment!
2703
+ @counter.count += 1
2089
2704
  }
2090
2705
  }
2091
2706
  }
2092
2707
  }
2093
2708
  end
2094
2709
 
2095
- HelloButton.render
2710
+ ButtonCounter.render
2096
2711
  ```
2097
2712
 
2098
2713
  That produces:
2099
2714
 
2100
2715
  ```html
2101
- <div id="application">
2102
- <button>
2716
+ <div data-parent="body" class="element element-1">
2717
+ <button class="element element-2">
2103
2718
  Click To Increment: 0
2104
2719
  </button>
2105
2720
  </div>
@@ -2108,8 +2723,8 @@ That produces:
2108
2723
  When clicked:
2109
2724
 
2110
2725
  ```html
2111
- <div id="application">
2112
- <button>
2726
+ <div data-parent="body" class="element element-1">
2727
+ <button class="element element-2">
2113
2728
  Click To Increment: 1
2114
2729
  </button>
2115
2730
  </div>
@@ -2118,13 +2733,17 @@ When clicked:
2118
2733
  When clicked 7 times:
2119
2734
 
2120
2735
  ```html
2121
- <div id="application">
2122
- <button>
2736
+ <div data-parent="body" class="element element-1">
2737
+ <button class="element element-2">
2123
2738
  Click To Increment: 7
2124
2739
  </button>
2125
2740
  </div>
2126
2741
  ```
2127
2742
 
2743
+ Screenshot:
2744
+
2745
+ ![Button Counter](/images/glimmer-dsl-web-samples-regular-button-counter.gif)
2746
+
2128
2747
  ## Glimmer Supporting Libraries
2129
2748
 
2130
2749
  Here is a list of notable 3rd party gems used by Glimmer DSL for Web: