glimmer-dsl-web 0.3.1 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +19 -2
- data/README.md +71 -8
- data/VERSION +1 -1
- data/glimmer-dsl-web.gemspec +7 -5
- data/lib/glimmer/data_binding/element_binding.rb +4 -4
- data/lib/glimmer/dsl/web/component_expression.rb +1 -1
- data/lib/glimmer/dsl/web/dsl.rb +5 -5
- data/lib/glimmer/dsl/web/formatting_element_expression.rb +11 -0
- data/lib/glimmer/web/component/component_style_container.rb +13 -0
- data/lib/glimmer/web/component.rb +148 -54
- data/lib/glimmer/web/element_proxy.rb +25 -9
- data/lib/glimmer-dsl-web/samples/hello/hello_observer_data_binding.rb +2 -2
- data/lib/glimmer-dsl-web/samples/hello/hello_style.rb +178 -0
- data/lib/glimmer-dsl-web/samples/regular/todo_mvc/views/edit_todo_input.rb +9 -17
- data/lib/glimmer-dsl-web/samples/regular/todo_mvc/views/new_todo_form.rb +15 -15
- data/lib/glimmer-dsl-web/samples/regular/todo_mvc/views/new_todo_input.rb +7 -15
- data/lib/glimmer-dsl-web/samples/regular/todo_mvc/views/todo_filters.rb +73 -73
- data/lib/glimmer-dsl-web/samples/regular/todo_mvc/views/todo_input.rb +27 -24
- data/lib/glimmer-dsl-web/samples/regular/todo_mvc/views/todo_list.rb +3 -9
- data/lib/glimmer-dsl-web/samples/regular/todo_mvc/views/todo_list_item.rb +8 -4
- data/lib/glimmer-dsl-web/samples/regular/todo_mvc/views/todo_mvc_footer.rb +2 -6
- data/lib/glimmer-dsl-web/samples/regular/todo_mvc.rb +2 -6
- metadata +8 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b8b106a50bf2ec2656480068685e725017a8b7a32f49d38459416b01c550e9aa
|
4
|
+
data.tar.gz: ca6c4c2e8028059c5a305c45f181d6f5902e5737d65011dd5ce34b5af334baf0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a95444fd3855a6e498e243eb06ffc80e7f25cdbe5ed3b178729450d37f3c528b42919dccddde64caa38e48fcb90b4bb16e42d4f5b3c66425ac2333c5176fe7ad
|
7
|
+
data.tar.gz: fc781cd899da6a3059b6b5b048c7f324f03d231a755403178996d90eed391cbbcb3c2a214eb6bf0b0e2b1e318560e1b8fded988f998582075b181d7ee74093cb
|
data/CHANGELOG.md
CHANGED
@@ -1,10 +1,27 @@
|
|
1
1
|
# Change Log
|
2
2
|
|
3
|
+
## 0.4.0
|
4
|
+
|
5
|
+
- Support `style {}` block in `Glimmer::Web::Component` that would automatically add style in one place for all components, without repeating style for repeating components
|
6
|
+
- Upgrade to Glimmer DSL for CSS 1.5.0
|
7
|
+
- Remove support for including multiple `before_render` and `after_render` blocks in a component as it is not needed and can be confusing
|
8
|
+
- Hello, Style! Sample: `require 'glimmer-dsl-web/samples/hello/hello_style'`
|
9
|
+
- Optimize performance of Todo MVC by not adding an edit input field to every todo, yet adding it only upon editing a todo.
|
10
|
+
- Fix issue with `ElementProxy#add_css_class` and `ElementProxy#remove_css_class` crashing if called before rendering an element
|
11
|
+
- Fix issue with Hello, Observer (Data-Binding)! crashing if run after Hello, Observer! due to both samples sharing the same class by mistake
|
12
|
+
|
13
|
+
## 0.3.2
|
14
|
+
|
15
|
+
- Optimize performance (~248% faster) of rendering by changing DSL ordering to avoid component checks at the top
|
16
|
+
- Optimize performance of formatting html elements by adding Glimmer DSL shortcut methods
|
17
|
+
- Optimize performance of component expressions by indexing component keywords
|
18
|
+
- Upgrade to glimmer 2.7.9
|
19
|
+
|
3
20
|
## 0.3.1
|
4
21
|
|
5
22
|
- Optimize Todo MVC performance for filtering between all, active, and completed (it happens instantly now)
|
6
|
-
- Append Todo MVC todos at the bottom instead of prepending them at the top (
|
7
|
-
- Make Todo MVC "items left" text show "item left" if there is only 1 todo
|
23
|
+
- Append Todo MVC todos at the bottom instead of prepending them at the top (the ES6 version was copied initially, which ordered todos the opposite way from how Todo MVC behaves in other versions)
|
24
|
+
- Make Todo MVC "items left" text show "item left" if there is only 1 todo
|
8
25
|
- Make Todo MVC footer links open a new tab/window (with `target: '_blank'` option)
|
9
26
|
- Refactor/Simplify Todo MVC sample code
|
10
27
|
- Upgrade to glimmer 2.7.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.
|
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.4.0 (Beta)
|
2
2
|
## Ruby in the Browser Web Frontend Framework
|
3
3
|
### Finally, Ruby Developer Productivity, Happiness, and Fun in the Frontend!!!
|
4
4
|
[![Gem Version](https://badge.fury.io/rb/glimmer-dsl-web.svg)](http://badge.fury.io/rb/glimmer-dsl-web)
|
@@ -14,7 +14,7 @@ You can finally have Ruby developer happiness and productivity in the Frontend!
|
|
14
14
|
|
15
15
|
[Glimmer](https://github.com/AndyObtiva/glimmer) DSL for Web enables building Web 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 supports Rails' principle of the One Person Framework by not requiring any extra developers with JavaScript expertise, yet enabling Ruby (Backend) Software Engineers to develop the Frontend with Ruby code that is better than any JavaScript code produced by JS developers. It aims at providing the simplest, most intuitive, most straight-forward, and most productive frontend framework in existence. The framework 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)) in building Isomorphic Ruby on Rails Applications. It provides a Ruby [HTML DSL](#usage), which uniquely enables writing both structure code and logic code in one language. It supports both Unidirectional (One-Way) [Data-Binding](#hello-data-binding) (using `<=`) and Bidirectional (Two-Way) [Data-Binding](#hello-data-binding) (using `<=>`). Dynamic rendering (and re-rendering) of HTML content is also supported via [Content Data-Binding](#hello-content-data-binding). Modular design is supported with [Glimmer Web Components](#hello-component). And, a Ruby CSS DSL is supported with the included [Glimmer DSL for CSS](https://github.com/AndyObtiva/glimmer-dsl-css). To automatically convert legacy HTML & CSS code to Glimmer DSL Ruby code, Software Engineers could use the included [`html_to_glimmer`](https://github.com/AndyObtiva/glimmer-dsl-xml#html-to-glimmer-converter) and [`css_to_glimmer`](https://github.com/AndyObtiva/glimmer-dsl-css#css-to-glimmer-converter) commands. Many [samples](#samples) are demonstrated in the [Rails sample app](https://github.com/AndyObtiva/sample-glimmer-dsl-web-rails7-app) (there is a very minimal [Standalone [No Rails] static site sample app](https://github.com/Largo/glimmer-dsl-web-standalone-demo) too). 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)!
|
16
16
|
|
17
|
-
[Glimmer DSL for Web](https://rubygems.org/gems/glimmer-dsl-web) aims to be a very simple Ruby-based drop-in replacement for your existing JavaScript Frontend library (e.g. React, Angular, Vue, Ember, Svelte) or your JavaScript Frontend layer in general. It does not change how your Frontend interacts with the Backend, meaning you can continue to write Rails Backend API endpoints as needed and make Ajax
|
17
|
+
[Glimmer DSL for Web](https://rubygems.org/gems/glimmer-dsl-web) aims to be a very simple Ruby-based drop-in replacement for your existing JavaScript Frontend library (e.g. React, Angular, Vue, Ember, Svelte) or your JavaScript Frontend layer in general. It does not change how your Frontend interacts with the Backend, meaning you can continue to write Rails Backend API endpoints as needed and make HTTP/Ajax requests or read data embedded in elements, but from [Ruby in the Browser](https://www.youtube.com/watch?v=4AdcfbI6A4c). Whatever is possible in JavaScript is possible when using Glimmer DSL for Web as it integrates with any existing JavaScript library. The [Rails sample app](https://github.com/AndyObtiva/sample-glimmer-dsl-web-rails7-app) demonstrates how to [make HTTP calls](https://github.com/AndyObtiva/sample-glimmer-dsl-web-rails7-app/blob/master/app/assets/opal/sample_selector/models/sample_api.rb) and how to [integrate with a JavaScript library](https://github.com/AndyObtiva/sample-glimmer-dsl-web-rails7-app/blob/master/app/views/layouts/application.html.erb) (highlightjs) that performs [code syntax highlighting](https://github.com/AndyObtiva/sample-glimmer-dsl-web-rails7-app/blob/master/app/assets/opal/sample_selector.rb).
|
18
18
|
|
19
19
|
After looking through the [samples](#samples) below, read the [FAQ (Frequently Asked Questions)](#faq) to learn more about how Glimmer DSL for Web compares to other approaches/libraries like Hotwire (Turbo), Phlex, ViewComponent, Angular, Vue, React, Svelte, and other JS frameworks.
|
20
20
|
|
@@ -265,8 +265,64 @@ Glimmer HTML DSL Ruby code in the frontend:
|
|
265
265
|
```ruby
|
266
266
|
require 'glimmer-dsl-web'
|
267
267
|
|
268
|
-
Address = Struct.new(:street, :street2, :city, :state, :zip_code, keyword_init: true) do
|
269
|
-
STATES = {
|
268
|
+
Address = Struct.new(:street, :street2, :city, :state, :zip_code, :billing_and_shipping, keyword_init: true) do
|
269
|
+
STATES = {
|
270
|
+
"AK"=>"Alaska",
|
271
|
+
"AL"=>"Alabama",
|
272
|
+
"AR"=>"Arkansas",
|
273
|
+
"AS"=>"American Samoa",
|
274
|
+
"AZ"=>"Arizona",
|
275
|
+
"CA"=>"California",
|
276
|
+
"CO"=>"Colorado",
|
277
|
+
"CT"=>"Connecticut",
|
278
|
+
"DC"=>"District of Columbia",
|
279
|
+
"DE"=>"Delaware",
|
280
|
+
"FL"=>"Florida",
|
281
|
+
"GA"=>"Georgia",
|
282
|
+
"GU"=>"Guam",
|
283
|
+
"HI"=>"Hawaii",
|
284
|
+
"IA"=>"Iowa",
|
285
|
+
"ID"=>"Idaho",
|
286
|
+
"IL"=>"Illinois",
|
287
|
+
"IN"=>"Indiana",
|
288
|
+
"KS"=>"Kansas",
|
289
|
+
"KY"=>"Kentucky",
|
290
|
+
"LA"=>"Louisiana",
|
291
|
+
"MA"=>"Massachusetts",
|
292
|
+
"MD"=>"Maryland",
|
293
|
+
"ME"=>"Maine",
|
294
|
+
"MI"=>"Michigan",
|
295
|
+
"MN"=>"Minnesota",
|
296
|
+
"MO"=>"Missouri",
|
297
|
+
"MS"=>"Mississippi",
|
298
|
+
"MT"=>"Montana",
|
299
|
+
"NC"=>"North Carolina",
|
300
|
+
"ND"=>"North Dakota",
|
301
|
+
"NE"=>"Nebraska",
|
302
|
+
"NH"=>"New Hampshire",
|
303
|
+
"NJ"=>"New Jersey",
|
304
|
+
"NM"=>"New Mexico",
|
305
|
+
"NV"=>"Nevada",
|
306
|
+
"NY"=>"New York",
|
307
|
+
"OH"=>"Ohio",
|
308
|
+
"OK"=>"Oklahoma",
|
309
|
+
"OR"=>"Oregon",
|
310
|
+
"PA"=>"Pennsylvania",
|
311
|
+
"PR"=>"Puerto Rico",
|
312
|
+
"RI"=>"Rhode Island",
|
313
|
+
"SC"=>"South Carolina",
|
314
|
+
"SD"=>"South Dakota",
|
315
|
+
"TN"=>"Tennessee",
|
316
|
+
"TX"=>"Texas",
|
317
|
+
"UT"=>"Utah",
|
318
|
+
"VA"=>"Virginia",
|
319
|
+
"VI"=>"Virgin Islands",
|
320
|
+
"VT"=>"Vermont",
|
321
|
+
"WA"=>"Washington",
|
322
|
+
"WI"=>"Wisconsin",
|
323
|
+
"WV"=>"West Virginia",
|
324
|
+
"WY"=>"Wyoming"
|
325
|
+
}
|
270
326
|
|
271
327
|
def state_code
|
272
328
|
STATES.invert[state]
|
@@ -1310,6 +1366,8 @@ Learn more about the differences between various [Glimmer](https://github.com/An
|
|
1310
1366
|
|
1311
1367
|
You can setup Glimmer DSL for Web in [Rails 7](#rails-7), [Rails 6](#rails-6), or [Standalone (No Rails)](#standalone-no-rails).
|
1312
1368
|
|
1369
|
+
Once done, read [Usage](#usage) instructions. Note that for serious app usage, it is recommended to build [components](#hello-component) and use the [`glimmer_component` Rails Helper](#hello-glimmer_component-rails-helper) to embed the top-level Web Frontend component in a Rails View.
|
1370
|
+
|
1313
1371
|
(NOTE: Keep in mind this is a Beta. If you run into issues, try to go back to a [previous revision](https://rubygems.org/gems/glimmer-dsl-web/versions). Also, there is a slight chance any issues you encounter are fixed in master or some other branch that you could check out instead)
|
1314
1372
|
|
1315
1373
|
### Rails 7
|
@@ -1333,7 +1391,7 @@ rails new glimmer_app_server
|
|
1333
1391
|
Add the following to `Gemfile`:
|
1334
1392
|
|
1335
1393
|
```
|
1336
|
-
gem 'glimmer-dsl-web', '~> 0.
|
1394
|
+
gem 'glimmer-dsl-web', '~> 0.4.0'
|
1337
1395
|
```
|
1338
1396
|
|
1339
1397
|
Run:
|
@@ -1562,7 +1620,7 @@ Disable the `webpacker` gem line in `Gemfile`:
|
|
1562
1620
|
Add the following to `Gemfile`:
|
1563
1621
|
|
1564
1622
|
```ruby
|
1565
|
-
gem 'glimmer-dsl-web', '~> 0.
|
1623
|
+
gem 'glimmer-dsl-web', '~> 0.4.0'
|
1566
1624
|
```
|
1567
1625
|
|
1568
1626
|
Run:
|
@@ -1803,6 +1861,8 @@ You can get/set any element property or invoke any element function by simply ca
|
|
1803
1861
|
|
1804
1862
|
Next, check out [Samples](#samples).
|
1805
1863
|
|
1864
|
+
Note that for serious app usage, it is recommended to build [components](#hello-component) and use the [`glimmer_component` Rails Helper](#hello-glimmer_component-rails-helper) to embed the top-level Web Frontend component in a Rails View.
|
1865
|
+
|
1806
1866
|
## Supported Glimmer DSL Keywords
|
1807
1867
|
|
1808
1868
|
[All HTML elements](https://developer.mozilla.org/en-US/docs/Web/HTML/Element), following the Ruby method name standard of lowercase and underscored names.
|
@@ -2256,7 +2316,7 @@ Glimmer HTML DSL Ruby code in the frontend:
|
|
2256
2316
|
```ruby
|
2257
2317
|
require 'glimmer-dsl-web'
|
2258
2318
|
|
2259
|
-
Address = Struct.new(:street, :street2, :city, :state, :zip_code, keyword_init: true) do
|
2319
|
+
Address = Struct.new(:street, :street2, :city, :state, :zip_code, :billing_and_shipping, keyword_init: true) do
|
2260
2320
|
STATES = {
|
2261
2321
|
"AK"=>"Alaska",
|
2262
2322
|
"AL"=>"Alabama",
|
@@ -2391,7 +2451,9 @@ Document.ready? do
|
|
2391
2451
|
}
|
2392
2452
|
}
|
2393
2453
|
|
2454
|
+
# Programmable CSS using Glimmer DSL for CSS
|
2394
2455
|
style {
|
2456
|
+
# `r` is an alias for `rule`, generating a CSS rule
|
2395
2457
|
r("#{address_div.selector} *") {
|
2396
2458
|
margin '5px'
|
2397
2459
|
}
|
@@ -3370,8 +3432,9 @@ end
|
|
3370
3432
|
- Declarative syntax that visually maps to the DOM (Document Object Model) hierarchy
|
3371
3433
|
- Ability to mix declarative and imperative code conveniently in one language
|
3372
3434
|
- Computers serve Software Engineers (not Software Engineers serve Computers)
|
3373
|
-
- Think only about real world concepts directly relevant to web page interaction
|
3435
|
+
- Think only about real world concepts directly relevant to web page interaction and application domain model
|
3374
3436
|
- Modular Software Design (e.g. support for Components)
|
3437
|
+
- No premature optimizations
|
3375
3438
|
|
3376
3439
|
## Supporting Libraries
|
3377
3440
|
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.4.0
|
data/glimmer-dsl-web.gemspec
CHANGED
@@ -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.
|
5
|
+
# stub: glimmer-dsl-web 0.4.0 ruby lib
|
6
6
|
|
7
7
|
Gem::Specification.new do |s|
|
8
8
|
s.name = "glimmer-dsl-web".freeze
|
9
|
-
s.version = "0.
|
9
|
+
s.version = "0.4.0"
|
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-07-
|
14
|
+
s.date = "2024-07-22"
|
15
15
|
s.description = "Glimmer DSL for Web (Ruby in the Browser Web Frontend Framework) enables building Web Frontends using Ruby in the Browser, as per Matz's recommendation in his RubyConf 2022 keynote speech to replace JavaScript with Ruby. It aims at providing the simplest, most intuitive, most straight-forward, and most productive frontend framework in existence. The framework follows the Ruby way (with DSLs and TIMTOWTDI) and the Rails way (Convention over Configuration) in building Isomorphic Ruby on Rails Applications. It provides a Ruby HTML DSL, which uniquely enables writing both structure code and logic code in one language. It supports 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. Modular design is supported with Glimmer Web Components. And, a Ruby CSS DSL is supported with the included Glimmer DSL for CSS. Many samples are demonstrated in the Rails sample app (there is a very minimal Standalone [No Rails] sample app too). You can finally live in pure Rubyland on the Web in both the frontend and backend with Glimmer DSL for Web! This gem relies on Opal Ruby.".freeze
|
16
16
|
s.email = "andy.am@gmail.com".freeze
|
17
17
|
s.extra_rdoc_files = [
|
@@ -46,6 +46,7 @@ Gem::Specification.new do |s|
|
|
46
46
|
"lib/glimmer-dsl-web/samples/hello/hello_observer.rb",
|
47
47
|
"lib/glimmer-dsl-web/samples/hello/hello_observer_data_binding.rb",
|
48
48
|
"lib/glimmer-dsl-web/samples/hello/hello_paragraph.rb",
|
49
|
+
"lib/glimmer-dsl-web/samples/hello/hello_style.rb",
|
49
50
|
"lib/glimmer-dsl-web/samples/hello/hello_world.rb",
|
50
51
|
"lib/glimmer-dsl-web/samples/regular/button_counter.rb",
|
51
52
|
"lib/glimmer-dsl-web/samples/regular/todo_mvc.rb",
|
@@ -81,6 +82,7 @@ Gem::Specification.new do |s|
|
|
81
82
|
"lib/glimmer/util/proc_tracker.rb",
|
82
83
|
"lib/glimmer/web.rb",
|
83
84
|
"lib/glimmer/web/component.rb",
|
85
|
+
"lib/glimmer/web/component/component_style_container.rb",
|
84
86
|
"lib/glimmer/web/element_proxy.rb",
|
85
87
|
"lib/glimmer/web/event_proxy.rb",
|
86
88
|
"lib/glimmer/web/formatting_element_proxy.rb",
|
@@ -93,9 +95,9 @@ Gem::Specification.new do |s|
|
|
93
95
|
|
94
96
|
s.specification_version = 4
|
95
97
|
|
96
|
-
s.add_runtime_dependency(%q<glimmer>.freeze, ["~> 2.
|
98
|
+
s.add_runtime_dependency(%q<glimmer>.freeze, ["~> 2.8.0"])
|
97
99
|
s.add_runtime_dependency(%q<glimmer-dsl-xml>.freeze, ["~> 1.4.0"])
|
98
|
-
s.add_runtime_dependency(%q<glimmer-dsl-css>.freeze, ["~> 1.
|
100
|
+
s.add_runtime_dependency(%q<glimmer-dsl-css>.freeze, ["~> 1.5.0"])
|
99
101
|
s.add_runtime_dependency(%q<opal>.freeze, ["= 1.8.2"])
|
100
102
|
s.add_runtime_dependency(%q<opal-rails>.freeze, ["= 2.0.3"])
|
101
103
|
s.add_runtime_dependency(%q<opal-async>.freeze, ["~> 1.4.1"])
|
@@ -4,7 +4,6 @@ require 'glimmer/data_binding/observer'
|
|
4
4
|
module Glimmer
|
5
5
|
module DataBinding
|
6
6
|
class ElementBinding
|
7
|
-
include Glimmer
|
8
7
|
include Observable
|
9
8
|
include Observer
|
10
9
|
|
@@ -12,7 +11,7 @@ module Glimmer
|
|
12
11
|
def initialize(element, property, translator = nil)
|
13
12
|
@element = element
|
14
13
|
@property = property
|
15
|
-
@translator = translator
|
14
|
+
@translator = translator
|
16
15
|
|
17
16
|
# TODO implement automatic cleanup upon calling element.remove
|
18
17
|
# Alternatively, have this be built into ElementProxy and remove this code
|
@@ -24,8 +23,9 @@ module Glimmer
|
|
24
23
|
end
|
25
24
|
|
26
25
|
def call(value)
|
27
|
-
|
28
|
-
@
|
26
|
+
evaluated_property_value = evaluate_property
|
27
|
+
converted_value = @translator&.call(value, evaluated_property_value) || value
|
28
|
+
@element.send("#{@property}=", converted_value) unless converted_value == evaluated_property_value
|
29
29
|
end
|
30
30
|
|
31
31
|
def evaluate_property
|
data/lib/glimmer/dsl/web/dsl.rb
CHANGED
@@ -40,14 +40,14 @@ module Glimmer
|
|
40
40
|
Engine.add_dynamic_expressions(
|
41
41
|
Web,
|
42
42
|
%w[
|
43
|
-
component
|
44
43
|
listener
|
45
|
-
data_binding
|
46
|
-
property
|
47
|
-
content_data_binding
|
48
|
-
shine_data_binding
|
49
44
|
style
|
45
|
+
content_data_binding
|
46
|
+
component
|
50
47
|
formatting_element
|
48
|
+
data_binding
|
49
|
+
shine_data_binding
|
50
|
+
property
|
51
51
|
]
|
52
52
|
)
|
53
53
|
end
|
@@ -17,3 +17,14 @@ module Glimmer
|
|
17
17
|
end
|
18
18
|
end
|
19
19
|
end
|
20
|
+
|
21
|
+
module Glimmer
|
22
|
+
# Optimize performance through shortcut methods for all HTML formatting elements that circumvent the DSL chain of responsibility
|
23
|
+
element_expression = Glimmer::DSL::Web::FormattingElementExpression.new
|
24
|
+
Glimmer::Web::FormattingElementProxy::FORMATTING_ELEMENT_KEYWORDS.each do |keyword|
|
25
|
+
Glimmer::DSL::Engine.static_expressions[keyword] ||= Concurrent::Hash.new
|
26
|
+
element_expression_dsl = element_expression.class.dsl
|
27
|
+
Glimmer::DSL::Engine.static_expressions[keyword][element_expression_dsl] = element_expression
|
28
|
+
Glimmer.send(:define_method, keyword, &Glimmer::DSL::Engine::STATIC_EXPRESSION_METHOD_FACTORY.call(keyword))
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'glimmer/web/component'
|
2
|
+
|
3
|
+
class ComponentStyleContainer
|
4
|
+
include Glimmer::Web::Component
|
5
|
+
|
6
|
+
option :component
|
7
|
+
|
8
|
+
markup {
|
9
|
+
component_style_container_block = component.class.instance_variable_get("@style_block")
|
10
|
+
component_style_container_class = "#{component.class.component_element_class}-style"
|
11
|
+
style(class: component_style_container_class, &component_style_container_block)
|
12
|
+
}
|
13
|
+
end
|
@@ -73,17 +73,20 @@ module Glimmer
|
|
73
73
|
end
|
74
74
|
|
75
75
|
def before_render(&block)
|
76
|
-
@
|
77
|
-
@before_render_blocks << block
|
76
|
+
@before_render = block
|
78
77
|
end
|
79
78
|
|
80
79
|
def markup(&block)
|
81
80
|
@markup_block = block
|
82
81
|
end
|
83
82
|
|
83
|
+
# TODO in the future support a string value too
|
84
|
+
def style(&block)
|
85
|
+
@style_block = block
|
86
|
+
end
|
87
|
+
|
84
88
|
def after_render(&block)
|
85
|
-
@
|
86
|
-
@after_render_blocks << block
|
89
|
+
@after_render = block
|
87
90
|
end
|
88
91
|
|
89
92
|
def keyword
|
@@ -95,6 +98,14 @@ module Glimmer
|
|
95
98
|
self.name.underscore.gsub('::', '__').split('__').last
|
96
99
|
end
|
97
100
|
|
101
|
+
def component_element_class
|
102
|
+
self.keyword.gsub('_', '-')
|
103
|
+
end
|
104
|
+
|
105
|
+
def component_shortcut_element_class
|
106
|
+
self.shortcut_keyword.gsub('_', '-')
|
107
|
+
end
|
108
|
+
|
98
109
|
# Creates component without rendering
|
99
110
|
def create(*args)
|
100
111
|
args << {} unless args.last.is_a?(Hash)
|
@@ -105,6 +116,7 @@ module Glimmer
|
|
105
116
|
|
106
117
|
# Creates and renders component
|
107
118
|
def render(*args)
|
119
|
+
Glimmer::DSL::Engine.new_parent_stack unless Glimmer::DSL::Engine.parent.nil?
|
108
120
|
rendered_component = send(keyword, *args)
|
109
121
|
rendered_component
|
110
122
|
end
|
@@ -119,84 +131,129 @@ module Glimmer
|
|
119
131
|
end
|
120
132
|
end
|
121
133
|
|
134
|
+
ADD_COMPONENT_KEYWORDS_UPON_INHERITANCE = proc do
|
135
|
+
class << self
|
136
|
+
def inherited(subclass)
|
137
|
+
Glimmer::Web::Component.add_component_keyword_to_classes_map_for(subclass)
|
138
|
+
subclass.class_eval(&Glimmer::Web::Component::ADD_COMPONENT_KEYWORDS_UPON_INHERITANCE)
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
122
143
|
class << self
|
123
144
|
def included(klass)
|
124
145
|
if !klass.ancestors.include?(GlimmerSupersedable)
|
125
146
|
klass.extend(ClassMethods)
|
126
147
|
klass.include(Glimmer)
|
127
148
|
klass.prepend(GlimmerSupersedable)
|
128
|
-
Glimmer::Web::Component.
|
149
|
+
Glimmer::Web::Component.add_component_keyword_to_classes_map_for(klass)
|
150
|
+
klass.class_eval(&Glimmer::Web::Component::ADD_COMPONENT_KEYWORDS_UPON_INHERITANCE)
|
129
151
|
end
|
130
152
|
end
|
131
153
|
|
132
154
|
def for(underscored_component_name)
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
extracted_namespaces.reduce(base) do |result, namespace|
|
140
|
-
if !result.constants.include?(namespace)
|
141
|
-
namespace = result.constants.detect {|c| c.to_s.upcase == namespace.to_s.upcase } || namespace
|
142
|
-
end
|
143
|
-
begin
|
144
|
-
constant = result.const_get(namespace)
|
145
|
-
return constant if constant&.respond_to?(:ancestors) &&
|
146
|
-
(
|
147
|
-
constant&.ancestors&.to_a.include?(Glimmer::Web::Component) ||
|
148
|
-
# TODO checking GlimmerSupersedable as a hack because when a class is loaded twice (like when loading samples
|
149
|
-
# by reloading ruby files), it loses its Glimmer::Web::Component ancestor as a bug in Opal
|
150
|
-
# but somehow the prepend module remains
|
151
|
-
constant&.ancestors&.to_a.include?(GlimmerSupersedable)
|
152
|
-
)
|
153
|
-
constant
|
154
|
-
rescue => e
|
155
|
-
# Glimmer::Config.logger.debug {"#{e.message}\n#{e.backtrace.join("\n")}"}
|
156
|
-
result
|
157
|
-
end
|
158
|
-
end
|
155
|
+
component_classes = Glimmer::Web::Component.component_keyword_to_classes_map[underscored_component_name]
|
156
|
+
if component_classes.nil? || component_classes.empty?
|
157
|
+
Glimmer::Config.logger.debug {"#{underscored_component_name} has no Glimmer web component class!" }
|
158
|
+
nil
|
159
|
+
else
|
160
|
+
component_class = component_classes.first
|
159
161
|
end
|
160
|
-
raise "#{underscored_component_name} has no Glimmer web component class!"
|
161
|
-
rescue => e
|
162
|
-
Glimmer::Config.logger.debug {e.message}
|
163
|
-
Glimmer::Config.logger.debug {"#{e.message}\n#{e.backtrace.join("\n")}"}
|
164
|
-
nil
|
165
162
|
end
|
166
163
|
|
167
|
-
def
|
168
|
-
|
169
|
-
Glimmer::Web::Component.
|
164
|
+
def add_component_keyword_to_classes_map_for(component_class)
|
165
|
+
keywords_for_class(component_class).each do |keyword|
|
166
|
+
Glimmer::Web::Component.component_keyword_to_classes_map[keyword] ||= []
|
167
|
+
Glimmer::Web::Component.component_keyword_to_classes_map[keyword] << component_class
|
170
168
|
end
|
171
169
|
end
|
172
170
|
|
173
|
-
def
|
174
|
-
|
175
|
-
|
176
|
-
namespace_constants.reduce([Object]) do |output, namespace_constant|
|
177
|
-
output += [output.last.const_get(namespace_constant)]
|
178
|
-
end[1..-1].uniq.reverse
|
171
|
+
def keywords_for_class(component_class)
|
172
|
+
namespaces = component_class.to_s.split(/::/).map(&:underscore).reverse
|
173
|
+
namespaces.size.times.map { |n| namespaces[0..n].reverse.join('__') }
|
179
174
|
end
|
180
175
|
|
181
|
-
def
|
182
|
-
@
|
176
|
+
def component_keyword_to_classes_map
|
177
|
+
@component_keyword_to_classes_map ||= reset_component_keyword_to_classes_map
|
183
178
|
end
|
184
179
|
|
185
|
-
def
|
186
|
-
@
|
180
|
+
def reset_component_keyword_to_classes_map
|
181
|
+
@component_keyword_to_classes_map = {}
|
187
182
|
end
|
188
183
|
|
189
184
|
def interpretation_stack
|
190
185
|
@interpretation_stack ||= []
|
191
186
|
end
|
187
|
+
|
188
|
+
def add_component(component)
|
189
|
+
component_class_to_components_map[component.class] ||= {}
|
190
|
+
component_class_to_components_map[component.class][component.object_id] = component
|
191
|
+
end
|
192
|
+
|
193
|
+
def remove_component(component)
|
194
|
+
component_class_to_components_map[component.class].delete(component.object_id)
|
195
|
+
component_class_to_components_map.delete(component.class) if component_class_to_components_map[component.class].empty?
|
196
|
+
end
|
197
|
+
|
198
|
+
def add_component_style(component)
|
199
|
+
# We must not remove the head style element until all components are removed of a component class
|
200
|
+
if Glimmer::Web::Component.component_count(component.class) == 1
|
201
|
+
Glimmer::Web::Component.component_styles[component.class] = ComponentStyleContainer.render(parent: 'head', component: component, component_style_container_block: component.style_block)
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
def remove_component_style(component)
|
206
|
+
if Glimmer::Web::Component.any_component_style?(component.class)
|
207
|
+
# TODO in the future, you would need to remove style using a jQuery call if you created head element in bulk
|
208
|
+
Glimmer::Web::Component.component_styles[component.class].remove
|
209
|
+
Glimmer::Web::Component.component_styles.delete(component.class)
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
def any_component?(component_class)
|
214
|
+
component_class_to_components_map.has_key?(component_class)
|
215
|
+
end
|
216
|
+
|
217
|
+
def any_component_style?(component_class)
|
218
|
+
component_styles.has_key?(component_class)
|
219
|
+
end
|
220
|
+
|
221
|
+
def component_count(component_class)
|
222
|
+
component_class_to_components_map[component_class].size
|
223
|
+
end
|
224
|
+
|
225
|
+
def components
|
226
|
+
component_class_to_components_map.values.map(&:values).flatten
|
227
|
+
end
|
228
|
+
|
229
|
+
def body_components
|
230
|
+
components.reject {|component| component.is_a?(ComponentStyleContainer)}
|
231
|
+
end
|
232
|
+
|
233
|
+
def head_components
|
234
|
+
components.select {|component| component.is_a?(ComponentStyleContainer)}
|
235
|
+
end
|
236
|
+
|
237
|
+
def remove_all_components
|
238
|
+
# removing body components automatically removes corresponding head components
|
239
|
+
body_components.each(&:remove)
|
240
|
+
end
|
241
|
+
|
242
|
+
def component_class_to_components_map
|
243
|
+
@component_class_to_components_map ||= {}
|
244
|
+
end
|
245
|
+
|
246
|
+
def component_styles
|
247
|
+
@component_styles ||= {}
|
248
|
+
end
|
192
249
|
end
|
193
250
|
# <- end of class methods
|
194
251
|
|
195
|
-
|
196
|
-
attr_reader :markup_root, :parent, :args, :options
|
252
|
+
attr_reader :markup_root, :parent, :args, :options, :style_block, :component_style
|
197
253
|
alias parent_proxy parent
|
198
254
|
|
199
255
|
def initialize(parent, args, options, &content)
|
256
|
+
Glimmer::Web::Component.add_component(self)
|
200
257
|
Component.interpretation_stack.push(self)
|
201
258
|
@parent = parent
|
202
259
|
options = args.delete_at(-1) if args.is_a?(Array) && args.last.is_a?(Hash)
|
@@ -209,10 +266,14 @@ module Glimmer
|
|
209
266
|
options ||= {}
|
210
267
|
@options = self.class.options.merge(options)
|
211
268
|
@content = Util::ProcTracker.new(content) if content
|
269
|
+
# @style_blocks = {} # TODO enable when doing bulk head rendering in the future
|
212
270
|
execute_hooks('before_render')
|
213
271
|
markup_block = self.class.instance_variable_get("@markup_block")
|
272
|
+
# add_style_block
|
214
273
|
raise Glimmer::Error, 'Invalid Glimmer web component for having no markup! Please define markup block!' if markup_block.nil?
|
215
274
|
@markup_root = instance_exec(&markup_block)
|
275
|
+
add_style_block
|
276
|
+
# add_style_to_markup_root
|
216
277
|
@markup_root.options[:parent] = options[:parent] if !options[:parent].nil?
|
217
278
|
@parent ||= @markup_root.parent
|
218
279
|
raise Glimmer::Error, 'Invalid Glimmer web component for having an empty markup! Please fill markup block!' if @markup_root.nil?
|
@@ -319,6 +380,14 @@ module Glimmer
|
|
319
380
|
def remove
|
320
381
|
@markup_root&.remove
|
321
382
|
end
|
383
|
+
|
384
|
+
def data_bind(property, model_binding)
|
385
|
+
@markup_root&.data_bind(property, model_binding)
|
386
|
+
end
|
387
|
+
|
388
|
+
def bind_content(*binding_args, &content_block)
|
389
|
+
@markup_root&.bind_content(*binding_args, &content_block)
|
390
|
+
end
|
322
391
|
|
323
392
|
# Returns content block if used as an attribute reader (no args)
|
324
393
|
# Otherwise, if a block is passed, it adds it as content to this Glimmer web component
|
@@ -355,10 +424,35 @@ module Glimmer
|
|
355
424
|
private
|
356
425
|
|
357
426
|
def execute_hooks(hook_name)
|
358
|
-
self.class.instance_variable_get("@#{hook_name}
|
359
|
-
|
360
|
-
|
427
|
+
hook_block = self.class.instance_variable_get("@#{hook_name}")
|
428
|
+
instance_exec(&hook_block) if hook_block
|
429
|
+
end
|
430
|
+
|
431
|
+
def add_style_block
|
432
|
+
style_block = self.class.instance_variable_get("@style_block")
|
433
|
+
# TODO handle case of style_block being a nil with style value being a string
|
434
|
+
return if style_block.nil?
|
435
|
+
# style_block_component_index = Component.interpretation_stack.size > 1 ? -2 : -1
|
436
|
+
# TODO It might be better to have each component create a style tag in head by accumulating style blocks here first
|
437
|
+
# Component.interpretation_stack[style_block_component_index].style_blocks << style_block
|
438
|
+
Glimmer::Web::Component.add_component_style(self)
|
439
|
+
end
|
440
|
+
|
441
|
+
# TODO render style blocks in head in bulk in the future
|
442
|
+
# def add_style_to_markup_root
|
443
|
+
# if Component.interpretation_stack.size == 1 && !style_blocks.empty?
|
444
|
+
## TODO it might be better to generate element directly instead of relying on ComponentStyle
|
445
|
+
## for performance reasons
|
446
|
+
## TODO rename component_style and capture it by component (might need to have style_blocks a hash mapping component classes to style blocks)
|
447
|
+
# @component_style = ComponentStyleContainer.render(parent: 'head', component: self, style_blocks:)
|
448
|
+
# end
|
449
|
+
# end
|
450
|
+
|
451
|
+
def remove_style_block
|
452
|
+
Glimmer::Web::Component.remove_component_style(self)
|
361
453
|
end
|
362
454
|
end
|
363
455
|
end
|
364
456
|
end
|
457
|
+
|
458
|
+
require 'glimmer/web/component/component_style_container'
|