clearwater 1.0.0.rc4 → 1.0.0.rc5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/clearwater/component.rb +2 -6
- data/lib/clearwater/version.rb +1 -1
- data/opal/clearwater/application.rb +5 -1
- data/opal/clearwater/application_registry.rb +4 -0
- data/opal/clearwater/black_box_node.rb +14 -1
- data/opal/clearwater/cached_render/wrapper.rb +2 -1
- data/opal/clearwater/cached_render.rb +0 -1
- data/opal/clearwater/component.rb +11 -6
- data/opal/clearwater/dom_reference.rb +4 -2
- data/opal/clearwater/memoized_component.rb +79 -0
- data/opal/clearwater/virtual_dom.rb +10 -5
- data/shared/clearwater/router/route.rb +6 -4
- data/shared/clearwater/router.rb +39 -14
- data/spec/clearwater/component_spec.rb +11 -0
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 36c47513423cf3f1e0a6b3705f3e07d59bbbed31
|
4
|
+
data.tar.gz: 9040af257c8ce168472c92d4ff4dff608bf1da67
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 553ce8493b33aa68602b4d4b792083e1ab7c5bb957183e3a5474f9103ac97a3ffa5bfad03f5a1eb6e3075db9e0d04fa8309c8ebe110edc7006a36dacac587faa
|
7
|
+
data.tar.gz: 4341d6edfe0eef28e0a20b1e405a2ad6d93a675bf96471f69268aab0e9088670788da5a1f915128f91a9882fb2c8ee3cda78d267573dae22f05c8a412d6431a0
|
data/lib/clearwater/component.rb
CHANGED
@@ -27,12 +27,8 @@ module Clearwater
|
|
27
27
|
end
|
28
28
|
|
29
29
|
def to_s
|
30
|
-
html = render.
|
31
|
-
|
32
|
-
html = html.html_safe
|
33
|
-
end
|
34
|
-
|
35
|
-
html
|
30
|
+
html = Array(render).join
|
31
|
+
html.respond_to?(:html_safe) ? html.html_safe : html
|
36
32
|
end
|
37
33
|
|
38
34
|
def params
|
data/lib/clearwater/version.rb
CHANGED
@@ -47,11 +47,15 @@ module Clearwater
|
|
47
47
|
|
48
48
|
def unmount
|
49
49
|
AppRegistry.delete self
|
50
|
+
@window.off :popstate, &@popstate_listener
|
51
|
+
|
52
|
+
@popstate_listener = nil # Allow the proc to get GCed
|
53
|
+
@watching_url = false
|
50
54
|
end
|
51
55
|
|
52
56
|
def watch_url
|
53
57
|
unless @watching_url
|
54
|
-
@window.on(
|
58
|
+
@popstate_listener = @window.on(:popstate) { render_current_url }
|
55
59
|
@watching_url = true
|
56
60
|
end
|
57
61
|
end
|
@@ -20,9 +20,15 @@ module Clearwater
|
|
20
20
|
Renderable.new(self)
|
21
21
|
end
|
22
22
|
|
23
|
+
def key
|
24
|
+
end
|
25
|
+
|
23
26
|
class Renderable
|
24
27
|
def initialize delegate
|
25
28
|
@delegate = delegate
|
29
|
+
if delegate.key
|
30
|
+
@key = delegate.key
|
31
|
+
end
|
26
32
|
end
|
27
33
|
|
28
34
|
def wrap node
|
@@ -52,7 +58,14 @@ module Clearwater
|
|
52
58
|
// node: a Bowser-wrapped version of the DOM node
|
53
59
|
Opal.defn(self, 'update', function(previous, node) {
|
54
60
|
var self = this;
|
55
|
-
|
61
|
+
if(self.delegate.$$class === previous.delegate.$$class) {
|
62
|
+
#{@delegate.update(`previous.delegate`, wrap(`node`))};
|
63
|
+
} else {
|
64
|
+
previous.destroy(#{wrap(`node`)});
|
65
|
+
var new_node = #{create_element};
|
66
|
+
#{@delegate.mount(`new_node`)};
|
67
|
+
return new_node.native;
|
68
|
+
}
|
56
69
|
});
|
57
70
|
|
58
71
|
// virtual-dom destroy hook
|
@@ -5,7 +5,7 @@ module Clearwater
|
|
5
5
|
|
6
6
|
def initialize content
|
7
7
|
@content = content
|
8
|
-
@key = content.key
|
8
|
+
@key = content.key if content.key
|
9
9
|
end
|
10
10
|
|
11
11
|
# Hook into vdom diff/patch
|
@@ -15,6 +15,7 @@ module Clearwater
|
|
15
15
|
var self = this;
|
16
16
|
|
17
17
|
if(prev && prev.vnode && #{!@content.should_render?(`prev.content`)}) {
|
18
|
+
#{ @content = `prev.content` }
|
18
19
|
return prev.vnode;
|
19
20
|
} else {
|
20
21
|
var content = #{Component.sanitize_content(@content.render)};
|
@@ -43,10 +43,9 @@ module Clearwater
|
|
43
43
|
|
44
44
|
def self.sanitize_content content
|
45
45
|
%x{
|
46
|
+
// Is this a Ruby object?
|
46
47
|
if(content && content.$$class) {
|
47
|
-
if(content.$$is_array) {
|
48
|
-
return #{content.map { |c| sanitize_content(c) }};
|
49
|
-
} else {
|
48
|
+
if(!content.$$is_array) {
|
50
49
|
var render = content.$render;
|
51
50
|
|
52
51
|
if(content.$$cached_render) {
|
@@ -56,7 +55,11 @@ module Clearwater
|
|
56
55
|
} else {
|
57
56
|
return content;
|
58
57
|
}
|
58
|
+
} else {
|
59
|
+
return #{content.map { |c| sanitize_content(c) }};
|
59
60
|
}
|
61
|
+
|
62
|
+
// If it's not a Ruby object, it's probably a virtual-dom node.
|
60
63
|
} else {
|
61
64
|
return content;
|
62
65
|
}
|
@@ -82,11 +85,13 @@ module Clearwater
|
|
82
85
|
end
|
83
86
|
end
|
84
87
|
|
88
|
+
`var vdom = #{VirtualDOM}`
|
89
|
+
`var component = self`
|
85
90
|
def tag tag_name, attributes=nil, content=nil
|
86
|
-
|
91
|
+
`vdom`.node(
|
87
92
|
tag_name,
|
88
|
-
|
89
|
-
|
93
|
+
`component`.sanitize_attributes(attributes),
|
94
|
+
`component`.sanitize_content(content)
|
90
95
|
)
|
91
96
|
end
|
92
97
|
|
@@ -1,5 +1,7 @@
|
|
1
1
|
module Clearwater
|
2
2
|
class DOMReference
|
3
|
+
attr_reader :node
|
4
|
+
|
3
5
|
def mount node, previous
|
4
6
|
@node = node
|
5
7
|
end
|
@@ -34,12 +36,12 @@ module Clearwater
|
|
34
36
|
%x{
|
35
37
|
Opal.defn(self, 'hook', function(node, name, previous) {
|
36
38
|
var self = this;
|
37
|
-
#{mount(wrap(`node`), `previous`)};
|
39
|
+
#{mount(wrap(`node`), `previous == null ? nil : previous`)};
|
38
40
|
});
|
39
41
|
|
40
42
|
Opal.defn(self, 'unhook', function(node, name, previous) {
|
41
43
|
var self = this;
|
42
|
-
#{unmount(wrap(`node`),
|
44
|
+
#{unmount(wrap(`node`), `previous == null ? nil : previous`)};
|
43
45
|
});
|
44
46
|
}
|
45
47
|
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
require 'clearwater/component'
|
2
|
+
require 'clearwater/black_box_node'
|
3
|
+
|
4
|
+
module Clearwater
|
5
|
+
class MemoizedComponent
|
6
|
+
include Clearwater::Component
|
7
|
+
|
8
|
+
def self.memoize *args, &block
|
9
|
+
Placeholder.new(self, args, block)
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.[] key
|
13
|
+
memoize[key]
|
14
|
+
end
|
15
|
+
|
16
|
+
def should_update?
|
17
|
+
true
|
18
|
+
end
|
19
|
+
|
20
|
+
def update
|
21
|
+
end
|
22
|
+
|
23
|
+
def destroy
|
24
|
+
end
|
25
|
+
|
26
|
+
class Placeholder
|
27
|
+
include Clearwater::BlackBoxNode
|
28
|
+
|
29
|
+
attr_reader :klass, :key, :vdom
|
30
|
+
|
31
|
+
def initialize klass, args, block
|
32
|
+
@klass = klass
|
33
|
+
@args = args
|
34
|
+
@block = block
|
35
|
+
end
|
36
|
+
|
37
|
+
def memoize *args, &block
|
38
|
+
initialize @klass, args, block
|
39
|
+
self
|
40
|
+
end
|
41
|
+
|
42
|
+
def [] key
|
43
|
+
@key = key.to_s
|
44
|
+
self
|
45
|
+
end
|
46
|
+
|
47
|
+
def component
|
48
|
+
@component ||= @klass.new(*@args, &@block)
|
49
|
+
end
|
50
|
+
|
51
|
+
def node
|
52
|
+
@node ||= Clearwater::Component.sanitize_content(component)
|
53
|
+
end
|
54
|
+
|
55
|
+
def mount element
|
56
|
+
@vdom = VirtualDOM::Document.new(element)
|
57
|
+
|
58
|
+
# TODO: add a public interface to generate a pre-initialized VDOM::Doc
|
59
|
+
`#@vdom.tree = #{element.to_n}`
|
60
|
+
`#@vdom.node = #{node}`
|
61
|
+
`#@vdom.rendered = true`
|
62
|
+
end
|
63
|
+
|
64
|
+
def update previous
|
65
|
+
@vdom = previous.vdom
|
66
|
+
@component = previous.component
|
67
|
+
|
68
|
+
if component.should_update?(*@args, &@block)
|
69
|
+
component.update(*@args, &@block)
|
70
|
+
@vdom.render component.render
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def unmount
|
75
|
+
component.destroy
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -2,11 +2,13 @@ require 'clearwater/virtual_dom/js/virtual_dom.js'
|
|
2
2
|
|
3
3
|
module Clearwater
|
4
4
|
module VirtualDOM
|
5
|
+
`var hash_utils;`
|
6
|
+
|
5
7
|
def self.node(tag_name, attributes=nil, content=nil)
|
6
8
|
%x{
|
7
9
|
return virtualDom.h(
|
8
10
|
tag_name,
|
9
|
-
#{
|
11
|
+
#{`hash_utils`.camelized_native(attributes)},
|
10
12
|
#{sanitize_content(content)}
|
11
13
|
);
|
12
14
|
}
|
@@ -36,15 +38,15 @@ module Clearwater
|
|
36
38
|
|
37
39
|
def self.sanitize_content content
|
38
40
|
%x{
|
39
|
-
if(content ===
|
41
|
+
if(content === #{nil} || content == null) return null;
|
40
42
|
if(content.$$is_array)
|
41
43
|
return #{content.map!{ |c| sanitize_content c }};
|
42
|
-
return content;
|
44
|
+
return content.valueOf();
|
43
45
|
}
|
44
46
|
end
|
45
47
|
|
46
48
|
class Document
|
47
|
-
def initialize(root=Bowser.document.create_element(
|
49
|
+
def initialize(root=Bowser.document.create_element(:div))
|
48
50
|
@root = root
|
49
51
|
end
|
50
52
|
|
@@ -91,6 +93,8 @@ module Clearwater
|
|
91
93
|
end
|
92
94
|
|
93
95
|
module HashUtils
|
96
|
+
`var string_utils = #{StringUtils}`
|
97
|
+
|
94
98
|
def self.camelized_native hash
|
95
99
|
return hash.to_n unless `!!hash.$$is_hash`
|
96
100
|
|
@@ -99,7 +103,7 @@ module Clearwater
|
|
99
103
|
for(var index = 0; index < keys.length; index++) {
|
100
104
|
key = keys[index];
|
101
105
|
v = #{hash[`key`]};
|
102
|
-
js_obj[#{
|
106
|
+
js_obj[#{`string_utils`.camelize(`key`)}] = v.$$is_hash
|
103
107
|
? self.$camelized_native(v)
|
104
108
|
: (
|
105
109
|
(v && v.$$class) // If this is a Ruby object, nativize it
|
@@ -111,5 +115,6 @@ module Clearwater
|
|
111
115
|
}
|
112
116
|
end
|
113
117
|
end
|
118
|
+
`hash_utils = #{HashUtils}`
|
114
119
|
end
|
115
120
|
end
|
@@ -10,10 +10,12 @@ module Clearwater
|
|
10
10
|
@target = options.fetch(:target)
|
11
11
|
@parent = options.fetch(:parent)
|
12
12
|
|
13
|
-
if
|
14
|
-
|
15
|
-
|
16
|
-
|
13
|
+
if RUBY_ENGINE == 'opal'
|
14
|
+
if `#@target.type === 'Thunk' && typeof #@target.render === 'function'`
|
15
|
+
warn "Route '#{key}' points to a cached component. Cached " +
|
16
|
+
"components must not be persistent components, such as " +
|
17
|
+
"application roots or routing targets."
|
18
|
+
end
|
17
19
|
end
|
18
20
|
end
|
19
21
|
|
data/shared/clearwater/router.rb
CHANGED
@@ -81,16 +81,26 @@ module Clearwater
|
|
81
81
|
end
|
82
82
|
|
83
83
|
def navigate_to path
|
84
|
+
self.class.previous_path = current_path
|
84
85
|
history.push path
|
85
86
|
set_outlets
|
86
87
|
render_application
|
87
88
|
end
|
88
89
|
|
89
90
|
def self.navigate_to path
|
91
|
+
self.previous_path = current_path
|
90
92
|
Bowser.window.history.push path
|
91
93
|
render_all_apps
|
92
94
|
end
|
93
95
|
|
96
|
+
def self.previous_path=(path)
|
97
|
+
@previous_path = path
|
98
|
+
end
|
99
|
+
|
100
|
+
def self.previous_path
|
101
|
+
@previous_path.to_s
|
102
|
+
end
|
103
|
+
|
94
104
|
def navigate_to_remote path
|
95
105
|
location.href = path
|
96
106
|
end
|
@@ -99,23 +109,38 @@ module Clearwater
|
|
99
109
|
history.back
|
100
110
|
end
|
101
111
|
|
102
|
-
def
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
+
def trigger_routing_callbacks(path:, previous_path:)
|
113
|
+
# If the paths are the same, there are no callbacks to trigger
|
114
|
+
return if path == previous_path
|
115
|
+
|
116
|
+
targets = targets_for_path(path)
|
117
|
+
old_targets = targets_for_path(previous_path)
|
118
|
+
routes = routes_for_path(path)
|
119
|
+
old_params = params(previous_path)
|
120
|
+
new_params = params(path)
|
121
|
+
|
122
|
+
changed_dynamic_segments = new_params
|
123
|
+
.select { |k, v| old_params[k] != v }
|
124
|
+
.map { |key, _| ":#{key}" }
|
125
|
+
|
126
|
+
changed_dynamic_targets = routes.drop_while { |route|
|
127
|
+
!changed_dynamic_segments.include?(route.key)
|
128
|
+
}.map(&:target)
|
129
|
+
|
130
|
+
navigating_from = old_targets - targets
|
131
|
+
navigating_to = targets - old_targets
|
132
|
+
|
133
|
+
(navigating_from | changed_dynamic_targets).each do |target|
|
134
|
+
target.on_route_from if target.respond_to? :on_route_from
|
112
135
|
end
|
113
136
|
|
114
|
-
navigating_to.each do |target|
|
115
|
-
if target.respond_to? :on_route_to
|
116
|
-
target.on_route_to
|
117
|
-
end
|
137
|
+
(navigating_to | changed_dynamic_targets).each do |target|
|
138
|
+
target.on_route_to if target.respond_to? :on_route_to
|
118
139
|
end
|
140
|
+
end
|
141
|
+
|
142
|
+
def set_outlets targets=targets_for_path(current_path)
|
143
|
+
trigger_routing_callbacks(path: current_path, previous_path: self.class.previous_path)
|
119
144
|
|
120
145
|
if targets.any?
|
121
146
|
(targets.count).times do |index|
|
@@ -13,6 +13,17 @@ module Clearwater
|
|
13
13
|
expect(html).to eq('<div id="foo" class="bar"><p>baz</p></div>')
|
14
14
|
end
|
15
15
|
|
16
|
+
it 'generates html for components rendering an array' do
|
17
|
+
component_class = Class.new do
|
18
|
+
include Clearwater::Component
|
19
|
+
|
20
|
+
def render
|
21
|
+
[ div({}, "1"), div({}, "2") ]
|
22
|
+
end
|
23
|
+
end
|
24
|
+
expect(component_class.new.to_s).to eq('<div>1</div><div>2</div>')
|
25
|
+
end
|
26
|
+
|
16
27
|
it 'converts styles into strings' do
|
17
28
|
html = component.div({
|
18
29
|
style: {
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: clearwater
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.0.
|
4
|
+
version: 1.0.0.rc5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jamie Gaskins
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2017-06-23 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: opal
|
@@ -132,6 +132,7 @@ files:
|
|
132
132
|
- opal/clearwater/component.rb
|
133
133
|
- opal/clearwater/dom_reference.rb
|
134
134
|
- opal/clearwater/link.rb
|
135
|
+
- opal/clearwater/memoized_component.rb
|
135
136
|
- opal/clearwater/svg_component.rb
|
136
137
|
- opal/clearwater/virtual_dom.rb
|
137
138
|
- opal/clearwater/virtual_dom/js/virtual_dom.js
|
@@ -164,7 +165,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
164
165
|
version: 1.3.1
|
165
166
|
requirements: []
|
166
167
|
rubyforge_project:
|
167
|
-
rubygems_version: 2.
|
168
|
+
rubygems_version: 2.6.12
|
168
169
|
signing_key:
|
169
170
|
specification_version: 4
|
170
171
|
summary: Front-end Ruby web framework for fast, reasonable, and composable applications
|