volt 0.7.23 → 0.8.0

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.
Files changed (106) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +8 -1
  3. data/CHANGELOG.md +22 -0
  4. data/Gemfile +8 -0
  5. data/Guardfile +2 -2
  6. data/Readme.md +139 -136
  7. data/VERSION +1 -1
  8. data/app/volt/assets/js/setImmediate.js +175 -0
  9. data/app/volt/tasks/live_query/data_store.rb +0 -2
  10. data/app/volt/tasks/live_query/live_query.rb +4 -4
  11. data/docs/GETTING_STARTED.md +24 -3
  12. data/docs/WHY.md +1 -22
  13. data/lib/volt.rb +20 -1
  14. data/lib/volt/console.rb +20 -0
  15. data/lib/volt/controllers/model_controller.rb +25 -11
  16. data/lib/volt/extra_core/object.rb +2 -14
  17. data/lib/volt/extra_core/string.rb +4 -0
  18. data/lib/volt/models.rb +0 -1
  19. data/lib/volt/models/array_model.rb +8 -16
  20. data/lib/volt/models/cursor.rb +1 -1
  21. data/lib/volt/models/model.rb +40 -60
  22. data/lib/volt/models/model_hash_behaviour.rb +10 -24
  23. data/lib/volt/models/model_helpers.rb +2 -2
  24. data/lib/volt/models/model_state.rb +1 -1
  25. data/lib/volt/models/model_wrapper.rb +4 -4
  26. data/lib/volt/models/persistors/array_store.rb +44 -28
  27. data/lib/volt/models/persistors/base.rb +1 -1
  28. data/lib/volt/models/persistors/model_store.rb +1 -1
  29. data/lib/volt/models/persistors/params.rb +5 -1
  30. data/lib/volt/models/persistors/query/query_listener.rb +2 -0
  31. data/lib/volt/models/persistors/store.rb +3 -2
  32. data/lib/volt/models/persistors/store_state.rb +7 -2
  33. data/lib/volt/models/url.rb +35 -29
  34. data/lib/volt/models/validations.rb +7 -17
  35. data/lib/volt/page/bindings/attribute_binding.rb +57 -39
  36. data/lib/volt/page/bindings/base_binding.rb +0 -14
  37. data/lib/volt/page/bindings/content_binding.rb +15 -18
  38. data/lib/volt/page/bindings/each_binding.rb +67 -34
  39. data/lib/volt/page/bindings/if_binding.rb +15 -12
  40. data/lib/volt/page/bindings/template_binding.rb +77 -59
  41. data/lib/volt/page/bindings/template_binding/grouped_controllers.rb +19 -4
  42. data/lib/volt/page/channel.rb +22 -38
  43. data/lib/volt/page/channel_stub.rb +3 -6
  44. data/lib/volt/page/page.rb +24 -26
  45. data/lib/volt/page/string_template_renderer.rb +46 -0
  46. data/lib/volt/page/sub_context.rb +7 -1
  47. data/lib/volt/page/targets/binding_document/component_node.rb +11 -9
  48. data/lib/volt/page/tasks.rb +3 -2
  49. data/lib/volt/page/url_tracker.rb +4 -3
  50. data/lib/volt/reactive/computation.rb +131 -0
  51. data/lib/volt/reactive/dependency.rb +71 -0
  52. data/lib/volt/reactive/eventable.rb +82 -0
  53. data/lib/volt/reactive/hash_dependency.rb +36 -0
  54. data/lib/volt/{controllers → reactive}/reactive_accessors.rb +8 -11
  55. data/lib/volt/reactive/reactive_array.rb +100 -193
  56. data/lib/volt/reactive/reactive_hash.rb +49 -0
  57. data/lib/volt/server/html_parser/attribute_scope.rb +24 -4
  58. data/lib/volt/server/html_parser/if_view_scope.rb +15 -15
  59. data/lib/volt/server/html_parser/view_scope.rb +31 -1
  60. data/spec/apps/kitchen_sink/Gemfile +4 -8
  61. data/spec/apps/kitchen_sink/app/main/config/dependencies.rb +8 -0
  62. data/spec/apps/kitchen_sink/app/main/config/routes.rb +8 -1
  63. data/spec/apps/kitchen_sink/app/main/controllers/main_controller.rb +8 -0
  64. data/spec/apps/kitchen_sink/app/main/views/main/bindings.html +73 -0
  65. data/spec/apps/kitchen_sink/app/main/views/main/index.html +6 -1
  66. data/spec/apps/kitchen_sink/app/main/views/main/main.html +26 -6
  67. data/spec/apps/kitchen_sink/app/main/views/main/store.html +6 -0
  68. data/spec/controllers/reactive_accessors_spec.rb +13 -15
  69. data/spec/integration/bindings_spec.rb +159 -0
  70. data/spec/integration/templates_spec.rb +15 -0
  71. data/spec/models/model_spec.rb +130 -228
  72. data/spec/reactive/computation_spec.rb +63 -0
  73. data/spec/reactive/dependency_spec.rb +5 -0
  74. data/spec/reactive/eventable_spec.rb +48 -0
  75. data/spec/reactive/reactive_array_spec.rb +97 -0
  76. data/spec/router/routes_spec.rb +26 -27
  77. data/spec/server/html_parser/view_parser_spec.rb +3 -21
  78. data/spec/server/rack/asset_files_spec.rb +1 -1
  79. data/templates/project/app/main/views/main/main.html +2 -2
  80. metadata +29 -41
  81. data/lib/volt/extra_core/time.rb +0 -16
  82. data/lib/volt/page/draw_cycle.rb +0 -31
  83. data/lib/volt/page/memory_test.rb +0 -26
  84. data/lib/volt/page/reactive_template.rb +0 -32
  85. data/lib/volt/reactive/array_extensions.rb +0 -12
  86. data/lib/volt/reactive/destructive_methods.rb +0 -19
  87. data/lib/volt/reactive/event_chain.rb +0 -125
  88. data/lib/volt/reactive/events.rb +0 -216
  89. data/lib/volt/reactive/object_tracking.rb +0 -14
  90. data/lib/volt/reactive/reactive_block.rb +0 -88
  91. data/lib/volt/reactive/reactive_generator.rb +0 -44
  92. data/lib/volt/reactive/reactive_tags.rb +0 -71
  93. data/lib/volt/reactive/reactive_value.rb +0 -427
  94. data/lib/volt/reactive/string_extensions.rb +0 -31
  95. data/spec/integration/test_integration_spec.rb +0 -14
  96. data/spec/models/event_chain_spec.rb +0 -150
  97. data/spec/models/model_buffers_spec.rb +0 -9
  98. data/spec/models/old_model_spec.rb +0 -67
  99. data/spec/models/reactive_array_spec.rb +0 -364
  100. data/spec/models/reactive_block_spec.rb +0 -13
  101. data/spec/models/reactive_call_times_spec.rb +0 -28
  102. data/spec/models/reactive_generator_spec.rb +0 -58
  103. data/spec/models/reactive_tags_spec.rb +0 -35
  104. data/spec/models/reactive_value_spec.rb +0 -370
  105. data/spec/models/store_spec.rb +0 -16
  106. data/spec/models/string_extensions_spec.rb +0 -57
