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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +10 -0
- data/README.md +658 -39
- data/VERSION +1 -1
- data/glimmer-dsl-web.gemspec +7 -4
- data/lib/glimmer/dsl/web/component_expression.rb +2 -1
- data/lib/glimmer/dsl/web/dsl.rb +22 -0
- data/lib/glimmer/dsl/web/observe_expression.rb +42 -0
- data/lib/glimmer/helpers/glimmer_helper.rb +5 -7
- data/lib/glimmer/web/component.rb +22 -0
- data/lib/glimmer/web/element_proxy.rb +9 -14
- data/lib/glimmer/web/event_proxy.rb +7 -4
- data/lib/glimmer/web/listener_proxy.rb +12 -11
- data/lib/glimmer-dsl-web/samples/hello/hello_observer.rb +88 -0
- data/lib/glimmer-dsl-web/samples/regular/button_counter.rb +36 -0
- metadata +7 -4
data/README.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# [<img src="https://raw.githubusercontent.com/AndyObtiva/glimmer/master/images/glimmer-logo-hi-res.png" height=85 />](https://github.com/AndyObtiva/glimmer) Glimmer DSL for Web 0.0.
|
1
|
+
# [<img src="https://raw.githubusercontent.com/AndyObtiva/glimmer/master/images/glimmer-logo-hi-res.png" height=85 />](https://github.com/AndyObtiva/glimmer) Glimmer DSL for Web 0.0.11 (Early Alpha)
|
2
2
|
## Ruby in the Browser Web GUI Frontend Library
|
3
3
|
[![Gem Version](https://badge.fury.io/rb/glimmer-dsl-web.svg)](http://badge.fury.io/rb/glimmer-dsl-web)
|
4
4
|
[![Join the chat at https://gitter.im/AndyObtiva/glimmer](https://badges.gitter.im/AndyObtiva/glimmer.svg)](https://gitter.im/AndyObtiva/glimmer?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
@@ -220,6 +220,86 @@ Screenshot:
|
|
220
220
|
|
221
221
|
![Hello, Form!](/images/glimmer-dsl-web-samples-hello-hello-form.gif)
|
222
222
|
|
223
|
+
**Hello, Observer!**
|
224
|
+
|
225
|
+
[Glimmer DSL for Web](https://rubygems.org/gems/glimmer-dsl-web) provides the `observe(model, attribute) { ... }` keyword to employ the [Observer Design Pattern](https://en.wikipedia.org/wiki/Observer_pattern) as per [MVC](https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller) (Model View Controller), enabling Views to observe Models and update themselves in response to changes. If the `observe` keyword is used from inside a [Component](#hello-component) (covered later), when the Component is removed or its top-level element is removed, the observer is automatically cleaned up. The need for such explicit observers is significantly diminished by the availablility of the more advanced Unidirectional [Data-Binding](#hello-data-binding) Support and Bidirectional [Data-Binding](#hello-data-binding) Support (covered later).
|
226
|
+
|
227
|
+
Glimmer GUI code:
|
228
|
+
|
229
|
+
```ruby
|
230
|
+
require 'glimmer-dsl-web'
|
231
|
+
|
232
|
+
class NumberHolder
|
233
|
+
attr_accessor :number
|
234
|
+
|
235
|
+
def initialize
|
236
|
+
self.number = 50
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
class HelloObserver
|
241
|
+
include Glimmer::Web::Component
|
242
|
+
|
243
|
+
before_render do
|
244
|
+
@number_holder = NumberHolder.new
|
245
|
+
end
|
246
|
+
|
247
|
+
after_render do
|
248
|
+
# observe Model attribute @number_holder.number for changes and update View
|
249
|
+
observe(@number_holder, :number) do
|
250
|
+
number_string = @number_holder.number.to_s
|
251
|
+
@number_input.value = number_string unless @number_input.value == number_string
|
252
|
+
@range_input.value = number_string unless @range_input.value == number_string
|
253
|
+
end
|
254
|
+
# Bidirectional Data-Binding does the same thing automatically
|
255
|
+
# Just disable the observe block above as well as the oninput listeners below
|
256
|
+
# and enable the `value <=> [@number_holder, :number]` lines to try the data-binding version
|
257
|
+
# Learn more about Bidirectional and Unidirectional Data-Binding in hello_data_binding.rb
|
258
|
+
end
|
259
|
+
|
260
|
+
markup {
|
261
|
+
div {
|
262
|
+
div {
|
263
|
+
@number_input = input(type: 'number', value: @number_holder.number, min: 0, max: 100) {
|
264
|
+
# oninput listener updates Model attribute @number_holder.number
|
265
|
+
oninput do
|
266
|
+
@number_holder.number = @number_input.value.to_i
|
267
|
+
end
|
268
|
+
|
269
|
+
# Bidirectional Data-Binding simplifies the implementation significantly
|
270
|
+
# by enabling the following line and disabling oninput listeners as well
|
271
|
+
# as the after_body observe block observer
|
272
|
+
# Learn more about Bidirectional and Unidirectional Data-Binding in hello_data_binding.rb
|
273
|
+
# value <=> [@number_holder, :number]
|
274
|
+
}
|
275
|
+
}
|
276
|
+
div {
|
277
|
+
@range_input = input(type: 'range', value: @number_holder.number, min: 0, max: 100) {
|
278
|
+
# oninput listener updates Model attribute @number_holder.number
|
279
|
+
oninput do
|
280
|
+
@number_holder.number = @range_input.value.to_i
|
281
|
+
end
|
282
|
+
|
283
|
+
# Bidirectional Data-Binding simplifies the implementation significantly
|
284
|
+
# by enabling the following line and disabling oninput listeners as well
|
285
|
+
# as the after_body observe block observer
|
286
|
+
# Learn more about Bidirectional and Unidirectional Data-Binding in hello_data_binding.rb
|
287
|
+
# value <=> [@number_holder, :number]
|
288
|
+
}
|
289
|
+
}
|
290
|
+
}
|
291
|
+
}
|
292
|
+
end
|
293
|
+
|
294
|
+
Document.ready? do
|
295
|
+
HelloObserver.render
|
296
|
+
end
|
297
|
+
```
|
298
|
+
|
299
|
+
Screenshot:
|
300
|
+
|
301
|
+
![Hello, Observer!](/images/glimmer-dsl-web-samples-hello-hello-observer.gif)
|
302
|
+
|
223
303
|
**Hello, Data-Binding!**
|
224
304
|
|
225
305
|
[Glimmer DSL for Web](https://rubygems.org/gems/glimmer-dsl-web) intuitively supports both Unidirectional (One-Way) Data-Binding via the `<=` operator and Bidirectional (Two-Way) Data-Binding via the `<=>` operator, incredibly simplifying how to sync View properties with Model attributes with the simplest code to reason about.
|
@@ -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="/"><< 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
|
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.
|
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
|
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
|
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
|
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
|
-
|
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.
|
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
|
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="/"><< 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
|
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
|
-
|
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,
|
2698
|
+
inner_text <= [@counter, :count,
|
2699
|
+
on_read: ->(value) { "Click To Increment: #{value} " }
|
2700
|
+
]
|
2086
2701
|
|
2087
2702
|
onclick {
|
2088
|
-
@counter.
|
2703
|
+
@counter.count += 1
|
2089
2704
|
}
|
2090
2705
|
}
|
2091
2706
|
}
|
2092
2707
|
}
|
2093
2708
|
end
|
2094
2709
|
|
2095
|
-
|
2710
|
+
ButtonCounter.render
|
2096
2711
|
```
|
2097
2712
|
|
2098
2713
|
That produces:
|
2099
2714
|
|
2100
2715
|
```html
|
2101
|
-
<div
|
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
|
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
|
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:
|