volt 0.7.13 → 0.7.14
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Readme.md +21 -1
- data/VERSION +1 -1
- data/lib/volt/controllers/model_controller.rb +4 -0
- data/lib/volt/controllers/reactive_accessors.rb +48 -0
- data/lib/volt/models/url.rb +7 -1
- data/lib/volt/page/bindings/base_binding.rb +5 -5
- data/lib/volt/page/bindings/content_binding.rb +4 -1
- data/lib/volt/page/bindings/each_binding.rb +2 -2
- data/lib/volt/page/bindings/template_binding.rb +1 -1
- data/lib/volt/page/targets/attribute_target.rb +1 -1
- data/lib/volt/page/targets/dom_target.rb +1 -1
- data/lib/volt/page/template_renderer.rb +1 -1
- data/lib/volt/reactive/reactive_count.rb +8 -3
- data/lib/volt/reactive/reactive_value.rb +16 -14
- data/spec/controllers/reactive_accessors_spec.rb +41 -0
- data/spec/models/reactive_count_spec.rb +13 -0
- metadata +7 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1fc433aea390d72ea67bc96a667a68184162990c
|
4
|
+
data.tar.gz: d75d3644a9633f81c9ccb20da751586f67463c73
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6a9a01e11d069071794c7d279a49bbbafb54b16a9a9f7a4f4d1210291914390c26383908884428d79ff5fa139608d1df270d8e83a742d15ec10e5e0de3db4487
|
7
|
+
data.tar.gz: a72b58ada2bda7c71d5be6ba48328b8c7390052f6a4ba479c948f1c1923d07bf7f7cea60736f97b7e4964652108187b19de9fd065b3db6fe96149ecdbf85b0dd
|
data/Readme.md
CHANGED
@@ -14,7 +14,15 @@ Instead of syncing data between the client and server via HTTP, volt uses a pers
|
|
14
14
|
|
15
15
|
Pages HTML is written in a handlebars like template language. Volt uses data flow/reactive programming to automatically and intelligently propagate changes to the DOM (or anything other code wanting to know when a value updates) When something in the DOM changes, Volt intelligently updates only the nodes that need to be changed.
|
16
16
|
|
17
|
-
See
|
17
|
+
See some demo videos here:
|
18
|
+
- [https://www.youtube.com/watch?v=6ZIvs0oKnYs](https://www.youtube.com/watch?v=6ZIvs0oKnYs)
|
19
|
+
- [https://www.youtube.com/watch?v=c478sMlhx1o](https://www.youtube.com/watch?v=c478sMlhx1o)
|
20
|
+
- [https://www.youtube.com/watch?v=yZIQ-2irY-Q](https://www.youtube.com/watch?v=yZIQ-2irY-Q)
|
21
|
+
|
22
|
+
Check out demo apps:
|
23
|
+
- https://github.com/voltrb/todos
|
24
|
+
- https://github.com/voltrb/blog
|
25
|
+
- https://github.com/voltrb/contactsdemo
|
18
26
|
|
19
27
|
|
20
28
|
## Goals
|
@@ -544,6 +552,18 @@ Controllers in the app/home component do not need to be namespaced, all other co
|
|
544
552
|
|
545
553
|
Here "auth" would be the component name.
|
546
554
|
|
555
|
+
## Reactive Accessors
|
556
|
+
|
557
|
+
The default ModelController proxies any missing methods to its model. Since models are wrapped in ReactiveValue's, they return ReactiveValue's by default. Sometimes you need to store additional data reactively in the controller outside of the model. (Though often you may want to condier doing another control/controller) In this case, you can add a ```reactive_accessor```. These behave just like ```attr_accessor``` except the values assigned and returned are wrapped in a ReactiveValue. Updates update the existing ReactiveValue.
|
558
|
+
|
559
|
+
```ruby
|
560
|
+
class Contacts < ModelController
|
561
|
+
reactive_accessor :_query
|
562
|
+
end
|
563
|
+
```
|
564
|
+
|
565
|
+
Now from the view we can bind to _query while also changing in and out the model. You can also use ```reactive_reader``` and ```reactive_writer```
|
566
|
+
|
547
567
|
# Components
|
548
568
|
|
549
569
|
Apps are made up of Components. Each folder under app/ is a component. When you visit a route, it loads all of the files in the component on the front end, so new pages within the component can be rendered without a new http request. If a URL is visited that routes to a different component, the request will be loaded as a normal page load and all of that components files will be loaded. You can think of components as the "reload boundary" between sections of your app.
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.7.
|
1
|
+
0.7.14
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module ReactiveAccessors
|
2
|
+
|
3
|
+
module ClassMethods
|
4
|
+
# Create a method to read a reactive value from an instance value. If it
|
5
|
+
# is not setup, create it so it can be updated through the reactive value
|
6
|
+
# at a later point.
|
7
|
+
def reactive_reader(*names)
|
8
|
+
names.each do |name|
|
9
|
+
var_name = :"@#{name}"
|
10
|
+
define_method(name.to_sym) do
|
11
|
+
value = instance_variable_get(var_name)
|
12
|
+
|
13
|
+
unless value
|
14
|
+
value = ReactiveValue.new(nil)
|
15
|
+
|
16
|
+
instance_variable_set(var_name, value)
|
17
|
+
end
|
18
|
+
|
19
|
+
value
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def reactive_writer(*names)
|
25
|
+
names.each do |name|
|
26
|
+
var_name = :"@#{name}"
|
27
|
+
define_method(:"#{name}=") do |new_value|
|
28
|
+
value = instance_variable_get(var_name)
|
29
|
+
|
30
|
+
if value
|
31
|
+
value.cur = new_value
|
32
|
+
else
|
33
|
+
instance_variable_set(var_name, ReactiveValue.new(value))
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def reactive_accessor(*names)
|
40
|
+
reactive_reader(*names)
|
41
|
+
reactive_writer(*names)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.included(base)
|
46
|
+
base.send :extend, ClassMethods
|
47
|
+
end
|
48
|
+
end
|
data/lib/volt/models/url.rb
CHANGED
@@ -130,7 +130,13 @@ class URL
|
|
130
130
|
query_hash = self.query_hash
|
131
131
|
|
132
132
|
# Get the params that are in the route
|
133
|
-
|
133
|
+
new_params = @router.url_to_params(@path)
|
134
|
+
|
135
|
+
if new_params == false
|
136
|
+
raise "no routes match path: #{@path}"
|
137
|
+
end
|
138
|
+
|
139
|
+
query_hash.merge!(new_params)
|
134
140
|
|
135
141
|
# Loop through the .params we already have assigned.
|
136
142
|
assign_from_old(@params, query_hash)
|
@@ -20,21 +20,21 @@ class BaseBinding
|
|
20
20
|
@@binding_number ||= 10000
|
21
21
|
end
|
22
22
|
|
23
|
-
def
|
24
|
-
@
|
23
|
+
def dom_section
|
24
|
+
@dom_section ||= target.dom_section(@binding_name)
|
25
25
|
end
|
26
26
|
|
27
27
|
def remove
|
28
|
-
|
28
|
+
@dom_section.remove if @dom_section
|
29
29
|
|
30
30
|
# Clear any references
|
31
31
|
@target = nil
|
32
32
|
@context = nil
|
33
|
-
@
|
33
|
+
@dom_section = nil
|
34
34
|
end
|
35
35
|
|
36
36
|
def remove_anchors
|
37
|
-
|
37
|
+
@dom_section.remove_anchors if @dom_section
|
38
38
|
end
|
39
39
|
|
40
40
|
def queue_update
|
@@ -17,13 +17,16 @@ class ContentBinding < BaseBinding
|
|
17
17
|
|
18
18
|
def update
|
19
19
|
value = @value.cur.or('')
|
20
|
+
if value.reactive?
|
21
|
+
puts "GOT CUR: #{value.inspect}"
|
22
|
+
end
|
20
23
|
|
21
24
|
# Exception values display the exception as a string
|
22
25
|
value = value.to_s
|
23
26
|
|
24
27
|
# Update the html in this section
|
25
28
|
# TODO: Move the formatter into another class.
|
26
|
-
|
29
|
+
dom_section.html = value.gsub("\n", "<br />\n")
|
27
30
|
end
|
28
31
|
|
29
32
|
def remove
|
@@ -56,10 +56,10 @@ class EachBinding < BaseBinding
|
|
56
56
|
|
57
57
|
if position >= @templates.size
|
58
58
|
# Setup new bindings in the spot we want to insert the item
|
59
|
-
|
59
|
+
dom_section.insert_anchor_before_end(binding_name)
|
60
60
|
else
|
61
61
|
# Insert the item before an existing item
|
62
|
-
|
62
|
+
dom_section.insert_anchor_before(binding_name, @templates[position].binding_name)
|
63
63
|
end
|
64
64
|
|
65
65
|
index = ReactiveValue.new(position)
|
@@ -174,7 +174,7 @@ class TemplateBinding < BaseBinding
|
|
174
174
|
# Set the current section on the controller if it wants so it can manipulate
|
175
175
|
# the dom if needed
|
176
176
|
if @controller.respond_to?(:section=)
|
177
|
-
@controller.section = @current_template.
|
177
|
+
@controller.section = @current_template.dom_section
|
178
178
|
end
|
179
179
|
|
180
180
|
if @controller.respond_to?(:dom_ready)
|
@@ -5,7 +5,7 @@ require 'volt/page/targets/dom_section'
|
|
5
5
|
# the dom. Currently only one "dom" is supported, but multiple
|
6
6
|
# may be allowed in the future (iframes?)
|
7
7
|
class DomTarget < BaseSection
|
8
|
-
def
|
8
|
+
def dom_section(*args)
|
9
9
|
return DomSection.new(*args)
|
10
10
|
end
|
11
11
|
end
|
@@ -7,7 +7,7 @@ class TemplateRenderer < BaseBinding
|
|
7
7
|
|
8
8
|
@sub_bindings = []
|
9
9
|
|
10
|
-
bindings = self.
|
10
|
+
bindings = self.dom_section.set_content_to_template(page, template_name)
|
11
11
|
|
12
12
|
bindings.each_pair do |id,bindings_for_id|
|
13
13
|
bindings_for_id.each do |binding|
|
@@ -17,6 +17,8 @@ class ReactiveCount
|
|
17
17
|
# After events are bound, we keep a cache of each cell's count
|
18
18
|
# value, and base the results
|
19
19
|
def cached_count
|
20
|
+
@cached_results = []
|
21
|
+
|
20
22
|
|
21
23
|
end
|
22
24
|
|
@@ -24,7 +26,7 @@ class ReactiveCount
|
|
24
26
|
# run the count on the source object.
|
25
27
|
def direct_count
|
26
28
|
count = 0
|
27
|
-
@source.size.
|
29
|
+
@source.cur.size.times do |index|
|
28
30
|
val = @source[index]
|
29
31
|
result = @block.call(val).cur
|
30
32
|
if result == true
|
@@ -37,16 +39,17 @@ class ReactiveCount
|
|
37
39
|
|
38
40
|
def setup_listeners
|
39
41
|
@cell_trackers = []
|
42
|
+
@setup = false
|
40
43
|
@added_tracker = @source.on('added') do |_, index|
|
41
44
|
change_cell_count(@source.size.cur)
|
42
|
-
trigger!('changed')
|
43
45
|
end
|
44
46
|
|
45
47
|
@removed_tracker = @source.on('removed') do |_, index|
|
46
48
|
change_cell_count(@source.size.cur)
|
47
|
-
trigger!('changed')
|
48
49
|
end
|
49
50
|
|
51
|
+
@setup = true
|
52
|
+
|
50
53
|
# Initial cell tracking
|
51
54
|
change_cell_count(@source.size.cur)
|
52
55
|
end
|
@@ -54,6 +57,7 @@ class ReactiveCount
|
|
54
57
|
# We need to make sure we're listening on the result from each cell,
|
55
58
|
# that way we can trigger when the value changes.
|
56
59
|
def change_cell_count(size)
|
60
|
+
# puts "CHANGE SIZE: #{size}"
|
57
61
|
current_size = @cell_trackers.size
|
58
62
|
|
59
63
|
if current_size < size
|
@@ -66,6 +70,7 @@ class ReactiveCount
|
|
66
70
|
result = @block.call(val)
|
67
71
|
|
68
72
|
@cell_trackers << result.on('changed') do
|
73
|
+
# puts "RESULT CHANGED: #{index}"
|
69
74
|
trigger!('changed')
|
70
75
|
end
|
71
76
|
end
|
@@ -239,21 +239,23 @@ class ReactiveManager
|
|
239
239
|
|
240
240
|
# Fetch the current value
|
241
241
|
def cur(shallow=false, ignore_cache=false)
|
242
|
-
#
|
242
|
+
# Use cache if it is cached
|
243
243
|
if @cur_cache && !shallow && !ignore_cache
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
if @getter.class == ::Proc
|
248
|
-
# Get the current value, capture any errors
|
249
|
-
begin
|
250
|
-
result = @getter.call
|
251
|
-
rescue => e
|
252
|
-
result = e
|
253
|
-
end
|
244
|
+
# We might be caching another reactive value, so we just set
|
245
|
+
# it as the result and let it get unwrapped.
|
246
|
+
result = @cur_cache
|
254
247
|
else
|
255
|
-
|
256
|
-
|
248
|
+
if @getter.class == ::Proc
|
249
|
+
# Get the current value, capture any errors
|
250
|
+
begin
|
251
|
+
result = @getter.call
|
252
|
+
rescue => e
|
253
|
+
result = e
|
254
|
+
end
|
255
|
+
else
|
256
|
+
# getter is just an object, return it
|
257
|
+
result = @getter
|
258
|
+
end
|
257
259
|
end
|
258
260
|
|
259
261
|
if !shallow && result.reactive?
|
@@ -268,7 +270,7 @@ class ReactiveManager
|
|
268
270
|
def update_followers
|
269
271
|
return if @setting_up
|
270
272
|
if has_listeners?
|
271
|
-
current_obj = cur(
|
273
|
+
current_obj = cur(true, true)
|
272
274
|
should_attach = current_obj.respond_to?(:on)
|
273
275
|
|
274
276
|
if should_attach
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'volt/controllers/reactive_accessors'
|
2
|
+
|
3
|
+
class TestReactiveAccessors
|
4
|
+
include ReactiveAccessors
|
5
|
+
|
6
|
+
reactive_accessor :_name
|
7
|
+
end
|
8
|
+
|
9
|
+
describe ReactiveAccessors do
|
10
|
+
it "should return the same reactive value after each read" do
|
11
|
+
inst = TestReactiveAccessors.new
|
12
|
+
|
13
|
+
expect(inst._name.reactive_manager.object_id).to eq(inst._name.reactive_manager.object_id)
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should assign a reactive value" do
|
17
|
+
inst = TestReactiveAccessors.new
|
18
|
+
|
19
|
+
inst._name = 'Ryan'
|
20
|
+
expect(inst._name).to eq('Ryan')
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should start nil" do
|
24
|
+
inst = TestReactiveAccessors.new
|
25
|
+
|
26
|
+
expect(inst._name.cur).to eq(nil)
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should keep the same reactive value when reassigning" do
|
30
|
+
inst = TestReactiveAccessors.new
|
31
|
+
|
32
|
+
inst._name = 'Ryan'
|
33
|
+
rv1_id = inst._name.reactive_manager.object_id
|
34
|
+
|
35
|
+
inst._name = 'Jim'
|
36
|
+
rv2_id = inst._name.reactive_manager.object_id
|
37
|
+
|
38
|
+
expect(rv1_id).to eq(rv2_id)
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'volt/models'
|
2
|
+
|
3
|
+
describe ReactiveCount do
|
4
|
+
it "should call cur through the reactive count to the number" do
|
5
|
+
model = ReactiveValue.new(Model.new)
|
6
|
+
|
7
|
+
model._items << {_name: 'ok'}
|
8
|
+
|
9
|
+
count = model._items.count {|m| m._name == 'ok' }
|
10
|
+
|
11
|
+
expect(count.cur).to eq(1)
|
12
|
+
end
|
13
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: volt
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.7.
|
4
|
+
version: 0.7.14
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ryan Stout
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-03-
|
11
|
+
date: 2014-03-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: thor
|
@@ -361,6 +361,7 @@ files:
|
|
361
361
|
- lib/volt/cli/new_gem.rb
|
362
362
|
- lib/volt/console.rb
|
363
363
|
- lib/volt/controllers/model_controller.rb
|
364
|
+
- lib/volt/controllers/reactive_accessors.rb
|
364
365
|
- lib/volt/extra_core/array.rb
|
365
366
|
- lib/volt/extra_core/blank.rb
|
366
367
|
- lib/volt/extra_core/extra_core.rb
|
@@ -485,6 +486,7 @@ files:
|
|
485
486
|
- spec/apps/kitchen_sink/app/main/views/main/main.html
|
486
487
|
- spec/apps/kitchen_sink/config.ru
|
487
488
|
- spec/apps/kitchen_sink/public/index.html
|
489
|
+
- spec/controllers/reactive_accessors_spec.rb
|
488
490
|
- spec/extra_core/inflector_spec.rb
|
489
491
|
- spec/integration/test_integration_spec.rb
|
490
492
|
- spec/models/event_chain_spec.rb
|
@@ -495,6 +497,7 @@ files:
|
|
495
497
|
- spec/models/persistors/store_spec.rb
|
496
498
|
- spec/models/reactive_array_spec.rb
|
497
499
|
- spec/models/reactive_call_times_spec.rb
|
500
|
+
- spec/models/reactive_count_spec.rb
|
498
501
|
- spec/models/reactive_generator_spec.rb
|
499
502
|
- spec/models/reactive_tags_spec.rb
|
500
503
|
- spec/models/reactive_value_spec.rb
|
@@ -596,6 +599,7 @@ test_files:
|
|
596
599
|
- spec/apps/kitchen_sink/app/main/views/main/main.html
|
597
600
|
- spec/apps/kitchen_sink/config.ru
|
598
601
|
- spec/apps/kitchen_sink/public/index.html
|
602
|
+
- spec/controllers/reactive_accessors_spec.rb
|
599
603
|
- spec/extra_core/inflector_spec.rb
|
600
604
|
- spec/integration/test_integration_spec.rb
|
601
605
|
- spec/models/event_chain_spec.rb
|
@@ -606,6 +610,7 @@ test_files:
|
|
606
610
|
- spec/models/persistors/store_spec.rb
|
607
611
|
- spec/models/reactive_array_spec.rb
|
608
612
|
- spec/models/reactive_call_times_spec.rb
|
613
|
+
- spec/models/reactive_count_spec.rb
|
609
614
|
- spec/models/reactive_generator_spec.rb
|
610
615
|
- spec/models/reactive_tags_spec.rb
|
611
616
|
- spec/models/reactive_value_spec.rb
|