@@ -0,0 +1,49 @@
1
+ require 'volt/reactive/hash_dependency'
2
+
3
+ class ReactiveHash
4
+ def initialize(values={})
5
+ @hash = values
6
+ @deps = HashDependency.new
7
+ @all_deps = Dependency.new
8
+ end
9
+
10
+ def ==(val)
11
+ @all_deps.depend
12
+ @hash == val
13
+ end
14
+
15
+ # TODO: We should finish off this class for reactivity
16
+ def method_missing(method_name, *args, &block)
17
+ @all_deps.depend
18
+
19
+ return @hash.send(method_name, *args, &block)
20
+ end
21
+
22
+ def [](key)
23
+ @deps.depend(key)
24
+
25
+ return @hash[key]
26
+ end
27
+
28
+ def []=(key, value)
29
+ @deps.changed!(key)
30
+ @all_deps.changed!
31
+
32
+ @hash[key] = value
33
+ end
34
+
35
+ def delete(key)
36
+ @deps.delete(key)
37
+ @hash.delete(key)
38
+ end
39
+
40
+ def clear
41
+ @hash.each_pair do |key,_|
42
+ delete(key)
43
+ end
44
+ end
45
+
46
+ def inspect
47
+ "#<ReactiveHash #{@hash.inspect}>"
48
+ end
49
+ end
@@ -53,11 +53,31 @@ module AttributeScope
53
53
  end
