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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +16 -0
- data/README.md +865 -45
- data/VERSION +1 -1
- data/glimmer-dsl-web.gemspec +11 -5
- data/lib/glimmer/dsl/web/component_expression.rb +2 -1
- data/lib/glimmer/dsl/web/dsl.rb +24 -0
- data/lib/glimmer/dsl/web/formatting_element_expression.rb +19 -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 +10 -14
- data/lib/glimmer/web/event_proxy.rb +7 -4
- data/lib/glimmer/web/formatting_element_proxy.rb +56 -0
- data/lib/glimmer/web/listener_proxy.rb +12 -11
- data/lib/glimmer-dsl-web/samples/hello/hello_observer.rb +90 -0
- data/lib/glimmer-dsl-web/samples/hello/hello_paragraph.rb +67 -0
- data/lib/glimmer-dsl-web/samples/regular/button_counter.rb +36 -0
- metadata +21 -8
data/README.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# [<img src="https://raw.githubusercontent.com/AndyObtiva/glimmer/master/images/glimmer-logo-hi-res.png" height=85 />](https://github.com/AndyObtiva/glimmer) Glimmer DSL for Web 0.0.
|
1
|
+
# [<img src="https://raw.githubusercontent.com/AndyObtiva/glimmer/master/images/glimmer-logo-hi-res.png" height=85 />](https://github.com/AndyObtiva/glimmer) Glimmer DSL for Web 0.0.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="/"><< 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
|
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.
|
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
|
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
|
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
|
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
|
-
|
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.
|
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
|
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,
|
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="/"><< 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
|
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
|
-
|
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,
|
2888
|
+
inner_text <= [@counter, :count,
|
2889
|
+
on_read: ->(value) { "Click To Increment: #{value} " }
|
2890
|
+
]
|
2086
2891
|
|
2087
2892
|
onclick {
|
2088
|
-
@counter.
|
2893
|
+
@counter.count += 1
|
2089
2894
|
}
|
2090
2895
|
}
|
2091
2896
|
}
|
2092
2897
|
}
|
2093
2898
|
end
|
2094
2899
|
|
2095
|
-
|
2900
|
+
ButtonCounter.render
|
2096
2901
|
```
|
2097
2902
|
|
2098
2903
|
That produces:
|
2099
2904
|
|
2100
2905
|
```html
|
2101
|
-
<div
|
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
|
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
|
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
|
-
|
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.
|