glimmer-dsl-web 0.0.4 → 0.0.6

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -1,26 +1,11 @@
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.4 (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.6 (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)
5
5
 
6
- This project is inspired-by [Glimmer DSL for Opal](https://github.com/AndyObtiva/glimmer-dsl-opal) and is similar in enabling frontend GUI development with Ruby, but it mainly differs from Glimmer DSL for Opal by adopting a DSL that follows web-like HTML syntax in Ruby (enabling transfer of HTML/CSS/JS skills) instead of adopting a desktop GUI DSL that is webified. Also, it will begin by supporting [Opal Ruby](https://opalrb.com/), but it 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.
6
+ [Glimmer](https://github.com/AndyObtiva/glimmer) DSL for Web enables building Web GUI frontends using [Ruby in the Browser](https://www.youtube.com/watch?v=4AdcfbI6A4c), as per [Matz's recommendation in his RubyConf 2022 keynote speech to replace JavaScript with Ruby](https://youtu.be/knutsgHTrfQ?t=789). It aims at providing the simplest frontend library in existence. The library follows the Ruby way (with [DSLs](https://martinfowler.com/books/dsl.html) and [TIMTOWTDI](https://en.wiktionary.org/wiki/TMTOWTDI#English)) and the Rails way ([Convention over Configuration](https://rubyonrails.org/doctrine)) while supporting both Unidirectional (One-Way) Data-Binding (using `<=`) and Bidirectional (Two-Way) Data-Binding (using `<=>`). You can finally live in pure Rubyland on the Web in both the frontend and backend with [Glimmer DSL for Web](https://rubygems.org/gems/glimmer-dsl-web)!
7
7
 
8
- Note that the library is an Early Alpha and its APIs might change frequently until hitting a minor release at least.
9
-
10
- ### You can finally live in pure Rubyland on the web!
11
-
12
- [Glimmer](https://github.com/AndyObtiva/glimmer) DSL for Web is an upcoming **pre-alpha** [gem](https://rubygems.org/gems/glimmer-dsl-web) that enables building web GUI in pure Ruby via [Opal](https://opalrb.com/) on [Rails](https://rubyonrails.org/) (and potentially [Ruby WASM](https://github.com/ruby/ruby.wasm) in the future).
13
-
14
- **Sample**
15
-
16
- Initial HTML Markup:
17
-
18
- ```html
19
- ...
20
- <div id="app-container">
21
- </div>
22
- ...
23
- ```
8
+ **Hello, World! Sample**
24
9
 
25
10
  Glimmer GUI code:
26
11
 
@@ -30,32 +15,25 @@ require 'glimmer-dsl-web'
30
15
  include Glimmer
31
16
 
32
17
  Document.ready? do
33
- # This will hook into element #app-container and then build HTML inside it using Ruby DSL code
34
- div(parent: '#app-container') {
35
- label(class: 'greeting') {
36
- 'Hello, World!'
37
- }
18
+ div {
19
+ 'Hello, World!'
38
20
  }.render
39
21
  end
40
22
  ```
41
23
 
42
- That produces:
24
+ That produces the following under `<body></body>`:
43
25
 
44
26
  ```html
45
- ...
46
- <div id="app-container">
47
- <div data-parent="#app-container" class="element element-1">
48
- <label class="greeting element element-2">
49
- Hello, World!
50
- </label>
51
- </div>
27
+ <div data-parent="body" class="element element-1">
28
+ Hello, World!
52
29
  </div>
53
- ...
54
30
  ```
55
31
 
56
32
  ![setup is working](/images/glimmer-dsl-web-setup-example-working.png)
57
33
 
58
- **Hello, World! Sample**
34
+ **Hello, Button!**
35
+
36
+ Event listeners can be setup on any element using the same event names used in HTML (e.g. `onclick`) while passing in a standard Ruby block to handle behavior. `$$` gives access to `window` to invoke functions like `alert`.
59
37
 
60
38
  Glimmer GUI code:
61
39
 
@@ -66,7 +44,11 @@ include Glimmer
66
44
 
67
45
  Document.ready? do
68
46
  div {
69
- 'Hello, World!'
47
+ button('Greet') {
48
+ onclick do
49
+ $$.alert('Hello, Button!')
50
+ end
51
+ }
70
52
  }.render
71
53
  end
72
54
  ```
@@ -75,13 +57,17 @@ That produces the following under `<body></body>`:
75
57
 
76
58
  ```html
77
59
  <div data-parent="body" class="element element-1">
78
- Hello, World!
60
+ <button class="element element-2">Greet</button>
79
61
  </div>
80
62
  ```
81
63
 
82
- ![setup is working](/images/glimmer-dsl-web-setup-example-working.png)
64
+ Screenshot:
83
65
 
84
- **Hello, Button! Sample**
66
+ ![Hello, Button!](/images/glimmer-dsl-web-samples-hello-hello-button.gif)
67
+
68
+ **Hello, Form!**
69
+
70
+ [Glimmer DSL for Web](https://rubygems.org/gems/glimmer-dsl-web) gives access to all Web Browser built-in features like HTML form validations, input focus, events, and element functions from a very terse and productive Ruby GUI DSL.
85
71
 
86
72
  Glimmer GUI code:
87
73
 
@@ -93,44 +79,50 @@ include Glimmer
93
79
  Document.ready? do
94
80
  div {
95
81
  h1('Contact Form')
82
+
96
83
  form {
97
- div(class: 'field-row') {
84
+ div {
98
85
  label('Name: ', for: 'name-field')
99
- @name_input = input(id: 'name-field', class: 'field', type: 'text', required: true)
86
+ @name_input = input(type: 'text', id: 'name-field', required: true, autofocus: true)
100
87
  }
101
- div(class: 'field-row') {
88
+
89
+ div {
102
90
  label('Email: ', for: 'email-field')
103
- @email_input = input(id: 'email-field', class: 'field', type: 'email', required: true)
91
+ @email_input = input(type: 'email', id: 'email-field', required: true)
104
92
  }
105
- button('Add Contact', class: 'submit-button') {
106
- on_click do
107
- if ([@name_input, @email_input].all? {|input| input.check_validity })
108
- @table.content {
109
- tr {
110
- td { @name_input.value }
111
- td { @email_input.value }
93
+
94
+ div {
95
+ input(type: 'submit', value: 'Add Contact') {
96
+ onclick do |event|
97
+ if ([@name_input, @email_input].all? {|input| input.check_validity })
98
+ # re-open table content and add row
99
+ @table.content {
100
+ tr {
101
+ td { @name_input.value }
102
+ td { @email_input.value }
103
+ }
112
104
  }
113
- }
114
- @email_input.value = @name_input.value = ''
115
- else
116
- error_messages = []
117
- error_messages << "Name is not valid! Make sure it is filled." if !@name_input.check_validity
118
- error_messages << "Email is not valid! Make sure it is filled and has a valid format." if !@email_input.check_validity
119
- $$.alert(error_messages.join("\n"))
105
+ @email_input.value = @name_input.value = ''
106
+ @name_input.focus
107
+ end
120
108
  end
121
- end
109
+ }
122
110
  }
123
111
  }
112
+
124
113
  h1('Contacts Table')
114
+
125
115
  @table = table {
126
116
  tr {
127
117
  th('Name')
128
118
  th('Email')
129
119
  }
120
+
130
121
  tr {
131
122
  td('John Doe')
132
123
  td('johndoe@example.com')
133
124
  }
125
+
134
126
  tr {
135
127
  td('Jane Doe')
136
128
  td('janedoe@example.com')
@@ -140,15 +132,11 @@ Document.ready? do
140
132
  # CSS Styles
141
133
  style {
142
134
  <<~CSS
143
- .field-row {
144
- margin: 10px 5px;
145
- }
146
- .field {
147
- margin-left: 5px;
135
+ input {
136
+ margin: 5px;
148
137
  }
149
- .submit-button {
150
- display: block;
151
- margin: 10px 5px;
138
+ input[type=submit] {
139
+ margin: 5px 0;
152
140
  }
153
141
  table {
154
142
  border:1px solid grey;
@@ -171,83 +159,166 @@ That produces the following under `<body></body>`:
171
159
  ```html
172
160
  <div data-parent="body" class="element element-1">
173
161
  <h1 class="element element-2">Contact Form</h1>
162
+
174
163
  <form class="element element-3">
175
- <div class="field-row element element-4">
164
+ <div class="element element-4">
176
165
  <label for="name-field" class="element element-5">Name: </label>
177
- <input id="name-field" class="field element element-6" type="text" required="true">
166
+ <input type="text" id="name-field" required="true" autofocus="true" class="element element-6">
178
167
  </div>
179
- <div class="field-row element element-7">
168
+
169
+ <div class="element element-7">
180
170
  <label for="email-field" class="element element-8">Email: </label>
181
- <input id="email-field" class="field element element-9" type="email" required="true">
171
+ <input type="email" id="email-field" required="true" class="element element-9">
172
+ </div>
173
+
174
+ <div class="element element-10">
175
+ <input type="submit" value="Add Contact" class="element element-11">
182
176
  </div>
183
- <button class="submit-button element element-10">Add Contact</button>
184
177
  </form>
185
- <h1 class="element element-11">Contacts Table</h1>
186
- <table class="element element-12">
187
- <tr class="element element-13">
188
- <th class="element element-14">Name</th>
189
- <th class="element element-15">Email</th>
178
+
179
+ <h1 class="element element-12">Contacts Table</h1>
180
+
181
+ <table class="element element-13">
182
+ <tr class="element element-14">
183
+ <th class="element element-15">Name</th>
184
+ <th class="element element-16">Email</th>
190
185
  </tr>
191
- <tr class="element element-16">
192
- <td class="element element-17">John Doe</td>
193
- <td class="element element-18">johndoe@example.com</td>
186
+
187
+ <tr class="element element-17">
188
+ <td class="element element-18">John Doe</td>
189
+ <td class="element element-19">johndoe@example.com</td>
194
190
  </tr>
195
- <tr class="element element-19">
196
- <td class="element element-20">Jane Doe</td>
197
- <td class="element element-21">janedoe@example.com</td>
191
+
192
+ <tr class="element element-20">
193
+ <td class="element element-21">Jane Doe</td>
194
+ <td class="element element-22">janedoe@example.com</td>
198
195
  </tr>
199
196
  </table>
200
- <style class="element element-22">.field-row {
201
- margin: 10px 5px;
202
- }
203
- .field {
204
- margin-left: 5px;
205
- }
206
- .submit-button {
207
- display: block;
208
- margin: 10px 5px;
209
- }
210
- table {
211
- border:1px solid grey;
212
- border-spacing: 0;
213
- }
214
- table tr td, table tr th {
215
- padding: 5px;
216
- }
217
- table tr:nth-child(even) {
218
- background: #ccc;
219
- }
197
+
198
+ <style class="element element-23">
199
+ input {
200
+ margin: 5px;
201
+ }
202
+ input[type=submit] {
203
+ margin: 5px 0;
204
+ }
205
+ table {
206
+ border:1px solid grey;
207
+ border-spacing: 0;
208
+ }
209
+ table tr td, table tr th {
210
+ padding: 5px;
211
+ }
212
+ table tr:nth-child(even) {
213
+ background: #ccc;
214
+ }
220
215
  </style>
221
216
  </div>
222
217
  ```
223
218
 
224
- Screenshots:
225
-
226
- ---
227
-
228
- ***Hello, Button!***
219
+ Screenshot:
229
220
 
230
- ![Hello, Button!](/images/glimmer-dsl-web-samples-hello-hello-button.png)
221
+ ![Hello, Form!](/images/glimmer-dsl-web-samples-hello-hello-form.gif)
231
222
 
232
- ---
223
+ **Hello, Data-Binding!**
233
224
 
234
- ***Hello, Button! Submitted Invalid Data***
225
+ Glimmer GUI code:
235
226
 
236
- ![Hello, Button! Invalid Data](/images/glimmer-dsl-web-samples-hello-hello-button-invalid-data.png)
227
+ ```ruby
228
+ require 'glimmer-dsl-web'
237
229
 
238
- ---
230
+ Address = Struct.new(:street, :street2, :city, :state, :zip_code, keyword_init: true) do
231
+ STATES = {...} # contains US States
232
+
233
+ def state_code
234
+ STATES.invert[state]
235
+ end
236
+
237
+ def state_code=(value)
238
+ self.state = STATES[value]
239
+ end
239
240
 
240
- ***Hello, Button! Filled Valid Name and Email***
241
+ def summary
242
+ values.map(&:to_s).reject(&:empty?).join(', ')
243
+ end
244
+ end
241
245
 
242
- ![Hello, Button! Filled](/images/glimmer-dsl-web-samples-hello-hello-button-filled-name-and-email.png)
246
+ @address = Address.new(
247
+ street: '123 Main St',
248
+ street2: 'Apartment 3C, 2nd door to the right',
249
+ city: 'San Diego',
250
+ state: 'California',
251
+ zip_code: '91911'
252
+ )
243
253
 
244
- ---
254
+ include Glimmer
245
255
 
246
- ***Hello, Button! Added Contact***
256
+ Document.ready? do
257
+ div {
258
+ form(style: 'display: grid; grid-auto-columns: 80px 200px;') { |address_form|
259
+ label('Street: ', for: 'street-field')
260
+ input(id: 'street-field') {
261
+ # Bidirectional Data-Binding with <=> ensures input.value and @address.street
262
+ # automatically stay in sync when either side changes
263
+ value <=> [@address, :street]
264
+ }
265
+
266
+ label('Street 2: ', for: 'street2-field')
267
+ textarea(id: 'street2-field') {
268
+ value <=> [@address, :street2]
269
+ }
270
+
271
+ label('City: ', for: 'city-field')
272
+ input(id: 'city-field') {
273
+ value <=> [@address, :city]
274
+ }
275
+
276
+ label('State: ', for: 'state-field')
277
+ select(id: 'state-field') {
278
+ Address::STATES.each do |state_code, state|
279
+ option(value: state_code) { state }
280
+ end
281
+
282
+ value <=> [@address, :state_code]
283
+ }
284
+
285
+ label('Zip Code: ', for: 'zip-code-field')
286
+ input(id: 'zip-code-field', type: 'number', min: '0', max: '99999') {
287
+ # Bidirectional Data-Binding with <=> ensures input.value and @address.zip_code
288
+ # automatically stay in sync when either side changes
289
+ # on_write option specifies :to_s method to invoke on value before writing to model attribute
290
+ # to ensure the numeric zip code value is stored as a String
291
+ value <=> [@address, :zip_code,
292
+ on_write: :to_s
293
+ ]
294
+ }
295
+
296
+ style {
297
+ <<~CSS
298
+ .#{address_form.element_id} * {
299
+ margin: 5px;
300
+ }
301
+ .#{address_form.element_id} input, .#{address_form.element_id} select {
302
+ grid-column: 2;
303
+ }
304
+ CSS
305
+ }
306
+ }
307
+
308
+ div(style: 'margin: 5px') {
309
+ # Unidirectional Data-Binding is done with <= to ensure @address.summary changes update div.inner_text
310
+ # as computed by changes to the address member attributes + state_code address custom attribute
311
+ inner_text <= [@address, :summary,
312
+ computed_by: @address.members + ['state_code']
313
+ ]
314
+ }
315
+ }.render
316
+ end
317
+ ```
247
318
 
248
- ![Hello, Button! Added Contact](/images/glimmer-dsl-web-samples-hello-hello-button-added-contact.png)
319
+ Screenshot:
249
320
 
250
- ---
321
+ ![Hello, Data-Binding!](/images/glimmer-dsl-web-samples-hello-hello-data-binding.gif)
251
322
 
252
323
  **Button Counter Sample**
253
324
 
@@ -279,7 +350,7 @@ class HelloButton
279
350
 
280
351
  markup {
281
352
  # This will hook into element #app-container and then build HTML inside it using Ruby DSL code
282
- div(root_css_selector) {
353
+ div(parent: parent_selector) {
283
354
  text 'Button Counter'
284
355
 
285
356
  button {
@@ -288,7 +359,7 @@ class HelloButton
288
359
  # copied to button innerText (content) to display to the user
289
360
  inner_text <= [@counter, :count, on_read: ->(value) { "Click To Increment: #{value} " }]
290
361
 
291
- on_click {
362
+ onclick {
292
363
  @counter.increment!
293
364
  }
294
365
  }
@@ -331,7 +402,7 @@ When clicked 7 times:
331
402
 
332
403
 
333
404
 
334
- NOTE: Glimmer DSL for Web is a pre-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.
405
+ 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.
335
406
 
336
407
  Learn more about the differences between various [Glimmer](https://github.com/AndyObtiva/glimmer) DSLs by looking at:
337
408
 
@@ -346,10 +417,13 @@ Learn more about the differences between various [Glimmer](https://github.com/An
346
417
  - [Setup](#setup)
347
418
  - [Usage](#usage)
348
419
  - [Supported Glimmer DSL Keywords](#supported-glimmer-dsl-keywords)
420
+ - [Coming from Glimmer DSL for Opal](#coming-from-glimmer-dsl-for-opal)
349
421
  - [Samples](#samples)
350
422
  - [Hello Samples](#hello-samples)
351
423
  - [Hello, World!](#hello-world)
352
424
  - [Hello, Button!](#hello-button)
425
+ - [Hello, Form!](#hello-form)
426
+ - [Hello, Data-Binding!](#hello-data-binding)
353
427
  - [Button Counter](#button-counter)
354
428
  - [Glimmer Process](#glimmer-process)
355
429
  - [Help](#help)
@@ -363,11 +437,12 @@ Learn more about the differences between various [Glimmer](https://github.com/An
363
437
 
364
438
  ## Prerequisites
365
439
 
440
+ [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.
441
+
366
442
  - Ruby 3.0 (newer Ruby versions are not supported at this time)
367
443
  - Rails 6-7: [https://github.com/rails/rails](https://github.com/rails/rails)
368
- - Opal 1.4.1 for Rails 6-7 or Opal 1.0.5 for Rails 5: [https://github.com/opal/opal](https://github.com/opal/opal)
369
- - Opal-Rails 2.0.2 for Rails 6-7 or Opal-Rails 1.1.2 for Rails 5: [https://github.com/opal/opal-rails](https://github.com/opal/opal-rails)
370
- - jQuery 3 (included): [https://code.jquery.com/](https://code.jquery.com/) (jQuery 3.6.0 is included in the [glimmer-dsl-web](https://rubygems.org/gems/glimmer-dsl-web) gem)
444
+ - Opal 1.4.1 for Rails 6-7: [https://github.com/opal/opal](https://github.com/opal/opal)
445
+ - Opal-Rails 2.0.2 for Rails 6-7: [https://github.com/opal/opal-rails](https://github.com/opal/opal-rails)
371
446
 
372
447
  ## Setup
373
448
 
@@ -398,7 +473,7 @@ gem 'opal', '1.4.1'
398
473
  gem 'opal-rails', '2.0.2'
399
474
  gem 'opal-async', '~> 1.4.0'
400
475
  gem 'opal-jquery', '~> 0.4.6'
401
- gem 'glimmer-dsl-web', '~> 0.0.4'
476
+ gem 'glimmer-dsl-web', '~> 0.0.6'
402
477
  gem 'glimmer-dsl-xml', '~> 1.3.1', require: false
403
478
  gem 'glimmer-dsl-css', '~> 1.2.1', require: false
404
479
  ```
@@ -543,7 +618,7 @@ gem 'opal', '1.4.1'
543
618
  gem 'opal-rails', '2.0.2'
544
619
  gem 'opal-async', '~> 1.4.0'
545
620
  gem 'opal-jquery', '~> 0.4.6'
546
- gem 'glimmer-dsl-web', '~> 0.0.4'
621
+ gem 'glimmer-dsl-web', '~> 0.0.6'
547
622
  gem 'glimmer-dsl-xml', '~> 1.3.1', require: false
548
623
  gem 'glimmer-dsl-css', '~> 1.2.1', require: false
549
624
  ```
@@ -669,21 +744,21 @@ Otherwise, if you still cannot setup successfully (even with the help of the sam
669
744
 
670
745
  Glimmer DSL for Web offers a GUI DSL for building HTML Web User Interfaces declaratively in Ruby.
671
746
 
672
- 1- Keywords (HTML Elements)
747
+ 1- **Keywords (HTML Elements)**
673
748
 
674
749
  You can declare any HTML element by simply using the lowercase underscored version of its name (Ruby convention for method names) like `div`, `span`, `form`, `input`, `button`, `table`, `tr`, `th`, and `td`.
675
750
 
676
751
  Under the hood, HTML element DSL keywords are invoked as Ruby methods.
677
752
 
678
- 2- Arguments (HTML Attributes + Text Content)
753
+ 2- **Arguments (HTML Attributes + Text Content)**
679
754
 
680
755
  You can set any HTML element attributes by passing as keyword arguments to element methods like `div(id: 'container', class: 'stack')` or `input(type: 'email', required: true)`
681
756
 
682
757
  Also, if the element has a little bit of text content that can fit in one line, it can be passed as the 1st argument like `label('Name: ', for: 'name_field')`, `button('Calculate', class: 'round-button')`, or `span('Mr')`
683
758
 
684
- 3- Content Block (Properties + Listeners + Nested Elements + Text Content)
759
+ 3- **Content Block (Properties + Listeners + Nested Elements + Text Content)**
685
760
 
686
- Element methods can accept a Ruby content block. It intentionally has a `{...}` style even as a multi-line block to indicate that the code is declarative GUI structure code.
761
+ Element methods can accept a Ruby content block. It intentionally has a `{...}` style even as a multi-line block to indicate that the code is declarative GUI structure code (intentionally breaking away from Ruby imperative code conventions given this is a declarative GUI DSL, meaning a different language that has its own conventions, embedded within Ruby).
687
762
 
688
763
  You can nest HTML element properties under an element like:
689
764
 
@@ -693,11 +768,11 @@ input(type: 'text') {
693
768
  }
694
769
  ```
695
770
 
696
- You can nest HTML event listeners under an element by using a more friendly underscored version of listener properties (e.g. `onclick` becomes `on_click`):
771
+ You can nest HTML event listeners under an element by using the HTML event listener name (e.g. `onclick`, `onchange`, `onblur`):
697
772
 
698
773
  ```ruby
699
774
  button('Add') {
700
- on_click do
775
+ onclick do
701
776
  @model.add_selected_element
702
777
  end
703
778
  }
@@ -718,26 +793,36 @@ form {
718
793
  input(id: 'email-field', class: 'field', type: 'email', required: true)
719
794
  }
720
795
  button('Add Contact', class: 'submit-button') {
721
- on_click do
796
+ onclick do
722
797
  ...
723
798
  end
724
799
  }
725
800
  }
726
801
  ```
727
802
 
728
- You can nest text content underneath an element's Ruby block, like:
803
+ You can nest text content underneath an element's Ruby block provided it is the return value of the block (last declared value), like:
729
804
 
730
805
  ```ruby
731
- span(class: 'summary') {
806
+ p(class: 'summary') {
732
807
  'This text content is going into the body of the span element'
733
808
  }
734
809
  ```
735
810
 
811
+ 4- **Operations (Properties + Functions)**
812
+
813
+ You can get/set any element property or invoke any element function by simply calling the lowercase underscored version of their name in Ruby like `input.check_validity`, `input.value`, and `input.id`.
814
+
736
815
  ## Supported Glimmer DSL Keywords
737
816
 
738
- [All HTML elements](https://developer.mozilla.org/en-US/docs/Web/HTML/Element).
817
+ [All HTML elements](https://developer.mozilla.org/en-US/docs/Web/HTML/Element), following the Ruby method name standard of lowercase and underscored names.
739
818
 
740
- [All HTML attributes](https://www.w3schools.com/html/html_attributes.asp).
819
+ [All HTML attributes](https://www.w3schools.com/html/html_attributes.asp), following the Ruby method name standard of lowercase and underscored names.
820
+
821
+ [All HTML events](https://www.w3schools.com/tags/ref_eventattributes.asp), same event attribute names as in HTML.
822
+
823
+ ## Coming from Glimmer DSL for Opal
824
+
825
+ This project is inspired by [Glimmer DSL for Opal](https://github.com/AndyObtiva/glimmer-dsl-opal) and is similar in enabling frontend GUI development with Ruby. [Glimmer DSL for Web](https://rubygems.org/gems/glimmer-dsl-web) mainly differs from Glimmer DSL for Opal by adopting a DSL that follows web-like HTML syntax in Ruby to facilitate leveraging existing HTML/CSS/JS skills instead of adopting a desktop GUI DSL that is webified. As a result, applications written in Glimmer DSL for Opal are not compatible with Glimmer DSL for Web.
741
826
 
742
827
  ## Samples
743
828
 
@@ -745,8 +830,6 @@ This external sample app contains all the samples mentioned below configured ins
745
830
 
746
831
  https://github.com/AndyObtiva/sample-glimmer-dsl-web-rails7-app
747
832
 
748
- **[NOT RELEASED OR SUPPORTED YET]** https://github.com/AndyObtiva/sample-glimmer-dsl-web-rails-app
749
-
750
833
  ### Hello Samples
751
834
 
752
835
  #### Hello, World!
@@ -775,7 +858,7 @@ That produces the following under `<body></body>`:
775
858
 
776
859
  ![setup is working](/images/glimmer-dsl-web-setup-example-working.png)
777
860
 
778
- Alternative syntax when an element only has text content:
861
+ Alternative syntax (useful when an element has text content that fits in one line):
779
862
 
780
863
  ```ruby
781
864
  require 'glimmer-dsl-web'
@@ -806,47 +889,85 @@ require 'glimmer-dsl-web'
806
889
 
807
890
  include Glimmer
808
891
 
892
+ Document.ready? do
893
+ div {
894
+ button('Greet') {
895
+ onclick do
896
+ $$.alert('Hello, Button!')
897
+ end
898
+ }
899
+ }.render
900
+ end
901
+ ```
902
+
903
+ That produces the following under `<body></body>`:
904
+
905
+ ```html
906
+ <div data-parent="body" class="element element-1">
907
+ <button class="element element-2">Greet</button>
908
+ </div>
909
+ ```
910
+
911
+ Screenshot:
912
+
913
+ ![Hello, Button!](/images/glimmer-dsl-web-samples-hello-hello-button.gif)
914
+
915
+ #### Hello, Form!
916
+
917
+ Glimmer GUI code:
918
+
919
+ ```ruby
920
+ require 'glimmer-dsl-web'
921
+
922
+ include Glimmer
923
+
809
924
  Document.ready? do
810
925
  div {
811
926
  h1('Contact Form')
927
+
812
928
  form {
813
- div(class: 'field-row') {
929
+ div {
814
930
  label('Name: ', for: 'name-field')
815
- @name_input = input(id: 'name-field', class: 'field', type: 'text', required: true)
931
+ @name_input = input(type: 'text', id: 'name-field', required: true, autofocus: true)
816
932
  }
817
- div(class: 'field-row') {
933
+
934
+ div {
818
935
  label('Email: ', for: 'email-field')
819
- @email_input = input(id: 'email-field', class: 'field', type: 'email', required: true)
936
+ @email_input = input(type: 'email', id: 'email-field', required: true)
820
937
  }
821
- button('Add Contact', class: 'submit-button') {
822
- on_click do
823
- if ([@name_input, @email_input].all? {|input| input.check_validity })
824
- @table.content {
825
- tr {
826
- td { @name_input.value }
827
- td { @email_input.value }
938
+
939
+ div {
940
+ input(type: 'submit', value: 'Add Contact') {
941
+ onclick do |event|
942
+ if ([@name_input, @email_input].all? {|input| input.check_validity })
943
+ # re-open table content and add row
944
+ @table.content {
945
+ tr {
946
+ td { @name_input.value }
947
+ td { @email_input.value }
948
+ }
828
949
  }
829
- }
830
- @email_input.value = @name_input.value = ''
831
- else
832
- error_messages = []
833
- error_messages << "Name is not valid! Make sure it is filled." if !@name_input.check_validity
834
- error_messages << "Email is not valid! Make sure it is filled and has a valid format." if !@email_input.check_validity
835
- $$.alert(error_messages.join("\n"))
950
+ @email_input.value = @name_input.value = ''
951
+ @name_input.focus
952
+ end
836
953
  end
837
- end
954
+ }
838
955
  }
839
956
  }
957
+
840
958
  h1('Contacts Table')
959
+
841
960
  @table = table {
842
961
  tr {
843
962
  th('Name')
844
963
  th('Email')
845
964
  }
965
+
846
966
  tr {
847
967
  td('John Doe')
848
968
  td('johndoe@example.com')
849
969
  }
970
+
850
971
  tr {
851
972
  td('Jane Doe')
852
973
  td('janedoe@example.com')
@@ -856,15 +977,11 @@ Document.ready? do
856
977
  # CSS Styles
857
978
  style {
858
979
  <<~CSS
859
- .field-row {
860
- margin: 10px 5px;
980
+ input {
981
+ margin: 5px;
861
982
  }
862
- .field {
863
- margin-left: 5px;
864
- }
865
- .submit-button {
866
- display: block;
867
- margin: 10px 5px;
983
+ input[type=submit] {
984
+ margin: 5px 0;
868
985
  }
869
986
  table {
870
987
  border:1px solid grey;
@@ -887,83 +1004,222 @@ That produces the following under `<body></body>`:
887
1004
  ```html
888
1005
  <div data-parent="body" class="element element-1">
889
1006
  <h1 class="element element-2">Contact Form</h1>
1007
+
890
1008
  <form class="element element-3">
891
- <div class="field-row element element-4">
1009
+ <div class="element element-4">
892
1010
  <label for="name-field" class="element element-5">Name: </label>
893
- <input id="name-field" class="field element element-6" type="text" required="true">
1011
+ <input type="text" id="name-field" required="true" autofocus="true" class="element element-6">
894
1012
  </div>
895
- <div class="field-row element element-7">
1013
+
1014
+ <div class="element element-7">
896
1015
  <label for="email-field" class="element element-8">Email: </label>
897
- <input id="email-field" class="field element element-9" type="email" required="true">
1016
+ <input type="email" id="email-field" required="true" class="element element-9">
1017
+ </div>
1018
+
1019
+ <div class="element element-10">
1020
+ <input type="submit" value="Add Contact" class="element element-11">
898
1021
  </div>
899
- <button class="submit-button element element-10">Add Contact</button>
900
1022
  </form>
901
- <h1 class="element element-11">Contacts Table</h1>
902
- <table class="element element-12">
903
- <tr class="element element-13">
904
- <th class="element element-14">Name</th>
905
- <th class="element element-15">Email</th>
1023
+
1024
+ <h1 class="element element-12">Contacts Table</h1>
1025
+
1026
+ <table class="element element-13">
1027
+ <tr class="element element-14">
1028
+ <th class="element element-15">Name</th>
1029
+ <th class="element element-16">Email</th>
906
1030
  </tr>
907
- <tr class="element element-16">
908
- <td class="element element-17">John Doe</td>
909
- <td class="element element-18">johndoe@example.com</td>
1031
+
1032
+ <tr class="element element-17">
1033
+ <td class="element element-18">John Doe</td>
1034
+ <td class="element element-19">johndoe@example.com</td>
910
1035
  </tr>
911
- <tr class="element element-19">
912
- <td class="element element-20">Jane Doe</td>
913
- <td class="element element-21">janedoe@example.com</td>
1036
+
1037
+ <tr class="element element-20">
1038
+ <td class="element element-21">Jane Doe</td>
1039
+ <td class="element element-22">janedoe@example.com</td>
914
1040
  </tr>
915
1041
  </table>
916
- <style class="element element-22">.field-row {
917
- margin: 10px 5px;
918
- }
919
- .field {
920
- margin-left: 5px;
921
- }
922
- .submit-button {
923
- display: block;
924
- margin: 10px 5px;
925
- }
926
- table {
927
- border:1px solid grey;
928
- border-spacing: 0;
929
- }
930
- table tr td, table tr th {
931
- padding: 5px;
932
- }
933
- table tr:nth-child(even) {
934
- background: #ccc;
935
- }
1042
+
1043
+ <style class="element element-23">
1044
+ input {
1045
+ margin: 5px;
1046
+ }
1047
+ input[type=submit] {
1048
+ margin: 5px 0;
1049
+ }
1050
+ table {
1051
+ border:1px solid grey;
1052
+ border-spacing: 0;
1053
+ }
1054
+ table tr td, table tr th {
1055
+ padding: 5px;
1056
+ }
1057
+ table tr:nth-child(even) {
1058
+ background: #ccc;
1059
+ }
936
1060
  </style>
937
1061
  </div>
938
1062
  ```
939
1063
 
940
- Screenshots:
941
-
942
- ---
1064
+ Screenshot:
943
1065
 
944
- ***Hello, Button!***
1066
+ ![Hello, Form!](/images/glimmer-dsl-web-samples-hello-hello-form.gif)
945
1067
 
946
- ![Hello, Button!](/images/glimmer-dsl-web-samples-hello-hello-button.png)
1068
+ #### Hello, Data-Binding!
947
1069
 
948
- ---
949
-
950
- ***Hello, Button! Submitted Invalid Data***
1070
+ Glimmer GUI code:
951
1071
 
952
- ![Hello, Button! Invalid Data](/images/glimmer-dsl-web-samples-hello-hello-button-invalid-data.png)
1072
+ ```ruby
1073
+ require 'glimmer-dsl-web'
953
1074
 
954
- ---
1075
+ Address = Struct.new(:street, :street2, :city, :state, :zip_code, keyword_init: true) do
1076
+ STATES = {
1077
+ "AK"=>"Alaska",
1078
+ "AL"=>"Alabama",
1079
+ "AR"=>"Arkansas",
1080
+ "AS"=>"American Samoa",
1081
+ "AZ"=>"Arizona",
1082
+ "CA"=>"California",
1083
+ "CO"=>"Colorado",
1084
+ "CT"=>"Connecticut",
1085
+ "DC"=>"District of Columbia",
1086
+ "DE"=>"Delaware",
1087
+ "FL"=>"Florida",
1088
+ "GA"=>"Georgia",
1089
+ "GU"=>"Guam",
1090
+ "HI"=>"Hawaii",
1091
+ "IA"=>"Iowa",
1092
+ "ID"=>"Idaho",
1093
+ "IL"=>"Illinois",
1094
+ "IN"=>"Indiana",
1095
+ "KS"=>"Kansas",
1096
+ "KY"=>"Kentucky",
1097
+ "LA"=>"Louisiana",
1098
+ "MA"=>"Massachusetts",
1099
+ "MD"=>"Maryland",
1100
+ "ME"=>"Maine",
1101
+ "MI"=>"Michigan",
1102
+ "MN"=>"Minnesota",
1103
+ "MO"=>"Missouri",
1104
+ "MS"=>"Mississippi",
1105
+ "MT"=>"Montana",
1106
+ "NC"=>"North Carolina",
1107
+ "ND"=>"North Dakota",
1108
+ "NE"=>"Nebraska",
1109
+ "NH"=>"New Hampshire",
1110
+ "NJ"=>"New Jersey",
1111
+ "NM"=>"New Mexico",
1112
+ "NV"=>"Nevada",
1113
+ "NY"=>"New York",
1114
+ "OH"=>"Ohio",
1115
+ "OK"=>"Oklahoma",
1116
+ "OR"=>"Oregon",
1117
+ "PA"=>"Pennsylvania",
1118
+ "PR"=>"Puerto Rico",
1119
+ "RI"=>"Rhode Island",
1120
+ "SC"=>"South Carolina",
1121
+ "SD"=>"South Dakota",
1122
+ "TN"=>"Tennessee",
1123
+ "TX"=>"Texas",
1124
+ "UT"=>"Utah",
1125
+ "VA"=>"Virginia",
1126
+ "VI"=>"Virgin Islands",
1127
+ "VT"=>"Vermont",
1128
+ "WA"=>"Washington",
1129
+ "WI"=>"Wisconsin",
1130
+ "WV"=>"West Virginia",
1131
+ "WY"=>"Wyoming"
1132
+ }
1133
+
1134
+ def state_code
1135
+ STATES.invert[state]
1136
+ end
1137
+
1138
+ def state_code=(value)
1139
+ self.state = STATES[value]
1140
+ end
955
1141
 
956
- ***Hello, Button! Filled Valid Name and Email***
1142
+ def summary
1143
+ values.map(&:to_s).reject(&:empty?).join(', ')
1144
+ end
1145
+ end
957
1146
 
958
- ![Hello, Button! Filled](/images/glimmer-dsl-web-samples-hello-hello-button-filled-name-and-email.png)
1147
+ @address = Address.new(
1148
+ street: '123 Main St',
1149
+ street2: 'Apartment 3C, 2nd door to the right',
1150
+ city: 'San Diego',
1151
+ state: 'California',
1152
+ zip_code: '91911'
1153
+ )
959
1154
 
960
- ---
1155
+ include Glimmer
961
1156
 
962
- ***Hello, Button! Added Contact***
1157
+ Document.ready? do
1158
+ div {
1159
+ form(style: 'display: grid; grid-auto-columns: 80px 200px;') { |address_form|
1160
+ label('Street: ', for: 'street-field')
1161
+ input(id: 'street-field') {
1162
+ # Bidirectional Data-Binding with <=> ensures input.value and @address.street
1163
+ # automatically stay in sync when either side changes
1164
+ value <=> [@address, :street]
1165
+ }
1166
+
1167
+ label('Street 2: ', for: 'street2-field')
1168
+ textarea(id: 'street2-field') {
1169
+ value <=> [@address, :street2]
1170
+ }
1171
+
1172
+ label('City: ', for: 'city-field')
1173
+ input(id: 'city-field') {
1174
+ value <=> [@address, :city]
1175
+ }
1176
+
1177
+ label('State: ', for: 'state-field')
1178
+ select(id: 'state-field') {
1179
+ Address::STATES.each do |state_code, state|
1180
+ option(value: state_code) { state }
1181
+ end
1182
+
1183
+ value <=> [@address, :state_code]
1184
+ }
1185
+
1186
+ label('Zip Code: ', for: 'zip-code-field')
1187
+ input(id: 'zip-code-field', type: 'number', min: '0', max: '99999') {
1188
+ # Bidirectional Data-Binding with <=> ensures input.value and @address.zip_code
1189
+ # automatically stay in sync when either side changes
1190
+ # on_write option specifies :to_s method to invoke on value before writing to model attribute
1191
+ # to ensure the numeric zip code value is stored as a String
1192
+ value <=> [@address, :zip_code,
1193
+ on_write: :to_s
1194
+ ]
1195
+ }
1196
+
1197
+ style {
1198
+ <<~CSS
1199
+ .#{address_form.element_id} * {
1200
+ margin: 5px;
1201
+ }
1202
+ .#{address_form.element_id} input, .#{address_form.element_id} select {
1203
+ grid-column: 2;
1204
+ }
1205
+ CSS
1206
+ }
1207
+ }
1208
+
1209
+ div(style: 'margin: 5px') {
1210
+ # Unidirectional Data-Binding is done with <= to ensure @address.summary changes update div.inner_text
1211
+ # as computed by changes to the address member attributes + state_code address custom attribute
1212
+ inner_text <= [@address, :summary,
1213
+ computed_by: @address.members + ['state_code']
1214
+ ]
1215
+ }
1216
+ }.render
1217
+ end
1218
+ ```
963
1219
 
964
- ![Hello, Button! Added Contact](/images/glimmer-dsl-web-samples-hello-hello-button-added-contact.png)
1220
+ Screenshot:
965
1221
 
966
- ---
1222
+ ![Hello, Data-Binding!](/images/glimmer-dsl-web-samples-hello-hello-data-binding.gif)
967
1223
 
968
1224
  #### Button Counter
969
1225
 
@@ -995,7 +1251,7 @@ class HelloButton
995
1251
 
996
1252
  markup {
997
1253
  # This will hook into element #app-container and then build HTML inside it using Ruby DSL code
998
- div(root_css_selector) {
1254
+ div(parent: parent_selector) {
999
1255
  text 'Button Counter'
1000
1256
 
1001
1257
  button {
@@ -1004,7 +1260,7 @@ class HelloButton
1004
1260
  # copied to button innerText (content) to display to the user
1005
1261
  inner_text <= [@counter, :count, on_read: ->(value) { "Click To Increment: #{value} " }]
1006
1262
 
1007
- on_click {
1263
+ onclick {
1008
1264
  @counter.increment!
1009
1265
  }
1010
1266
  }