glimmer-dsl-web 0.2.5 → 0.2.7
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +11 -0
- data/README.md +111 -6
- data/VERSION +1 -1
- data/glimmer-dsl-web.gemspec +18 -6
- data/lib/glimmer/dsl/web/a_expression.rb +17 -0
- data/lib/glimmer/dsl/web/dsl.rb +2 -0
- data/lib/glimmer/dsl/web/element_expression.rb +1 -1
- data/lib/glimmer/dsl/web/style_expression.rb +7 -2
- data/lib/glimmer/web/formatting_element_proxy.rb +2 -1
- data/lib/glimmer-dsl-web/samples/regular/todo_mvc/models/todo.rb +44 -0
- data/lib/glimmer-dsl-web/samples/regular/todo_mvc/presenters/todo_presenter.rb +102 -0
- data/lib/glimmer-dsl-web/samples/regular/todo_mvc/views/edit_todo_input.rb +55 -0
- data/lib/glimmer-dsl-web/samples/regular/todo_mvc/views/new_todo_form.rb +30 -0
- data/lib/glimmer-dsl-web/samples/regular/todo_mvc/views/new_todo_input.rb +41 -0
- data/lib/glimmer-dsl-web/samples/regular/todo_mvc/views/todo_filters.rb +123 -0
- data/lib/glimmer-dsl-web/samples/regular/todo_mvc/views/todo_input.rb +30 -0
- data/lib/glimmer-dsl-web/samples/regular/todo_mvc/views/todo_list.rb +88 -0
- data/lib/glimmer-dsl-web/samples/regular/todo_mvc/views/todo_list_item.rb +158 -0
- data/lib/glimmer-dsl-web/samples/regular/todo_mvc/views/todo_mvc_footer.rb +45 -0
- data/lib/glimmer-dsl-web/samples/regular/todo_mvc.rb +87 -0
- metadata +20 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ebc8e783bfbc67d9366e1887368e89bb7a75db102e56ca966268a2091c5a8e90
|
4
|
+
data.tar.gz: a44da8782e6ca10c5183f4f7377eac39bb2b7af4d7e03f088d885a2fce34b219
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 577aea1ffced7cb27937958583c877416921d3f784fd4a2a9589ed3a3b95f39037b1a22f596ab03ed2c336abdab080fce67533b4120e800cb81d3cef144954a8
|
7
|
+
data.tar.gz: 05726c636474ad543649bb3200a51c2a238e79248f89207698b229a09fc3e3c6cb9b19489142d71c973505098c1809c8d2978c17ab8628ef9c339666f6fae2f5
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,16 @@
|
|
1
1
|
# Change Log
|
2
2
|
|
3
|
+
## 0.2.7
|
4
|
+
|
5
|
+
- Unidirectional Data-Binding of element `style` property
|
6
|
+
- Support `a` as a formatting element under `p`
|
7
|
+
- Todo MVC Sample: `require 'glimmer-dsl-web/samples/regular/todo_mvc'`
|
8
|
+
|
9
|
+
## 0.2.6
|
10
|
+
|
11
|
+
- Upgrade to `glimmer-dsl-xml` 1.4.0 to provide access to `html_to_glimmer` converter command (converts legacy HTML to Glimmer DSL syntax)
|
12
|
+
- Upgrade to `glimmer-dsl-css` 1.4.0 to provide access to `css_to_glimmer` converter command (converts legacy CSS to Glimmer DSL syntax)
|
13
|
+
|
3
14
|
## 0.2.5
|
4
15
|
|
5
16
|
- Support `Element#class_name` as an alternative to `Element#class` because `class` is a reserved method in Ruby
|
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.2.
|
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.2.7 (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)
|
@@ -8,9 +8,11 @@
|
|
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
|
-
|
11
|
+
[![Todo MVC](/images/glimmer-dsl-web-samples-regular-todo-mvc.gif)](/lib/glimmer-dsl-web/samples/regular/todo_mvc.rb)
|
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, companies can cut their hiring budget in half by having Backend Ruby Software Engineers do Frontend Development with Ruby! [Ruby in the Browser](https://www.youtube.com/watch?v=4AdcfbI6A4c) finally fulfills every 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
|
+
|
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)!
|
14
16
|
|
15
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).
|
16
18
|
|
@@ -1148,6 +1150,100 @@ Screenshot:
|
|
1148
1150
|
|
1149
1151
|
![Hello, Observer!](/images/glimmer-dsl-web-samples-hello-hello-observer.gif)
|
1150
1152
|
|
1153
|
+
**Todo MVC**
|
1154
|
+
|
1155
|
+
```ruby
|
1156
|
+
require 'glimmer-dsl-web'
|
1157
|
+
|
1158
|
+
require_relative 'todo_mvc/presenters/todo_presenter'
|
1159
|
+
|
1160
|
+
require_relative 'todo_mvc/views/new_todo_form'
|
1161
|
+
require_relative 'todo_mvc/views/todo_list'
|
1162
|
+
require_relative 'todo_mvc/views/todo_filters'
|
1163
|
+
require_relative 'todo_mvc/views/todo_mvc_footer'
|
1164
|
+
|
1165
|
+
class TodoMvc
|
1166
|
+
include Glimmer::Web::Component
|
1167
|
+
|
1168
|
+
before_render do
|
1169
|
+
@presenter = TodoPresenter.new
|
1170
|
+
end
|
1171
|
+
|
1172
|
+
after_render do
|
1173
|
+
@presenter.setup_filter_routes
|
1174
|
+
end
|
1175
|
+
|
1176
|
+
markup {
|
1177
|
+
div(class: 'todomvc') {
|
1178
|
+
section(class: 'todoapp') {
|
1179
|
+
new_todo_form(presenter: @presenter)
|
1180
|
+
|
1181
|
+
todo_list(presenter: @presenter)
|
1182
|
+
|
1183
|
+
todo_filters(presenter: @presenter)
|
1184
|
+
|
1185
|
+
style {
|
1186
|
+
todo_mvc_styles
|
1187
|
+
}
|
1188
|
+
}
|
1189
|
+
|
1190
|
+
todo_mvc_footer
|
1191
|
+
|
1192
|
+
on_remove do
|
1193
|
+
@presenter.unsetup_filter_routes
|
1194
|
+
end
|
1195
|
+
}
|
1196
|
+
}
|
1197
|
+
|
1198
|
+
def todo_mvc_styles
|
1199
|
+
rule('body, button, html') {
|
1200
|
+
margin '0'
|
1201
|
+
padding '0'
|
1202
|
+
}
|
1203
|
+
|
1204
|
+
rule('button') {
|
1205
|
+
_webkit_font_smoothing 'antialiased'
|
1206
|
+
_webkit_appearance 'none'
|
1207
|
+
appearance 'none'
|
1208
|
+
background 'none'
|
1209
|
+
border '0'
|
1210
|
+
color 'inherit'
|
1211
|
+
font_family 'inherit'
|
1212
|
+
font_size '100%'
|
1213
|
+
font_weight 'inherit'
|
1214
|
+
vertical_align 'baseline'
|
1215
|
+
}
|
1216
|
+
|
1217
|
+
rule('.todoapp') {
|
1218
|
+
background '#fff'
|
1219
|
+
margin '130px 0 40px 0'
|
1220
|
+
position 'relative'
|
1221
|
+
box_shadow '0 2px 4px 0 rgba(0, 0, 0, 0.2), 0 25px 50px 0 rgba(0, 0, 0, 0.1)'
|
1222
|
+
}
|
1223
|
+
|
1224
|
+
media('screen and (-webkit-min-device-pixel-ratio:0)') {
|
1225
|
+
rule('body') {
|
1226
|
+
font "14px 'Helvetica Neue', Helvetica, Arial, sans-serif"
|
1227
|
+
line_height '1.4em'
|
1228
|
+
background '#f5f5f5'
|
1229
|
+
color '#111111'
|
1230
|
+
min_width '230px'
|
1231
|
+
max_width '550px'
|
1232
|
+
margin '0 auto'
|
1233
|
+
_webkit_font_smoothing 'antialiased'
|
1234
|
+
font_weight '300'
|
1235
|
+
}
|
1236
|
+
}
|
1237
|
+
end
|
1238
|
+
end
|
1239
|
+
|
1240
|
+
Document.ready? do
|
1241
|
+
TodoMvc.render
|
1242
|
+
end
|
1243
|
+
```
|
1244
|
+
|
1245
|
+
![Todo MVC](/images/glimmer-dsl-web-samples-regular-todo-mvc.gif)
|
1246
|
+
|
1151
1247
|
To get started, [Setup](#setup) Ruby gem, read [Usage](#usage) instructions, and check out more [Samples](#samples) (including playing around with a [Rails sample app](https://github.com/AndyObtiva/sample-glimmer-dsl-web-rails7-app)).
|
1152
1248
|
|
1153
1249
|
--
|
@@ -1211,6 +1307,8 @@ You can setup Glimmer DSL for Web in [Rails 7](#rails-7), [Rails 6](#rails-6), o
|
|
1211
1307
|
|
1212
1308
|
### Rails 7
|
1213
1309
|
|
1310
|
+
(NOTE: In the future, we plan to automate the setup steps below. If you would like to help contribute that to the project, please do so and open a Pull Request.)
|
1311
|
+
|
1214
1312
|
Please follow these steps to setup.
|
1215
1313
|
|
1216
1314
|
Install a Rails 7 gem:
|
@@ -1228,7 +1326,7 @@ rails new glimmer_app_server
|
|
1228
1326
|
Add the following to `Gemfile`:
|
1229
1327
|
|
1230
1328
|
```
|
1231
|
-
gem 'glimmer-dsl-web', '~> 0.2.
|
1329
|
+
gem 'glimmer-dsl-web', '~> 0.2.7'
|
1232
1330
|
```
|
1233
1331
|
|
1234
1332
|
Run:
|
@@ -1430,6 +1528,8 @@ Next, read [Usage](#usage) instructions, and check out [Samples](#samples).
|
|
1430
1528
|
|
1431
1529
|
### Rails 6
|
1432
1530
|
|
1531
|
+
(NOTE: In the future, we plan to automate the setup steps below. If you would like to help contribute that to the project, please do so and open a Pull Request.)
|
1532
|
+
|
1433
1533
|
Please follow these steps to setup.
|
1434
1534
|
|
1435
1535
|
Install a Rails 6 gem:
|
@@ -1453,7 +1553,7 @@ Disable the `webpacker` gem line in `Gemfile`:
|
|
1453
1553
|
Add the following to `Gemfile`:
|
1454
1554
|
|
1455
1555
|
```ruby
|
1456
|
-
gem 'glimmer-dsl-web', '~> 0.2.
|
1556
|
+
gem 'glimmer-dsl-web', '~> 0.2.7'
|
1457
1557
|
```
|
1458
1558
|
|
1459
1559
|
Run:
|
@@ -3184,9 +3284,13 @@ Learn more by reading the [GPG](https://github.com/AndyObtiva/glimmer/blob/maste
|
|
3184
3284
|
|
3185
3285
|
F.A.Q. (Frequently Asked Questions):
|
3186
3286
|
|
3287
|
+
**Can I reuse JavaScript libraries from Glimmer DSL for Web in Ruby?**
|
3288
|
+
|
3289
|
+
Absolutely. Glimmer DSL for Web can integrate with any JavaScript libraries. You can either load the JavaScript libraries in advance by linking to them in the Rails View/Layout (e.g. linking to JS library CDN URLs) or by including JavaScript files in the lookup directories of Opal Ruby, and adding a Ruby `require('path_to_js_lib')` call in the code. In Ruby, the `$$` global variable gives access to the top-level JavaScript global scope, which enables invocations on any JavaScript objects. For example, `$$.hljs` gives access to the loaded `window.hljs` object for the Highlight.js library, and that enables invoking any functions from that library as needed, like `$$.hljs.highlightAll` to activate code syntax highlighting.
|
3290
|
+
|
3187
3291
|
**How does Glimmer DSL for Web compare to Rails Hotwire (Turbo)?**
|
3188
3292
|
|
3189
|
-
Glimmer DSL for Web is a Frontend library, meaning it replaces the JavaScript layer in a web application (e.g. Rails app) with Ruby code. On the other hand, Rails Hotwire (Turbo) is mostly a Backend-driven technology that enables automatically replacing HTML DOM elements with HTML markup sent over the wire from a Rails Backend. So, the two technologies are mostly orthogonal, but can be used in the same Rails web application, albeit exclusively on separate web pages at the moment. In the future, we might explore supporting the ability to combine both technologies in the same pages, but until then, use on separate pages. Glimmer DSL for Web can handle any sort of Frontend interactions even without making HTTP calls to the Backend, so it can implement more use-cases than Hotwire. Using Glimmer DSL for Web with Rails API end-points is simpler than using Hotwire overall. That is because Glimmer Content Data-Binding is a simpler version of Turbo Frames that does not require worrying about setting and using element IDs (as that is handled automatically) and that operates at a more correct abstraction level for how we think about View component updates in relation to Model changes (we make updates at the Model layer, and they automatically get reflected in the View through data-binding). Also, Glimmer DSL for Web supports Glimmer Web Components, which enable better division of View code
|
3293
|
+
Glimmer DSL for Web is a Frontend library, meaning it replaces the JavaScript layer in a web application (e.g. Rails app) with Ruby code. On the other hand, Rails Hotwire (Turbo) is mostly a Backend-driven technology that enables automatically replacing HTML DOM elements with HTML markup sent over the wire from a Rails Backend. So, the two technologies are mostly orthogonal, but can be used in the same Rails web application, albeit exclusively on separate web pages at the moment. In the future, we might explore supporting the ability to combine both technologies in the same pages, but until then, use on separate pages. Glimmer DSL for Web can handle any sort of Frontend interactions even without making HTTP calls to the Backend, so it can implement more use-cases than Hotwire. Using Glimmer DSL for Web with Rails API end-points is simpler than using Hotwire overall. That is because Glimmer Content Data-Binding is a simpler version of Turbo Frames that does not require worrying about setting and using element IDs (as that is handled automatically) and that operates at a more correct abstraction level for how we think about View component updates in relation to Model changes (we make updates at the Model layer, and they automatically get reflected in the View through data-binding). Also, Glimmer DSL for Web supports Glimmer Web Components, which enable better division and organization of View code into modular components than ERB plus having higher readability. It is true that Hotwire is an improvement over using JavaScript frameworks like React when there is not much logic beyond updating elements with Server-Side rendered HTML. And, Glimmer DSL for Web is the next big advancement that provides an even simpler approach while also giving you full Frontend Development capabilities using Ruby in the Browser.
|
3190
3294
|
|
3191
3295
|
**How does Glimmer DSL for Web compare to Phlex or ViewComponent?**
|
3192
3296
|
|
@@ -3204,6 +3308,7 @@ Without delving into details, Glimmer DSL for Web is meant to be a Ruby-based dr
|
|
3204
3308
|
|
3205
3309
|
In the future, support for HTML Web Components will be added, and that will enable reuse of React components by using a library that converts them to HTML Web Components first like [react-to-web-component](https://github.com/bitovi/react-to-web-component) or [react-webcomponent](https://github.com/adobe/react-webcomponent).
|
3206
3310
|
|
3311
|
+
|
3207
3312
|
### Issues
|
3208
3313
|
|
3209
3314
|
You may submit [issues](https://github.com/AndyObtiva/glimmer-dsl-web/issues) on [GitHub](https://github.com/AndyObtiva/glimmer-dsl-web/issues).
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.2.
|
1
|
+
0.2.7
|
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.2.
|
5
|
+
# stub: glimmer-dsl-web 0.2.7 ruby lib
|
6
6
|
|
7
7
|
Gem::Specification.new do |s|
|
8
8
|
s.name = "glimmer-dsl-web".freeze
|
9
|
-
s.version = "0.2.
|
9
|
+
s.version = "0.2.7"
|
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-
|
14
|
+
s.date = "2024-06-15"
|
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 = [
|
@@ -48,9 +48,21 @@ Gem::Specification.new do |s|
|
|
48
48
|
"lib/glimmer-dsl-web/samples/hello/hello_paragraph.rb",
|
49
49
|
"lib/glimmer-dsl-web/samples/hello/hello_world.rb",
|
50
50
|
"lib/glimmer-dsl-web/samples/regular/button_counter.rb",
|
51
|
+
"lib/glimmer-dsl-web/samples/regular/todo_mvc.rb",
|
52
|
+
"lib/glimmer-dsl-web/samples/regular/todo_mvc/models/todo.rb",
|
53
|
+
"lib/glimmer-dsl-web/samples/regular/todo_mvc/presenters/todo_presenter.rb",
|
54
|
+
"lib/glimmer-dsl-web/samples/regular/todo_mvc/views/edit_todo_input.rb",
|
55
|
+
"lib/glimmer-dsl-web/samples/regular/todo_mvc/views/new_todo_form.rb",
|
56
|
+
"lib/glimmer-dsl-web/samples/regular/todo_mvc/views/new_todo_input.rb",
|
57
|
+
"lib/glimmer-dsl-web/samples/regular/todo_mvc/views/todo_filters.rb",
|
58
|
+
"lib/glimmer-dsl-web/samples/regular/todo_mvc/views/todo_input.rb",
|
59
|
+
"lib/glimmer-dsl-web/samples/regular/todo_mvc/views/todo_list.rb",
|
60
|
+
"lib/glimmer-dsl-web/samples/regular/todo_mvc/views/todo_list_item.rb",
|
61
|
+
"lib/glimmer-dsl-web/samples/regular/todo_mvc/views/todo_mvc_footer.rb",
|
51
62
|
"lib/glimmer-dsl-web/vendor/jquery.js",
|
52
63
|
"lib/glimmer/config/opal_logger.rb",
|
53
64
|
"lib/glimmer/data_binding/element_binding.rb",
|
65
|
+
"lib/glimmer/dsl/web/a_expression.rb",
|
54
66
|
"lib/glimmer/dsl/web/bind_expression.rb",
|
55
67
|
"lib/glimmer/dsl/web/component_expression.rb",
|
56
68
|
"lib/glimmer/dsl/web/content_data_binding_expression.rb",
|
@@ -82,14 +94,14 @@ Gem::Specification.new do |s|
|
|
82
94
|
s.specification_version = 4
|
83
95
|
|
84
96
|
s.add_runtime_dependency(%q<glimmer>.freeze, ["~> 2.7.6"])
|
85
|
-
s.add_runtime_dependency(%q<glimmer-dsl-xml>.freeze, ["~> 1.
|
86
|
-
s.add_runtime_dependency(%q<glimmer-dsl-css>.freeze, ["~> 1.
|
97
|
+
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"])
|
87
99
|
s.add_runtime_dependency(%q<opal>.freeze, ["= 1.8.2"])
|
88
100
|
s.add_runtime_dependency(%q<opal-rails>.freeze, ["= 2.0.3"])
|
89
101
|
s.add_runtime_dependency(%q<opal-async>.freeze, ["~> 1.4.1"])
|
90
102
|
s.add_runtime_dependency(%q<opal-jquery>.freeze, ["~> 0.5.1"])
|
91
103
|
s.add_runtime_dependency(%q<to_collection>.freeze, [">= 2.0.1", "< 3.0.0"])
|
92
|
-
s.add_development_dependency(%q<puts_debuggerer>.freeze, [">= 0"])
|
104
|
+
s.add_development_dependency(%q<puts_debuggerer>.freeze, [">= 1.0.0"])
|
93
105
|
s.add_development_dependency(%q<rake>.freeze, [">= 10.1.0", "< 14.0.0"])
|
94
106
|
s.add_development_dependency(%q<rake-tui>.freeze, [">= 0"])
|
95
107
|
s.add_development_dependency(%q<jeweler>.freeze, [">= 2.3.9", "< 3.0.0"])
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'glimmer/dsl/static_expression'
|
2
|
+
require 'glimmer/dsl/web/general_element_expression'
|
3
|
+
|
4
|
+
module Glimmer
|
5
|
+
module DSL
|
6
|
+
module Web
|
7
|
+
class AExpression < StaticExpression
|
8
|
+
include GeneralElementExpression
|
9
|
+
|
10
|
+
def can_interpret?(parent, keyword, *args, &block)
|
11
|
+
(parent.nil? ||
|
12
|
+
(parent.respond_to?(:keyword) && parent.keyword != 'p'))
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
data/lib/glimmer/dsl/web/dsl.rb
CHANGED
@@ -24,6 +24,7 @@ require 'glimmer/dsl/web/element_expression'
|
|
24
24
|
require 'glimmer/dsl/web/formatting_element_expression'
|
25
25
|
require 'glimmer/dsl/web/listener_expression'
|
26
26
|
require 'glimmer/dsl/web/property_expression'
|
27
|
+
require 'glimmer/dsl/web/a_expression'
|
27
28
|
require 'glimmer/dsl/web/span_expression'
|
28
29
|
require 'glimmer/dsl/web/style_expression'
|
29
30
|
require 'glimmer/dsl/web/bind_expression'
|
@@ -45,6 +46,7 @@ module Glimmer
|
|
45
46
|
property
|
46
47
|
content_data_binding
|
47
48
|
shine_data_binding
|
49
|
+
style
|
48
50
|
formatting_element
|
49
51
|
]
|
50
52
|
)
|
@@ -24,7 +24,7 @@ end
|
|
24
24
|
module Glimmer
|
25
25
|
# Optimize performance through shortcut methods for all HTML elements that circumvent the DSL chain of responsibility
|
26
26
|
element_expression = Glimmer::DSL::Web::ElementExpression.new
|
27
|
-
(Glimmer::Web::ElementProxy::ELEMENT_KEYWORDS - ['span', 'style']).each do |keyword|
|
27
|
+
(Glimmer::Web::ElementProxy::ELEMENT_KEYWORDS - ['a', 'span', 'style']).each do |keyword|
|
28
28
|
Glimmer::DSL::Engine.static_expressions[keyword] ||= Concurrent::Hash.new
|
29
29
|
element_expression_dsl = element_expression.class.dsl
|
30
30
|
Glimmer::DSL::Engine.static_expressions[keyword][element_expression_dsl] = element_expression
|
@@ -1,13 +1,18 @@
|
|
1
|
-
require 'glimmer/dsl/
|
1
|
+
require 'glimmer/dsl/expression'
|
2
2
|
require 'glimmer/dsl/web/general_element_expression'
|
3
3
|
|
4
4
|
module Glimmer
|
5
5
|
module DSL
|
6
6
|
module Web
|
7
|
-
class StyleExpression <
|
7
|
+
class StyleExpression < Expression
|
8
8
|
include GeneralElementExpression
|
9
9
|
include Glimmer
|
10
10
|
|
11
|
+
def can_interpret?(parent, keyword, *args, &block)
|
12
|
+
keyword == 'style' &&
|
13
|
+
!block.nil?
|
14
|
+
end
|
15
|
+
|
11
16
|
def add_content(parent, keyword, *args, &block)
|
12
17
|
if parent.rendered? || parent.skip_content_on_render_blocks?
|
13
18
|
return_value = css(&block).to_s
|
@@ -33,7 +33,8 @@ module Glimmer
|
|
33
33
|
keyword = keyword.to_s
|
34
34
|
(
|
35
35
|
FORMATTING_ELEMENT_KEYWORDS.include?(keyword) ||
|
36
|
-
(keyword == 'span' && parent&.keyword == 'p')
|
36
|
+
(keyword == 'span' && parent&.keyword == 'p') ||
|
37
|
+
(keyword == 'a' && parent&.keyword == 'p')
|
37
38
|
)
|
38
39
|
end
|
39
40
|
|
@@ -0,0 +1,44 @@
|
|
1
|
+
Todo = Struct.new(:task, :completed, :editing, keyword_init: true) do
|
2
|
+
class << self
|
3
|
+
attr_writer :all
|
4
|
+
|
5
|
+
def all
|
6
|
+
@all ||= []
|
7
|
+
end
|
8
|
+
|
9
|
+
def active
|
10
|
+
all.select(&:active?)
|
11
|
+
end
|
12
|
+
|
13
|
+
def completed
|
14
|
+
all.select(&:completed?)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
FILTERS = [:all, :active, :completed]
|
19
|
+
|
20
|
+
alias completed? completed
|
21
|
+
alias editing? editing
|
22
|
+
|
23
|
+
def active
|
24
|
+
!completed
|
25
|
+
end
|
26
|
+
alias active? active
|
27
|
+
|
28
|
+
def start_editing
|
29
|
+
return if editing?
|
30
|
+
@original_task = task
|
31
|
+
self.editing = true
|
32
|
+
end
|
33
|
+
|
34
|
+
def cancel_editing
|
35
|
+
return unless editing?
|
36
|
+
self.task = @original_task
|
37
|
+
self.editing = false
|
38
|
+
end
|
39
|
+
|
40
|
+
def save_editing
|
41
|
+
return unless editing?
|
42
|
+
self.editing = false
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
require 'glimmer/data_binding/observer'
|
2
|
+
|
3
|
+
require_relative '../models/todo'
|
4
|
+
|
5
|
+
class TodoPresenter
|
6
|
+
FILTER_ROUTE_REGEXP = /\#\/([^\/]*)$/
|
7
|
+
|
8
|
+
attr_accessor :todos, :can_clear_completed, :active_todo_count
|
9
|
+
attr_reader :new_todo, :filter
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
@todos = Todo.all.clone
|
13
|
+
@new_todo = Todo.new(task: '')
|
14
|
+
@filter = :all
|
15
|
+
refresh_todo_stats
|
16
|
+
end
|
17
|
+
|
18
|
+
def create_todo(todo = nil)
|
19
|
+
todo ||= new_todo.clone
|
20
|
+
Todo.all.prepend(todo)
|
21
|
+
observers_for_todo_stats[todo.object_id] = todo_stat_observer.observe(todo, :completed) unless observers_for_todo_stats.has_key?(todo.object_id)
|
22
|
+
refresh_todos_with_filter
|
23
|
+
refresh_todo_stats
|
24
|
+
new_todo.task = ''
|
25
|
+
end
|
26
|
+
|
27
|
+
def refresh_todos_with_filter
|
28
|
+
self.todos = Todo.send(filter).clone
|
29
|
+
end
|
30
|
+
|
31
|
+
def filter=(filter)
|
32
|
+
return if filter == @filter
|
33
|
+
@filter = filter
|
34
|
+
refresh_todos_with_filter
|
35
|
+
end
|
36
|
+
|
37
|
+
def destroy(todo)
|
38
|
+
delete(todo)
|
39
|
+
refresh_todos_with_filter
|
40
|
+
refresh_todo_stats
|
41
|
+
end
|
42
|
+
|
43
|
+
def clear_completed
|
44
|
+
Todo.completed.each { |todo| delete(todo) }
|
45
|
+
refresh_todos_with_filter
|
46
|
+
refresh_todo_stats
|
47
|
+
end
|
48
|
+
|
49
|
+
def toggle_all_completed
|
50
|
+
target_completed_value = Todo.active.any?
|
51
|
+
todos_to_update = target_completed_value ? Todo.active : Todo.completed
|
52
|
+
todos_to_update.each { |todo| todo.completed = target_completed_value }
|
53
|
+
end
|
54
|
+
|
55
|
+
def setup_filter_routes
|
56
|
+
@filter_router_function = -> (event) { apply_route_filter }
|
57
|
+
$$.addEventListener('popstate', &@filter_router_function)
|
58
|
+
apply_route_filter
|
59
|
+
end
|
60
|
+
|
61
|
+
def apply_route_filter
|
62
|
+
route_filter_match = $$.document.location.href.to_s.match(FILTER_ROUTE_REGEXP)
|
63
|
+
return if route_filter_match.nil?
|
64
|
+
route_filter = route_filter_match[1]
|
65
|
+
route_filter = 'all' if route_filter == ''
|
66
|
+
self.filter = route_filter
|
67
|
+
end
|
68
|
+
|
69
|
+
def unsetup_filter_routes
|
70
|
+
$$.removeEventListener('popstate', &@filter_router_function)
|
71
|
+
@filter_router_function = nil
|
72
|
+
end
|
73
|
+
|
74
|
+
private
|
75
|
+
|
76
|
+
def delete(todo)
|
77
|
+
Todo.all.delete(todo)
|
78
|
+
observer_registration = observers_for_todo_stats.delete(todo.object_id)
|
79
|
+
observer_registration&.deregister
|
80
|
+
end
|
81
|
+
|
82
|
+
def observers_for_todo_stats
|
83
|
+
@observers_for_todo_stats = {}
|
84
|
+
end
|
85
|
+
|
86
|
+
def todo_stat_observer
|
87
|
+
@todo_stat_observer ||= Glimmer::DataBinding::Observer.proc { refresh_todo_stats }
|
88
|
+
end
|
89
|
+
|
90
|
+
def refresh_todo_stats
|
91
|
+
refresh_can_clear_completed
|
92
|
+
refresh_active_todo_count
|
93
|
+
end
|
94
|
+
|
95
|
+
def refresh_can_clear_completed
|
96
|
+
self.can_clear_completed = Todo.completed.any?
|
97
|
+
end
|
98
|
+
|
99
|
+
def refresh_active_todo_count
|
100
|
+
self.active_todo_count = Todo.active.count
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require_relative 'todo_input'
|
2
|
+
|
3
|
+
class EditTodoInput < TodoInput
|
4
|
+
option :presenter
|
5
|
+
option :todo
|
6
|
+
|
7
|
+
markup {
|
8
|
+
input(class: todo_input_class) { |edit_input|
|
9
|
+
style <= [ todo, :editing,
|
10
|
+
on_read: ->(editing) { editing ? '' : 'display: none;' },
|
11
|
+
after_read: ->(_) { edit_input.focus if todo.editing? }
|
12
|
+
]
|
13
|
+
|
14
|
+
value <=> [todo, :task]
|
15
|
+
|
16
|
+
onkeyup do |event|
|
17
|
+
if event.key == 'Enter' || event.keyCode == "\r"
|
18
|
+
todo.save_editing
|
19
|
+
presenter.destroy(todo) if todo.task.strip.empty?
|
20
|
+
elsif event.key == 'Escape' || event.keyCode == 27
|
21
|
+
todo.cancel_editing
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
onblur do |event|
|
26
|
+
todo.save_editing
|
27
|
+
end
|
28
|
+
|
29
|
+
style {
|
30
|
+
todo_input_styles
|
31
|
+
}
|
32
|
+
}
|
33
|
+
}
|
34
|
+
|
35
|
+
def todo_input_class
|
36
|
+
'edit-todo'
|
37
|
+
end
|
38
|
+
|
39
|
+
def todo_input_styles
|
40
|
+
super
|
41
|
+
|
42
|
+
rule("*:has(> .#{todo_input_class})") {
|
43
|
+
position 'relative'
|
44
|
+
}
|
45
|
+
|
46
|
+
rule(".#{todo_input_class}") {
|
47
|
+
position 'absolute'
|
48
|
+
display 'block'
|
49
|
+
width 'calc(100% - 43px)'
|
50
|
+
padding '12px 16px'
|
51
|
+
margin '0 0 0 43px'
|
52
|
+
top '0'
|
53
|
+
}
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require_relative 'new_todo_input'
|
2
|
+
|
3
|
+
class NewTodoForm
|
4
|
+
include Glimmer::Web::Component
|
5
|
+
|
6
|
+
option :presenter
|
7
|
+
|
8
|
+
markup {
|
9
|
+
header(class: 'header') {
|
10
|
+
h1('todos')
|
11
|
+
|
12
|
+
new_todo_input(presenter: presenter)
|
13
|
+
|
14
|
+
style {
|
15
|
+
rule('.header h1') {
|
16
|
+
color '#b83f45'
|
17
|
+
font_size '80px'
|
18
|
+
font_weight '200'
|
19
|
+
position 'absolute'
|
20
|
+
text_align 'center'
|
21
|
+
_webkit_text_rendering 'optimizeLegibility'
|
22
|
+
_moz_text_rendering 'optimizeLegibility'
|
23
|
+
text_rendering 'optimizeLegibility'
|
24
|
+
top '-140px'
|
25
|
+
width '100%'
|
26
|
+
}
|
27
|
+
}
|
28
|
+
}
|
29
|
+
}
|
30
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require_relative 'todo_input'
|
2
|
+
|
3
|
+
class NewTodoInput < TodoInput
|
4
|
+
option :presenter
|
5
|
+
|
6
|
+
markup {
|
7
|
+
input(class: todo_input_class, placeholder: "What needs to be done?", autofocus: "") {
|
8
|
+
value <=> [presenter.new_todo, :task]
|
9
|
+
|
10
|
+
onkeyup do |event|
|
11
|
+
presenter.create_todo if event.key == 'Enter' || event.keyCode == "\r"
|
12
|
+
end
|
13
|
+
|
14
|
+
style {
|
15
|
+
todo_input_styles
|
16
|
+
}
|
17
|
+
}
|
18
|
+
}
|
19
|
+
|
20
|
+
def todo_input_class
|
21
|
+
'new-todo'
|
22
|
+
end
|
23
|
+
|
24
|
+
def todo_input_styles
|
25
|
+
super
|
26
|
+
|
27
|
+
rule(".#{todo_input_class}") {
|
28
|
+
padding '16px 16px 16px 60px'
|
29
|
+
height '65px'
|
30
|
+
border 'none'
|
31
|
+
background 'rgba(0, 0, 0, 0.003)'
|
32
|
+
box_shadow 'inset 0 -2px 1px rgba(0,0,0,0.03)'
|
33
|
+
}
|
34
|
+
|
35
|
+
rule(".#{todo_input_class}::placeholder") {
|
36
|
+
font_style 'italic'
|
37
|
+
font_weight '400'
|
38
|
+
color 'rgba(0, 0, 0, 0.4)'
|
39
|
+
}
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,123 @@
|
|
1
|
+
class TodoFilters
|
2
|
+
include Glimmer::Web::Component
|
3
|
+
|
4
|
+
option :presenter
|
5
|
+
|
6
|
+
markup {
|
7
|
+
footer(class: 'todo-filters') {
|
8
|
+
style <= [ Todo, :all,
|
9
|
+
on_read: ->(todos) { todos.empty? ? 'display: none;' : '' }
|
10
|
+
]
|
11
|
+
|
12
|
+
span(class: 'todo-count') {
|
13
|
+
span('.strong') {
|
14
|
+
inner_text <= [presenter, :active_todo_count]
|
15
|
+
}
|
16
|
+
span {
|
17
|
+
" items left"
|
18
|
+
}
|
19
|
+
}
|
20
|
+
|
21
|
+
ul(class: 'filters') {
|
22
|
+
Todo::FILTERS.each do |filter|
|
23
|
+
li {
|
24
|
+
a(filter.to_s.capitalize, href: "#/#{filter unless filter == :all}") {
|
25
|
+
class_name <= [ presenter, :filter,
|
26
|
+
on_read: -> (presenter_filter) { presenter_filter == filter ? 'selected' : '' }
|
27
|
+
]
|
28
|
+
|
29
|
+
onclick do |event|
|
30
|
+
presenter.filter = filter
|
31
|
+
end
|
32
|
+
}
|
33
|
+
}
|
34
|
+
end
|
35
|
+
}
|
36
|
+
|
37
|
+
button('Clear completed', class: 'clear-completed') {
|
38
|
+
style <= [ presenter, :can_clear_completed,
|
39
|
+
on_read: -> (can_clear_completed) { can_clear_completed ? '' : 'display: none;' },
|
40
|
+
]
|
41
|
+
|
42
|
+
onclick do |event|
|
43
|
+
presenter.clear_completed
|
44
|
+
end
|
45
|
+
}
|
46
|
+
|
47
|
+
style {
|
48
|
+
rule('.todo-filters') {
|
49
|
+
border_top '1px solid #e6e6e6'
|
50
|
+
font_size '15px'
|
51
|
+
height '20px'
|
52
|
+
padding '10px 15px'
|
53
|
+
text_align 'center'
|
54
|
+
}
|
55
|
+
|
56
|
+
rule('.todo-filters:before') {
|
57
|
+
bottom '0'
|
58
|
+
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)'
|
59
|
+
content '""'
|
60
|
+
height '50px'
|
61
|
+
left '0'
|
62
|
+
overflow 'hidden'
|
63
|
+
position 'absolute'
|
64
|
+
right '0'
|
65
|
+
}
|
66
|
+
|
67
|
+
rule('.todo-count') {
|
68
|
+
float 'left'
|
69
|
+
text_align 'left'
|
70
|
+
}
|
71
|
+
|
72
|
+
rule('.todo-count .strong') {
|
73
|
+
font_weight '300'
|
74
|
+
}
|
75
|
+
|
76
|
+
rule('.filters') {
|
77
|
+
left '0'
|
78
|
+
list_style 'none'
|
79
|
+
margin '0'
|
80
|
+
padding '0'
|
81
|
+
position 'absolute'
|
82
|
+
right '0'
|
83
|
+
}
|
84
|
+
|
85
|
+
rule('.filters li') {
|
86
|
+
display 'inline'
|
87
|
+
}
|
88
|
+
|
89
|
+
rule('.filters li a') {
|
90
|
+
border '1px solid transparent'
|
91
|
+
border_radius '3px'
|
92
|
+
color 'inherit'
|
93
|
+
margin '3px'
|
94
|
+
padding '3px 7px'
|
95
|
+
text_decoration 'none'
|
96
|
+
cursor 'pointer'
|
97
|
+
}
|
98
|
+
|
99
|
+
rule('.filters li a.selected') {
|
100
|
+
border_color '#ce4646'
|
101
|
+
}
|
102
|
+
|
103
|
+
rule('.clear-completed, html .clear-completed:active') {
|
104
|
+
cursor 'pointer'
|
105
|
+
float 'right'
|
106
|
+
line_height '19px'
|
107
|
+
position 'relative'
|
108
|
+
text_decoration 'none'
|
109
|
+
}
|
110
|
+
|
111
|
+
media('(max-width: 430px)') {
|
112
|
+
rule('.todo-filters') {
|
113
|
+
height '50px'
|
114
|
+
}
|
115
|
+
|
116
|
+
rule('.filters') {
|
117
|
+
bottom '10px'
|
118
|
+
}
|
119
|
+
}
|
120
|
+
}
|
121
|
+
}
|
122
|
+
}
|
123
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# Superclass for NewTodoInput and EditTodoInput with common styles
|
2
|
+
class TodoInput
|
3
|
+
include Glimmer::Web::Component
|
4
|
+
|
5
|
+
def todo_input_class
|
6
|
+
'todo-input'
|
7
|
+
end
|
8
|
+
|
9
|
+
def todo_input_styles
|
10
|
+
rule(".#{todo_input_class}") {
|
11
|
+
position 'relative'
|
12
|
+
margin '0'
|
13
|
+
width '100%'
|
14
|
+
font_size '24px'
|
15
|
+
font_family 'inherit'
|
16
|
+
font_weight 'inherit'
|
17
|
+
line_height '1.4em'
|
18
|
+
color 'inherit'
|
19
|
+
padding '6px'
|
20
|
+
border '1px solid #999'
|
21
|
+
box_shadow 'inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2)'
|
22
|
+
box_sizing 'border-box'
|
23
|
+
_webkit_font_smoothing 'antialiased'
|
24
|
+
}
|
25
|
+
|
26
|
+
rule(".#{todo_input_class}::selection") {
|
27
|
+
background 'red'
|
28
|
+
}
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
require_relative 'todo_list_item'
|
2
|
+
|
3
|
+
class TodoList
|
4
|
+
include Glimmer::Web::Component
|
5
|
+
|
6
|
+
option :presenter
|
7
|
+
|
8
|
+
markup {
|
9
|
+
main(class: 'main') {
|
10
|
+
style <= [ Todo, :all,
|
11
|
+
on_read: ->(todos) { todos.empty? ? 'display: none;' : '' }
|
12
|
+
]
|
13
|
+
|
14
|
+
div(class: 'toggle-all-container') {
|
15
|
+
input(class: 'toggle-all', type: 'checkbox')
|
16
|
+
|
17
|
+
label('Mark all as complete', class: 'toggle-all-label', for: 'toggle-all') {
|
18
|
+
onclick do |event|
|
19
|
+
presenter.toggle_all_completed
|
20
|
+
end
|
21
|
+
}
|
22
|
+
}
|
23
|
+
|
24
|
+
ul(class: 'todo-list') {
|
25
|
+
content(presenter, :todos) {
|
26
|
+
presenter.todos.each do |todo|
|
27
|
+
todo_list_item(presenter:, todo:)
|
28
|
+
end
|
29
|
+
}
|
30
|
+
}
|
31
|
+
|
32
|
+
style {
|
33
|
+
todo_list_styles
|
34
|
+
}
|
35
|
+
}
|
36
|
+
}
|
37
|
+
|
38
|
+
def todo_list_styles
|
39
|
+
rule('.main') {
|
40
|
+
border_top '1px solid #e6e6e6'
|
41
|
+
position 'relative'
|
42
|
+
z_index '2'
|
43
|
+
}
|
44
|
+
|
45
|
+
rule('.toggle-all') {
|
46
|
+
border 'none'
|
47
|
+
bottom '100%'
|
48
|
+
height '1px'
|
49
|
+
opacity '0'
|
50
|
+
position 'absolute'
|
51
|
+
right '100%'
|
52
|
+
width '1px'
|
53
|
+
}
|
54
|
+
|
55
|
+
rule('.toggle-all+label') {
|
56
|
+
align_items 'center'
|
57
|
+
display 'flex'
|
58
|
+
font_size '0'
|
59
|
+
height '65px'
|
60
|
+
justify_content 'center'
|
61
|
+
left '0'
|
62
|
+
position 'absolute'
|
63
|
+
top '-65px'
|
64
|
+
width '45px'
|
65
|
+
}
|
66
|
+
|
67
|
+
rule('.toggle-all+label:before') {
|
68
|
+
color '#949494'
|
69
|
+
content '"❯"'
|
70
|
+
display 'inline-block'
|
71
|
+
font_size '22px'
|
72
|
+
padding '10px 27px'
|
73
|
+
_webkit_transform 'rotate(90deg)'
|
74
|
+
transform 'rotate(90deg)'
|
75
|
+
}
|
76
|
+
|
77
|
+
rule('.toggle-all:focus+label, .toggle:focus+label, :focus') {
|
78
|
+
box_shadow '0 0 2px 2px #cf7d7d'
|
79
|
+
outline '0'
|
80
|
+
}
|
81
|
+
|
82
|
+
rule('.todo-list') {
|
83
|
+
list_style 'none'
|
84
|
+
margin '0'
|
85
|
+
padding '0'
|
86
|
+
}
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,158 @@
|
|
1
|
+
require_relative 'edit_todo_input'
|
2
|
+
|
3
|
+
class TodoListItem
|
4
|
+
include Glimmer::Web::Component
|
5
|
+
|
6
|
+
option :presenter
|
7
|
+
option :todo
|
8
|
+
|
9
|
+
markup {
|
10
|
+
li {
|
11
|
+
class_name <= [ todo, :completed,
|
12
|
+
on_read: -> (completed) { li_class_name(todo) }
|
13
|
+
]
|
14
|
+
class_name <= [ todo, :editing,
|
15
|
+
on_read: -> (editing) { li_class_name(todo) }
|
16
|
+
]
|
17
|
+
|
18
|
+
div(class: 'view') {
|
19
|
+
input(class: 'toggle', type: 'checkbox') {
|
20
|
+
checked <=> [ todo, :completed,
|
21
|
+
after_write: -> (_) { presenter.refresh_todos_with_filter if presenter.filter != :all }
|
22
|
+
]
|
23
|
+
}
|
24
|
+
|
25
|
+
label {
|
26
|
+
inner_html <= [todo, :task]
|
27
|
+
|
28
|
+
ondblclick do |event|
|
29
|
+
todo.start_editing
|
30
|
+
end
|
31
|
+
}
|
32
|
+
|
33
|
+
button(class: 'destroy') {
|
34
|
+
onclick do |event|
|
35
|
+
presenter.destroy(todo)
|
36
|
+
end
|
37
|
+
}
|
38
|
+
}
|
39
|
+
|
40
|
+
edit_todo_input(presenter:, todo:)
|
41
|
+
|
42
|
+
if todo == presenter.todos.first
|
43
|
+
style {
|
44
|
+
todo_list_item_styles
|
45
|
+
}
|
46
|
+
end
|
47
|
+
}
|
48
|
+
}
|
49
|
+
|
50
|
+
def li_class_name(todo)
|
51
|
+
classes = []
|
52
|
+
classes << 'completed' if todo.completed?
|
53
|
+
classes << 'editing' if todo.editing?
|
54
|
+
classes.join(' ')
|
55
|
+
end
|
56
|
+
|
57
|
+
def todo_list_item_styles
|
58
|
+
rule('.todo-list li.completed label') {
|
59
|
+
color '#949494'
|
60
|
+
text_decoration 'line-through'
|
61
|
+
}
|
62
|
+
|
63
|
+
rule('.todo-list li') {
|
64
|
+
border_bottom '1px solid #ededed'
|
65
|
+
font_size '24px'
|
66
|
+
position 'relative'
|
67
|
+
}
|
68
|
+
|
69
|
+
rule('.todo-list li .toggle') {
|
70
|
+
_webkit_appearance 'none'
|
71
|
+
appearance 'none'
|
72
|
+
border 'none'
|
73
|
+
bottom '0'
|
74
|
+
height 'auto'
|
75
|
+
margin 'auto 0'
|
76
|
+
opacity '0'
|
77
|
+
position 'absolute'
|
78
|
+
text_align 'center'
|
79
|
+
top '0'
|
80
|
+
width '40px'
|
81
|
+
}
|
82
|
+
|
83
|
+
rule('.todo-list li label') {
|
84
|
+
color '#484848'
|
85
|
+
display 'block'
|
86
|
+
font_weight '400'
|
87
|
+
line_height '1.2'
|
88
|
+
min_height '40px'
|
89
|
+
padding '15px 15px 15px 60px'
|
90
|
+
transition 'color .4s'
|
91
|
+
word_break 'break-all'
|
92
|
+
}
|
93
|
+
|
94
|
+
rule('.todo-list li .toggle+label') {
|
95
|
+
background_image 'url(data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%2240%22%20height%3D%2240%22%20viewBox%3D%22-10%20-18%20100%20135%22%3E%3Ccircle%20cx%3D%2250%22%20cy%3D%2250%22%20r%3D%2250%22%20fill%3D%22none%22%20stroke%3D%22%23949494%22%20stroke-width%3D%223%22/%3E%3C/svg%3E)'
|
96
|
+
background_position '0'
|
97
|
+
background_repeat 'no-repeat'
|
98
|
+
}
|
99
|
+
|
100
|
+
rule('.todo-list li.completed label') {
|
101
|
+
color '#949494'
|
102
|
+
text_decoration 'line-through'
|
103
|
+
}
|
104
|
+
|
105
|
+
rule('.todo-list li .toggle:checked+label') {
|
106
|
+
background_image 'url(data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2240%22%20height%3D%2240%22%20viewBox%3D%22-10%20-18%20100%20135%22%3E%3Ccircle%20cx%3D%2250%22%20cy%3D%2250%22%20r%3D%2250%22%20fill%3D%22none%22%20stroke%3D%22%2359A193%22%20stroke-width%3D%223%22%2F%3E%3Cpath%20fill%3D%22%233EA390%22%20d%3D%22M72%2025L42%2071%2027%2056l-4%204%2020%2020%2034-52z%22%2F%3E%3C%2Fsvg%3E)'
|
107
|
+
}
|
108
|
+
|
109
|
+
rule('.todo-list li.editing') {
|
110
|
+
border_bottom 'none'
|
111
|
+
padding '0'
|
112
|
+
}
|
113
|
+
|
114
|
+
rule('.todo-list li.editing input[type=checkbox], .todo-list li.editing label') {
|
115
|
+
opacity '0'
|
116
|
+
}
|
117
|
+
|
118
|
+
rule('.todo-list li .destroy') {
|
119
|
+
bottom '0'
|
120
|
+
color '#949494'
|
121
|
+
display 'none'
|
122
|
+
font_size '30px'
|
123
|
+
height '40px'
|
124
|
+
margin 'auto 0'
|
125
|
+
position 'absolute'
|
126
|
+
right '10px'
|
127
|
+
top '0'
|
128
|
+
transition 'color .2s ease-out'
|
129
|
+
width '40px'
|
130
|
+
}
|
131
|
+
|
132
|
+
rule('.todo-list li:focus .destroy, .todo-list li:hover .destroy') {
|
133
|
+
display 'block'
|
134
|
+
}
|
135
|
+
|
136
|
+
rule('.todo-list li .destroy:focus, .todo-list li .destroy:hover') {
|
137
|
+
color '#c18585'
|
138
|
+
}
|
139
|
+
|
140
|
+
rule('.todo-list li .destroy:after') {
|
141
|
+
content '"×"'
|
142
|
+
display 'block'
|
143
|
+
height '100%'
|
144
|
+
line_height '1.1'
|
145
|
+
}
|
146
|
+
|
147
|
+
media ('screen and (-webkit-min-device-pixel-ratio: 0)') {
|
148
|
+
rule('.todo-list li .toggle, .toggle-all') {
|
149
|
+
background 'none'
|
150
|
+
}
|
151
|
+
|
152
|
+
rule('.todo-list li .toggle') {
|
153
|
+
height '40px'
|
154
|
+
}
|
155
|
+
}
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
@@ -0,0 +1,45 @@
|
|
1
|
+
class TodoMvcFooter
|
2
|
+
include Glimmer::Web::Component
|
3
|
+
|
4
|
+
markup {
|
5
|
+
footer(class: 'info') {
|
6
|
+
p {
|
7
|
+
"Double-click to edit a todo"
|
8
|
+
}
|
9
|
+
p {
|
10
|
+
"Created by #{a('Andy Maleh', href: 'https://github.com/AndyObtiva')}"
|
11
|
+
}
|
12
|
+
p {
|
13
|
+
"Part of #{a('TodoMVC', href: 'http://todomvc.com')}"
|
14
|
+
}
|
15
|
+
|
16
|
+
style {
|
17
|
+
todo_mvc_styles
|
18
|
+
}
|
19
|
+
}
|
20
|
+
}
|
21
|
+
|
22
|
+
def todo_mvc_styles
|
23
|
+
rule('footer.info') {
|
24
|
+
margin '65px auto 0'
|
25
|
+
color '#4d4d4d'
|
26
|
+
font_size '11px'
|
27
|
+
text_shadow '0 1px 0 rgba(255, 255, 255, 0.5)'
|
28
|
+
text_align 'center'
|
29
|
+
}
|
30
|
+
|
31
|
+
rule('footer.info p') {
|
32
|
+
line_height '1'
|
33
|
+
}
|
34
|
+
|
35
|
+
rule('footer.info a') {
|
36
|
+
color 'inherit'
|
37
|
+
text_decoration 'none'
|
38
|
+
font_weight '400'
|
39
|
+
}
|
40
|
+
|
41
|
+
rule('footer.info a:hover') {
|
42
|
+
text_decoration 'underline'
|
43
|
+
}
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
require 'glimmer-dsl-web'
|
2
|
+
|
3
|
+
require_relative 'todo_mvc/presenters/todo_presenter'
|
4
|
+
|
5
|
+
require_relative 'todo_mvc/views/new_todo_form'
|
6
|
+
require_relative 'todo_mvc/views/todo_list'
|
7
|
+
require_relative 'todo_mvc/views/todo_filters'
|
8
|
+
require_relative 'todo_mvc/views/todo_mvc_footer'
|
9
|
+
|
10
|
+
class TodoMvc
|
11
|
+
include Glimmer::Web::Component
|
12
|
+
|
13
|
+
before_render do
|
14
|
+
@presenter = TodoPresenter.new
|
15
|
+
end
|
16
|
+
|
17
|
+
after_render do
|
18
|
+
@presenter.setup_filter_routes
|
19
|
+
end
|
20
|
+
|
21
|
+
markup {
|
22
|
+
div(class: 'todomvc') {
|
23
|
+
section(class: 'todoapp') {
|
24
|
+
new_todo_form(presenter: @presenter)
|
25
|
+
|
26
|
+
todo_list(presenter: @presenter)
|
27
|
+
|
28
|
+
todo_filters(presenter: @presenter)
|
29
|
+
|
30
|
+
style {
|
31
|
+
todo_mvc_styles
|
32
|
+
}
|
33
|
+
}
|
34
|
+
|
35
|
+
todo_mvc_footer
|
36
|
+
|
37
|
+
on_remove do
|
38
|
+
@presenter.unsetup_filter_routes
|
39
|
+
end
|
40
|
+
}
|
41
|
+
}
|
42
|
+
|
43
|
+
def todo_mvc_styles
|
44
|
+
rule('body, button, html') {
|
45
|
+
margin '0'
|
46
|
+
padding '0'
|
47
|
+
}
|
48
|
+
|
49
|
+
rule('button') {
|
50
|
+
_webkit_font_smoothing 'antialiased'
|
51
|
+
_webkit_appearance 'none'
|
52
|
+
appearance 'none'
|
53
|
+
background 'none'
|
54
|
+
border '0'
|
55
|
+
color 'inherit'
|
56
|
+
font_family 'inherit'
|
57
|
+
font_size '100%'
|
58
|
+
font_weight 'inherit'
|
59
|
+
vertical_align 'baseline'
|
60
|
+
}
|
61
|
+
|
62
|
+
rule('.todoapp') {
|
63
|
+
background '#fff'
|
64
|
+
margin '130px 0 40px 0'
|
65
|
+
position 'relative'
|
66
|
+
box_shadow '0 2px 4px 0 rgba(0, 0, 0, 0.2), 0 25px 50px 0 rgba(0, 0, 0, 0.1)'
|
67
|
+
}
|
68
|
+
|
69
|
+
media('screen and (-webkit-min-device-pixel-ratio:0)') {
|
70
|
+
rule('body') {
|
71
|
+
font "14px 'Helvetica Neue', Helvetica, Arial, sans-serif"
|
72
|
+
line_height '1.4em'
|
73
|
+
background '#f5f5f5'
|
74
|
+
color '#111111'
|
75
|
+
min_width '230px'
|
76
|
+
max_width '550px'
|
77
|
+
margin '0 auto'
|
78
|
+
_webkit_font_smoothing 'antialiased'
|
79
|
+
font_weight '300'
|
80
|
+
}
|
81
|
+
}
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
Document.ready? do
|
86
|
+
TodoMvc.render
|
87
|
+
end
|
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.2.
|
4
|
+
version: 0.2.7
|
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-
|
11
|
+
date: 2024-06-15 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: glimmer
|
@@ -30,28 +30,28 @@ dependencies:
|
|
30
30
|
requirements:
|
31
31
|
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: 1.
|
33
|
+
version: 1.4.0
|
34
34
|
type: :runtime
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version: 1.
|
40
|
+
version: 1.4.0
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: glimmer-dsl-css
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
45
|
- - "~>"
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version: 1.
|
47
|
+
version: 1.4.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.4.0
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
56
|
name: opal
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -134,14 +134,14 @@ dependencies:
|
|
134
134
|
requirements:
|
135
135
|
- - ">="
|
136
136
|
- !ruby/object:Gem::Version
|
137
|
-
version:
|
137
|
+
version: 1.0.0
|
138
138
|
type: :development
|
139
139
|
prerelease: false
|
140
140
|
version_requirements: !ruby/object:Gem::Requirement
|
141
141
|
requirements:
|
142
142
|
- - ">="
|
143
143
|
- !ruby/object:Gem::Version
|
144
|
-
version:
|
144
|
+
version: 1.0.0
|
145
145
|
- !ruby/object:Gem::Dependency
|
146
146
|
name: rake
|
147
147
|
requirement: !ruby/object:Gem::Requirement
|
@@ -281,9 +281,21 @@ files:
|
|
281
281
|
- lib/glimmer-dsl-web/samples/hello/hello_paragraph.rb
|
282
282
|
- lib/glimmer-dsl-web/samples/hello/hello_world.rb
|
283
283
|
- lib/glimmer-dsl-web/samples/regular/button_counter.rb
|
284
|
+
- lib/glimmer-dsl-web/samples/regular/todo_mvc.rb
|
285
|
+
- lib/glimmer-dsl-web/samples/regular/todo_mvc/models/todo.rb
|
286
|
+
- lib/glimmer-dsl-web/samples/regular/todo_mvc/presenters/todo_presenter.rb
|
287
|
+
- lib/glimmer-dsl-web/samples/regular/todo_mvc/views/edit_todo_input.rb
|
288
|
+
- lib/glimmer-dsl-web/samples/regular/todo_mvc/views/new_todo_form.rb
|
289
|
+
- lib/glimmer-dsl-web/samples/regular/todo_mvc/views/new_todo_input.rb
|
290
|
+
- lib/glimmer-dsl-web/samples/regular/todo_mvc/views/todo_filters.rb
|
291
|
+
- lib/glimmer-dsl-web/samples/regular/todo_mvc/views/todo_input.rb
|
292
|
+
- lib/glimmer-dsl-web/samples/regular/todo_mvc/views/todo_list.rb
|
293
|
+
- lib/glimmer-dsl-web/samples/regular/todo_mvc/views/todo_list_item.rb
|
294
|
+
- lib/glimmer-dsl-web/samples/regular/todo_mvc/views/todo_mvc_footer.rb
|
284
295
|
- lib/glimmer-dsl-web/vendor/jquery.js
|
285
296
|
- lib/glimmer/config/opal_logger.rb
|
286
297
|
- lib/glimmer/data_binding/element_binding.rb
|
298
|
+
- lib/glimmer/dsl/web/a_expression.rb
|
287
299
|
- lib/glimmer/dsl/web/bind_expression.rb
|
288
300
|
- lib/glimmer/dsl/web/component_expression.rb
|
289
301
|
- lib/glimmer/dsl/web/content_data_binding_expression.rb
|