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.
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.9 (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.
@@ -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="/">&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
+
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 (newer Ruby versions are not supported at this time)
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.9'
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/application.js`
1102
+ Delete `app/javascript` directory
815
1103
 
816
- Edit and replace `app/assets/javascript/application.js.rb` content with code below (optionally including a require statement for one of the [samples](#samples) below):
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
- Initial HTML Markup:
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="#app-container" class="element element-1">
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
- Start the Rails server:
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
- Visit `http://localhost:3000`
1226
+ `app/helpers/application_helper.rb` should look like this after the change:
872
1227
 
873
- You should see:
1228
+ ```ruby
1229
+ require 'glimmer/helpers/glimmer_helper'
874
1230
 
875
- ![setup is working](/images/glimmer-dsl-web-setup-example-working.png)
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.9'
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
- Edit and replace `app/assets/javascript/application.js.rb` content with code below (optionally including a require statement for one of the [samples](#samples) below):
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="/">&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
+
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 HelloButton
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
- # This will hook into element #app-container and then build HTML inside it using Ruby DSL code
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, on_read: ->(value) { "Click To Increment: #{value} " }]
2698
+ inner_text <= [@counter, :count,
2699
+ on_read: ->(value) { "Click To Increment: #{value} " }
2700
+ ]
2031
2701
 
2032
2702
  onclick {
2033
- @counter.increment!
2703
+ @counter.count += 1
2034
2704
  }
2035
2705
  }
2036
2706
  }
2037
2707
  }
2038
2708
  end
2039
2709
 
2040
- HelloButton.render
2710
+ ButtonCounter.render
2041
2711
  ```
2042
2712
 
2043
2713
  That produces:
2044
2714
 
2045
2715
  ```html
2046
- <div id="application">
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 id="application">
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 id="application">
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: