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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 162d98e6fa4d8eefcec1f4edb2f5af6c3d5d3a88aaa01704ded825b982aabbd9
4
- data.tar.gz: 8c133105ac1cf160658c476771a9b58c0ac351116a1b919cdef37db19bd085e7
3
+ metadata.gz: b8b106a50bf2ec2656480068685e725017a8b7a32f49d38459416b01c550e9aa
4
+ data.tar.gz: ca6c4c2e8028059c5a305c45f181d6f5902e5737d65011dd5ce34b5af334baf0
5
5
  SHA512:
6
- metadata.gz: 5bc70f98db8b2fec83310e789967447663f9d1d7017ad6dd8b1984ce5e520ef835430d5f5b8c885fa92282d63617a58ba42cb30dda6b0212cafd63e3ce378b93
7
- data.tar.gz: 75561cba8a7a7d61e71e286e197dbbb80879261715500249ddd565f0833273777039632f2553a1cb5e0e3c4c1258704fc83f73ea7e163c1d4ab64a0af0e2f307
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 (I copied the ES6 version initially which did things the opposite way from how Todo MVC behaves normally in other versions)
7
- - Make Todo MVC "items left" text show "item left" if there is only 1 todo (I missed this detail before)
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.3.1 (Beta)
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 HTTP 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 Ajax HTTP calls](https://github.com/AndyObtiva/sample-glimmer-dsl-web-rails7-app/blob/master/app/assets/opal/sample_selector/models/sample_selector_presenter.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).
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 = {...} # contains US 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.3.1'
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.3.1'
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.3.1
1
+ 0.4.0
@@ -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.3.1 ruby lib
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.3.1"
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-01"
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.7.8"])
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.4.0"])
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 || proc {|value| value}
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
- converted_value = translated_value = @translator.call(value, evaluate_property)
28
- @element.send("#{@property}=", converted_value) unless evaluate_property == converted_value
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
@@ -8,7 +8,7 @@ module Glimmer
8
8
  include ParentExpression
9
9
 
10
10
  def can_interpret?(parent, keyword, *args, &block)
11
- !!Glimmer::Web::Component.for(keyword)
11
+ Glimmer::Web::Component.for(keyword)
12
12
  end
13
13
 
14
14
  def interpret(parent, keyword, *args, &block)
@@ -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
- @before_render_blocks ||= []
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
- @after_render_blocks ||= []
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.add_component_namespaces_for(klass)
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
- extracted_namespaces = underscored_component_name.
134
- to_s.
135
- split(/__/).map do |namespace|
136
- namespace.camelcase(:upper)
137
- end
138
- Glimmer::Web::Component.component_namespaces.each do |base|
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 add_component_namespaces_for(klass)
168
- Glimmer::Web::Component.namespaces_for_class(klass).drop(1).each do |namespace|
169
- Glimmer::Web::Component.component_namespaces << namespace
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 namespaces_for_class(m)
174
- return [m] if m.name.nil?
175
- namespace_constants = m.name.split(/::/).map(&:to_sym)
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 component_namespaces
182
- @component_namespaces ||= reset_component_namespaces
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 reset_component_namespaces
186
- @component_namespaces = Set[Object, Glimmer::Web]
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}_blocks")&.each do |hook_block|
359
- instance_exec(&hook_block)
360
- end
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'