54
54
  end
55
55
 
56
+ # TODO: We should use a real parser for this
57
+ def getter_to_setter(getter)
58
+ getter = getter.strip
59
+
60
+ # Convert a getter into a setter
61
+ if getter.index('.') || getter.index('@')
62
+ prefix = ''
63
+ else
64
+ prefix = 'self.'
65
+ end
66
+
67
+ return "#{prefix}#{getter}=(val)"
68
+ end
69
+
56
70
  # Add an attribute binding on the tag, bind directly to the getter in the binding
57
71
  def add_single_attribute(id, attribute_name, parts)
58
72
  getter = parts[0][1..-2]
59
73
 
60
- save_binding(id, "lambda { |__p, __t, __c, __id| AttributeBinding.new(__p, __t, __c, __id, #{attribute_name.inspect}, Proc.new { #{getter} }) }")
74
+ # if getter.index('@')
75
+ # raise "Bindings currently do not support instance variables"
76
+ # end
77
+
78
+ setter = getter_to_setter(getter)
79
+
80
+ save_binding(id, "lambda { |__p, __t, __c, __id| AttributeBinding.new(__p, __t, __c, __id, #{attribute_name.inspect}, Proc.new { #{getter} }, Proc.new { |val| #{setter} }) }")
61
81
  end
62
82
 
63
83
 
@@ -74,12 +94,12 @@ module AttributeScope
74
94
  end
75
95
  end
76
96
 
77
- reactive_template_path = add_reactive_template(content)
97
+ string_template_renderer_path = add_string_template_renderer(content)
78
98
 
79
- save_binding(id, "lambda { |__p, __t, __c, __id| AttributeBinding.new(__p, __t, __c, __id, #{attribute_name.inspect}, Proc.new { ReactiveTemplate.new(__p, __c, #{reactive_template_path.inspect}) }) }")
99
+ save_binding(id, "lambda { |__p, __t, __c, __id| AttributeBinding.new(__p, __t, __c, __id, #{attribute_name.inspect}, Proc.new { StringTemplateRender.new(__p, __c, #{string_template_renderer_path.inspect}) }) }")
80
100
  end
81
101
 
82
- def add_reactive_template(content)
102
+ def add_string_template_renderer(content)
83
103
  path = @path + "/_rv#{@binding_number}"
84
104
  new_handler = ViewHandler.new(path, false)
85
105
  @binding_number += 1
@@ -1,21 +1,21 @@
1
1
  class IfViewScope < ViewScope
2
2
  def initialize(handler, path, content)
3
3
  super(handler, path)
4
-
4
+
5
5
  @original_path = @path
6
-
6
+
7
7
  @last_content = content
8
8
  @branches = []
9
-
9
+
10
10
  # We haven't added the if yet
11
11
  @if_binding_number = @handler.last.binding_number
12
12
  @handler.last.binding_number += 1
13
-
13
+
14
14
  @path_number = 0
15
-
15
+
16
16
  new_path
17
17
  end
18
-
18
+
19
19
  def new_path
20
20
  @path = @original_path + "/__if#{@path_number}"
21
21
  @path_number += 1
@@ -27,26 +27,26 @@ class IfViewScope < ViewScope
27
27
  close_scope(false)
28
28
 
29
29
  @last_content = content
30
-
30
+
31
31
  # Clear existing
32
32
  @html = ''
33
33
  @bindings = {}
34
-
34
+
35
35
  # Close scope removes us, so lets add it back.
36
36
  @handler.scope << self
37
-
37
+
38
38
  @binding_number = 0
39
39
 
40
40
  # Generate a new template path for this section.
41
41
  new_path
42
42
  end
43
-
43
+
44
44
  def close_scope(final=true)
45
- @branches << [@last_content, path]
45
+ @branches << [@last_content, path]
46
46
 
47
47
  super()
48
48
 
49
- if final
49
+ if final
50
50
  # Add the binding to the parent
51
51
  branches = @branches.map do |branch|
52
52
  content = branch[0]
@@ -55,16 +55,16 @@ class IfViewScope < ViewScope
55
55
  else
56
56
  content = "Proc.new { #{branch[0]} }"
57
57
  end
58
-
58
+
59
59
  "[#{content}, #{branch[1].inspect}]"
60
60
  end.join(', ')
61
-
61
+
62
62
  new_scope = @handler.last
63
63
 
64
64
  # variables are captured for branches, so we must prefix them so they don't conflict.
65
65
  # page, target, context, id
66
66
  new_scope.save_binding(@if_binding_number, "lambda { |__p, __t, __c, __id| IfBinding.new(__p, __t, __c, __id, [#{branches}]) }")
67
-
67
+
68
68
  new_scope.html << "<!-- $#{@if_binding_number} --><!-- $/#{@if_binding_number} -->"
69
69
  end
70
70
  end
@@ -77,6 +77,22 @@ class ViewScope
77
77
  @binding_number += 1
78
78
  end
79
79
 
80
+ # Returns ruby code to fetch the parent. (by removing the last fetch)
81
+ # TODO: Probably want to do this with AST transforms with the parser/unparser gems
82
+ def parent_fetcher(getter)
83
+ parent = getter.strip.gsub(/[.][^.]+$/, '')
84
+
85
+ if parent.blank? || !getter.index('.')
86
+ parent = 'self'
87
+ end
88
+
89
+ return parent
90
+ end
91
+
92
+ def last_method_name(getter)
93
+ return getter.strip[/[^.]+$/]
94
+ end
95
+
80
96
  def add_component(tag_name, attributes, unary)
81
97
  component_name = tag_name[1..-1].gsub(':', '/')
82
98
 
@@ -93,7 +109,21 @@ class ViewScope
93
109
  # Multiple bindings
94
110
  elsif parts.size == 1 && binding_count == 1
95
111
  # A single binding
96
- data_hash << "#{name.inspect} => Proc.new { #{value[1..-2]} }"
112
+ getter = value[1..-2]
113
+ data_hash << "#{name.inspect} => Proc.new { #{getter} }"
114
+
115
+ setter = getter_to_setter(getter)
116
+ data_hash << "#{(name + "=").inspect} => Proc.new { |val| #{setter} }"
117
+
118
+ # Add an _parent fetcher. Useful for things like volt-fields to get the parent model.
119
+ parent = parent_fetcher(getter)
120
+
121
+ # TODO: This adds some overhead, perhaps there is a way to compute this dynamically on the
122
+ # front-end.
123
+ data_hash << "#{(name + "_parent").inspect} => Proc.new { #{parent} }"
124
+
125
+ # Add a _last_method property. This is useful
126
+ data_hash << "#{(name + "_last_method").inspect} => #{last_method_name(getter).inspect}"
97
127
  end
98
128
  else
99
129
  # String
@@ -1,7 +1,6 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
- gem 'volt', path: '/Users/ryanstout/Sites/volt/volt'
4
-
3
+ gem 'volt', path: '../../../'
5
4
 
6
5
  # The following gem's are optional for themeing
7
6
 
@@ -12,12 +11,10 @@ gem 'volt-bootstrap'
12
11
  gem 'volt-bootstrap-jumbotron-theme'
13
12
 
14
13
 
15
- gem 'volt-fields', path: '/Users/ryanstout/Sites/volt/apps/volt-fields'
16
-
17
14
  # Server for MRI
18
15
  platform :mri do
19
16
  gem 'thin', '~> 1.6.0'
20
- gem 'bson_ext'
17
+ gem 'bson_ext', '~> 1.9.0'
21
18
  end
22
19
 
23
20
  # Server for jruby
@@ -25,8 +22,7 @@ platform :jruby do
25
22
  gem 'jubilee'
26
23
  end
27
24
 
25
+
28
26
  #---------------------
29
27
  # Needed at the moment
30
- gem 'opal', git: 'https://github.com/opal/opal.git'
31
- gem 'opal-jquery', :git => 'https://github.com/opal/opal-jquery.git'
32
- gem 'sockjs', git: 'https://github.com/kacperk/sockjs-ruby.git', require: false, platforms: :mri
28
+ gem 'volt-sockjs', require: false, platforms: :mri
@@ -0,0 +1,8 @@
1
+ # Specify which components you wish to include when
2
+ # the "home" component loads.
3
+
4
+ # bootstrap css framework
5
+ component 'bootstrap'
6
+
7
+ # a default theme for the bootstrap framework
8
+ component 'bootstrap-jumbotron-theme'
@@ -1 +1,8 @@
1
- get '/'
1
+ # See https://github.com/voltrb/volt#routes for more info on routes
2
+
3
+ get "/bindings/{_route_test}", _action: 'bindings'
4
+ get "/bindings", _action: 'bindings'
5
+ get "/store", _action: 'store'
6
+
7
+ # The main route, this should be last. It will match any params not previously matched.
8
+ get '/', {}
@@ -1,3 +1,11 @@
1
1
  class MainController < ModelController
2
2
  model :page
3
+
4
+ private
5
+ # the main template contains a #template binding that shows another
6
+ # template. This is the path to that template. It may change based
7
+ # on the params._controller and params._action values.
8
+ def main_path
9
+ params._controller.or('main') + "/" + params._action.or('index')
10
+ end
3
11
  end
@@ -0,0 +1,73 @@
1
+ <:Title>
2
+ Bindings
3
+
4
+ <:Body>
5
+ <h1>Bindings</h1>
6
+
7
+
8
+ <h2>Text</h2>
9
+ <table class="table">
10
+ <tr>
11
+ <td>Page</td>
12
+ <td><input type="text" id="pageName1" value="{page._name}"></td>
13
+ <td><input type="text" id="pageName2" value="{page._name}"></td>
14
+ <td id="pageName3">{page._name}</td>
15
+ </tr>
16
+ <tr>
17
+ <td>Params</td>
18
+ <td><input type="text" id="paramsName1" value="{params._name}"></td>
19
+ <td><input type="text" id="paramsName2" value="{params._name}"></td>
20
+ <td id="paramsName3">{params._name}</td>
21
+ </tr>
22
+ <tr>
23
+ <td>Routes</td>
24
+ <td><input type="text" id="routesName1" value="{params._route_test}"></td>
25
+ <td><input type="text" id="routesName2" value="{params._route_test}"></td>
26
+ <td id="routesName3">{params._route_test}</td>
27
+ </tr>
28
+ </table>
29
+
30
+ <h2>Checkbox</h2>
31
+
32
+ <table class="table">
33
+ <tr>
34
+ <td>Page</td>
35
+ <td><input type="checkbox" id="pageCheck1" checked="{page._check}" /></td>
36
+ <td><input type="checkbox" id="pageCheck2" checked="{page._check}" /></td>
37
+ <td id="pageCheck3">{page._check}</td>
38
+ </tr>
39
+ <tr>
40
+ <td>Params</td>
41
+ <td><input type="checkbox" id="paramsCheck1" checked="{params._check}" /></td>
42
+ <td><input type="checkbox" id="paramsCheck2" checked="{params._check}" /></td>
43
+ <td id="paramsCheck3">{params._check}</td>
44
+ </tr>
45
+ </table>
46
+
47
+ <h2>Radio</h2>
48
+
49
+ <table class="table">
50
+ <tr>
51
+ <td>Page</td>
52
+ <td><input type="radio" id="pageRadio1" checked="{page._radio}" value="one" /></td>
53
+ <td><input type="radio" id="pageRadio2" checked="{page._radio}" value="two" /></td>
54
+ <td id="pageRadio3">{page._radio}</td>
55
+ </tr>
56
+ <tr>
57
+ <td></td>
58
+ <td><input type="radio" checked="{page._radio}" value="one" /></td>
59
+ <td><input type="radio" checked="{page._radio}" value="two" /></td>
60
+ <td></td>
61
+ </tr>
62
+ </table>
63
+
64
+ <h2>Textarea</h2>
65
+ <table class="table">
66
+ <tr>
67
+ <td>Page</td>
68
+ <td><textarea id="textareaName1">{page._textarea_name}</textarea></td>
69
+ <td><textarea id="textareaName2">{page._textarea_name}</textarea></td>
70
+ <td id="textareaName3">{page._textarea_name}</td>
71
+ </tr>
72
+ </table>
73
+
@@ -1,2 +1,7 @@
1
+ <:Title>
2
+ KitchenSink
3
+
1
4
  <:Body>
