glimmer-dsl-web 0.0.7 → 0.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2615511253319f0227b782825764b4b009c0a3d4072b366d7409a3255f653d47
4
- data.tar.gz: 0d09f5a38cd26cd6f8edad82aab884b89178ffa79043e5e7dc88fbd19f2e5958
3
+ metadata.gz: '028be0c05ee74f8e639b3b43adbdd52e7b90e7e5b52a0e7026fbf74e0d017300'
4
+ data.tar.gz: d2b8e3fc639dfa55655f05faf740fd36fcf518bc143eb8f08a874be3e34f72ec
5
5
  SHA512:
6
- metadata.gz: 54ce5174ba09451931424f9c7d30b4abc7aebfd8486ecb6fe0863ece4a4747c908c9445753cc68e535bfb90835d1ad1951fde8f31f9fe2e90f34980754b4ecbf
7
- data.tar.gz: d654378ed6fa4c7d05322c09054a44039d83ef2d4dcecc048963b2dc31c723a68c679072f71da2a1984ba94cd4630a382d363928ca57315ca673ae33dfe62d59
6
+ metadata.gz: e9a0add34e7db5cd7d0816e0006ed0426f9bc739402e4951f672195f6dcce472943b5f5c501213269b0ec84fb636aaea701fe35326639827bd580b94db370a5a
7
+ data.tar.gz: 19ccf1280735e472c6f643a21e105f0b1020fdacc53a66f63588be6f7b8faddb1e8391667094687a204c3d1363ee760c6b752c84866b86323437c17b8fdbe7d6
data/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # Change Log
2
2
 
3
+ ## 0.0.8
4
+
5
+ - Validate that element keywords belong to the valid list of HTML element tag names to prevent rendering fake elements the keyword does not match a real HTML element tag
6
+ - Support inner_html/outer_html properties (it seems they did not convert to innerHTML/outerHTML correctly because of the all caps HTML)
7
+ - Support element properties via original JS names (e.g. innerHTML) to prevent logic from breaking if some do not convert correctly from underscored versions
8
+ - Package dependencies properly in the gem to avoid instructing users to add in Rails Gemfile manually
9
+ - Content Data-Binding support to regenerate element content based on changes to an observed model attribute.
10
+ Content Data-Binding Example:
11
+ content(*data_binding_options) { |data_binding_value|
12
+ li {
13
+ data_binding_value
14
+ }
15
+ }
16
+ - Ensuring removing data-binding model listeners when calling `element#remove` in addition to already removing standard HTML event listeners
17
+ - New Hello, Content Data-Binding! Sample: `require 'glimmer-dsl-web/samples/hello/hello_content_data_binding'`
18
+
3
19
  ## 0.0.7
4
20
 
5
21
  - Support input[type=number] value data-binding as a Ruby Numeric object (Integer or Float)
