volt 0.7.23 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
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