glimmer-dsl-web 0.4.0 → 0.4.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +14 -2
- data/README.md +20 -6
- data/VERSION +1 -1
- data/glimmer-dsl-web.gemspec +7 -5
- data/lib/glimmer/data_binding/element_binding.rb +29 -13
- data/lib/glimmer/dsl/web/dsl.rb +4 -2
- data/lib/glimmer/dsl/web/element_expression.rb +12 -1
- data/lib/glimmer/dsl/web/inline_style_data_binding_expression.rb +21 -0
- data/lib/glimmer/dsl/web/{style_expression.rb → style_element_expression.rb} +1 -1
- data/lib/glimmer/web/element_proxy.rb +80 -16
- data/lib/glimmer-dsl-web/samples/hello/hello_style.rb +58 -41
- data/lib/glimmer-dsl-web/samples/hello/hello_svg.rb +45 -0
- data/lib/glimmer-dsl-web/samples/regular/todo_mvc/views/new_todo_input.rb +4 -4
- metadata +7 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b8629cf007ddb0ff30d296d3c3dbddaf87572953cdbe9ee18128794a2c435441
|
4
|
+
data.tar.gz: 4353e741c102e61df81da7352a4b999c487e579b235993a8720ecc3ba7b8ec19
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3c28bcc2664f375e9e7e795f80a104e62d8b73f54e72a6f7d649db92e68da6c3632bf1cede91ae9163a7b29cb4cb573f2bb8988f36bb75ca16be20a551182a34
|
7
|
+
data.tar.gz: 4d08a8fcf6b8d6ec2ab61d0e0e940806336c514e2f9ed01179f0b3d0a8299957ce4d0d2677ac5cd0bf3f84d114cfca69fc46592378a9d720ec4f52a6f278be16
|
data/CHANGELOG.md
CHANGED
@@ -1,11 +1,23 @@
|
|
1
1
|
# Change Log
|
2
2
|
|
3
|
+
## 0.4.2
|
4
|
+
|
5
|
+
- Support element inline style data-binding (e.g. `style(:background_color) <= [@button_model, :background_color]`)
|
6
|
+
- Support SVG element keywords
|
7
|
+
- Hello, SVG! Sample: `require 'glimmer-dsl-web/samples/hello/hello_svg'`
|
8
|
+
|
9
|
+
## 0.4.1
|
10
|
+
|
11
|
+
- Enhance Hello, Style! sample to allow setting styled button background color
|
12
|
+
- Fixed issue with `Glimmer::Web::Component::component_element_class` not working in components when programmatically changing the component markup root CSS class
|
13
|
+
|
3
14
|
## 0.4.0
|
4
15
|
|
5
|
-
- Support `style {}` block in `Glimmer::Web::Component` that would automatically add style in one place for all
|
16
|
+
- Support `style {}` block in `Glimmer::Web::Component` that would automatically add style in one place for all component instances
|
17
|
+
- Support `Glimmer::Web::Component::component_element_class` to return CSS class automatically generated for a component based on its name (e.g. `StyledButton` gets `styled-button` CSS class)
|
18
|
+
- Hello, Style! Sample: `require 'glimmer-dsl-web/samples/hello/hello_style'`
|
6
19
|
- Upgrade to Glimmer DSL for CSS 1.5.0
|
7
20
|
- 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
21
|
- Optimize performance of Todo MVC by not adding an edit input field to every todo, yet adding it only upon editing a todo.
|
10
22
|
- Fix issue with `ElementProxy#add_css_class` and `ElementProxy#remove_css_class` crashing if called before rendering an element
|
11
23
|
- Fix issue with Hello, Observer (Data-Binding)! crashing if run after Hello, Observer! due to both samples sharing the same class by mistake
|
data/README.md
CHANGED
@@ -1,5 +1,5 @@
|
|
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.
|
2
|
-
## Ruby
|
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.2 (Beta)
|
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)
|
5
5
|
[![Join the chat at https://gitter.im/AndyObtiva/glimmer](https://badges.gitter.im/AndyObtiva/glimmer.svg)](https://gitter.im/AndyObtiva/glimmer?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
@@ -8,7 +8,7 @@
|
|
8
8
|
|
9
9
|
**(Talk Videos: [Intro to Ruby in the Browser](https://youtu.be/4AdcfbI6A4c?si=MmxOrkhIXTDHQoYi) & [Frontend Ruby with Glimmer DSL for Web](https://youtu.be/rIZ-ILUv9ME?si=raygUXVPd_7ypWuE))**
|
10
10
|
|
11
|
-
[![Todo MVC](/images/glimmer-dsl-web-samples-regular-todo-mvc.gif)](
|
11
|
+
[![Todo MVC](/images/glimmer-dsl-web-samples-regular-todo-mvc.gif)](https://sample-glimmer-dsl-web-rails7-app-black-sound-6793.fly.dev/)
|
12
12
|
|
13
13
|
You can finally have Ruby developer happiness and productivity in the Frontend! No more wasting time splitting your resources across multiple languages, using badly engineered, over-engineered, or premature-optimization-obsessed JavaScript libraries, fighting JavaScript build issues (e.g. webpack), or rewriting Ruby Backend code in Frontend JavaScript. With [Ruby in the Browser](https://www.youtube.com/watch?v=4AdcfbI6A4c), you can have an exponential jump in development productivity (2x or higher), time-to-release (1/2 or less time), cost (1/2 or cheaper), and maintainability (~50% the code that is simpler and more readable) over JavaScript libraries like React, Angular, Ember, Vue, and Svelte, while being able to reuse Backend Ruby code as is in the Frontend for faster interactions when needed. Also, with Frontend Ruby, companies can cut their hiring budget in half by having Backend Ruby Software Engineers do Frontend Development in Ruby! [Ruby in the Browser](https://www.youtube.com/watch?v=4AdcfbI6A4c) finally fulfills every smart highly-productive Rubyist's dream by bringing Ruby productivity fun to Frontend Development, the same productivity fun you had for years and decades in Backend Development.
|
14
14
|
|
@@ -1345,6 +1345,7 @@ Learn more about the differences between various [Glimmer](https://github.com/An
|
|
1345
1345
|
- [Todo MVC](#todo-mvc)
|
1346
1346
|
- [Design Principles](#design-principles)
|
1347
1347
|
- [Supporting Libraries](#supporting-libraries)
|
1348
|
+
- [Influences and Inspiration](#influences-and-inspiration)
|
1348
1349
|
- [Glimmer Process](#glimmer-process)
|
1349
1350
|
- [Help](#help)
|
1350
1351
|
- [Issues](#issues)
|
@@ -1391,7 +1392,7 @@ rails new glimmer_app_server
|
|
1391
1392
|
Add the following to `Gemfile`:
|
1392
1393
|
|
1393
1394
|
```
|
1394
|
-
gem 'glimmer-dsl-web', '~> 0.4.
|
1395
|
+
gem 'glimmer-dsl-web', '~> 0.4.2'
|
1395
1396
|
```
|
1396
1397
|
|
1397
1398
|
Run:
|
@@ -1620,7 +1621,7 @@ Disable the `webpacker` gem line in `Gemfile`:
|
|
1620
1621
|
Add the following to `Gemfile`:
|
1621
1622
|
|
1622
1623
|
```ruby
|
1623
|
-
gem 'glimmer-dsl-web', '~> 0.4.
|
1624
|
+
gem 'glimmer-dsl-web', '~> 0.4.2'
|
1624
1625
|
```
|
1625
1626
|
|
1626
1627
|
Run:
|
@@ -1881,7 +1882,11 @@ This external Sample Selector app is built using Rails and Glimmer DSL for Web,
|
|
1881
1882
|
|
1882
1883
|
https://github.com/AndyObtiva/sample-glimmer-dsl-web-rails7-app
|
1883
1884
|
|
1884
|
-
|
1885
|
+
A deployed version of the Sample Selector app can be accessed over here:
|
1886
|
+
|
1887
|
+
https://sample-glimmer-dsl-web-rails7-app-black-sound-6793.fly.dev/
|
1888
|
+
|
1889
|
+
[![Sample Selector](https://raw.githubusercontent.com/AndyObtiva/sample-glimmer-dsl-web-rails7-app/master/sample-glimmer-dsl-web-rails7-app.png)](https://sample-glimmer-dsl-web-rails7-app-black-sound-6793.fly.dev/)
|
1885
1890
|
|
1886
1891
|
### Hello Samples
|
1887
1892
|
|
@@ -3444,6 +3449,15 @@ Here is a list of notable 3rd party gems used by Glimmer DSL for Web:
|
|
3444
3449
|
- [opal-async](https://github.com/AndyObtiva/opal-async): Non-blocking tasks and enumerators for Web.
|
3445
3450
|
- [to_collection](https://github.com/AndyObtiva/to_collection): Treat an array of objects and a singular object uniformly as a collection of objects.
|
3446
3451
|
|
3452
|
+
## Influences and Inspiration
|
3453
|
+
|
3454
|
+
- https://github.com/inesita-rb/inesita
|
3455
|
+
- https://github.com/opal/paggio
|
3456
|
+
- https://github.com/ruby-hyperloop/hyperloop
|
3457
|
+
- https://docs.hyperstack.org/
|
3458
|
+
- https://github.com/AndyObtiva/glimmer-dsl-opal
|
3459
|
+
- https://github.com/AndyObtiva/glimmer
|
3460
|
+
|
3447
3461
|
## Glimmer Process
|
3448
3462
|
|
3449
3463
|
[Glimmer Process](https://github.com/AndyObtiva/glimmer/blob/master/PROCESS.md) is the lightweight software development process used for building Glimmer libraries and Glimmer apps, which goes beyond Agile, rendering all Agile processes obsolete. [Glimmer Process](https://github.com/AndyObtiva/glimmer/blob/master/PROCESS.md) is simply made up of 7 guidelines to pick and choose as necessary until software development needs are satisfied.
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.4.
|
1
|
+
0.4.2
|
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.4.
|
5
|
+
# stub: glimmer-dsl-web 0.4.2 ruby lib
|
6
6
|
|
7
7
|
Gem::Specification.new do |s|
|
8
8
|
s.name = "glimmer-dsl-web".freeze
|
9
|
-
s.version = "0.4.
|
9
|
+
s.version = "0.4.2"
|
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-28"
|
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 = [
|
@@ -47,6 +47,7 @@ Gem::Specification.new do |s|
|
|
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
49
|
"lib/glimmer-dsl-web/samples/hello/hello_style.rb",
|
50
|
+
"lib/glimmer-dsl-web/samples/hello/hello_svg.rb",
|
50
51
|
"lib/glimmer-dsl-web/samples/hello/hello_world.rb",
|
51
52
|
"lib/glimmer-dsl-web/samples/regular/button_counter.rb",
|
52
53
|
"lib/glimmer-dsl-web/samples/regular/todo_mvc.rb",
|
@@ -72,12 +73,13 @@ Gem::Specification.new do |s|
|
|
72
73
|
"lib/glimmer/dsl/web/element_expression.rb",
|
73
74
|
"lib/glimmer/dsl/web/formatting_element_expression.rb",
|
74
75
|
"lib/glimmer/dsl/web/general_element_expression.rb",
|
76
|
+
"lib/glimmer/dsl/web/inline_style_data_binding_expression.rb",
|
75
77
|
"lib/glimmer/dsl/web/listener_expression.rb",
|
76
78
|
"lib/glimmer/dsl/web/observe_expression.rb",
|
77
79
|
"lib/glimmer/dsl/web/property_expression.rb",
|
78
80
|
"lib/glimmer/dsl/web/shine_data_binding_expression.rb",
|
79
81
|
"lib/glimmer/dsl/web/span_expression.rb",
|
80
|
-
"lib/glimmer/dsl/web/
|
82
|
+
"lib/glimmer/dsl/web/style_element_expression.rb",
|
81
83
|
"lib/glimmer/helpers/glimmer_helper.rb",
|
82
84
|
"lib/glimmer/util/proc_tracker.rb",
|
83
85
|
"lib/glimmer/web.rb",
|
@@ -97,7 +99,7 @@ Gem::Specification.new do |s|
|
|
97
99
|
|
98
100
|
s.add_runtime_dependency(%q<glimmer>.freeze, ["~> 2.8.0"])
|
99
101
|
s.add_runtime_dependency(%q<glimmer-dsl-xml>.freeze, ["~> 1.4.0"])
|
100
|
-
s.add_runtime_dependency(%q<glimmer-dsl-css>.freeze, ["~> 1.5.
|
102
|
+
s.add_runtime_dependency(%q<glimmer-dsl-css>.freeze, ["~> 1.5.1"])
|
101
103
|
s.add_runtime_dependency(%q<opal>.freeze, ["= 1.8.2"])
|
102
104
|
s.add_runtime_dependency(%q<opal-rails>.freeze, ["= 2.0.3"])
|
103
105
|
s.add_runtime_dependency(%q<opal-async>.freeze, ["~> 1.4.1"])
|
@@ -6,30 +6,46 @@ module Glimmer
|
|
6
6
|
class ElementBinding
|
7
7
|
include Observable
|
8
8
|
include Observer
|
9
|
-
|
10
|
-
attr_reader :element, :property
|
11
|
-
def initialize(element, property, translator
|
9
|
+
|
10
|
+
attr_reader :element, :property, :translator, :sub_property
|
11
|
+
def initialize(element, property, translator: nil)
|
12
12
|
@element = element
|
13
|
-
|
13
|
+
if (property_parts = property.to_s.match(Glimmer::Web::ElementProxy::REGEX_STYLE_SUB_PROPERTY))
|
14
|
+
@property, @sub_property = property_parts.to_a.drop(1)
|
15
|
+
@sub_property = @sub_property.gsub('_', '-')
|
16
|
+
else
|
17
|
+
@property = property
|
18
|
+
end
|
14
19
|
@translator = translator
|
15
20
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
# unregister_all_observables
|
21
|
-
# end
|
22
|
-
# end
|
21
|
+
if @element.respond_to?(:remove)
|
22
|
+
unregister_handler = lambda { |dispose_event| unregister_all_observables }
|
23
|
+
@element.handle_observation_request('on_remove', unregister_handler)
|
24
|
+
end
|
23
25
|
end
|
24
26
|
|
25
27
|
def call(value)
|
26
28
|
evaluated_property_value = evaluate_property
|
27
29
|
converted_value = @translator&.call(value, evaluated_property_value) || value
|
28
|
-
|
30
|
+
unless converted_value == evaluated_property_value
|
31
|
+
if @sub_property
|
32
|
+
if @property.to_s == 'style'
|
33
|
+
@element.style_property(@sub_property, converted_value)
|
34
|
+
end
|
35
|
+
else
|
36
|
+
@element.send("#{@property}=", converted_value)
|
37
|
+
end
|
38
|
+
end
|
29
39
|
end
|
30
40
|
|
31
41
|
def evaluate_property
|
32
|
-
@
|
42
|
+
if @sub_property
|
43
|
+
if @property.to_s == 'style'
|
44
|
+
@element.style_property(@sub_property)
|
45
|
+
end
|
46
|
+
else
|
47
|
+
@element.send(@property)
|
48
|
+
end
|
33
49
|
end
|
34
50
|
end
|
35
51
|
end
|
data/lib/glimmer/dsl/web/dsl.rb
CHANGED
@@ -26,7 +26,8 @@ require 'glimmer/dsl/web/listener_expression'
|
|
26
26
|
require 'glimmer/dsl/web/property_expression'
|
27
27
|
require 'glimmer/dsl/web/a_expression'
|
28
28
|
require 'glimmer/dsl/web/span_expression'
|
29
|
-
require 'glimmer/dsl/web/
|
29
|
+
require 'glimmer/dsl/web/style_element_expression'
|
30
|
+
require 'glimmer/dsl/web/inline_style_data_binding_expression'
|
30
31
|
require 'glimmer/dsl/web/bind_expression'
|
31
32
|
require 'glimmer/dsl/web/data_binding_expression'
|
32
33
|
require 'glimmer/dsl/web/content_data_binding_expression'
|
@@ -41,8 +42,9 @@ module Glimmer
|
|
41
42
|
Web,
|
42
43
|
%w[
|
43
44
|
listener
|
44
|
-
|
45
|
+
style_element
|
45
46
|
content_data_binding
|
47
|
+
inline_style_data_binding
|
46
48
|
component
|
47
49
|
formatting_element
|
48
50
|
data_binding
|
@@ -10,7 +10,18 @@ module Glimmer
|
|
10
10
|
include GeneralElementExpression
|
11
11
|
|
12
12
|
def can_interpret?(parent, keyword, *args, &block)
|
13
|
-
Glimmer::Web::ElementProxy.keyword_supported?(keyword)
|
13
|
+
Glimmer::Web::ElementProxy.keyword_supported?(keyword) &&
|
14
|
+
(
|
15
|
+
args.empty? ||
|
16
|
+
args.size == 1 && args.first.is_a?(String) ||
|
17
|
+
args.size == 1 && args.first.is_a?(Hash) ||
|
18
|
+
args.size == 2 && args.first.is_a?(String) && args.last.is_a?(Hash)
|
19
|
+
) &&
|
20
|
+
( # ensure SVG keywords only live under SVG element (unless it's the SVG element itself)
|
21
|
+
!Glimmer::Web::ElementProxy.svg_keyword_supported?(keyword) ||
|
22
|
+
keyword == 'svg' ||
|
23
|
+
parent.find_ancestor(include_self: true) { |ancestor| ancestor.keyword == 'svg' }
|
24
|
+
)
|
14
25
|
end
|
15
26
|
end
|
16
27
|
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'glimmer/dsl/expression'
|
2
|
+
|
3
|
+
module Glimmer
|
4
|
+
module DSL
|
5
|
+
module Web
|
6
|
+
class InlineStyleDataBindingExpression < Expression
|
7
|
+
def can_interpret?(parent, keyword, *args, &block)
|
8
|
+
keyword == 'style' &&
|
9
|
+
block.nil? &&
|
10
|
+
args.size == 1 &&
|
11
|
+
textual?(args.first)
|
12
|
+
end
|
13
|
+
|
14
|
+
def interpret(parent, keyword, *args, &block)
|
15
|
+
parent_attribute = "#{keyword}_#{args.first.to_s.underscore}"
|
16
|
+
Glimmer::DataBinding::Shine.new(parent, parent_attribute)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -31,6 +31,14 @@ module Glimmer
|
|
31
31
|
ELEMENT_KEYWORDS.include?(keyword.to_s)
|
32
32
|
end
|
33
33
|
|
34
|
+
def html_keyword_supported?(keyword)
|
35
|
+
HTML_ELEMENT_KEYWORDS.include?(keyword.to_s)
|
36
|
+
end
|
37
|
+
|
38
|
+
def svg_keyword_supported?(keyword)
|
39
|
+
SVG_ELEMENT_KEYWORDS.include?(keyword.to_s)
|
40
|
+
end
|
41
|
+
|
34
42
|
# NOTE: Avoid using this method until we start supporting ElementProxy subclasses
|
35
43
|
# in which case, we must cache them to avoid the slow performance of element_type
|
36
44
|
# Factory Method that translates a Glimmer DSL keyword into a ElementProxy object
|
@@ -94,7 +102,7 @@ module Glimmer
|
|
94
102
|
|
95
103
|
Event = Struct.new(:widget, keyword_init: true)
|
96
104
|
|
97
|
-
|
105
|
+
HTML_ELEMENT_KEYWORDS = [
|
98
106
|
"a", "abbr", "acronym", "address", "applet", "area", "article", "aside", "audio",
|
99
107
|
"base", "basefont", "bdi", "bdo", "bgsound", "big", "blink", "blockquote", "body",
|
100
108
|
"button", "canvas", "caption", "center", "cite", "code", "col", "colgroup", "data",
|
@@ -110,6 +118,19 @@ module Glimmer
|
|
110
118
|
"template", "textarea", "tfoot", "th", "thead", "time", "title", "tr", "track", "tt",
|
111
119
|
"u", "ul", "var", "video", "wbr", "xmp",
|
112
120
|
]
|
121
|
+
|
122
|
+
SVG_ELEMENT_KEYWORDS = [
|
123
|
+
"animate", "animateMotion", "animateTransform", "circle", "clipPath", "defs", "desc", "ellipse",
|
124
|
+
"feBlend", "feColorMatrix", "feComponentTransfer", "feComposite", "feConvolveMatrix",
|
125
|
+
"feDiffuseLighting", "feDisplacementMap", "feDistantLight", "feDropShadow", "feFlood", "feFuncA",
|
126
|
+
"feFuncB", "feFuncG", "feFuncR", "feGaussianBlur", "feImage", "feMerge", "feMergeNode",
|
127
|
+
"feMorphology", "feOffset", "fePointLight", "feSpecularLighting", "feSpotLight", "feTile",
|
128
|
+
"feTurbulence", "filter", "foreignObject", "g", "image", "line", "linearGradient", "marker",
|
129
|
+
"mask", "metadata", "mpath", "path", "pattern", "polygon", "polyline", "radialGradient", "rect",
|
130
|
+
"set", "stop", "svg", "switch", "symbol", "text", "textPath", "tspan", "use", "view",
|
131
|
+
].map(&:downcase)
|
132
|
+
|
133
|
+
ELEMENT_KEYWORDS = HTML_ELEMENT_KEYWORDS + SVG_ELEMENT_KEYWORDS
|
113
134
|
|
114
135
|
GLIMMER_ATTRIBUTES = [:parent]
|
115
136
|
PROPERTY_ALIASES = {
|
@@ -122,6 +143,7 @@ module Glimmer
|
|
122
143
|
REGEX_FORMAT_DATETIME = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}$/
|
123
144
|
REGEX_FORMAT_DATE = /^\d{4}-\d{2}-\d{2}$/
|
124
145
|
REGEX_FORMAT_TIME = /^\d{2}:\d{2}$/
|
146
|
+
REGEX_STYLE_SUB_PROPERTY = /^(style)_(.*)$/
|
125
147
|
|
126
148
|
attr_reader :keyword, :parent, :parent_component, :component, :args, :options, :children, :enabled, :foreground, :background, :removed, :rendered
|
127
149
|
alias rendered? rendered
|
@@ -176,6 +198,14 @@ module Glimmer
|
|
176
198
|
dom_element.attr('class').to_s.split if rendered?
|
177
199
|
end
|
178
200
|
|
201
|
+
def html?
|
202
|
+
ElementProxy.html_keyword_supported?(keyword)
|
203
|
+
end
|
204
|
+
|
205
|
+
def svg?
|
206
|
+
ElementProxy.svg_keyword_supported?(keyword)
|
207
|
+
end
|
208
|
+
|
179
209
|
def remove
|
180
210
|
return if @removed
|
181
211
|
on_remove_listeners = listeners_for('on_remove').dup
|
@@ -233,9 +263,16 @@ module Glimmer
|
|
233
263
|
end
|
234
264
|
parents_array
|
235
265
|
end
|
266
|
+
alias ancestors parents
|
236
267
|
|
237
|
-
def
|
238
|
-
|
268
|
+
def find_ancestor(include_self: false, &condition)
|
269
|
+
current_element_proxy = self
|
270
|
+
return current_element_proxy if include_self && condition.call(current_element_proxy)
|
271
|
+
until current_element_proxy.parent.nil?
|
272
|
+
current_element_proxy = current_element_proxy.parent
|
273
|
+
return current_element_proxy if condition.call(current_element_proxy)
|
274
|
+
end
|
275
|
+
nil
|
239
276
|
end
|
240
277
|
|
241
278
|
def print
|
@@ -272,6 +309,21 @@ module Glimmer
|
|
272
309
|
end
|
273
310
|
end
|
274
311
|
|
312
|
+
def style_property(property, value = nil)
|
313
|
+
if rendered?
|
314
|
+
property = property.to_s.gsub('_', '-')
|
315
|
+
if value.nil?
|
316
|
+
dom_element.css(property)
|
317
|
+
else
|
318
|
+
dom_element.css(property, value)
|
319
|
+
end
|
320
|
+
else
|
321
|
+
enqueue_args = ['style_property', property]
|
322
|
+
enqueue_args << value unless value.nil?
|
323
|
+
enqueue_post_render_method_call(*enqueue_args)
|
324
|
+
end
|
325
|
+
end
|
326
|
+
|
275
327
|
def parent_selector
|
276
328
|
@parent&.selector
|
277
329
|
end
|
@@ -372,12 +424,7 @@ module Glimmer
|
|
372
424
|
end
|
373
425
|
|
374
426
|
def html_options
|
375
|
-
|
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(' ')
|
427
|
+
body_class = (base_css_classes + css_classes.to_a).uniq.compact.join(' ')
|
381
428
|
html_options = options.dup
|
382
429
|
GLIMMER_ATTRIBUTES.each do |attribute|
|
383
430
|
next unless html_options.include?(attribute)
|
@@ -416,8 +463,8 @@ module Glimmer
|
|
416
463
|
|
417
464
|
def class_name=(value)
|
418
465
|
if rendered?
|
419
|
-
|
420
|
-
new_class_name =
|
466
|
+
values = value.is_a?(Array) ? value : [value.to_s]
|
467
|
+
new_class_name = (base_css_classes + values).uniq.compact.join(' ')
|
421
468
|
dom_element.prop('className', new_class_name)
|
422
469
|
else
|
423
470
|
enqueue_post_render_method_call('class_name=', value)
|
@@ -555,13 +602,12 @@ module Glimmer
|
|
555
602
|
|
556
603
|
def data_bind(property, model_binding)
|
557
604
|
element_binding_read_translator = value_converters_for_input_type(type)&.[](:model_to_view)
|
558
|
-
|
559
|
-
element_binding = DataBinding::ElementBinding.new(*element_binding_parameters)
|
605
|
+
element_binding = DataBinding::ElementBinding.new(self, property, translator: element_binding_read_translator)
|
560
606
|
#TODO make this options observer dependent and all similar observers in element specific data binding handlers
|
561
607
|
element_binding.observe(model_binding)
|
562
608
|
element_binding.call(model_binding.evaluate_property)
|
563
609
|
data_bindings[element_binding] = model_binding
|
564
|
-
|
610
|
+
if !model_binding.binding_options[:read_only]
|
565
611
|
# TODO add guards against nil cases for hash below
|
566
612
|
listener_keyword = data_binding_listener_for_element_and_property(keyword, property)
|
567
613
|
if listener_keyword
|
@@ -610,14 +656,23 @@ module Glimmer
|
|
610
656
|
dom_element.respond_to?(method_name, include_private) ||
|
611
657
|
(!dom_element.prop(property_name).nil? && !dom_element.prop(property_name).is_a?(Proc)) ||
|
612
658
|
(!dom_element.prop(unnormalized_property_name).nil? && !dom_element.prop(unnormalized_property_name).is_a?(Proc)) ||
|
613
|
-
method_name.to_s.start_with?('on_')
|
659
|
+
method_name.to_s.start_with?('on_') ||
|
660
|
+
method_name.to_s.start_with?('style_')
|
614
661
|
end
|
615
662
|
|
616
663
|
def method_missing(method_name, *args, &block)
|
617
664
|
# TODO consider doing more correct checking of availability of properties/methods using native ticks
|
618
665
|
property_name = property_name_for(method_name)
|
619
666
|
unnormalized_property_name = unnormalized_property_name_for(method_name)
|
620
|
-
if method_name.to_s.start_with?('
|
667
|
+
if method_name.to_s.start_with?('style_')
|
668
|
+
property, sub_property = method_name.to_s.match(REGEX_STYLE_SUB_PROPERTY).to_a.drop(1)
|
669
|
+
sub_property = sub_property.gsub('_', '-')
|
670
|
+
if args.empty?
|
671
|
+
style_property(sub_property)
|
672
|
+
else
|
673
|
+
style_property(sub_property, args.first) # TODO in the future, support more sophisticated forms of CSS sub-property values, like [1.px, :solid, :black] for border
|
674
|
+
end
|
675
|
+
elsif method_name.to_s.start_with?('on_')
|
621
676
|
handle_observation_request(method_name, block)
|
622
677
|
elsif dom_element.respond_to?(method_name)
|
623
678
|
if rendered?
|
@@ -831,6 +886,15 @@ module Glimmer
|
|
831
886
|
|
832
887
|
private
|
833
888
|
|
889
|
+
def base_css_classes
|
890
|
+
framework_css_classes = [name, element_id]
|
891
|
+
if component
|
892
|
+
framework_css_classes.prepend(component.class.component_element_class)
|
893
|
+
framework_css_classes.prepend(component.class.component_shortcut_element_class) if component.class.component_shortcut_element_class != component.class.component_element_class
|
894
|
+
end
|
895
|
+
framework_css_classes
|
896
|
+
end
|
897
|
+
|
834
898
|
def valid_js_date_string?(string)
|
835
899
|
[REGEX_FORMAT_DATETIME, REGEX_FORMAT_DATE, REGEX_FORMAT_TIME].any? do |format|
|
836
900
|
string.match(format)
|
@@ -29,13 +29,14 @@ class ButtonModel
|
|
29
29
|
FONT_SIZE_MIN = 40
|
30
30
|
FONT_SIZE_MAX = 200
|
31
31
|
|
32
|
-
attr_accessor :text, :pushed, :width, :height, :font_size
|
32
|
+
attr_accessor :text, :pushed, :background_color, :width, :height, :font_size
|
33
33
|
|
34
34
|
def initialize
|
35
35
|
@text = 'Push'
|
36
36
|
@width = WIDTH_MIN
|
37
37
|
@height = HEIGHT_MIN
|
38
38
|
@font_size = FONT_SIZE_MIN
|
39
|
+
@background_color = '#add8e6'
|
39
40
|
end
|
40
41
|
|
41
42
|
def push
|
@@ -61,6 +62,16 @@ class ButtonModel
|
|
61
62
|
self.width = @font_size*4 if @height < @font_size*4
|
62
63
|
self.height = @font_size*2.5 if @height < @font_size*2.5
|
63
64
|
end
|
65
|
+
|
66
|
+
def border_color
|
67
|
+
red = background_color[1..2].hex
|
68
|
+
green = background_color[3..4].hex
|
69
|
+
blue = background_color[5..6].hex
|
70
|
+
new_red = red - 10
|
71
|
+
new_green = green - 10
|
72
|
+
new_blue = blue - 10
|
73
|
+
"##{new_red.to_s(16)}#{new_green.to_s(16)}#{new_blue.to_s(16)}"
|
74
|
+
end
|
64
75
|
end
|
65
76
|
|
66
77
|
class StyledButton
|
@@ -76,17 +87,11 @@ class StyledButton
|
|
76
87
|
on_read: ->(pushed) { pushed ? 'pushed' : 'pulled' }
|
77
88
|
]
|
78
89
|
|
79
|
-
style <= [
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
style <= [
|
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
|
+
style(:width) <= [button_model, :width, on_read: :px]
|
91
|
+
style(:height) <= [button_model, :height, on_read: :px]
|
92
|
+
style(:font_size) <= [button_model, :font_size, on_read: :px]
|
93
|
+
style(:background_color) <= [button_model, :background_color]
|
94
|
+
style(:border_color) <= [button_model, :border_color, computed_by: :background_color]
|
90
95
|
|
91
96
|
onclick do
|
92
97
|
button_model.push
|
@@ -94,32 +99,22 @@ class StyledButton
|
|
94
99
|
}
|
95
100
|
}
|
96
101
|
|
97
|
-
style {
|
98
|
-
|
102
|
+
style {"
|
103
|
+
.#{component_element_class} {
|
99
104
|
font-family: Courrier New, Courrier;
|
100
105
|
border-radius: 5px;
|
101
106
|
border-width: 17px;
|
102
|
-
border-color: #ACC7D5;
|
103
|
-
background-color: #ADD8E6;
|
104
107
|
margin: 5px;
|
105
108
|
}
|
106
109
|
|
107
|
-
|
110
|
+
.#{component_element_class}.pulled {
|
108
111
|
border-style: outset;
|
109
112
|
}
|
110
113
|
|
111
|
-
|
114
|
+
.#{component_element_class}.pushed {
|
112
115
|
border-style: inset;
|
113
116
|
}
|
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
|
117
|
+
"}
|
123
118
|
end
|
124
119
|
|
125
120
|
class StyledButtonRangeInput
|
@@ -137,6 +132,19 @@ class StyledButtonRangeInput
|
|
137
132
|
}
|
138
133
|
end
|
139
134
|
|
135
|
+
class StyledButtonColorInput
|
136
|
+
include Glimmer::Web::Component
|
137
|
+
|
138
|
+
option :button_model
|
139
|
+
option :property
|
140
|
+
|
141
|
+
markup {
|
142
|
+
input(type: 'color') {
|
143
|
+
value <=> [button_model, property]
|
144
|
+
}
|
145
|
+
}
|
146
|
+
end
|
147
|
+
|
140
148
|
class HelloStyle
|
141
149
|
include Glimmer::Web::Component
|
142
150
|
|
@@ -145,32 +153,41 @@ class HelloStyle
|
|
145
153
|
end
|
146
154
|
|
147
155
|
markup {
|
148
|
-
div
|
149
|
-
div(class: 'form
|
150
|
-
label('Styled Button Width:', for: 'styled-button-width-input')
|
156
|
+
div {
|
157
|
+
div(class: 'styled-button-form') {
|
158
|
+
label('Styled Button Width:', class: 'property-label', for: 'styled-button-width-input')
|
151
159
|
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
|
-
|
154
|
-
label('Styled Button Height:', for: 'styled-button-height-input')
|
160
|
+
|
161
|
+
label('Styled Button Height:', class: 'property-label', for: 'styled-button-height-input')
|
155
162
|
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
|
-
|
158
|
-
label('Styled Button Font Size:', for: 'styled-button-font-size-input')
|
163
|
+
|
164
|
+
label('Styled Button Font Size:', class: 'property-label', for: 'styled-button-font-size-input')
|
159
165
|
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')
|
166
|
+
|
167
|
+
label('Styled Button Background Color:', for: 'styled-button-background-color-input')
|
168
|
+
styled_button_color_input(button_model: @button_model, property: :background_color, id: 'styled-button-background-color-input')
|
160
169
|
}
|
170
|
+
|
161
171
|
styled_button(button_model: @button_model)
|
162
172
|
}
|
163
173
|
}
|
164
174
|
|
165
|
-
style {
|
166
|
-
.
|
175
|
+
style {"
|
176
|
+
.styled-button-form {
|
167
177
|
padding: 20px;
|
178
|
+
display: inline-grid;
|
179
|
+
grid-template-columns: auto auto;
|
180
|
+
}
|
181
|
+
|
182
|
+
.styled-button-form label, input {
|
183
|
+
display: block;
|
184
|
+
margin: 5px 5px 5px 0;
|
168
185
|
}
|
169
186
|
|
170
|
-
|
171
|
-
|
187
|
+
.#{component_element_class} .styled-button {
|
188
|
+
display: block;
|
172
189
|
}
|
173
|
-
|
190
|
+
"}
|
174
191
|
end
|
175
192
|
|
176
193
|
Document.ready? do
|
@@ -0,0 +1,45 @@
|
|
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 HelloSvg
|
25
|
+
include Glimmer::Web::Component
|
26
|
+
|
27
|
+
markup {
|
28
|
+
div {
|
29
|
+
svg(width: '100%', height: '100') {
|
30
|
+
circle(cx: '50', cy: '50', r: '50', style: 'fill:blue;') {
|
31
|
+
animate(attributename: 'cx', begin: '0s', dur: '8s', from: '50', to: '90%', repeatcount: 'indefinite')
|
32
|
+
}
|
33
|
+
}
|
34
|
+
svg(width: '200', height: '180') {
|
35
|
+
rect(x: '30', y: '30', height: '110', width: '110', style: 'stroke:green;fill:red') {
|
36
|
+
animatetransform(attributename: 'transform', begin: '0.1s', dur: '10s', type: 'rotate', from: '0 85 85', to: '360 85 85', repeatcount: 'indefinite')
|
37
|
+
}
|
38
|
+
}
|
39
|
+
}
|
40
|
+
}
|
41
|
+
end
|
42
|
+
|
43
|
+
Document.ready? do
|
44
|
+
HelloSvg.render
|
45
|
+
end
|
@@ -3,7 +3,7 @@ require_relative 'todo_input'
|
|
3
3
|
class NewTodoInput < TodoInput
|
4
4
|
option :presenter
|
5
5
|
|
6
|
-
markup { # evaluated against instance as a smart
|
6
|
+
markup { # evaluated against instance as a smart convention
|
7
7
|
input(placeholder: "What needs to be done?", autofocus: "") {
|
8
8
|
value <=> [presenter.new_todo, :task]
|
9
9
|
|
@@ -13,10 +13,10 @@ class NewTodoInput < TodoInput
|
|
13
13
|
}
|
14
14
|
}
|
15
15
|
|
16
|
-
style { # evaluated against class as a smart
|
16
|
+
style { # evaluated against class as a smart convention (common to all instances)
|
17
17
|
todo_input_styles
|
18
18
|
|
19
|
-
rule(".#{component_element_class}") { #
|
19
|
+
rule(".#{component_element_class}") { # NewTodoInput has component_element_class as 'new-todo-input'
|
20
20
|
padding '16px 16px 16px 60px'
|
21
21
|
height '65px'
|
22
22
|
border 'none'
|
@@ -24,7 +24,7 @@ class NewTodoInput < TodoInput
|
|
24
24
|
box_shadow 'inset 0 -2px 1px rgba(0,0,0,0.03)'
|
25
25
|
}
|
26
26
|
|
27
|
-
rule(".#{component_element_class}::placeholder") {
|
27
|
+
rule(".#{component_element_class}::placeholder") {
|
28
28
|
font_style 'italic'
|
29
29
|
font_weight '400'
|
30
30
|
color 'rgba(0, 0, 0, 0.4)'
|
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.
|
4
|
+
version: 0.4.2
|
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-28 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: glimmer
|
@@ -44,14 +44,14 @@ dependencies:
|
|
44
44
|
requirements:
|
45
45
|
- - "~>"
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version: 1.5.
|
47
|
+
version: 1.5.1
|
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.5.
|
54
|
+
version: 1.5.1
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
56
|
name: opal
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -280,6 +280,7 @@ files:
|
|
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
282
|
- lib/glimmer-dsl-web/samples/hello/hello_style.rb
|
283
|
+
- lib/glimmer-dsl-web/samples/hello/hello_svg.rb
|
283
284
|
- lib/glimmer-dsl-web/samples/hello/hello_world.rb
|
284
285
|
- lib/glimmer-dsl-web/samples/regular/button_counter.rb
|
285
286
|
- lib/glimmer-dsl-web/samples/regular/todo_mvc.rb
|
@@ -305,12 +306,13 @@ files:
|
|
305
306
|
- lib/glimmer/dsl/web/element_expression.rb
|
306
307
|
- lib/glimmer/dsl/web/formatting_element_expression.rb
|
307
308
|
- lib/glimmer/dsl/web/general_element_expression.rb
|
309
|
+
- lib/glimmer/dsl/web/inline_style_data_binding_expression.rb
|
308
310
|
- lib/glimmer/dsl/web/listener_expression.rb
|
309
311
|
- lib/glimmer/dsl/web/observe_expression.rb
|
310
312
|
- lib/glimmer/dsl/web/property_expression.rb
|
311
313
|
- lib/glimmer/dsl/web/shine_data_binding_expression.rb
|
312
314
|
- lib/glimmer/dsl/web/span_expression.rb
|
313
|
-
- lib/glimmer/dsl/web/
|
315
|
+
- lib/glimmer/dsl/web/style_element_expression.rb
|
314
316
|
- lib/glimmer/helpers/glimmer_helper.rb
|
315
317
|
- lib/glimmer/util/proc_tracker.rb
|
316
318
|
- lib/glimmer/web.rb
|