data/README.md CHANGED
@@ -1,9 +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.7 (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.8 (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
- [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, most intuitive, and most straight-forward 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)!
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, most intuitive, most straight-forward, and most productive 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 `<=>`). Dynamic rendering (and re-rendering) of HTML content is also supported via Content Data-Binding. 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
+
8
+ (the project plans to add component support very soon, albeit components are already supported by creating your own Ruby classes and having them render part of the GUI hierarchy)
7
9
 
8
10
  **Hello, World! Sample**
9
11
 
@@ -337,6 +339,137 @@ Screenshot:
337
339
 
338
340
  ![Hello, Data-Binding!](/images/glimmer-dsl-web-samples-hello-hello-data-binding.gif)
339
341
 
342
+ **Hello, Content Data-Binding!**
343
+
344
+ If you need to regenerate HTML element content dynamically, you can use Content Data-Binding to effortlessly
345
+ rebuild HTML elements based on changes in a Model attribute that provides the source data.
346
+ In this example, we generate multiple address forms based on the number of addresses the user has.
347
+
348
+ Glimmer GUI code:
349
+
350
+ ```ruby
351
+ require 'glimmer-dsl-web'
352
+
353
+ class Address
354
+ attr_accessor :text
355
+ attr_reader :name, :street, :city, :state, :zip
356
+
357
+ def name=(value)
358
+ @name = value
359
+ update_text
360
+ end
361
+
362
+ def street=(value)
363
+ @street = value
364
+ update_text
365
+ end
366
+
367
+ def city=(value)
368
+ @city = value
369
+ update_text
370
+ end
371
+
372
+ def state=(value)
373
+ @state = value
374
+ update_text
375
+ end
376
+
377
+ def zip=(value)
378
+ @zip = value
379
+ update_text
380
+ end
381
+
382
+ private
383
+
384
+ def update_text
385
+ self.text = [name, street, city, state, zip].compact.reject(&:empty?).join(', ')
386
+ end
387
+ end
388
+
389
+ class User
390
+ attr_accessor :addresses
391
+ attr_reader :address_count
392
+
393
+ def initialize
394
+ @address_count = 1
395
+ @addresses = []
396
+ update_addresses
397
+ end
398
+
399
+ def address_count=(value)
400
+ value = [[1, value.to_i].max, 3].min
401
+ @address_count = value
402
+ update_addresses
403
+ end
404
+
405
+ private
406
+
407
+ def update_addresses
408
+ address_count_change = address_count - addresses.size
409
+ if address_count_change > 0
410
+ address_count_change.times { addresses << Address.new }
411
+ else
412
+ address_count_change.abs.times { addresses.pop }
413
+ end
414
+ end
415
+ end
416
+
417
+ @user = User.new
418
+
419
+ div {
420
+ div {
421
+ label('Number of addresses: ', for: 'address-count-field')
422
+ input(id: 'address-count-field', type: 'number', min: 1, max: 3) {
423
+ value <=> [@user, :address_count]
424
+ }
425
+ }
426
+
427
+ div {
428
+ # Content Data-Binding is used to dynamically (re)generate content of div
429
+ # based on changes to @user.addresses, replacing older content on every change
430
+ content(@user, :addresses) do
431
+ @user.addresses.each do |address|
432
+ div {
433
+ div(style: 'display: grid; grid-auto-columns: 80px 280px;') { |address_div|
434
+ [:name, :street, :city, :state, :zip].each do |attribute|
435
+ label(attribute.to_s.capitalize, for: "#{attribute}-field")
436
+ input(id: "#{attribute}-field", type: 'text') {
437
+ value <=> [address, attribute]
438
+ }
439
+ end
440
+
441
+ div(style: 'grid-column: 1 / span 2;') {
442
+ inner_text <= [address, :text]
443
+ }
444
+
445
+ style {
446
+ <<~CSS
447
+ #{address_div.selector} {
448
+ margin: 10px 0;
449
+ }
450
+ #{address_div.selector} * {
451
+ margin: 5px;
452
+ }
453
+ #{address_div.selector} label {
454
+ grid-column: 1;
455
+ }
456
+ #{address_div.selector} input, #{address_div.selector} select {
457
+ grid-column: 2;
458
+ }
459
+ CSS
460
+ }
461
+ }
462
+ }
463
+ end
464
+ end
465
+ }
466
+ }.render
467
+ ```
468
+
469
+ Screenshot:
470
+
471
+ ![Hello, Content Data-Binding!](/images/glimmer-dsl-web-samples-hello-hello-content-data-binding.gif)
472
+
340
473
  **Button Counter Sample**
341
474
 
342
475
  **UPCOMING (NOT RELEASED OR SUPPORTED YET)**
@@ -441,6 +574,7 @@ Learn more about the differences between various [Glimmer](https://github.com/An
441
574
  - [Hello, Button!](#hello-button)
442
575
  - [Hello, Form!](#hello-form)
443
576
  - [Hello, Data-Binding!](#hello-data-binding)
577
+ - [Hello, Content Data-Binding!](#hello-content-data-binding)
444
578
  - [Hello, Input (Date/Time)!](#hello-input-datetime)
445
579
  - [Button Counter](#button-counter)
446
580
  - [Glimmer Process](#glimmer-process)
@@ -487,13 +621,7 @@ rails new glimmer_app_server
487
621
  Add the following to `Gemfile`:
488
622
 
489
623
  ```
490
- gem 'opal', '1.4.1'
491
- gem 'opal-rails', '2.0.2'
492
- gem 'opal-async', '~> 1.4.0'
493
- gem 'opal-jquery', '~> 0.4.6'
494
- gem 'glimmer-dsl-web', '~> 0.0.7'
495
- gem 'glimmer-dsl-xml', '~> 1.3.1', require: false
496
- gem 'glimmer-dsl-css', '~> 1.2.1', require: false
624
+ gem 'glimmer-dsl-web', '~> 0.0.8'
497
625
  ```
498
626
 
499
627
  Run:
@@ -632,13 +760,7 @@ Disable the `webpacker` gem line in `Gemfile`:
632
760
  Add the following to `Gemfile`:
633
761
 
634
762
  ```ruby
635
- gem 'opal', '1.4.1'
636
- gem 'opal-rails', '2.0.2'
637
- gem 'opal-async', '~> 1.4.0'
638
- gem 'opal-jquery', '~> 0.4.6'
639
- gem 'glimmer-dsl-web', '~> 0.0.7'
640
- gem 'glimmer-dsl-xml', '~> 1.3.1', require: false
641
- gem 'glimmer-dsl-css', '~> 1.2.1', require: false
763
+ gem 'glimmer-dsl-web', '~> 0.0.8'
642
764
  ```
643
765
 
644
766
  Run:
@@ -1254,6 +1376,133 @@ Screenshot:
1254
1376
 
1255
1377
  ![Hello, Data-Binding!](/images/glimmer-dsl-web-samples-hello-hello-data-binding.gif)
1256
1378
 
1379
+ #### Hello, Content Data-Binding!
1380
+
1381
+ Glimmer GUI code:
1382
+
1383
+ ```ruby
1384
+ require 'glimmer-dsl-web'
1385
+
1386
+ class Address
1387
+ attr_accessor :text
1388
+ attr_reader :name, :street, :city, :state, :zip
1389
+
1390
+ def name=(value)
1391
+ @name = value
1392
+ update_text
1393
+ end
1394
+
1395
+ def street=(value)
1396
+ @street = value
1397
+ update_text
1398
+ end
1399
+
1400
+ def city=(value)
1401
+ @city = value
1402
+ update_text
1403
+ end
1404
+
1405
+ def state=(value)
1406
+ @state = value
1407
+ update_text
1408
+ end
1409
+
1410
+ def zip=(value)
1411
+ @zip = value
1412
+ update_text
1413
+ end
1414
+
1415
+ private
1416
+
1417
+ def update_text
1418
+ self.text = [name, street, city, state, zip].compact.reject(&:empty?).join(', ')
1419
+ end
1420
+ end
1421
+
1422
+ class User
1423
+ attr_accessor :addresses
1424
+ attr_reader :address_count
1425
+
1426
+ def initialize
1427
+ @address_count = 1
1428
+ @addresses = []
1429
+ update_addresses
1430
+ end
1431
+
1432
+ def address_count=(value)
1433
+ value = [[1, value.to_i].max, 3].min
1434
+ @address_count = value
1435
+ update_addresses
1436
+ end
1437
+
1438
+ private
1439
+
1440
+ def update_addresses
1441
+ address_count_change = address_count - addresses.size
1442
+ if address_count_change > 0
1443
+ address_count_change.times { addresses << Address.new }
1444
+ else
1445
+ address_count_change.abs.times { addresses.pop }
1446
+ end
1447
+ end
1448
+ end
1449
+
1450
+ @user = User.new
1451
+
1452
+ div {
1453
+ div {
1454
+ label('Number of addresses: ', for: 'address-count-field')
1455
+ input(id: 'address-count-field', type: 'number', min: 1, max: 3) {
1456
+ value <=> [@user, :address_count]
1457
+ }
1458
+ }
1459
+
1460
+ div {
1461
+ # Content Data-Binding is used to dynamically (re)generate content of div
1462
+ # based on changes to @user.addresses, replacing older content on every change
1463
+ content(@user, :addresses) do
1464
+ @user.addresses.each do |address|
1465
+ div {
1466
+ div(style: 'display: grid; grid-auto-columns: 80px 280px;') { |address_div|
1467
+ [:name, :street, :city, :state, :zip].each do |attribute|
1468
+ label(attribute.to_s.capitalize, for: "#{attribute}-field")
1469
+ input(id: "#{attribute}-field", type: 'text') {
1470
+ value <=> [address, attribute]
1471
+ }
1472
+ end
1473
+
1474
+ div(style: 'grid-column: 1 / span 2;') {
1475
+ inner_text <= [address, :text]
1476
+ }
1477
+
1478
+ style {
1479
+ <<~CSS
1480
+ #{address_div.selector} {
1481
+ margin: 10px 0;
1482
+ }
1483
+ #{address_div.selector} * {
1484
+ margin: 5px;
1485
+ }
1486
+ #{address_div.selector} label {
1487
+ grid-column: 1;
1488
+ }
1489
+ #{address_div.selector} input, #{address_div.selector} select {
1490
+ grid-column: 2;
1491
+ }
1492
+ CSS
1493
+ }
1494
+ }
1495
+ }
1496
+ end
1497
+ end
1498
+ }
1499
+ }.render
1500
+ ```
1501
+
1502
+ Screenshot:
1503
+
1504
+ ![Hello, Content Data-Binding!](/images/glimmer-dsl-web-samples-hello-hello-content-data-binding.gif)
1505
+
1257
1506
  #### Hello, Input (Date/Time)!
1258
1507
 
1259
1508
  Glimmer GUI code:
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.7
1
+ 0.0.8
@@ -2,16 +2,16 @@
2
2
  # DO NOT EDIT THIS FILE DIRECTLY
3
3
  # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
4
  # -*- encoding: utf-8 -*-
5
- # stub: glimmer-dsl-web 0.0.7 ruby lib
5
+ # stub: glimmer-dsl-web 0.0.8 ruby lib
6
6
 
7
7
  Gem::Specification.new do |s|
8
8
  s.name = "glimmer-dsl-web".freeze
9
- s.version = "0.0.7".freeze
9
+ s.version = "0.0.8".freeze
10
10
 
11
11
  s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version=
12
12
  s.require_paths = ["lib".freeze]
13
13
  s.authors = ["Andy Maleh".freeze]
14
- s.date = "2024-01-02"
14
+ s.date = "2024-01-03"
15
15
  s.description = "Glimmer DSL for Web (Ruby in the Browser Web GUI Frontend Library) - Enables frontend GUI development with Ruby by adopting a DSL that follows web-like HTML syntax, enabling the transfer of HTML/CSS/JS skills to Ruby frontend development. This library relies on Opal Ruby.".freeze
16
16
  s.email = "andy.am@gmail.com".freeze
17
17
  s.extra_rdoc_files = [
@@ -31,6 +31,7 @@ Gem::Specification.new do |s|
31
31
  "lib/glimmer-dsl-web/ext/date.rb",
32
32
  "lib/glimmer-dsl-web/ext/exception.rb",
33
33
  "lib/glimmer-dsl-web/samples/hello/hello_button.rb",
34
+ "lib/glimmer-dsl-web/samples/hello/hello_content_data_binding.rb",
34
35
  "lib/glimmer-dsl-web/samples/hello/hello_data_binding.rb",
35
36
  "lib/glimmer-dsl-web/samples/hello/hello_form.rb",
36
37
  "lib/glimmer-dsl-web/samples/hello/hello_input_date_time.rb",
@@ -39,6 +40,7 @@ Gem::Specification.new do |s|
39
40
  "lib/glimmer/config/opal_logger.rb",
40
41
  "lib/glimmer/data_binding/element_binding.rb",
41
42
  "lib/glimmer/dsl/web/bind_expression.rb",
43
+ "lib/glimmer/dsl/web/content_data_binding_expression.rb",
42
44
  "lib/glimmer/dsl/web/data_binding_expression.rb",
43
45
  "lib/glimmer/dsl/web/dsl.rb",
44
46
  "lib/glimmer/dsl/web/element_expression.rb",
@@ -61,10 +63,13 @@ Gem::Specification.new do |s|
61
63
 
62
64
  s.specification_version = 4
63
65
 
64
- s.add_runtime_dependency(%q<glimmer>.freeze, ["~> 2.4.1".freeze])
66
+ s.add_runtime_dependency(%q<glimmer>.freeze, ["~> 2.7.6".freeze])
65
67
  s.add_runtime_dependency(%q<glimmer-dsl-xml>.freeze, ["~> 1.3.2".freeze])
66
68
  s.add_runtime_dependency(%q<glimmer-dsl-css>.freeze, ["~> 1.2.2".freeze])
69
+ s.add_runtime_dependency(%q<opal>.freeze, ["= 1.4.1".freeze])
70
+ s.add_runtime_dependency(%q<opal-rails>.freeze, ["= 2.0.2".freeze])
67
71
  s.add_runtime_dependency(%q<opal-async>.freeze, ["~> 1.4.0".freeze])
72
+ s.add_runtime_dependency(%q<opal-jquery>.freeze, ["~> 0.4.6".freeze])
68
73
  s.add_runtime_dependency(%q<to_collection>.freeze, [">= 2.0.1".freeze, "< 3.0.0".freeze])
69
74
  s.add_development_dependency(%q<puts_debuggerer>.freeze, [">= 0".freeze])
70
75
  s.add_development_dependency(%q<rake>.freeze, [">= 10.1.0".freeze, "< 14.0.0".freeze])
@@ -72,7 +77,5 @@ Gem::Specification.new do |s|
72
77
  s.add_development_dependency(%q<jeweler>.freeze, [">= 2.3.9".freeze, "< 3.0.0".freeze])
73
78
  s.add_development_dependency(%q<rdoc>.freeze, [">= 6.2.1".freeze, "< 7.0.0".freeze])
74
79
  s.add_development_dependency(%q<opal-rspec>.freeze, ["~> 0.8.0.alpha2".freeze])
75
- s.add_development_dependency(%q<opal-rails>.freeze, ["~> 1.1.2".freeze])
76
- s.add_development_dependency(%q<opal-jquery>.freeze, ["~> 0.4.4".freeze])
77
80
  end
78
81
 
@@ -0,0 +1,41 @@
1
+ # Copyright (c) 2023-2024 Andy Maleh
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining
4
+ # a copy of this software and associated documentation files (the
5
+ # "Software"), to deal in the Software without restriction, including
6
+ # without limitation the rights to use, copy, modify, merge, publish,
7
+ # distribute, sublicense, and/or sell copies of the Software, and to
8
+ # permit persons to whom the Software is furnished to do so, subject to
9
+ # the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be
12
+ # included in all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
+
22
+ require 'glimmer/dsl/expression'
23
+
24
+ module Glimmer
25
+ module DSL
26
+ module Web
27
+ class ContentDataBindingExpression < Expression
28
+ def can_interpret?(parent, keyword, *args, &block)
29
+ keyword == 'content' &&
30
+ block_given? &&
31
+ args.size > 0 &&
32
+ parent&.respond_to?(:bind_content)
33
+ end
34
+
35
+ def interpret(parent, keyword, *args, &block)
36
+ parent.bind_content(*args, &block)
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -1,5 +1,4 @@
1
1
  require 'glimmer/dsl/engine'
2
- # Dir[File.expand_path('../*_expression.rb', __FILE__)].each {|f| require f}
3
2
  require 'glimmer/dsl/web/element_expression'
4
3
  require 'glimmer/dsl/web/listener_expression'
5
4
  require 'glimmer/dsl/web/property_expression'
@@ -7,25 +6,19 @@ require 'glimmer/dsl/web/p_expression'
7
6
  require 'glimmer/dsl/web/select_expression'
8
7
  require 'glimmer/dsl/web/bind_expression'
9
8
  require 'glimmer/dsl/web/data_binding_expression'
9
+ require 'glimmer/dsl/web/content_data_binding_expression'
10
10
  require 'glimmer/dsl/web/shine_data_binding_expression'
11
11
 
12
12
  module Glimmer
13
13
  module DSL
14
14
  module Web
15
- # TODO implement all those expressions
16
- # %w[
17
- # listener
18
- # data_binding
19
- # property
20
- # shine_data_binding
21
- # element
22
- # ]
23
15
  Engine.add_dynamic_expressions(
24
16
  Web,
25
17
  %w[
26
18
  listener
27
19
  data_binding
28
20
  property
21
+ content_data_binding
29
22
  shine_data_binding
30
23
  element
31
24
  ]
@@ -1,6 +1,8 @@
1
1
  require 'glimmer/dsl/expression'
2
2
  require 'glimmer/dsl/web/general_element_expression'
3
3
 
4
+ require 'glimmer/web/element_proxy'
5
+
4
6
  module Glimmer
5
7
  module DSL
6
8
  module Web
@@ -8,9 +10,7 @@ module Glimmer
8
10
  include GeneralElementExpression
9
11
 
10
12
  def can_interpret?(parent, keyword, *args, &block)
11
- # TODO automatically pass parent option as element if not passed instead of rejecting elements without a paraent nor root
12
- # TODO raise a proper error if root is an element that is not found (maybe do this in model)
13
- !keyword.to_s.start_with?('on')
13
+ Glimmer::Web::ElementProxy.keyword_supported?(keyword)
14
14
  end
15
15
  end
16
16
  end
@@ -25,6 +25,10 @@ module Glimmer
25
25
  module Web
26
26
  class ElementProxy
27
27
  class << self
28
+ def keyword_supported?(keyword)
29
+ ELEMENT_KEYWORDS.include?(keyword.to_s)
30
+ end
31
+
28
32
  # Factory Method that translates a Glimmer DSL keyword into a ElementProxy object
29
33
  def for(keyword, parent, args, block)
30
34
  element_type(keyword).new(keyword, parent, args, block)
@@ -67,7 +71,28 @@ module Glimmer
67
71
 
68
72
  Event = Struct.new(:widget, keyword_init: true)
69
73
 
74
+ ELEMENT_KEYWORDS = [
75
+ "a", "abbr", "acronym", "address", "applet", "area", "article", "aside", "audio", "b",
76
+ "base", "basefont", "bdi", "bdo", "bgsound", "big", "blink", "blockquote", "body", "br",
77
+ "button", "canvas", "caption", "center", "cite", "code", "col", "colgroup", "content", "data",
78
+ "datalist", "dd", "decorator", "del", "details", "dfn", "dir", "div", "dl", "dt",
79
+ "element", "em", "embed", "fieldset", "figcaption", "figure", "font", "footer", "form", "frame",
80
+ "frameset", "h1", "h2", "h3", "h4", "h5", "h6", "head", "header", "hgroup",
81
+ "hr", "html", "i", "iframe", "img", "input", "ins", "isindex", "kbd", "keygen",
82
+ "label", "legend", "li", "link", "listing", "main", "map", "mark", "marquee", "menu",
83
+ "menuitem", "meta", "meter", "nav", "nobr", "noframes", "noscript", "object", "ol", "optgroup",
84
+ "option", "output", "p", "param", "plaintext", "pre", "progress", "q", "rp", "rt",
85
+ "ruby", "s", "samp", "script", "section", "select", "shadow", "small", "source", "spacer",
86
+ "span", "strike", "strong", "style", "sub", "summary", "sup", "table", "tbody", "td",
87
+ "template", "textarea", "tfoot", "th", "thead", "time", "title", "tr", "track", "tt",
88
+ "u", "ul", "var", "video", "wbr", "xmp",
89
+ ]
90
+
70
91
  GLIMMER_ATTRIBUTES = [:parent]
92
+ PROPERTY_ALIASES = {
93
+ 'inner_html' => 'innerHTML',
94
+ 'outer_html' => 'outerHTML',
95
+ }
71
96
  FORMAT_DATETIME = '%Y-%m-%dT%H:%M'
72
97
  FORMAT_DATE = '%Y-%m-%d'
73
98
  FORMAT_TIME = '%H:%M'
@@ -123,6 +148,11 @@ module Glimmer
123
148
  listeners.each do |event, event_listeners|
124
149
  event_listeners.dup.each(&:unregister)
125
150
  end
151
+ listeners.clear
152
+ data_bindings.each do |element_binding, model_binding|
153
+ element_binding.unregister_all_observables
154
+ end
155
+ data_bindings.clear
126
156
  end
127
157
 
128
158
  # Subclasses can override with their own selector
@@ -423,6 +453,10 @@ module Glimmer
423
453
  event_listener_proxies.clear
424
454
  end
425
455
 
456
+ def data_bindings
457
+ @data_bindings ||= {}
458
+ end
459
+
426
460
  def data_bind(property, model_binding)
427
461
  element_binding_translator = value_converters_for_input_type(type)[:model_to_view]
428
462
  element_binding_parameters = [self, property, element_binding_translator]
@@ -430,6 +464,7 @@ module Glimmer
430
464
  element_binding.call(model_binding.evaluate_property)
431
465
  #TODO make this options observer dependent and all similar observers in element specific data binding handlers
432
466
  element_binding.observe(model_binding)
467
+ data_bindings[element_binding] = model_binding
433
468
  unless model_binding.binding_options[:read_only]
434
469
  # TODO add guards against nil cases for hash below
435
470
  listener_keyword = data_binding_listener_for_element_and_property(keyword, property)
@@ -444,19 +479,36 @@ module Glimmer
444
479
  end
445
480
  end
446
481
 
482
+ # Data-binds the generation of nested content to a model/property (in binding args)
483
+ # consider providing an option to avoid initial rendering without any changes happening
484
+ def bind_content(*binding_args, &content_block)
485
+ # TODO in the future, consider optimizing code by diffing content if that makes sense
486
+ content_binding_work = proc do |*values|
487
+ children.dup.each { |child| child.remove }
488
+ content(&content_block)
489
+ end
490
+ content_binding_observer = Glimmer::DataBinding::Observer.proc(&content_binding_work)
491
+ content_binding_observer.observe(*binding_args)
492
+ content_binding_work.call # TODO inspect if we need to pass args here (from observed attributes) [but it's simpler not to pass anything at first]
493
+ end
494
+
447
495
  def respond_to_missing?(method_name, include_private = false)
448
496
  # TODO consider doing more correct checking of availability of properties/methods using native `` ticks
449
497
  property_name = property_name_for(method_name)
498
+ unnormalized_property_name = unnormalized_property_name_for(method_name)
450
499
  super(method_name, include_private) ||
451
500
  (dom_element && dom_element.length > 0 && Native.call(dom_element, '0').respond_to?(method_name.to_s.camelcase, include_private)) ||
501
+ (dom_element && dom_element.length > 0 && Native.call(dom_element, '0').respond_to?(method_name.to_s, include_private)) ||
452
502
  dom_element.respond_to?(method_name, include_private) ||
453
503
  (!dom_element.prop(property_name).nil? && !dom_element.prop(property_name).is_a?(Proc)) ||
504
+ (!dom_element.prop(unnormalized_property_name).nil? && !dom_element.prop(unnormalized_property_name).is_a?(Proc)) ||
454
505
  method_name.to_s.start_with?('on_')
455
506
  end
456
507
 
457
508
  def method_missing(method_name, *args, &block)
458
509
  # TODO consider doing more correct checking of availability of properties/methods using native `` ticks
459
510
  property_name = property_name_for(method_name)
511
+ unnormalized_property_name = unnormalized_property_name_for(method_name)
460
512
  if method_name.to_s.start_with?('on_')
461
513
  handle_observation_request(method_name, block)
462
514
  elsif dom_element.respond_to?(method_name)
@@ -467,12 +519,22 @@ module Glimmer
467
519
  else
468
520
  dom_element.prop(property_name)
469
521
  end
522
+ elsif !dom_element.prop(unnormalized_property_name).nil? && !dom_element.prop(unnormalized_property_name).is_a?(Proc)
523
+ if method_name.end_with?('=')
524
+ dom_element.prop(unnormalized_property_name, *args)
525
+ else
526
+ dom_element.prop(unnormalized_property_name)
527
+ end
470
528
  elsif dom_element && dom_element.length > 0
529
+ js_args = block.nil? ? args : (args + [block])
471
530
  begin
472
- js_args = block.nil? ? args : (args + [block])
473
531
  Native.call(dom_element, '0').method_missing(method_name.to_s.camelcase, *js_args)
474
532
  rescue Exception => e
475
- super(method_name, *args, &block)
533
+ begin
534
+ Native.call(dom_element, '0').method_missing(method_name.to_s, *js_args)
535
+ rescue Exception => e
536
+ super(method_name, *args, &block)
537
+ end
476
538
  end
477
539
  else
478
540
  super(method_name, *args, &block)
@@ -480,7 +542,12 @@ module Glimmer
480
542
  end
481
543
 
482
544
  def property_name_for(method_name)
483
- method_name.end_with?('=') ? method_name.to_s[0...-1].camelcase : method_name.to_s.camelcase
545
+ attribute_name = method_name.end_with?('=') ? method_name.to_s[0...-1] : method_name.to_s
546
+ PROPERTY_ALIASES[attribute_name] || attribute_name.camelcase
547
+ end
548
+
549
+ def unnormalized_property_name_for(method_name)
550
+ method_name.end_with?('=') ? method_name.to_s[0...-1] : method_name.to_s
484
551
  end
485
552
 
486
553
  def swt_widget
@@ -34,7 +34,6 @@ module Glimmer
34
34
  @selector = selector
35
35
  @listener = listener
36
36
  @js_listener = lambda do |js_event|
37
- # TODO wrap event with a Ruby Event object before passing to listener
38
37
  event = EventProxy.new(js_event: js_event, listener: self)
39
38
  result = listener.call(event)
40
39
  result = true if result.nil?
@@ -0,0 +1,137 @@
1
+ # Copyright (c) 2023-2024 Andy Maleh
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining
4
+ # a copy of this software and associated documentation files (the
5
+ # "Software"), to deal in the Software without restriction, including
6
+ # without limitation the rights to use, copy, modify, merge, publish,
7
+ # distribute, sublicense, and/or sell copies of the Software, and to
8
+ # permit persons to whom the Software is furnished to do so, subject to
9
+ # the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be
12
+ # included in all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
+
22
+ require 'glimmer-dsl-web'
23
+
24
+ class Address
25
+ attr_accessor :text
26
+ attr_reader :name, :street, :city, :state, :zip
27
+
28
+ def name=(value)
29
+ @name = value
30
+ update_text
31
+ end
32
+
33
+ def street=(value)
34
+ @street = value
35
+ update_text
36
+ end
37
+
38
+ def city=(value)
39
+ @city = value
40
+ update_text
41
+ end
42
+
43
+ def state=(value)
44
+ @state = value
45
+ update_text
46
+ end
47
+
48
+ def zip=(value)
49
+ @zip = value
50
+ update_text
51
+ end
52
+
53
+ private
54
+
55
+ def update_text
56
+ self.text = [name, street, city, state, zip].compact.reject(&:empty?).join(', ')
57
+ end
58
+ end
59
+
60
+ class User
61
+ attr_accessor :addresses
62
+ attr_reader :address_count
63
+
64
+ def initialize
65
+ @address_count = 1
66
+ @addresses = []
67
+ update_addresses
68
+ end
69
+
70
+ def address_count=(value)
71
+ value = [[1, value.to_i].max, 3].min
72
+ @address_count = value
73
+ update_addresses
74
+ end
75
+
76
+ private
77
+
78
+ def update_addresses
79
+ address_count_change = address_count - addresses.size
80
+ if address_count_change > 0
81
+ address_count_change.times { addresses << Address.new }
82
+ else
83
+ address_count_change.abs.times { addresses.pop }
84
+ end
85
+ end
86
+ end
87
+
88
+ @user = User.new
89
+
90
+ div {
91
+ div {
92
+ label('Number of addresses: ', for: 'address-count-field')
93
+ input(id: 'address-count-field', type: 'number', min: 1, max: 3) {
94
+ value <=> [@user, :address_count]
95
+ }
96
+ }
97
+
98
+ div {
99
+ # Content Data-Binding is used to dynamically (re)generate content of div
100
+ # based on changes to @user.addresses, replacing older content on every change
101
+ content(@user, :addresses) do
102
+ @user.addresses.each do |address|
103
+ div {
104
+ div(style: 'display: grid; grid-auto-columns: 80px 280px;') { |address_div|
105
+ [:name, :street, :city, :state, :zip].each do |attribute|
106
+ label(attribute.to_s.capitalize, for: "#{attribute}-field")
107
+ input(id: "#{attribute}-field", type: 'text') {
108
+ value <=> [address, attribute]
109
+ }
110
+ end
111
+
112
+ div(style: 'grid-column: 1 / span 2;') {
113
+ inner_text <= [address, :text]
114
+ }
115
+
116
+ style {
117
+ <<~CSS
118
+ #{address_div.selector} {
119
+ margin: 10px 0;
120
+ }
121
+ #{address_div.selector} * {
122
+ margin: 5px;
123
+ }
124
+ #{address_div.selector} label {
125
+ grid-column: 1;
126
+ }
127
+ #{address_div.selector} input, #{address_div.selector} select {
128
+ grid-column: 2;
129
+ }
130
+ CSS
131
+ }
132
+ }
133
+ }
134
+ end
135
+ end
136
+ }
137
+ }.render
@@ -26,7 +26,11 @@ GLIMMER_DSL_OPAL_LIB = File.join(GLIMMER_DSL_OPAL_ROOT, 'lib')
26
26
 
27
27
  $LOAD_PATH.unshift(GLIMMER_DSL_OPAL_LIB)
28
28
 
29
- if RUBY_ENGINE == 'opal'
29
+ if RUBY_ENGINE != 'opal'
30
+ require 'opal-rails'
31
+ require 'opal-async'
32
+ require 'opal-jquery'
33
+ else
30
34
  # GLIMMER_DSL_OPAL_MISSING = File.join(GLIMMER_DSL_OPAL_ROOT, 'lib', 'glimmer-dsl-opal', 'missing')
31
35
 
32
36
  # $LOAD_PATH.unshift(GLIMMER_DSL_OPAL_MISSING) # missing Ruby classes/methods
@@ -75,7 +79,7 @@ if RUBY_ENGINE == 'opal'
75
79
  require 'glimmer-dsl-xml'
76
80
  require 'glimmer-dsl-css'
77
81
 
78
- Glimmer::Config.loop_max_count = 150 # TODO consider disabling if preferred
82
+ Glimmer::Config.loop_max_count = 50 # TODO consider disabling if preferred
79
83
 
80
84
  original_logger_level = Glimmer::Config.logger.level
81
85
  Glimmer::Config.logger = Glimmer::Config::OpalLogger.new(STDOUT)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: glimmer-dsl-web
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.7
4
+ version: 0.0.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andy Maleh
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-01-02 00:00:00.000000000 Z
11
+ date: 2024-01-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: glimmer
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: 2.4.1
19
+ version: 2.7.6
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: 2.4.1
26
+ version: 2.7.6
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: glimmer-dsl-xml
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -52,6 +52,34 @@ dependencies:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
54
  version: 1.2.2
55
+ - !ruby/object:Gem::Dependency
56
+ name: opal
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '='
60
+ - !ruby/object:Gem::Version
61
+ version: 1.4.1
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '='
67
+ - !ruby/object:Gem::Version
68
+ version: 1.4.1
69
+ - !ruby/object:Gem::Dependency
70
+ name: opal-rails
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - '='
74
+ - !ruby/object:Gem::Version
75
+ version: 2.0.2
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - '='
81
+ - !ruby/object:Gem::Version
82
+ version: 2.0.2
55
83
  - !ruby/object:Gem::Dependency
56
84
  name: opal-async
57
85
  requirement: !ruby/object:Gem::Requirement
@@ -66,6 +94,20 @@ dependencies:
66
94
  - - "~>"
67
95
  - !ruby/object:Gem::Version
68
96
  version: 1.4.0
97
+ - !ruby/object:Gem::Dependency
98
+ name: opal-jquery
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: 0.4.6
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: 0.4.6
69
111
  - !ruby/object:Gem::Dependency
70
112
  name: to_collection
71
113
  requirement: !ruby/object:Gem::Requirement
@@ -188,34 +230,6 @@ dependencies:
188
230
  - - "~>"
189
231
  - !ruby/object:Gem::Version
190
232
  version: 0.8.0.alpha2
191
- - !ruby/object:Gem::Dependency
192
- name: opal-rails
193
- requirement: !ruby/object:Gem::Requirement
194
- requirements:
195
- - - "~>"
196
- - !ruby/object:Gem::Version
197
- version: 1.1.2
198
- type: :development
199
- prerelease: false
200
- version_requirements: !ruby/object:Gem::Requirement
201
- requirements:
202
- - - "~>"
203
- - !ruby/object:Gem::Version
204
- version: 1.1.2
205
- - !ruby/object:Gem::Dependency
206
- name: opal-jquery
207
- requirement: !ruby/object:Gem::Requirement
208
- requirements:
209
- - - "~>"
210
- - !ruby/object:Gem::Version
211
- version: 0.4.4
212
- type: :development
213
- prerelease: false
214
- version_requirements: !ruby/object:Gem::Requirement
215
- requirements:
216
- - - "~>"
217
- - !ruby/object:Gem::Version
218
- version: 0.4.4
219
233
  description: Glimmer DSL for Web (Ruby in the Browser Web GUI Frontend Library) -
220
234
  Enables frontend GUI development with Ruby by adopting a DSL that follows web-like
221
235
  HTML syntax, enabling the transfer of HTML/CSS/JS skills to Ruby frontend development.
@@ -239,6 +253,7 @@ files:
239
253
  - lib/glimmer-dsl-web/ext/date.rb
240
254
  - lib/glimmer-dsl-web/ext/exception.rb
241
255
  - lib/glimmer-dsl-web/samples/hello/hello_button.rb
256
+ - lib/glimmer-dsl-web/samples/hello/hello_content_data_binding.rb
242
257
  - lib/glimmer-dsl-web/samples/hello/hello_data_binding.rb
243
258
  - lib/glimmer-dsl-web/samples/hello/hello_form.rb
244
259
  - lib/glimmer-dsl-web/samples/hello/hello_input_date_time.rb
@@ -247,6 +262,7 @@ files:
247
262
  - lib/glimmer/config/opal_logger.rb
248
263
  - lib/glimmer/data_binding/element_binding.rb
249
264
  - lib/glimmer/dsl/web/bind_expression.rb
265
+ - lib/glimmer/dsl/web/content_data_binding_expression.rb
250
266
  - lib/glimmer/dsl/web/data_binding_expression.rb
251
267
  - lib/glimmer/dsl/web/dsl.rb
252
268
  - lib/glimmer/dsl/web/element_expression.rb