clearwater 1.0.0.rc4 → 1.0.0.rc5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 98660a461c2da1071bbc7a8f0cfd3db413ff2224
4
- data.tar.gz: 53727157dec9793aafce8f6ec538099fc11c57d3
3
+ metadata.gz: 36c47513423cf3f1e0a6b3705f3e07d59bbbed31
4
+ data.tar.gz: 9040af257c8ce168472c92d4ff4dff608bf1da67
5
5
  SHA512:
6
- metadata.gz: 1016b8ce23f014f05d8aa14df5e7721c59b1950896b45e7f60e498ec4722ab24dde35c58fbc24c2ecac9171c216bb9966fc5a0ace8e510f77de571a8dca1f521
7
- data.tar.gz: 38d578979f4aaed936e475d80c577e77a64dc76ce595a4c645eec7bf55d5688b15af2d5d05d604bc4c7d8986d4efd16073975253d0b7fbdcb68b159e45c743e6
6
+ metadata.gz: 553ce8493b33aa68602b4d4b792083e1ab7c5bb957183e3a5474f9103ac97a3ffa5bfad03f5a1eb6e3075db9e0d04fa8309c8ebe110edc7006a36dacac587faa
7
+ data.tar.gz: 4341d6edfe0eef28e0a20b1e405a2ad6d93a675bf96471f69268aab0e9088670788da5a1f915128f91a9882fb2c8ee3cda78d267573dae22f05c8a412d6431a0
@@ -27,12 +27,8 @@ module Clearwater
27
27
  end
28
28
 
29
29
  def to_s
30
- html = render.to_s
31
- if html.respond_to? :html_safe
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
@@ -1,3 +1,3 @@
1
1
  module Clearwater
2
- VERSION = "1.0.0.rc4"
2
+ VERSION = "1.0.0.rc5"
3
3
  end
@@ -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('popstate') { render_current_url }
58
+ @popstate_listener = @window.on(:popstate) { render_current_url }
55
59
  @watching_url = true
56
60
  end
57
61
  end
@@ -6,6 +6,10 @@ module Clearwater
6
6
  @apps = Set.new
7
7
  end
8
8
 
9
+ def each &block
10
+ @apps.each &block
11
+ end
12
+
9
13
  def << app
10
14
  @apps << app
11
15
  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
- #{@delegate.update(`previous.delegate`, wrap(`node`))};
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)};
@@ -13,7 +13,6 @@ module Clearwater
13
13
  end
14
14
 
15
15
  def key
16
- `undefined`
17
16
  end
18
17
  end
19
18
  end
@@ -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
- VirtualDOM.node(
91
+ `vdom`.node(
87
92
  tag_name,
88
- Component.sanitize_attributes(attributes),
89
- Component.sanitize_content(content)
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`), `previous`)};
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
- #{HashUtils.camelized_native(attributes)},
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 === Opal.nil || content === undefined) return null;
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('div'))
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[#{StringUtils.camelize(`key`)}] = v.$$is_hash
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 `#@target.type === 'Thunk' && typeof #@target.render === 'function'`
14
- warn "Route '#{key}' points to a cached component. Cached " +
15
- "components must not be persistent components, such as " +
16
- "application roots or routing targets."
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
 
@@ -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 set_outlets targets=targets_for_path(current_path)
103
- @old_targets = @targets || []
104
- @targets = targets
105
- navigating_from = @old_targets - @targets
106
- navigating_to = @targets - @old_targets
107
-
108
- navigating_from.each do |target|
109
- if target.respond_to? :on_route_from
110
- target.on_route_from
111
- end
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.rc4
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: 2016-09-17 00:00:00.000000000 Z
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.4.8
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