2
- <h2>Index Template</h2>
5
+ <h2>Kitchen Sink</h2>
6
+
7
+ <p>The kitchen sink app both showcases many of the features of Volt and acts as an app for integration testing.</p>
@@ -1,9 +1,29 @@
1
1
  <:Title>
2
- Home
2
+ {#template main_path, "title", {controller_group: 'main'}} - KitchenSink
3
3
 
4
4
  <:Body>
5
- <h1>Home</h1>
6
-
7
- {#template "index"}
8
-
9
- <p>After template</p>
5
+ <div class="container">
6
+ <div class="header">
7
+ <ul class="nav nav-pills pull-right">
8
+ <:nav href="/" text="Home" />
9
+ <:nav href="/bindings" text="Bindings" />
10
+ <:nav href="/store" text="Store" />
11
+ </ul>
12
+ <h3 class="text-muted">Project name</h3>
13
+ </div>
14
+
15
+ <:volt:notices />
16
+
17
+ {#template main_path, 'body', {controller_group: 'main'}}
18
+
19
+ <div class="footer">
20
+ <p>&copy; Company 2014</p>
21
+ </div>
22
+
23
+ </div>
24
+
25
+ <:Nav>
26
+ <li class="{#if url.path.split('/')[1] == attrs.href.split('/')[1]}active{/}">
27
+ <a href="{attrs.href}">{attrs.text}</a>
28
+ </li>
29
+
@@ -0,0 +1,6 @@
1
+ <:Title>
2
+ Store
3
+
4
+ <:Body>
5
+ <h1>Store</h1>
6
+
@@ -1,5 +1,5 @@
1
1
  require 'spec_helper'
2
- require 'volt/controllers/reactive_accessors'
2
+ require 'volt/reactive/reactive_accessors'
3
3
 
4
4
  class TestReactiveAccessors
5
5
  include ReactiveAccessors
@@ -8,12 +8,6 @@ class TestReactiveAccessors
8
8
  end
9
9
 
10
10
  describe ReactiveAccessors do
11
- it "should return the same reactive value after each read" do
12
- inst = TestReactiveAccessors.new
13
-
14
- expect(inst._name.reactive_manager.object_id).to eq(inst._name.reactive_manager.object_id)
15
- end
16
-
17
11
  it "should assign a reactive value" do
18
12
  inst = TestReactiveAccessors.new
19
13
 
@@ -24,19 +18,23 @@ describe ReactiveAccessors do
24
18
  it "should start nil" do
25
19
  inst = TestReactiveAccessors.new
26
20
 
27
- expect(inst._name.cur).to eq(nil)
21
+ expect(inst._name).to eq(nil)
28
22
  end
29
23
 
30
- it "should keep the same reactive value when reassigning" do
24
+ it 'should trigger changed when assigning a new value' do
31
25
  inst = TestReactiveAccessors.new
26
+ values = []
32
27
 
33
- inst._name = 'Ryan'
34
- rv1_id = inst._name.reactive_manager.object_id
28
+ -> { values << inst._name }.watch!
35
29
 
36
- inst._name = 'Jim'
37
- rv2_id = inst._name.reactive_manager.object_id
30
+ expect(values).to eq([nil])
38
31
 
39
- expect(rv1_id).to eq(rv2_id)
40
- end
32
+ inst._name = 'Ryan'
33
+ Computation.flush!
34
+ expect(values).to eq([nil,'Ryan'])
41
35
 
36
+ inst._name = 'Stout'
37
+ Computation.flush!
38
+ expect(values).to eq([nil,'Ryan','Stout'])
39
+ end
42
40
  end