glimmer-dsl-web 0.0.10 → 0.0.12

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.12 (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,88 @@ 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
+ # Observer is automatically cleaned up if remove method is called on rendered HelloObserver
250
+ # or its top-level element
251
+ observe(@number_holder, :number) do
252
+ number_string = @number_holder.number.to_s
253
+ @number_input.value = number_string unless @number_input.value == number_string
254
+ @range_input.value = number_string unless @range_input.value == number_string
255
+ end
256
+ # Bidirectional Data-Binding does the same thing automatically
257
+ # Just disable the observe block above as well as the oninput listeners below
258
+ # and enable the `value <=> [@number_holder, :number]` lines to try the data-binding version
259
+ # Learn more about Bidirectional and Unidirectional Data-Binding in hello_data_binding.rb
260
+ end
261
+
262
+ markup {
263
+ div {
264
+ div {
265
+ @number_input = input(type: 'number', value: @number_holder.number, min: 0, max: 100) {
266
+ # oninput listener updates Model attribute @number_holder.number
267
+ oninput do
268
+ @number_holder.number = @number_input.value.to_i
269
+ end
270
+
271
+ # Bidirectional Data-Binding simplifies the implementation significantly
272
+ # by enabling the following line and disabling oninput listeners as well
273
+ # as the after_body observe block observer
274
+ # Learn more about Bidirectional and Unidirectional Data-Binding in hello_data_binding.rb
275
+ # value <=> [@number_holder, :number]
276
+ }
277
+ }
278
+ div {
279
+ @range_input = input(type: 'range', value: @number_holder.number, min: 0, max: 100) {
280
+ # oninput listener updates Model attribute @number_holder.number
281
+ oninput do
282
+ @number_holder.number = @range_input.value.to_i
283
+ end
284
+
285
+ # Bidirectional Data-Binding simplifies the implementation significantly
286
+ # by enabling the following line and disabling oninput listeners as well
287
+ # as the after_body observe block observer
288
+ # Learn more about Bidirectional and Unidirectional Data-Binding in hello_data_binding.rb
289
+ # value <=> [@number_holder, :number]
290
+ }
291
+ }
292
+ }
293
+ }
294
+ end
295
+
296
+ Document.ready? do
297
+ HelloObserver.render
298
+ end
299
+ ```
300
+
301
+ Screenshot:
302
+
303
+ ![Hello, Observer!](/images/glimmer-dsl-web-samples-hello-hello-observer.gif)
304
+
223
305
  **Hello, Data-Binding!**
224
306
 
225
307
  [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 +561,6 @@ by simply defining a class with `include Glimmer::Web::Component` and encasing t
479
561
  a `markup {...}` block. Glimmer web components automatically extend the Glimmer GUI DSL with new keywords
480
562
  that match the underscored versions of the component class names (e.g. a `OrderSummary` class yields
481
563
  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
564
  Below, we define an `AddressForm` component that generates a `address_form` keyword, and then we
486
565
  reuse it twice inside an `AddressPage` component displaying a Shipping Address and a Billing Address.
487
566
 
@@ -696,6 +775,304 @@ Screenshot:
696
775
 
697
776
  ![Hello, Component!](/images/glimmer-dsl-web-samples-hello-hello-component.png)
698
777
 
778
+ **Hello, glimmer_component Rails Helper!**
779
+
780
+ You may insert a Glimmer component anywhere into a Rails View using
781
+ `glimmer_component(component_path, *args)` Rails helper. Add `include GlimmerHelper` to `ApplicationHelper`
782
+ or another Rails helper, and use `<%= glimmer_component("path/to/component", *args) %>` in Views.
783
+
784
+ Rails `ApplicationHelper` setup code:
785
+
786
+ ```ruby
787
+ require 'glimmer/helpers/glimmer_helper'
788
+
789
+ module ApplicationHelper
790
+ # ...
791
+ include GlimmerHelper
792
+ # ...
793
+ end
794
+ ```
795
+
796
+ Rails View code:
797
+
798
+ ```erb
799
+ <div id="address-container">
800
+ <h1>Shipping Address </h1>
801
+ <legend>Please enter your shipping address information (Zip Code must be a valid 5 digit number)</legend>
802
+ <!-- This sample demonstrates use of glimmer_component helper with arguments -->
803
+ <%= glimmer_component('address_form',
804
+ full_name: params[:full_name],
805
+ street: params[:street],
806
+ street2: params[:street2],
807
+ city: params[:city],
808
+ state: params[:state],
809
+ zip_code: params[:zip_code]
810
+ )
811
+ %>
812
+ <div>
813
+ <a href="/">&lt;&lt; Back Home</a>
814
+ </div>
815
+ </div>
816
+ ```
817
+
818
+ Glimmer GUI code:
819
+
820
+ ```ruby
821
+ require 'glimmer-dsl-web'
822
+
823
+ class AddressForm
824
+ Address = Struct.new(:full_name, :street, :street2, :city, :state, :zip_code, keyword_init: true) do
825
+ def state_code
826
+ STATES.invert[state]
827
+ end
828
+
829
+ def state_code=(value)
830
+ self.state = STATES[value]
831
+ end
832
+
833
+ def summary
834
+ to_h.values.map(&:to_s).reject(&:empty?).join(', ')
835
+ end
836
+ end
837
+
838
+ STATES = {
839
+ "AK"=>"Alaska",
840
+ "AL"=>"Alabama",
841
+ "AR"=>"Arkansas",
842
+ "AS"=>"American Samoa",
843
+ "AZ"=>"Arizona",
844
+ "CA"=>"California",
845
+ "CO"=>"Colorado",
846
+ "CT"=>"Connecticut",
847
+ "DC"=>"District of Columbia",
848
+ "DE"=>"Delaware",
849
+ "FL"=>"Florida",
850
+ "GA"=>"Georgia",
851
+ "GU"=>"Guam",
852
+ "HI"=>"Hawaii",
853
+ "IA"=>"Iowa",
854
+ "ID"=>"Idaho",
855
+ "IL"=>"Illinois",
856
+ "IN"=>"Indiana",
857
+ "KS"=>"Kansas",
858
+ "KY"=>"Kentucky",
859
+ "LA"=>"Louisiana",
860
+ "MA"=>"Massachusetts",
861
+ "MD"=>"Maryland",
862
+ "ME"=>"Maine",
863
+ "MI"=>"Michigan",
864
+ "MN"=>"Minnesota",
865
+ "MO"=>"Missouri",
866
+ "MS"=>"Mississippi",
867
+ "MT"=>"Montana",
868
+ "NC"=>"North Carolina",
869
+ "ND"=>"North Dakota",
870
+ "NE"=>"Nebraska",
871
+ "NH"=>"New Hampshire",
872
+ "NJ"=>"New Jersey",
873
+ "NM"=>"New Mexico",
874
+ "NV"=>"Nevada",
875
+ "NY"=>"New York",
876
+ "OH"=>"Ohio",
877
+ "OK"=>"Oklahoma",
878
+ "OR"=>"Oregon",
879
+ "PA"=>"Pennsylvania",
880
+ "PR"=>"Puerto Rico",
881
+ "RI"=>"Rhode Island",
882
+ "SC"=>"South Carolina",
883
+ "SD"=>"South Dakota",
884
+ "TN"=>"Tennessee",
885
+ "TX"=>"Texas",
886
+ "UT"=>"Utah",
887
+ "VA"=>"Virginia",
888
+ "VI"=>"Virgin Islands",
889
+ "VT"=>"Vermont",
890
+ "WA"=>"Washington",
891
+ "WI"=>"Wisconsin",
892
+ "WV"=>"West Virginia",
893
+ "WY"=>"Wyoming"
894
+ }
895
+
896
+ include Glimmer::Web::Component
897
+
898
+ option :full_name
899
+ option :street
900
+ option :street2
901
+ option :city
902
+ option :state
903
+ option :zip_code
904
+
905
+ attr_reader :address
906
+
907
+ before_render do
908
+ @address = Address.new(
909
+ full_name: full_name,
910
+ street: street,
911
+ street2: street2,
912
+ city: city,
913
+ state: state,
914
+ zip_code: zip_code,
915
+ )
916
+ end
917
+
918
+ markup {
919
+ div {
920
+ div(style: 'display: grid; grid-auto-columns: 80px 260px;') { |address_div|
921
+ label('Full Name: ', for: 'full-name-field')
922
+ input(id: 'full-name-field') {
923
+ value <=> [address, :full_name]
924
+ }
925
+
926
+ @somelabel = label('Street: ', for: 'street-field')
927
+ input(id: 'street-field') {
928
+ value <=> [address, :street]
929
+ }
930
+
931
+ label('Street 2: ', for: 'street2-field')
932
+ textarea(id: 'street2-field') {
933
+ value <=> [address, :street2]
934
+ }
935
+
936
+ label('City: ', for: 'city-field')
937
+ input(id: 'city-field') {
938
+ value <=> [address, :city]
939
+ }
940
+
941
+ label('State: ', for: 'state-field')
942
+ select(id: 'state-field') {
943
+ STATES.each do |state_code, state|
944
+ option(value: state_code) { state }
945
+ end
946
+
947
+ value <=> [address, :state_code]
948
+ }
949
+
950
+ label('Zip Code: ', for: 'zip-code-field')
951
+ input(id: 'zip-code-field', type: 'number', min: '0', max: '99999') {
952
+ value <=> [address, :zip_code,
953
+ on_write: :to_s,
954
+ ]
955
+ }
956
+
957
+ style {
958
+ <<~CSS
959
+ #{address_div.selector} * {
960
+ margin: 5px;
961
+ }
962
+ #{address_div.selector} input, #{address_div.selector} select {
963
+ grid-column: 2;
964
+ }
965
+ CSS
966
+ }
967
+ }
968
+
969
+ div(style: 'margin: 5px') {
970
+ inner_text <= [address, :summary,
971
+ computed_by: address.members + ['state_code'],
972
+ ]
973
+ }
974
+ }
975
+ }
976
+ end
977
+ ```
978
+
979
+ Screenshot:
980
+
981
+ ![Hello, glimmer_component Rails Helper!](/images/glimmer-dsl-web-samples-hello-hello-component.png)
982
+
983
+ **Hello, Paragraph!**
984
+
985
+ To facilitate building formatted textual paragraphs in Ruby, the Glimmer GUI DSL is advanced enough to behave differently when using HTML formatting elements: `<br>`, `<strong>`, `<em>`, `<br>`, `<i>`, `<sub>`, `<sup>`, `<del>`, `<ins>`, `<small>`, `<mark>`
986
+
987
+ Instead of returning Ruby objects that are nested as children within their parent, the Glimmer GUI DSL returns `String` objects directly that can be concatenated to or embedded within other `String` objects via interpolation.
988
+
989
+ This enables writing code like:
990
+
991
+ `p {"#{strong('Yesterday, ')}Robert suggested adding a new #{em('feature')} to our software product.#{br}}`
992
+
993
+ That is close to how it is written in HTML, albeit briefer in Ruby:
994
+
995
+ `<p><strong>Yesterday, </strong>Robert suggested adding a new <em>feature</em> to our software product.<br></p>`
996
+
997
+ Formatting elements just like regular elements can accept text content as their first argument or as their block return value. So, the code above could equally be written as follows:
998
+
999
+ `p {"#{strong{'Yesterday, '}}Robert suggested adding a new #{em{'feature'}} to our software product.#{br}}`
1000
+
1001
+ This enables seggregating formatting element attributes if desired, as in this example:
1002
+
1003
+ `p {"#{strong(class: 'very-string'){'Yesterday, '}}Robert suggested adding a new #{em(class: 'very-emphasized'){'feature'}} to our software product.#{br}}`
1004
+
1005
+ Another way of writing the same code is to pass the text content as the first argument, before attributes:
1006
+
1007
+
1008
+ `p {"#{strong('Yesterday, ', class: 'very-string')}Robert suggested adding a new #{em('feature', class: 'very-emphasized')} to our software product.#{br}}`
1009
+
1010
+ One last bit of info to keep in mind is that `<span>` generally generates a normal element, except when used inside a `<p>`'s content block, in which case it is assumed to be used for formatting, so
1011
+ it returns a `String` to enable code like this:
1012
+
1013
+ `p {"#{span('Yesterday, ', style: 'text-decoration: underline;')}Robert suggested adding a new #{em('feature', class: 'very-emphasized')} to our software product.#{br}}`
1014
+
1015
+ In any case, below is a full example leveraging the Glimmer GUI DSL alternative approach when utilizing formatting elements underneath a paragraph.
1016
+
1017
+ Glimmer GUI code:
1018
+
1019
+ ```ruby
1020
+ require 'glimmer-dsl-web'
1021
+
1022
+ class HelloParagraph
1023
+ include Glimmer::Web::Component
1024
+
1025
+ markup {
1026
+ div {
1027
+ h1(class: 'title') {
1028
+ 'Flying Cars Become 100% Safe with AI Powered Balance!'
1029
+ }
1030
+
1031
+ p(class: 'intro') {"
1032
+ In the early 2030's, #{em('flying cars')} became affordable after their prices dropped
1033
+ below #{small(del('$100,000'))}#{ins('$80,000')} as a result of the innovations of #{strong('Travel-X')}. Still, that did not
1034
+ make #{em('flying cars')} any popular due to the extreme difficulty in piloting such flying vehicles for the average
1035
+ person, making it very tough to pass the tests for getting a piloting license given the learning curve.
1036
+ "}
1037
+
1038
+ p {"
1039
+ That said, #{b('Travel-X')} has recently come up with a new feature for their flagship #{i('flying car')},
1040
+ the Ptero#{sub(1)}#{sup('TM')}, which relies on AI#{sub(2)} to automatically balance the flying cars in mid-air,
1041
+ thus significantly facilitating their piloting by the average consumer.
1042
+ "}
1043
+
1044
+ p(class: 'conclusion') {"
1045
+ That Ptero#{sup('TM')} will be so stable and well balanced while flying that the consumer will be able to drive
1046
+ as if it is a plain old car, with the only difference being vertical elevation, the control of which will be handled
1047
+ automatically by AI. The Ptero#{sup('TM')} will debut for #{span(style: 'text-decoration: underline dashed;'){'$79,000'}}.
1048
+ "}
1049
+
1050
+ h2(class: 'legend-title') {
1051
+ mark('Legend:')
1052
+ }
1053
+
1054
+ p(class: 'legend') {"
1055
+ #{strong("1- Ptero:")} Pterosaur is flying dinosaur species#{br}
1056
+ #{strong("2- AI:")} Artificial Intelligence#{br}
1057
+ "}
1058
+
1059
+ }
1060
+ }
1061
+ end
1062
+
1063
+ Document.ready? do
1064
+ HelloParagraph.render
1065
+ end
1066
+ ```
1067
+
1068
+ Screenshot:
1069
+
1070
+ --
1071
+
1072
+ ![Hello, Paragraph!](/images/glimmer-dsl-web-samples-hello-hello-paragraph.png)
1073
+
1074
+ --
1075
+
699
1076
  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
1077
 
701
1078
  Learn more about the differences between various [Glimmer](https://github.com/AndyObtiva/glimmer) DSLs by looking at:
@@ -705,8 +1082,6 @@ Learn more about the differences between various [Glimmer](https://github.com/An
705
1082
  ## Table of Contents
706
1083
 
707
1084
  - [Glimmer DSL for Web](#-glimmer-dsl-for-opal-alpha-pure-ruby-web-gui)
708
- - [Principles](#principles)
709
- - [Background](#background)
710
1085
  - [Prerequisites](#prerequisites)
711
1086
  - [Setup](#setup)
712
1087
  - [Usage](#usage)
@@ -717,11 +1092,16 @@ Learn more about the differences between various [Glimmer](https://github.com/An
717
1092
  - [Hello, World!](#hello-world)
718
1093
  - [Hello, Button!](#hello-button)
719
1094
  - [Hello, Form!](#hello-form)
1095
+ - [Hello, Observer!](#hello-observer)
720
1096
  - [Hello, Data-Binding!](#hello-data-binding)
721
1097
  - [Hello, Content Data-Binding!](#hello-content-data-binding)
722
1098
  - [Hello, Component!](#hello-content-data-binding)
1099
+ - [Hello, glimmer_component Rails Helper!](#hello-glimmer_component-rails-helper)
1100
+ - [Hello, Paragraph!](#hello-paragraph)
723
1101
  - [Hello, Input (Date/Time)!](#hello-input-datetime)
724
1102
  - [Button Counter](#button-counter)
1103
+ - [Design Principles](#design-principles)
1104
+ - [Supporting Libraries](#supporting-libraries)
725
1105
  - [Glimmer Process](#glimmer-process)
726
1106
  - [Help](#help)
727
1107
  - [Issues](#issues)
@@ -736,10 +1116,8 @@ Learn more about the differences between various [Glimmer](https://github.com/An
736
1116
 
737
1117
  [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
1118
 
739
- - Ruby 3.0 (newer Ruby versions are not supported at this time)
1119
+ - Ruby 3.0+
740
1120
  - 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
1121
 
744
1122
  ## Setup
745
1123
 
@@ -766,7 +1144,7 @@ rails new glimmer_app_server
766
1144
  Add the following to `Gemfile`:
767
1145
 
768
1146
  ```
769
- gem 'glimmer-dsl-web', '~> 0.0.10'
1147
+ gem 'glimmer-dsl-web', '~> 0.0.12'
770
1148
  ```
771
1149
 
772
1150
  Run:
@@ -817,7 +1195,7 @@ root to: 'welcomes#index'
817
1195
 
818
1196
  Clear the file `app/views/welcomes/index.html.erb` completely from all content.
819
1197
 
820
- Delete `app/javascript/application.js`
1198
+ Delete `app/javascript` directory
821
1199
 
822
1200
  Rename `app/assets/javascript` directory to `app/assets/opal`.
823
1201
 
@@ -830,7 +1208,7 @@ Add the following lines to `app/assets/config/manifest.js` (and delete their `ja
830
1208
 
831
1209
  Rename `app/assets/opal/application.js.rb` to `app/assets/opal/application.rb`.
832
1210
 
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):
1211
+ 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
1212
 
835
1213
  ```ruby
836
1214
  require 'glimmer-dsl-web' # brings opal and other dependencies automatically
@@ -838,6 +1216,22 @@ require 'glimmer-dsl-web' # brings opal and other dependencies automatically
838
1216
  # Add more require-statements or Glimmer GUI DSL code
839
1217
  ```
840
1218
 
1219
+ ```ruby
1220
+ require 'glimmer-dsl-web'
1221
+
1222
+ require 'glimmer-dsl-web/samples/hello/hello_world.rb'
1223
+ ```
1224
+
1225
+ 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):
1226
+
1227
+ ```ruby
1228
+ require 'glimmer-dsl-web'
1229
+
1230
+ Document.ready? do
1231
+ require 'glimmer-dsl-web/samples/hello/hello_world.rb'
1232
+ end
1233
+ ```
1234
+
841
1235
  Example to confirm setup is working:
842
1236
 
843
1237
  Glimmer GUI code:
@@ -849,7 +1243,7 @@ include Glimmer
849
1243
 
850
1244
  Document.ready? do
851
1245
  # This will hook into element #app-container and then build HTML inside it using Ruby DSL code
852
- div(parent: '#app-container') {
1246
+ div {
853
1247
  label(class: 'greeting') {
854
1248
  'Hello, World!'
855
1249
  }
@@ -860,13 +1254,13 @@ end
860
1254
  That produces:
861
1255
 
862
1256
  ```html
863
- ...
1257
+ <body>
864
1258
  <div data-parent="body" class="element element-1">
865
1259
  <label class="greeting element element-2">
866
1260
  Hello, World!
867
1261
  </label>
868
1262
  </div>
869
- ...
1263
+ </body>
870
1264
  ```
871
1265
 
872
1266
  Start the Rails server:
@@ -880,7 +1274,48 @@ You should see:
880
1274
 
881
1275
  ![setup is working](/images/glimmer-dsl-web-setup-example-working.png)
882
1276
 
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.
1277
+ If you want to customize where the top-level element is mounted, just pass a `parent: 'css_selector'` option.
1278
+
1279
+ HTML:
1280
+
1281
+ ```html
1282
+ ...
1283
+ <div id="app-container">
1284
+ </div>
1285
+ ...
1286
+ ```
1287
+
1288
+ Glimmer GUI code:
1289
+
1290
+ ```ruby
1291
+ require 'glimmer-dsl-web'
1292
+
1293
+ include Glimmer
1294
+
1295
+ Document.ready? do
1296
+ # This will hook into element #app-container and then build HTML inside it using Ruby DSL code
1297
+ div(parent: '#app-container') {
1298
+ label(class: 'greeting') {
1299
+ 'Hello, World!'
1300
+ }
1301
+ }.render
1302
+ end
1303
+ ```
1304
+
1305
+ That produces:
1306
+
1307
+ ```html
1308
+ ...
1309
+ <div id="app-container">
1310
+ <div data-parent="app-container" class="element element-1">
1311
+ <label class="greeting element element-2">
1312
+ Hello, World!
1313
+ </label>
1314
+ </div>
1315
+ </div>
1316
+ ...
1317
+ ```
1318
+ 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
1319
 
885
1320
  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
1321
 
@@ -925,7 +1360,7 @@ Disable the `webpacker` gem line in `Gemfile`:
925
1360
  Add the following to `Gemfile`:
926
1361
 
927
1362
  ```ruby
928
- gem 'glimmer-dsl-web', '~> 0.0.10'
1363
+ gem 'glimmer-dsl-web', '~> 0.0.12'
929
1364
  ```
930
1365
 
931
1366
  Run:
@@ -1055,7 +1490,7 @@ You should see:
1055
1490
 
1056
1491
  ![setup is working](/images/glimmer-dsl-web-setup-example-working.png)
1057
1492
 
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.
1493
+ 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
1494
 
1060
1495
  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
1496
 
@@ -1398,13 +1833,95 @@ That produces the following under `<body></body>`:
1398
1833
  table tr:nth-child(even) {
1399
1834
  background: #ccc;
1400
1835
  }
1401
- </style>
1402
- </div>
1836
+ </style>
1837
+ </div>
1838
+ ```
1839
+
1840
+ Screenshot:
1841
+
1842
+ ![Hello, Form!](/images/glimmer-dsl-web-samples-hello-hello-form.gif)
1843
+
1844
+ #### Hello, Observer!
1845
+
1846
+ [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).
1847
+
1848
+ Glimmer GUI code:
1849
+
1850
+ ```ruby
1851
+ require 'glimmer-dsl-web'
1852
+
1853
+ class NumberHolder
1854
+ attr_accessor :number
1855
+
1856
+ def initialize
1857
+ self.number = 50
1858
+ end
1859
+ end
1860
+
1861
+ class HelloObserver
1862
+ include Glimmer::Web::Component
1863
+
1864
+ before_render do
1865
+ @number_holder = NumberHolder.new
1866
+ end
1867
+
1868
+ after_render do
1869
+ # Observe Model attribute @number_holder.number for changes and update View
1870
+ # Observer is automatically cleaned up if remove method is called on rendered HelloObserver
1871
+ # or its top-level element
1872
+ observe(@number_holder, :number) do
1873
+ number_string = @number_holder.number.to_s
1874
+ @number_input.value = number_string unless @number_input.value == number_string
1875
+ @range_input.value = number_string unless @range_input.value == number_string
1876
+ end
1877
+ # Bidirectional Data-Binding does the same thing automatically
1878
+ # Just disable the observe block above as well as the oninput listeners below
1879
+ # and enable the `value <=> [@number_holder, :number]` lines to try the data-binding version
1880
+ # Learn more about Bidirectional and Unidirectional Data-Binding in hello_data_binding.rb
1881
+ end
1882
+
1883
+ markup {
1884
+ div {
1885
+ div {
1886
+ @number_input = input(type: 'number', value: @number_holder.number, min: 0, max: 100) {
1887
+ # oninput listener updates Model attribute @number_holder.number
1888
+ oninput do
1889
+ @number_holder.number = @number_input.value.to_i
1890
+ end
1891
+
1892
+ # Bidirectional Data-Binding simplifies the implementation significantly
1893
+ # by enabling the following line and disabling oninput listeners as well
1894
+ # as the after_body observe block observer
1895
+ # Learn more about Bidirectional and Unidirectional Data-Binding in hello_data_binding.rb
1896
+ # value <=> [@number_holder, :number]
1897
+ }
1898
+ }
1899
+ div {
1900
+ @range_input = input(type: 'range', value: @number_holder.number, min: 0, max: 100) {
1901
+ # oninput listener updates Model attribute @number_holder.number
1902
+ oninput do
1903
+ @number_holder.number = @range_input.value.to_i
1904
+ end
1905
+
1906
+ # Bidirectional Data-Binding simplifies the implementation significantly
1907
+ # by enabling the following line and disabling oninput listeners as well
1908
+ # as the after_body observe block observer
1909
+ # Learn more about Bidirectional and Unidirectional Data-Binding in hello_data_binding.rb
1910
+ # value <=> [@number_holder, :number]
1911
+ }
1912
+ }
1913
+ }
1914
+ }
1915
+ end
1916
+
1917
+ Document.ready? do
1918
+ HelloObserver.render
1919
+ end
1403
1920
  ```
1404
1921
 
1405
1922
  Screenshot:
1406
1923
 
1407
- ![Hello, Form!](/images/glimmer-dsl-web-samples-hello-hello-form.gif)
1924
+ ![Hello, Observer!](/images/glimmer-dsl-web-samples-hello-hello-observer.gif)
1408
1925
 
1409
1926
  #### Hello, Data-Binding!
1410
1927
 
@@ -1721,9 +2238,6 @@ by simply defining a class with `include Glimmer::Web::Component` and encasing t
1721
2238
  a `markup {...}` block. Glimmer web components automatically extend the Glimmer GUI DSL with new keywords
1722
2239
  that match the underscored versions of the component class names (e.g. a `OrderSummary` class yields
1723
2240
  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
2241
  Below, we define an `AddressForm` component that generates a `address_form` keyword, and then we
1728
2242
  reuse it twice inside an `AddressPage` component displaying a Shipping Address and a Billing Address.
1729
2243
 
@@ -1938,6 +2452,304 @@ Screenshot:
1938
2452
 
1939
2453
  ![Hello, Component!](/images/glimmer-dsl-web-samples-hello-hello-component.png)
1940
2454
 
2455
+ #### Hello, glimmer_component Rails Helper!
2456
+
2457
+ You may insert a Glimmer component anywhere into a Rails View using
2458
+ `glimmer_component(component_path, *args)` Rails helper. Add `include GlimmerHelper` to `ApplicationHelper`
2459
+ or another Rails helper, and use `<%= glimmer_component("path/to/component", *args) %>` in Views.
2460
+
2461
+ Rails `ApplicationHelper` setup code:
2462
+
2463
+ ```ruby
2464
+ require 'glimmer/helpers/glimmer_helper'
2465
+
2466
+ module ApplicationHelper
2467
+ # ...
2468
+ include GlimmerHelper
2469
+ # ...
2470
+ end
2471
+ ```
2472
+
2473
+ Rails View code:
2474
+
2475
+ ```erb
2476
+ <div id="address-container">
2477
+ <h1>Shipping Address </h1>
2478
+ <legend>Please enter your shipping address information (Zip Code must be a valid 5 digit number)</legend>
2479
+ <!-- This sample demonstrates use of glimmer_component helper with arguments -->
2480
+ <%= glimmer_component('address_form',
2481
+ full_name: params[:full_name],
2482
+ street: params[:street],
2483
+ street2: params[:street2],
2484
+ city: params[:city],
2485
+ state: params[:state],
2486
+ zip_code: params[:zip_code]
2487
+ )
2488
+ %>
2489
+ <div>
2490
+ <a href="/">&lt;&lt; Back Home</a>
2491
+ </div>
2492
+ </div>
2493
+ ```
2494
+
2495
+ Glimmer GUI code:
2496
+
2497
+ ```ruby
2498
+ require 'glimmer-dsl-web'
2499
+
2500
+ class AddressForm
2501
+ Address = Struct.new(:full_name, :street, :street2, :city, :state, :zip_code, keyword_init: true) do
2502
+ def state_code
2503
+ STATES.invert[state]
2504
+ end
2505
+
2506
+ def state_code=(value)
2507
+ self.state = STATES[value]
2508
+ end
2509
+
2510
+ def summary
2511
+ to_h.values.map(&:to_s).reject(&:empty?).join(', ')
2512
+ end
2513
+ end
2514
+
2515
+ STATES = {
2516
+ "AK"=>"Alaska",
2517
+ "AL"=>"Alabama",
2518
+ "AR"=>"Arkansas",
2519
+ "AS"=>"American Samoa",
2520
+ "AZ"=>"Arizona",
2521
+ "CA"=>"California",
2522
+ "CO"=>"Colorado",
2523
+ "CT"=>"Connecticut",
2524
+ "DC"=>"District of Columbia",
2525
+ "DE"=>"Delaware",
2526
+ "FL"=>"Florida",
2527
+ "GA"=>"Georgia",
2528
+ "GU"=>"Guam",
2529
+ "HI"=>"Hawaii",
2530
+ "IA"=>"Iowa",
2531
+ "ID"=>"Idaho",
2532
+ "IL"=>"Illinois",
2533
+ "IN"=>"Indiana",
2534
+ "KS"=>"Kansas",
2535
+ "KY"=>"Kentucky",
2536
+ "LA"=>"Louisiana",
2537
+ "MA"=>"Massachusetts",
2538
+ "MD"=>"Maryland",
2539
+ "ME"=>"Maine",
2540
+ "MI"=>"Michigan",
2541
+ "MN"=>"Minnesota",
2542
+ "MO"=>"Missouri",
2543
+ "MS"=>"Mississippi",
2544
+ "MT"=>"Montana",
2545
+ "NC"=>"North Carolina",
2546
+ "ND"=>"North Dakota",
2547
+ "NE"=>"Nebraska",
2548
+ "NH"=>"New Hampshire",
2549
+ "NJ"=>"New Jersey",
2550
+ "NM"=>"New Mexico",
2551
+ "NV"=>"Nevada",
2552
+ "NY"=>"New York",
2553
+ "OH"=>"Ohio",
2554
+ "OK"=>"Oklahoma",
2555
+ "OR"=>"Oregon",
2556
+ "PA"=>"Pennsylvania",
2557
+ "PR"=>"Puerto Rico",
2558
+ "RI"=>"Rhode Island",
2559
+ "SC"=>"South Carolina",
2560
+ "SD"=>"South Dakota",
2561
+ "TN"=>"Tennessee",
2562
+ "TX"=>"Texas",
2563
+ "UT"=>"Utah",
2564
+ "VA"=>"Virginia",
2565
+ "VI"=>"Virgin Islands",
2566
+ "VT"=>"Vermont",
2567
+ "WA"=>"Washington",
2568
+ "WI"=>"Wisconsin",
2569
+ "WV"=>"West Virginia",
2570
+ "WY"=>"Wyoming"
2571
+ }
2572
+
2573
+ include Glimmer::Web::Component
2574
+
2575
+ option :full_name
2576
+ option :street
2577
+ option :street2
2578
+ option :city
2579
+ option :state
2580
+ option :zip_code
2581
+
2582
+ attr_reader :address
2583
+
2584
+ before_render do
2585
+ @address = Address.new(
2586
+ full_name: full_name,
2587
+ street: street,
2588
+ street2: street2,
2589
+ city: city,
2590
+ state: state,
2591
+ zip_code: zip_code,
2592
+ )
2593
+ end
2594
+
2595
+ markup {
2596
+ div {
2597
+ div(style: 'display: grid; grid-auto-columns: 80px 260px;') { |address_div|
2598
+ label('Full Name: ', for: 'full-name-field')
2599
+ input(id: 'full-name-field') {
2600
+ value <=> [address, :full_name]
2601
+ }
2602
+
2603
+ @somelabel = label('Street: ', for: 'street-field')
2604
+ input(id: 'street-field') {
2605
+ value <=> [address, :street]
2606
+ }
2607
+
2608
+ label('Street 2: ', for: 'street2-field')
2609
+ textarea(id: 'street2-field') {
2610
+ value <=> [address, :street2]
2611
+ }
2612
+
2613
+ label('City: ', for: 'city-field')
2614
+ input(id: 'city-field') {
2615
+ value <=> [address, :city]
2616
+ }
2617
+
2618
+ label('State: ', for: 'state-field')
2619
+ select(id: 'state-field') {
2620
+ STATES.each do |state_code, state|
2621
+ option(value: state_code) { state }
2622
+ end
2623
+
2624
+ value <=> [address, :state_code]
2625
+ }
2626
+
2627
+ label('Zip Code: ', for: 'zip-code-field')
2628
+ input(id: 'zip-code-field', type: 'number', min: '0', max: '99999') {
2629
+ value <=> [address, :zip_code,
2630
+ on_write: :to_s,
2631
+ ]
2632
+ }
2633
+
2634
+ style {
2635
+ <<~CSS
2636
+ #{address_div.selector} * {
2637
+ margin: 5px;
2638
+ }
2639
+ #{address_div.selector} input, #{address_div.selector} select {
2640
+ grid-column: 2;
2641
+ }
2642
+ CSS
2643
+ }
2644
+ }
2645
+
2646
+ div(style: 'margin: 5px') {
2647
+ inner_text <= [address, :summary,
2648
+ computed_by: address.members + ['state_code'],
2649
+ ]
2650
+ }
2651
+ }
2652
+ }
2653
+ end
2654
+ ```
2655
+
2656
+ Screenshot:
2657
+
2658
+ ![Hello, glimmer_component Rails Helper!](/images/glimmer-dsl-web-samples-hello-hello-component.png)
2659
+
2660
+ #### Hello, Paragraph!
2661
+
2662
+ To facilitate building formatted textual paragraphs in Ruby, the Glimmer GUI DSL is advanced enough to behave differently when using HTML formatting elements: `<br>`, `<strong>`, `<em>`, `<br>`, `<i>`, `<sub>`, `<sup>`, `<del>`, `<ins>`, `<small>`, `<mark>`
2663
+
2664
+ Instead of returning Ruby objects that are nested as children within their parent, the Glimmer GUI DSL returns `String` objects directly that can be concatenated to or embedded within other `String` objects via interpolation.
2665
+
2666
+ This enables writing code like:
2667
+
2668
+ `p {"#{strong('Yesterday, ')}Robert suggested adding a new #{em('feature')} to our software product.#{br}}`
2669
+
2670
+ That is close to how it is written in HTML, albeit briefer in Ruby:
2671
+
2672
+ `<p><strong>Yesterday, </strong>Robert suggested adding a new <em>feature</em> to our software product.<br></p>`
2673
+
2674
+ Formatting elements just like regular elements can accept text content as their first argument or as their block return value. So, the code above could equally be written as follows:
2675
+
2676
+ `p {"#{strong{'Yesterday, '}}Robert suggested adding a new #{em{'feature'}} to our software product.#{br}}`
2677
+
2678
+ This enables seggregating formatting element attributes if desired, as in this example:
2679
+
2680
+ `p {"#{strong(class: 'very-string'){'Yesterday, '}}Robert suggested adding a new #{em(class: 'very-emphasized'){'feature'}} to our software product.#{br}}`
2681
+
2682
+ Another way of writing the same code is to pass the text content as the first argument, before attributes:
2683
+
2684
+
2685
+ `p {"#{strong('Yesterday, ', class: 'very-string')}Robert suggested adding a new #{em('feature', class: 'very-emphasized')} to our software product.#{br}}`
2686
+
2687
+ One last bit of info to keep in mind is that `<span>` generally generates a normal element, except when used inside a `<p>`'s content block, in which case it is assumed to be used for formatting, so
2688
+ it returns a `String` to enable code like this:
2689
+
2690
+ `p {"#{span('Yesterday, ', style: 'text-decoration: underline;')}Robert suggested adding a new #{em('feature', class: 'very-emphasized')} to our software product.#{br}}`
2691
+
2692
+ In any case, below is a full example leveraging the Glimmer GUI DSL alternative approach when utilizing formatting elements underneath a paragraph.
2693
+
2694
+ Glimmer GUI code:
2695
+
2696
+ ```ruby
2697
+ require 'glimmer-dsl-web'
2698
+
2699
+ class HelloParagraph
2700
+ include Glimmer::Web::Component
2701
+
2702
+ markup {
2703
+ div {
2704
+ h1(class: 'title') {
2705
+ 'Flying Cars Become 100% Safe with AI Powered Balance!'
2706
+ }
2707
+
2708
+ p(class: 'intro') {"
2709
+ In the early 2030's, #{em('flying cars')} became affordable after their prices dropped
2710
+ below #{small(del('$100,000'))}#{ins('$80,000')} as a result of the innovations of #{strong('Travel-X')}. Still, that did not
2711
+ make #{em('flying cars')} any popular due to the extreme difficulty in piloting such flying vehicles for the average
2712
+ person, making it very tough to pass the tests for getting a piloting license given the learning curve.
2713
+ "}
2714
+
2715
+ p {"
2716
+ That said, #{b('Travel-X')} has recently come up with a new feature for their flagship #{i('flying car')},
2717
+ the Ptero#{sub(1)}#{sup('TM')}, which relies on AI#{sub(2)} to automatically balance the flying cars in mid-air,
2718
+ thus significantly facilitating their piloting by the average consumer.
2719
+ "}
2720
+
2721
+ p(class: 'conclusion') {"
2722
+ That Ptero#{sup('TM')} will be so stable and well balanced while flying that the consumer will be able to drive
2723
+ as if it is a plain old car, with the only difference being vertical elevation, the control of which will be handled
2724
+ automatically by AI. The Ptero#{sup('TM')} will debut for #{span(style: 'text-decoration: underline dashed;'){'$79,000'}}.
2725
+ "}
2726
+
2727
+ h2(class: 'legend-title') {
2728
+ mark('Legend:')
2729
+ }
2730
+
2731
+ p(class: 'legend') {"
2732
+ #{strong("1- Ptero:")} Pterosaur is flying dinosaur species#{br}
2733
+ #{strong("2- AI:")} Artificial Intelligence#{br}
2734
+ "}
2735
+
2736
+ }
2737
+ }
2738
+ end
2739
+
2740
+ Document.ready? do
2741
+ HelloParagraph.render
2742
+ end
2743
+ ```
2744
+
2745
+ Screenshot:
2746
+
2747
+ --
2748
+
2749
+ ![Hello, Paragraph!](/images/glimmer-dsl-web-samples-hello-hello-paragraph.png)
2750
+
2751
+ --
2752
+
1941
2753
  #### Hello, Input (Date/Time)!
1942
2754
 
1943
2755
  Glimmer GUI code:
@@ -2047,8 +2859,6 @@ Screenshot:
2047
2859
 
2048
2860
  #### Button Counter
2049
2861
 
2050
- **UPCOMING (NOT RELEASED OR SUPPORTED YET)**
2051
-
2052
2862
  Glimmer GUI code demonstrating MVC + Glimmer Web Components (Views) + Data-Binding:
2053
2863
 
2054
2864
  ```ruby
@@ -2060,13 +2870,9 @@ class Counter
2060
2870
  def initialize
2061
2871
  self.count = 0
2062
2872
  end
2063
-
2064
- def increment!
2065
- self.count += 1
2066
- end
2067
2873
  end
2068
2874
 
2069
- class HelloButton
2875
+ class ButtonCounter
2070
2876
  include Glimmer::Web::Component
2071
2877
 
2072
2878
  before_render do
@@ -2074,32 +2880,31 @@ class HelloButton
2074
2880
  end
2075
2881
 
2076
2882
  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
-
2883
+ div {
2081
2884
  button {
2082
2885
  # Unidirectional Data-Binding indicating that on every change to @counter.count, the value
2083
2886
  # is read and converted to "Click To Increment: #{value} ", and then automatically
2084
2887
  # copied to button innerText (content) to display to the user
2085
- inner_text <= [@counter, :count, on_read: ->(value) { "Click To Increment: #{value} " }]
2888
+ inner_text <= [@counter, :count,
2889
+ on_read: ->(value) { "Click To Increment: #{value} " }
2890
+ ]
2086
2891
 
2087
2892
  onclick {
2088
- @counter.increment!
2893
+ @counter.count += 1
2089
2894
  }
2090
2895
  }
2091
2896
  }
2092
2897
  }
2093
2898
  end
2094
2899
 
2095
- HelloButton.render
2900
+ ButtonCounter.render
2096
2901
  ```
2097
2902
 
2098
2903
  That produces:
2099
2904
 
2100
2905
  ```html
2101
- <div id="application">
2102
- <button>
2906
+ <div data-parent="body" class="element element-1">
2907
+ <button class="element element-2">
2103
2908
  Click To Increment: 0
2104
2909
  </button>
2105
2910
  </div>
@@ -2108,8 +2913,8 @@ That produces:
2108
2913
  When clicked:
2109
2914
 
2110
2915
  ```html
2111
- <div id="application">
2112
- <button>
2916
+ <div data-parent="body" class="element element-1">
2917
+ <button class="element element-2">
2113
2918
  Click To Increment: 1
2114
2919
  </button>
2115
2920
  </div>
@@ -2118,14 +2923,29 @@ When clicked:
2118
2923
  When clicked 7 times:
2119
2924
 
2120
2925
  ```html
2121
- <div id="application">
2122
- <button>
2926
+ <div data-parent="body" class="element element-1">
2927
+ <button class="element element-2">
2123
2928
  Click To Increment: 7
2124
2929
  </button>
2125
2930
  </div>
2126
2931
  ```
2127
2932
 
2128
- ## Glimmer Supporting Libraries
2933
+ Screenshot:
2934
+
2935
+ ![Button Counter](/images/glimmer-dsl-web-samples-regular-button-counter.gif)
2936
+
2937
+ ## Design Principles
2938
+
2939
+ - The Ruby Way (including TIMTOWTDI: There Is More Than One Way To Do It)
2940
+ - The Rails Way Convention over Configuration via smart defaults and automation of low-level details
2941
+ - Requiring the least amount of syntax possible to build highly interactive web pages
2942
+ - Declarative syntax that visually maps to the DOM (Document Object Model) hierarchy
2943
+ - Ability to mix declarative and imperative code conveniently in one language
2944
+ - Computers serve Software Engineers (not Software Engineers serve Computers)
2945
+ - Think only about real world concepts directly relevant to web page interaction
2946
+ - Modular Software Design (e.g. support for Components)
2947
+
2948
+ ## Supporting Libraries
2129
2949
 
2130
2950
  Here is a list of notable 3rd party gems used by Glimmer DSL for Web:
2131
2951
  - [glimmer-dsl-xml](https://github.com/AndyObtiva/glimmer-dsl-xml): Glimmer DSL for XML & HTML in pure Ruby.