glimmer-dsl-web 0.3.2 → 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 +13 -2
- data/README.md +71 -8
- data/VERSION +1 -1
- data/glimmer-dsl-web.gemspec +7 -5
- data/lib/glimmer/web/component/component_style_container.rb +13 -0
- data/lib/glimmer/web/component.rb +112 -9
- data/lib/glimmer/web/element_proxy.rb +20 -5
- 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 +18 -24
- 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 +18 -28
- 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 +7 -6
- 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 -6
- 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,16 +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
|
+
|
3
13
|
## 0.3.2
|
4
14
|
|
5
15
|
- Optimize performance (~248% faster) of rendering by changing DSL ordering to avoid component checks at the top
|
6
16
|
- Optimize performance of formatting html elements by adding Glimmer DSL shortcut methods
|
7
17
|
- Optimize performance of component expressions by indexing component keywords
|
18
|
+
- Upgrade to glimmer 2.7.9
|
8
19
|
|
9
20
|
## 0.3.1
|
10
21
|
|
11
22
|
- Optimize Todo MVC performance for filtering between all, active, and completed (it happens instantly now)
|
12
|
-
- Append Todo MVC todos at the bottom instead of prepending them at the top (
|
13
|
-
- 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
|
14
25
|
- Make Todo MVC footer links open a new tab/window (with `target: '_blank'` option)
|
15
26
|
- Refactor/Simplify Todo MVC sample code
|
16
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"])
|
@@ -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
|
@@ -172,14 +184,76 @@ module Glimmer
|
|
172
184
|
def interpretation_stack
|
173
185
|
@interpretation_stack ||= []
|
174
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
|
175
249
|
end
|
176
250
|
# <- end of class methods
|
177
251
|
|
178
|
-
|
179
|
-
attr_reader :markup_root, :parent, :args, :options
|
252
|
+
attr_reader :markup_root, :parent, :args, :options, :style_block, :component_style
|
180
253
|
alias parent_proxy parent
|
181
254
|
|
182
255
|
def initialize(parent, args, options, &content)
|
256
|
+
Glimmer::Web::Component.add_component(self)
|
183
257
|
Component.interpretation_stack.push(self)
|
184
258
|
@parent = parent
|
185
259
|
options = args.delete_at(-1) if args.is_a?(Array) && args.last.is_a?(Hash)
|
@@ -192,10 +266,14 @@ module Glimmer
|
|
192
266
|
options ||= {}
|
193
267
|
@options = self.class.options.merge(options)
|
194
268
|
@content = Util::ProcTracker.new(content) if content
|
269
|
+
# @style_blocks = {} # TODO enable when doing bulk head rendering in the future
|
195
270
|
execute_hooks('before_render')
|
196
271
|
markup_block = self.class.instance_variable_get("@markup_block")
|
272
|
+
# add_style_block
|
197
273
|
raise Glimmer::Error, 'Invalid Glimmer web component for having no markup! Please define markup block!' if markup_block.nil?
|
198
274
|
@markup_root = instance_exec(&markup_block)
|
275
|
+
add_style_block
|
276
|
+
# add_style_to_markup_root
|
199
277
|
@markup_root.options[:parent] = options[:parent] if !options[:parent].nil?
|
200
278
|
@parent ||= @markup_root.parent
|
201
279
|
raise Glimmer::Error, 'Invalid Glimmer web component for having an empty markup! Please fill markup block!' if @markup_root.nil?
|
@@ -346,10 +424,35 @@ module Glimmer
|
|
346
424
|
private
|
347
425
|
|
348
426
|
def execute_hooks(hook_name)
|
349
|
-
self.class.instance_variable_get("@#{hook_name}
|
350
|
-
|
351
|
-
|
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)
|
352
453
|
end
|
353
454
|
end
|
354
455
|
end
|
355
456
|
end
|
457
|
+
|
458
|
+
require 'glimmer/web/component/component_style_container'
|
@@ -123,13 +123,18 @@ module Glimmer
|
|
123
123
|
REGEX_FORMAT_DATE = /^\d{4}-\d{2}-\d{2}$/
|
124
124
|
REGEX_FORMAT_TIME = /^\d{2}:\d{2}$/
|
125
125
|
|
126
|
-
attr_reader :keyword, :parent, :parent_component, :args, :options, :children, :enabled, :foreground, :background, :removed
|
126
|
+
attr_reader :keyword, :parent, :parent_component, :component, :args, :options, :children, :enabled, :foreground, :background, :removed, :rendered
|
127
127
|
alias rendered? rendered
|
128
|
+
alias removed? removed
|
128
129
|
|
129
130
|
def initialize(keyword, parent, args, block)
|
130
131
|
@keyword = keyword
|
131
132
|
@parent = parent.is_a?(Glimmer::Web::Component) ? parent.markup_root : parent
|
132
133
|
@parent_component = parent if parent.is_a?(Glimmer::Web::Component)
|
134
|
+
if Component.interpretation_stack.last&.markup_root.nil?
|
135
|
+
@component = Component.interpretation_stack.last
|
136
|
+
@component&.instance_variable_set("@markup_root", self)
|
137
|
+
end
|
133
138
|
@options = args.last.is_a?(Hash) ? args.last.symbolize_keys : {}
|
134
139
|
if parent.nil?
|
135
140
|
options[:parent] ||= Component.interpretation_stack.last&.options&.[](:parent)
|
@@ -164,7 +169,7 @@ module Glimmer
|
|
164
169
|
|
165
170
|
# Executes at the closing of a parent widget curly braces after all children/properties have been added/set
|
166
171
|
def post_add_content
|
167
|
-
render if bulk_render? && @parent.nil?
|
172
|
+
render if bulk_render? && @parent.nil? && !rendered?
|
168
173
|
end
|
169
174
|
|
170
175
|
def css_classes
|
@@ -172,6 +177,7 @@ module Glimmer
|
|
172
177
|
end
|
173
178
|
|
174
179
|
def remove
|
180
|
+
return if @removed
|
175
181
|
on_remove_listeners = listeners_for('on_remove').dup
|
176
182
|
if rendered?
|
177
183
|
@children.dup.each do |child|
|
@@ -181,6 +187,10 @@ module Glimmer
|
|
181
187
|
dom_element.remove
|
182
188
|
end
|
183
189
|
parent&.post_remove_child(self)
|
190
|
+
if component
|
191
|
+
Glimmer::Web::Component.remove_component(component)
|
192
|
+
component.remove_style_block
|
193
|
+
end
|
184
194
|
@removed = true
|
185
195
|
on_remove_listeners.each do |listener|
|
186
196
|
listener.original_event_listener.call(EventProxy.new(listener: listener))
|
@@ -362,7 +372,12 @@ module Glimmer
|
|
362
372
|
end
|
363
373
|
|
364
374
|
def html_options
|
365
|
-
|
375
|
+
framework_css_classes = [name, element_id]
|
376
|
+
if component
|
377
|
+
framework_css_classes.prepend(component.class.component_element_class)
|
378
|
+
framework_css_classes.prepend(component.class.component_shortcut_element_class) if component.class.component_shortcut_element_class != component.class.component_element_class
|
379
|
+
end
|
380
|
+
body_class = (framework_css_classes + css_classes.to_a).join(' ')
|
366
381
|
html_options = options.dup
|
367
382
|
GLIMMER_ATTRIBUTES.each do |attribute|
|
368
383
|
next unless html_options.include?(attribute)
|
@@ -413,7 +428,7 @@ module Glimmer
|
|
413
428
|
if rendered?
|
414
429
|
dom_element.add_class(css_class)
|
415
430
|
else
|
416
|
-
enqueue_post_render_method_call('
|
431
|
+
enqueue_post_render_method_call('add_css_class', css_class)
|
417
432
|
end
|
418
433
|
end
|
419
434
|
|
@@ -425,7 +440,7 @@ module Glimmer
|
|
425
440
|
if rendered?
|
426
441
|
dom_element.remove_class(css_class)
|
427
442
|
else
|
428
|
-
enqueue_post_render_method_call('
|
443
|
+
enqueue_post_render_method_call('remove_css_class', css_class)
|
429
444
|
end
|
430
445
|
end
|
431
446
|
|
@@ -29,7 +29,7 @@ class NumberHolder
|
|
29
29
|
end
|
30
30
|
end
|
31
31
|
|
32
|
-
class
|
32
|
+
class HelloObserverDataBinding
|
33
33
|
include Glimmer::Web::Component
|
34
34
|
|
35
35
|
before_render do
|
@@ -53,5 +53,5 @@ class HelloObserver
|
|
53
53
|
end
|
54
54
|
|
55
55
|
Document.ready? do
|
56
|
-
|
56
|
+
HelloObserverDataBinding.render
|
57
57
|
end
|
@@ -0,0 +1,178 @@
|
|
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 ButtonModel
|
25
|
+
WIDTH_MIN = 160
|
26
|
+
WIDTH_MAX = 960
|
27
|
+
HEIGHT_MIN = 100
|
28
|
+
HEIGHT_MAX = 600
|
29
|
+
FONT_SIZE_MIN = 40
|
30
|
+
FONT_SIZE_MAX = 200
|
31
|
+
|
32
|
+
attr_accessor :text, :pushed, :width, :height, :font_size
|
33
|
+
|
34
|
+
def initialize
|
35
|
+
@text = 'Push'
|
36
|
+
@width = WIDTH_MIN
|
37
|
+
@height = HEIGHT_MIN
|
38
|
+
@font_size = FONT_SIZE_MIN
|
39
|
+
end
|
40
|
+
|
41
|
+
def push
|
42
|
+
self.pushed = !pushed
|
43
|
+
end
|
44
|
+
|
45
|
+
def text
|
46
|
+
pushed ? 'Pull' : 'Push'
|
47
|
+
end
|
48
|
+
|
49
|
+
def width=(value)
|
50
|
+
@width = value
|
51
|
+
self.font_size = @width/4 if @font_size > @width/4
|
52
|
+
end
|
53
|
+
|
54
|
+
def height=(value)
|
55
|
+
@height = value
|
56
|
+
self.font_size = @height/2.5 if @font_size > @height/2.5
|
57
|
+
end
|
58
|
+
|
59
|
+
def font_size=(value)
|
60
|
+
@font_size = value
|
61
|
+
self.width = @font_size*4 if @height < @font_size*4
|
62
|
+
self.height = @font_size*2.5 if @height < @font_size*2.5
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
class StyledButton
|
67
|
+
include Glimmer::Web::Component
|
68
|
+
|
69
|
+
option :button_model
|
70
|
+
|
71
|
+
markup {
|
72
|
+
button {
|
73
|
+
inner_text <= [button_model, :text, computed_by: :pushed]
|
74
|
+
|
75
|
+
class_name <= [button_model, :pushed,
|
76
|
+
on_read: ->(pushed) { pushed ? 'pushed' : 'pulled' }
|
77
|
+
]
|
78
|
+
|
79
|
+
style <= [ button_model, :width,
|
80
|
+
on_read: method(:button_style_value) # convert value on read before storing in style
|
81
|
+
]
|
82
|
+
|
83
|
+
style <= [ button_model, :height,
|
84
|
+
on_read: method(:button_style_value) # convert value on read before storing in style
|
85
|
+
]
|
86
|
+
|
87
|
+
style <= [ button_model, :font_size,
|
88
|
+
on_read: method(:button_style_value) # convert value on read before storing in style
|
89
|
+
]
|
90
|
+
|
91
|
+
onclick do
|
92
|
+
button_model.push
|
93
|
+
end
|
94
|
+
}
|
95
|
+
}
|
96
|
+
|
97
|
+
style {'
|
98
|
+
button {
|
99
|
+
font-family: Courrier New, Courrier;
|
100
|
+
border-radius: 5px;
|
101
|
+
border-width: 17px;
|
102
|
+
border-color: #ACC7D5;
|
103
|
+
background-color: #ADD8E6;
|
104
|
+
margin: 5px;
|
105
|
+
}
|
106
|
+
|
107
|
+
button.pulled {
|
108
|
+
border-style: outset;
|
109
|
+
}
|
110
|
+
|
111
|
+
button.pushed {
|
112
|
+
border-style: inset;
|
113
|
+
}
|
114
|
+
'}
|
115
|
+
|
116
|
+
def button_style_value
|
117
|
+
"
|
118
|
+
width: #{button_model.width}px;
|
119
|
+
height: #{button_model.height}px;
|
120
|
+
font-size: #{button_model.font_size}px;
|
121
|
+
"
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
class StyledButtonRangeInput
|
126
|
+
include Glimmer::Web::Component
|
127
|
+
|
128
|
+
option :button_model
|
129
|
+
option :property
|
130
|
+
option :property_min
|
131
|
+
option :property_max
|
132
|
+
|
133
|
+
markup {
|
134
|
+
input(type: 'range', min: property_min, max: property_max) {
|
135
|
+
value <=> [button_model, property]
|
136
|
+
}
|
137
|
+
}
|
138
|
+
end
|
139
|
+
|
140
|
+
class HelloStyle
|
141
|
+
include Glimmer::Web::Component
|
142
|
+
|
143
|
+
before_render do
|
144
|
+
@button_model = ButtonModel.new
|
145
|
+
end
|
146
|
+
|
147
|
+
markup {
|
148
|
+
div(class: 'hello-style') {
|
149
|
+
div(class: 'form-row') {
|
150
|
+
label('Styled Button Width:', for: 'styled-button-width-input')
|
151
|
+
styled_button_range_input(button_model: @button_model, property: :width, property_min: ButtonModel::WIDTH_MIN, property_max: ButtonModel::WIDTH_MAX, id: 'styled-button-width-input')
|
152
|
+
}
|
153
|
+
div(class: 'form-row') {
|
154
|
+
label('Styled Button Height:', for: 'styled-button-height-input')
|
155
|
+
styled_button_range_input(button_model: @button_model, property: :height, property_min: ButtonModel::HEIGHT_MIN, property_max: ButtonModel::HEIGHT_MAX, id: 'styled-button-height-input')
|
156
|
+
}
|
157
|
+
div(class: 'form-row') {
|
158
|
+
label('Styled Button Font Size:', for: 'styled-button-font-size-input')
|
159
|
+
styled_button_range_input(button_model: @button_model, property: :font_size, property_min: ButtonModel::FONT_SIZE_MIN, property_max: ButtonModel::FONT_SIZE_MAX, id: 'styled-button-font-size-input')
|
160
|
+
}
|
161
|
+
styled_button(button_model: @button_model)
|
162
|
+
}
|
163
|
+
}
|
164
|
+
|
165
|
+
style {'
|
166
|
+
.hello-style {
|
167
|
+
padding: 20px;
|
168
|
+
}
|
169
|
+
|
170
|
+
.hello-style .form-row {
|
171
|
+
margin: 10px 0;
|
172
|
+
}
|
173
|
+
'}
|
174
|
+
end
|
175
|
+
|
176
|
+
Document.ready? do
|
177
|
+
HelloStyle.render
|
178
|
+
end
|
@@ -4,8 +4,8 @@ class EditTodoInput < TodoInput
|
|
4
4
|
option :presenter
|
5
5
|
option :todo
|
6
6
|
|
7
|
-
markup {
|
8
|
-
input
|
7
|
+
markup { # evaluated against instance as a smart default convention
|
8
|
+
input { |edit_input|
|
9
9
|
style <= [ todo, :editing,
|
10
10
|
on_read: ->(editing) { editing ? '' : 'display: none;' },
|
11
11
|
after_read: -> { edit_input.focus if todo.editing? }
|
@@ -27,27 +27,21 @@ class EditTodoInput < TodoInput
|
|
27
27
|
end
|
28
28
|
}
|
29
29
|
}
|
30
|
-
|
31
|
-
class << self
|
32
|
-
def todo_input_class
|
33
|
-
'edit-todo'
|
34
|
-
end
|
35
30
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
end
|
31
|
+
style { # evaluated against class as a smart default convention (common to all instances)
|
32
|
+
todo_input_styles
|
33
|
+
|
34
|
+
rule("*:has(> .#{component_element_class})") {
|
35
|
+
position 'relative'
|
36
|
+
}
|
37
|
+
|
38
|
+
rule(".#{component_element_class}") {
|
39
|
+
position 'absolute'
|
40
|
+
display 'block'
|
41
|
+
width 'calc(100% - 43px)'
|
42
|
+
padding '12px 16px'
|
43
|
+
margin '0 0 0 43px'
|
44
|
+
top '0'
|
45
|
+
}
|
46
|
+
}
|
53
47
|
end
|
@@ -10,21 +10,21 @@ class NewTodoForm
|
|
10
10
|
h1('todos')
|
11
11
|
|
12
12
|
new_todo_input(presenter: presenter)
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
13
|
+
}
|
14
|
+
}
|
15
|
+
|
16
|
+
style {
|
17
|
+
rule('.header h1') {
|
18
|
+
color '#b83f45'
|
19
|
+
font_size '80px'
|
20
|
+
font_weight '200'
|
21
|
+
position 'absolute'
|
22
|
+
text_align 'center'
|
23
|
+
_webkit_text_rendering 'optimizeLegibility'
|
24
|
+
_moz_text_rendering 'optimizeLegibility'
|
25
|
+
text_rendering 'optimizeLegibility'
|
26
|
+
top '-140px'
|
27
|
+
width '100%'
|
28
28
|
}
|
29
29
|
}
|
30
30
|
end
|
@@ -3,41 +3,31 @@ require_relative 'todo_input'
|
|
3
3
|
class NewTodoInput < TodoInput
|
4
4
|
option :presenter
|
5
5
|
|
6
|
-
markup {
|
7
|
-
input(
|
6
|
+
markup { # evaluated against instance as a smart default convention
|
7
|
+
input(placeholder: "What needs to be done?", autofocus: "") {
|
8
8
|
value <=> [presenter.new_todo, :task]
|
9
9
|
|
10
10
|
onkeyup do |event|
|
11
11
|
presenter.create_todo if event.key == 'Enter' || event.keyCode == "\r"
|
12
12
|
end
|
13
|
-
|
14
|
-
style {
|
15
|
-
self.class.todo_input_styles
|
16
|
-
}
|
17
13
|
}
|
18
14
|
}
|
19
15
|
|
20
|
-
class
|
21
|
-
|
22
|
-
|
23
|
-
|
16
|
+
style { # evaluated against class as a smart default convention (common to all instances)
|
17
|
+
todo_input_styles
|
18
|
+
|
19
|
+
rule(".#{component_element_class}") { # built-in component_class.component_element_class (e.g. NewTodoInput has CSS class as new-todo-input)
|
20
|
+
padding '16px 16px 16px 60px'
|
21
|
+
height '65px'
|
22
|
+
border 'none'
|
23
|
+
background 'rgba(0, 0, 0, 0.003)'
|
24
|
+
box_shadow 'inset 0 -2px 1px rgba(0,0,0,0.03)'
|
25
|
+
}
|
24
26
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
border 'none'
|
32
|
-
background 'rgba(0, 0, 0, 0.003)'
|
33
|
-
box_shadow 'inset 0 -2px 1px rgba(0,0,0,0.03)'
|
34
|
-
}
|
35
|
-
|
36
|
-
rule(".#{todo_input_class}::placeholder") {
|
37
|
-
font_style 'italic'
|
38
|
-
font_weight '400'
|
39
|
-
color 'rgba(0, 0, 0, 0.4)'
|
40
|
-
}
|
41
|
-
end
|
42
|
-
end
|
27
|
+
rule(".#{component_element_class}::placeholder") { # built-in component_class.component_element_class (e.g. NewTodoInput has CSS class as new-todo-input)
|
28
|
+
font_style 'italic'
|
29
|
+
font_weight '400'
|
30
|
+
color 'rgba(0, 0, 0, 0.4)'
|
31
|
+
}
|
32
|
+
}
|
43
33
|
end
|
@@ -45,80 +45,80 @@ class TodoFilters
|
|
45
45
|
presenter.clear_completed
|
46
46
|
end
|
47
47
|
}
|
48
|
+
}
|
49
|
+
}
|
50
|
+
|
51
|
+
style {
|
52
|
+
rule('.todo-filters') {
|
53
|
+
border_top '1px solid #e6e6e6'
|
54
|
+
font_size '15px'
|
55
|
+
height '20px'
|
56
|
+
padding '10px 15px'
|
57
|
+
text_align 'center'
|
58
|
+
}
|
59
|
+
|
60
|
+
rule('.todo-filters:before') {
|
61
|
+
bottom '0'
|
62
|
+
box_shadow '0 1px 1px rgba(0,0,0,.2), 0 8px 0 -3px #f6f6f6, 0 9px 1px -3px rgba(0,0,0,.2), 0 16px 0 -6px #f6f6f6, 0 17px 2px -6px rgba(0,0,0,.2)'
|
63
|
+
content '""'
|
64
|
+
height '50px'
|
65
|
+
left '0'
|
66
|
+
overflow 'hidden'
|
67
|
+
position 'absolute'
|
68
|
+
right '0'
|
69
|
+
}
|
70
|
+
|
71
|
+
rule('.todo-count') {
|
72
|
+
float 'left'
|
73
|
+
text_align 'left'
|
74
|
+
}
|
75
|
+
|
76
|
+
rule('.todo-count .strong') {
|
77
|
+
font_weight '300'
|
78
|
+
}
|
79
|
+
|
80
|
+
rule('.filters') {
|
81
|
+
left '0'
|
82
|
+
list_style 'none'
|
83
|
+
margin '0'
|
84
|
+
padding '0'
|
85
|
+
position 'absolute'
|
86
|
+
right '0'
|
87
|
+
}
|
88
|
+
|
89
|
+
rule('.filters li') {
|
90
|
+
display 'inline'
|
91
|
+
}
|
92
|
+
|
93
|
+
rule('.filters li a') {
|
94
|
+
border '1px solid transparent'
|
95
|
+
border_radius '3px'
|
96
|
+
color 'inherit'
|
97
|
+
margin '3px'
|
98
|
+
padding '3px 7px'
|
99
|
+
text_decoration 'none'
|
100
|
+
cursor 'pointer'
|
101
|
+
}
|
102
|
+
|
103
|
+
rule('.filters li a.selected') {
|
104
|
+
border_color '#ce4646'
|
105
|
+
}
|
106
|
+
|
107
|
+
rule('.clear-completed, html .clear-completed:active') {
|
108
|
+
cursor 'pointer'
|
109
|
+
float 'right'
|
110
|
+
line_height '19px'
|
111
|
+
position 'relative'
|
112
|
+
text_decoration 'none'
|
113
|
+
}
|
114
|
+
|
115
|
+
media('(max-width: 430px)') {
|
116
|
+
rule('.todo-filters') {
|
117
|
+
height '50px'
|
118
|
+
}
|
48
119
|
|
49
|
-
|
50
|
-
|
51
|
-
border_top '1px solid #e6e6e6'
|
52
|
-
font_size '15px'
|
53
|
-
height '20px'
|
54
|
-
padding '10px 15px'
|
55
|
-
text_align 'center'
|
56
|
-
}
|
57
|
-
|
58
|
-
rule('.todo-filters:before') {
|
59
|
-
bottom '0'
|
60
|
-
box_shadow '0 1px 1px rgba(0,0,0,.2), 0 8px 0 -3px #f6f6f6, 0 9px 1px -3px rgba(0,0,0,.2), 0 16px 0 -6px #f6f6f6, 0 17px 2px -6px rgba(0,0,0,.2)'
|
61
|
-
content '""'
|
62
|
-
height '50px'
|
63
|
-
left '0'
|
64
|
-
overflow 'hidden'
|
65
|
-
position 'absolute'
|
66
|
-
right '0'
|
67
|
-
}
|
68
|
-
|
69
|
-
rule('.todo-count') {
|
70
|
-
float 'left'
|
71
|
-
text_align 'left'
|
72
|
-
}
|
73
|
-
|
74
|
-
rule('.todo-count .strong') {
|
75
|
-
font_weight '300'
|
76
|
-
}
|
77
|
-
|
78
|
-
rule('.filters') {
|
79
|
-
left '0'
|
80
|
-
list_style 'none'
|
81
|
-
margin '0'
|
82
|
-
padding '0'
|
83
|
-
position 'absolute'
|
84
|
-
right '0'
|
85
|
-
}
|
86
|
-
|
87
|
-
rule('.filters li') {
|
88
|
-
display 'inline'
|
89
|
-
}
|
90
|
-
|
91
|
-
rule('.filters li a') {
|
92
|
-
border '1px solid transparent'
|
93
|
-
border_radius '3px'
|
94
|
-
color 'inherit'
|
95
|
-
margin '3px'
|
96
|
-
padding '3px 7px'
|
97
|
-
text_decoration 'none'
|
98
|
-
cursor 'pointer'
|
99
|
-
}
|
100
|
-
|
101
|
-
rule('.filters li a.selected') {
|
102
|
-
border_color '#ce4646'
|
103
|
-
}
|
104
|
-
|
105
|
-
rule('.clear-completed, html .clear-completed:active') {
|
106
|
-
cursor 'pointer'
|
107
|
-
float 'right'
|
108
|
-
line_height '19px'
|
109
|
-
position 'relative'
|
110
|
-
text_decoration 'none'
|
111
|
-
}
|
112
|
-
|
113
|
-
media('(max-width: 430px)') {
|
114
|
-
rule('.todo-filters') {
|
115
|
-
height '50px'
|
116
|
-
}
|
117
|
-
|
118
|
-
rule('.filters') {
|
119
|
-
bottom '10px'
|
120
|
-
}
|
121
|
-
}
|
120
|
+
rule('.filters') {
|
121
|
+
bottom '10px'
|
122
122
|
}
|
123
123
|
}
|
124
124
|
}
|
@@ -3,12 +3,8 @@ class TodoInput
|
|
3
3
|
include Glimmer::Web::Component
|
4
4
|
|
5
5
|
class << self
|
6
|
-
def todo_input_class
|
7
|
-
'todo-input'
|
8
|
-
end
|
9
|
-
|
10
6
|
def todo_input_styles
|
11
|
-
rule(".#{
|
7
|
+
rule(".#{component_element_class}") {
|
12
8
|
position 'relative'
|
13
9
|
margin '0'
|
14
10
|
width '100%'
|
@@ -24,9 +20,14 @@ class TodoInput
|
|
24
20
|
_webkit_font_smoothing 'antialiased'
|
25
21
|
}
|
26
22
|
|
27
|
-
rule(".#{
|
23
|
+
rule(".#{component_element_class}::selection") {
|
28
24
|
background 'red'
|
29
25
|
}
|
26
|
+
|
27
|
+
rule(".#{component_element_class}:focus") {
|
28
|
+
box_shadow '0 0 2px 2px #cf7d7d'
|
29
|
+
outline '0'
|
30
|
+
}
|
30
31
|
end
|
31
32
|
end
|
32
33
|
end
|
@@ -38,16 +38,10 @@ class TodoList
|
|
38
38
|
todo_list_item(presenter:, todo:)
|
39
39
|
end
|
40
40
|
}
|
41
|
-
|
42
|
-
style {
|
43
|
-
todo_list_styles
|
44
|
-
}
|
45
41
|
}
|
46
42
|
}
|
47
43
|
|
48
|
-
|
49
|
-
TodoListItem.todo_list_item_styles
|
50
|
-
|
44
|
+
style {
|
51
45
|
rule('.main') {
|
52
46
|
border_top '1px solid #e6e6e6'
|
53
47
|
position 'relative'
|
@@ -86,7 +80,7 @@ class TodoList
|
|
86
80
|
transform 'rotate(90deg)'
|
87
81
|
}
|
88
82
|
|
89
|
-
rule('.toggle-all:focus+label, .toggle:focus+label
|
83
|
+
rule('.toggle-all:focus+label, .toggle:focus+label') {
|
90
84
|
box_shadow '0 0 2px 2px #cf7d7d'
|
91
85
|
outline '0'
|
92
86
|
}
|
@@ -104,5 +98,5 @@ class TodoList
|
|
104
98
|
rule('.todo-list.completed li.active') {
|
105
99
|
display 'none'
|
106
100
|
}
|
107
|
-
|
101
|
+
}
|
108
102
|
end
|
@@ -31,6 +31,12 @@ class TodoListItem
|
|
31
31
|
inner_html <= [todo, :task]
|
32
32
|
|
33
33
|
ondblclick do |event|
|
34
|
+
# if the markup root (li) last child is not an input field, re-open content and add an edit input field
|
35
|
+
unless markup_root.children.last.keyword == 'input'
|
36
|
+
markup_root.content {
|
37
|
+
edit_todo_input(presenter:, todo:)
|
38
|
+
}
|
39
|
+
end
|
34
40
|
todo.start_editing
|
35
41
|
end
|
36
42
|
}
|
@@ -41,8 +47,6 @@ class TodoListItem
|
|
41
47
|
end
|
42
48
|
}
|
43
49
|
}
|
44
|
-
|
45
|
-
edit_todo_input(presenter:, todo:)
|
46
50
|
}
|
47
51
|
}
|
48
52
|
|
@@ -54,9 +58,7 @@ class TodoListItem
|
|
54
58
|
classes.join(' ')
|
55
59
|
end
|
56
60
|
|
57
|
-
|
58
|
-
EditTodoInput.todo_input_styles
|
59
|
-
|
61
|
+
style {
|
60
62
|
rule('.todo-list li.completed label') {
|
61
63
|
color '#949494'
|
62
64
|
text_decoration 'line-through'
|
@@ -155,6 +157,6 @@ class TodoListItem
|
|
155
157
|
height '40px'
|
156
158
|
}
|
157
159
|
}
|
158
|
-
|
160
|
+
}
|
159
161
|
end
|
160
162
|
|
@@ -12,14 +12,10 @@ class TodoMvcFooter
|
|
12
12
|
p {
|
13
13
|
"Part of #{a('TodoMVC', href: 'http://todomvc.com', target: '_blank')}"
|
14
14
|
}
|
15
|
-
|
16
|
-
style {
|
17
|
-
todo_mvc_styles
|
18
|
-
}
|
19
15
|
}
|
20
16
|
}
|
21
17
|
|
22
|
-
|
18
|
+
style {
|
23
19
|
rule('footer.info') {
|
24
20
|
margin '65px auto 0'
|
25
21
|
color '#4d4d4d'
|
@@ -41,5 +37,5 @@ class TodoMvcFooter
|
|
41
37
|
rule('footer.info a:hover') {
|
42
38
|
text_decoration 'underline'
|
43
39
|
}
|
44
|
-
|
40
|
+
}
|
45
41
|
end
|
@@ -26,10 +26,6 @@ class TodoMvc
|
|
26
26
|
todo_list(presenter: @presenter)
|
27
27
|
|
28
28
|
todo_filters(presenter: @presenter)
|
29
|
-
|
30
|
-
style {
|
31
|
-
todo_mvc_styles
|
32
|
-
}
|
33
29
|
}
|
34
30
|
|
35
31
|
todo_mvc_footer
|
@@ -40,7 +36,7 @@ class TodoMvc
|
|
40
36
|
}
|
41
37
|
}
|
42
38
|
|
43
|
-
|
39
|
+
style {
|
44
40
|
rule('body, button, html') {
|
45
41
|
margin '0'
|
46
42
|
padding '0'
|
@@ -79,7 +75,7 @@ class TodoMvc
|
|
79
75
|
font_weight '300'
|
80
76
|
}
|
81
77
|
}
|
82
|
-
|
78
|
+
}
|
83
79
|
end
|
84
80
|
|
85
81
|
Document.ready? do
|
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.
|
4
|
+
version: 0.4.0
|
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-07-
|
11
|
+
date: 2024-07-22 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.
|
19
|
+
version: 2.8.0
|
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.
|
26
|
+
version: 2.8.0
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: glimmer-dsl-xml
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -44,14 +44,14 @@ dependencies:
|
|
44
44
|
requirements:
|
45
45
|
- - "~>"
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version: 1.
|
47
|
+
version: 1.5.0
|
48
48
|
type: :runtime
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
52
|
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
|
-
version: 1.
|
54
|
+
version: 1.5.0
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
56
|
name: opal
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -279,6 +279,7 @@ files:
|
|
279
279
|
- lib/glimmer-dsl-web/samples/hello/hello_observer.rb
|
280
280
|
- lib/glimmer-dsl-web/samples/hello/hello_observer_data_binding.rb
|
281
281
|
- lib/glimmer-dsl-web/samples/hello/hello_paragraph.rb
|
282
|
+
- lib/glimmer-dsl-web/samples/hello/hello_style.rb
|
282
283
|
- lib/glimmer-dsl-web/samples/hello/hello_world.rb
|
283
284
|
- lib/glimmer-dsl-web/samples/regular/button_counter.rb
|
284
285
|
- lib/glimmer-dsl-web/samples/regular/todo_mvc.rb
|
@@ -314,6 +315,7 @@ files:
|
|
314
315
|
- lib/glimmer/util/proc_tracker.rb
|
315
316
|
- lib/glimmer/web.rb
|
316
317
|
- lib/glimmer/web/component.rb
|
318
|
+
- lib/glimmer/web/component/component_style_container.rb
|
317
319
|
- lib/glimmer/web/element_proxy.rb
|
318
320
|
- lib/glimmer/web/event_proxy.rb
|
319
321
|
- lib/glimmer/web/formatting_element_proxy.rb
|