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
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.7.23
1
+ 0.8.0
@@ -0,0 +1,175 @@
1
+ (function (global, undefined) {
2
+ "use strict";
3
+
4
+ if (global.setImmediate) {
5
+ return;
6
+ }
7
+
8
+ var nextHandle = 1; // Spec says greater than zero
9
+ var tasksByHandle = {};
10
+ var currentlyRunningATask = false;
11
+ var doc = global.document;
12
+ var setImmediate;
13
+
14
+ function addFromSetImmediateArguments(args) {
15
+ tasksByHandle[nextHandle] = partiallyApplied.apply(undefined, args);
16
+ return nextHandle++;
17
+ }
18
+
19
+ // This function accepts the same arguments as setImmediate, but
20
+ // returns a function that requires no arguments.
21
+ function partiallyApplied(handler) {
22
+ var args = [].slice.call(arguments, 1);
23
+ return function() {
24
+ if (typeof handler === "function") {
25
+ handler.apply(undefined, args);
26
+ } else {
27
+ (new Function("" + handler))();
28
+ }
29
+ };
30
+ }
31
+
32
+ function runIfPresent(handle) {
33
+ // From the spec: "Wait until any invocations of this algorithm started before this one have completed."
34
+ // So if we're currently running a task, we'll need to delay this invocation.
35
+ if (currentlyRunningATask) {
36
+ // Delay by doing a setTimeout. setImmediate was tried instead, but in Firefox 7 it generated a
37
+ // "too much recursion" error.
38
+ setTimeout(partiallyApplied(runIfPresent, handle), 0);
39
+ } else {
40
+ var task = tasksByHandle[handle];
41
+ if (task) {
42
+ currentlyRunningATask = true;
43
+ try {
44
+ task();
45
+ } finally {
46
+ clearImmediate(handle);
47
+ currentlyRunningATask = false;
48
+ }
49
+ }
50
+ }
51
+ }
52
+
53
+ function clearImmediate(handle) {
54
+ delete tasksByHandle[handle];
55
+ }
56
+
57
+ function installNextTickImplementation() {
58
+ setImmediate = function() {
59
+ var handle = addFromSetImmediateArguments(arguments);
60
+ process.nextTick(partiallyApplied(runIfPresent, handle));
61
+ return handle;
62
+ };
63
+ }
64
+
65
+ function canUsePostMessage() {
66
+ // The test against `importScripts` prevents this implementation from being installed inside a web worker,
67
+ // where `global.postMessage` means something completely different and can't be used for this purpose.
68
+ if (global.postMessage && !global.importScripts) {
69
+ var postMessageIsAsynchronous = true;
70
+ var oldOnMessage = global.onmessage;
71
+ global.onmessage = function() {
72
+ postMessageIsAsynchronous = false;
73
+ };
74
+ global.postMessage("", "*");
75
+ global.onmessage = oldOnMessage;
76
+ return postMessageIsAsynchronous;
77
+ }
78
+ }
79
+
80
+ function installPostMessageImplementation() {
81
+ // Installs an event handler on `global` for the `message` event: see
82
+ // * https://developer.mozilla.org/en/DOM/window.postMessage
83
+ // * http://www.whatwg.org/specs/web-apps/current-work/multipage/comms.html#crossDocumentMessages
84
+
85
+ var messagePrefix = "setImmediate$" + Math.random() + "$";
86
+ var onGlobalMessage = function(event) {
87
+ if (event.source === global &&
88
+ typeof event.data === "string" &&
89
+ event.data.indexOf(messagePrefix) === 0) {
90
+ runIfPresent(+event.data.slice(messagePrefix.length));
91
+ }
92
+ };
93
+
94
+ if (global.addEventListener) {
95
+ global.addEventListener("message", onGlobalMessage, false);
96
+ } else {
97
+ global.attachEvent("onmessage", onGlobalMessage);
98
+ }
99
+
100
+ setImmediate = function() {
101
+ var handle = addFromSetImmediateArguments(arguments);
102
+ global.postMessage(messagePrefix + handle, "*");
103
+ return handle;
104
+ };
105
+ }
106
+
107
+ function installMessageChannelImplementation() {
108
+ var channel = new MessageChannel();
109
+ channel.port1.onmessage = function(event) {
110
+ var handle = event.data;
111
+ runIfPresent(handle);
112
+ };
113
+
114
+ setImmediate = function() {
115
+ var handle = addFromSetImmediateArguments(arguments);
116
+ channel.port2.postMessage(handle);
117
+ return handle;
118
+ };
119
+ }
120
+
121
+ function installReadyStateChangeImplementation() {
122
+ var html = doc.documentElement;
123
+ setImmediate = function() {
124
+ var handle = addFromSetImmediateArguments(arguments);
125
+ // Create a <script> element; its readystatechange event will be fired asynchronously once it is inserted
126
+ // into the document. Do so, thus queuing up the task. Remember to clean up once it's been called.
127
+ var script = doc.createElement("script");
128
+ script.onreadystatechange = function () {
129
+ runIfPresent(handle);
130
+ script.onreadystatechange = null;
131
+ html.removeChild(script);
132
+ script = null;
133
+ };
134
+ html.appendChild(script);
135
+ return handle;
136
+ };
137
+ }
138
+
139
+ function installSetTimeoutImplementation() {
140
+ setImmediate = function() {
141
+ var handle = addFromSetImmediateArguments(arguments);
142
+ setTimeout(partiallyApplied(runIfPresent, handle), 0);
143
+ return handle;
144
+ };
145
+ }
146
+
147
+ // If supported, we should attach to the prototype of global, since that is where setTimeout et al. live.
148
+ var attachTo = Object.getPrototypeOf && Object.getPrototypeOf(global);
149
+ attachTo = attachTo && attachTo.setTimeout ? attachTo : global;
150
+
151
+ // Don't get fooled by e.g. browserify environments.
152
+ if ({}.toString.call(global.process) === "[object process]") {
153
+ // For Node.js before 0.9
154
+ installNextTickImplementation();
155
+
156
+ } else if (canUsePostMessage()) {
157
+ // For non-IE10 modern browsers
158
+ installPostMessageImplementation();
159
+
160
+ } else if (global.MessageChannel) {
161
+ // For web workers, where supported
162
+ installMessageChannelImplementation();
163
+
164
+ } else if (doc && "onreadystatechange" in doc.createElement("script")) {
165
+ // For IE 6–8
166
+ installReadyStateChangeImplementation();
167
+
168
+ } else {
169
+ // For older browsers
170
+ installSetTimeoutImplementation();
171
+ }
172
+
173
+ attachTo.setImmediate = setImmediate;
174
+ attachTo.clearImmediate = clearImmediate;
175
+ }(new Function("return this")()));
@@ -7,8 +7,6 @@ class DataStore
7
7
  end
8
8
 
9
9
  def query(collection, query)
10
- puts "QUERY: #{collection} - #{query.inspect}"
11
-
12
10
  query = query.dup
13
11
  query.keys.each do |key|
14
12
  if key =~ /_id$/
@@ -23,29 +23,29 @@ class LiveQuery
23
23
  end
24
24
 
25
25
  def notify_removed(ids, skip_channel)
26
+ # puts "Removed: #{ids.inspect}"
26
27
  notify! do |channel|
27
- # puts "Removed: #{ids.inspect} to #{channel.inspect}"
28
28
  channel.send_message("removed", nil, @collection, @query, ids)
29
29
  end
30
30
  end
31
31
 
32
32
  def notify_added(index, data, skip_channel)
33
+ # puts "Added: #{index} - #{data.inspect}"
33
34
  notify! do |channel|
34
- # puts "Added: #{index} - #{data.inspect} to #{channel.inspect}"
35
35
  channel.send_message("added", nil, @collection, @query, index, data)
36
36
  end
37
37
  end
38
38
 
39
39
  def notify_moved(id, new_position, skip_channel)
40
+ # puts "Moved: #{id}, #{new_position}"
40
41
  notify! do |channel|
41
- # puts "Moved: #{id}, #{new_position} to #{channel.inspect}"
42
42
  channel.send_message("moved", nil, @collection, @query, id, new_position)
43
43
  end
44
44
  end
45
45
 
46
46
  def notify_changed(id, data, skip_channel)
47
+ # puts "Changed: #{id}, #{data}"
47
48
  notify!(skip_channel) do |channel|
48
- # puts "Changed: #{id}, #{data} to #{channel.inspect}"
49
49
  channel.send_message("changed", nil, @collection, @query, id, data)
50
50
  end
51
51
  end
@@ -1,7 +1,28 @@
1
1
  # Getting Started
2
2
 
3
- Volt relies on a few concepts to take make web development faster and easier. The first of these is reactive programming. Data on the front and back end is stored in models. Instead of manually updating a page when the data changes, the page is coded using a handlebars like templating language which automatically updates when the data changes.
3
+ Volt relies on a few concepts to take make web development faster and easier. The first of these is reactive programming. Data on the front and back end is stored in models. Instead of manually updating a page when the data changes, the page is coded using a templating language which automatically updates when the data changes.
4
4
 
5
- ## Reactive Values
5
+ ## Bindings and Models
6
6
 
7
- The key to making these updates happen is something we call "Reactive Values." A ReactiveValue wraps another v
7
+ This automaic updating is done via bindings and models. In Volt app's all data is stored in a model. From your html, you can bind things like attributes and text to a value in a model.
8
+
9
+ ### Name Example
10
+
11
+ ```html
12
+ <label>Name:</label>
13
+ <input type="text" value="{page._name}" />
14
+ <p>Hello {page._name}</p>
15
+ ```
16
+
17
+ In the example above, our model is called page (more about page later). Any time a user changes the value of the field, page._name will be updated to the fields value. When page._name is changed, the fields value changes. Also when ```page._name``` changes, the page will show the text "Hello ..." where ... is the value of page._name. These "two-way bindings" help us eliminiate a lot of code by keeping all of our application state in our models. Data displayed in a view is always computed live from the data in the models.
18
+
19
+ ### Meal Cost Splitter Example
20
+
21
+ ```html
22
+ <label>Cost:</label><input type="text" value="{page._cost}" /><br />
23
+ <label>People:</label><input type="text" value="{page._people}" /><br />
24
+ <p>Cost Per Person: {page._cost.to_f / page._people.to_f}</p>
25
+ ```
26
+ In this example, a user can enter a cost and a number of people. When either changes, the Cost Per Person will update.
27
+
28
+ ###
@@ -18,30 +18,9 @@ Data in volt is reactive by default. Changes to the data is automatically updat
18
18
  A lot of modern web development is moving data between the front-end to the back-end. Volt eliminates all of that work. Model's on the front-end automatically sync to the back-end, and vice versa. Validations are run on both sides for security. Models on the front-end are automatically updated whenever they are changed anywhere else (another browser, a background task, etc..)
19
19
 
20
20
 
21
- ## Speed
22
-
23
- Volt's reactive objects contain extra data about how to propigate events. Things that don't need to be updated when data changes aren't. Volt uses an intellegent managed draw cycle to do efficient DOM updates. Only the changed part of the page is re-rendered.
24
-
25
-
26
-
27
-
28
21
  # Why Volt is Awesome
29
22
 
30
- - only the relevant DOM is updated. There is no match and patch algorithm to update from strings like other frameworks, all associations are tracked through our reactive values, so we know exactly what needs to be updated without the need to generate any extra HTML. This has a few advantages, namely that things like input fields are retained, so any properties (focus, tab position, etc...) are also retained.
31
-
32
-
33
-
34
-
35
-
36
-
37
-
38
-
39
-
40
-
41
-
42
-
43
-
44
-
23
+ - only the relevant DOM is updated. There is no match and patch algorithm to update from strings like other frameworks, all associations are tracked through our reactive core, so we know exactly what needs to be updated without the need to generate any extra HTML. This has a few advantages, namely that things like input fields are retained, so any properties (focus, tab position, etc...) are also retained.
45
24
 
46
25
 
47
26
  # Why Ruby
@@ -1,8 +1,15 @@
1
1
  require 'volt/volt/environment'
2
2
  require 'volt/extra_core/extra_core'
3
- require 'volt/reactive/reactive_value'
3
+ require 'volt/reactive/computation'
4
+ require 'volt/reactive/dependency'
4
5
 
5
6
  class Volt
7
+ if RUBY_PLATFORM == 'opal'
8
+ @@in_browser = `!!document && !window.OPAL_SPEC_PHANTOM`
9
+ else
10
+ @@in_browser = false
11
+ end
12
+
6
13
  def self.root
7
14
  @root ||= File.expand_path(Dir.pwd)
8
15
  end
@@ -26,4 +33,16 @@ class Volt
26
33
  def self.env
27
34
  @env ||= Volt::Environment.new
28
35
  end
36
+
37
+ def self.logger
38
+ @logger ||= Logger.new
39
+ end
40
+
41
+ def self.logger=(val)
42
+ @logger = val
43
+ end
44
+
45
+ def self.in_browser?
46
+ @@in_browser
47
+ end
29
48
  end
@@ -1,3 +1,23 @@
1
+ require 'pry'
2
+
3
+ class Pry
4
+ # To make the console more useful, we make it so we flush the event registry
5
+ # after each line. This makes it so events are triggered after each line.
6
+ # To accomplish this we monkey-patch pry.
7
+ def rep(target=TOPLEVEL_BINDING)
8
+ target = Pry.binding_for(target)
9
+ result = re(target)
10
+
11
+ Pry.critical_section do
12
+ show_result(result)
13
+ end
14
+
15
+ # Automatically flush after each line
16
+ Computation.flush!
17
+ end
18
+ end
19
+
20
+
1
21
  class Console
2
22
  def self.start
3
23
  require 'pry'
@@ -1,8 +1,10 @@
1
- require 'volt/controllers/reactive_accessors'
1
+ require 'volt/reactive/reactive_accessors'
2
2
 
3
3
  class ModelController
4
4
  include ReactiveAccessors
5
5
 
6
+ reactive_accessor :current_model
7
+
6
8
  def self.model(val)
7
9
  @default_model = val
8
10
  end
@@ -10,24 +12,31 @@ class ModelController
10
12
  # Sets the current model on this controller
11
13
  def model=(val)
12
14
  # Start with a nil reactive value.
13
- @model ||= ReactiveValue.new(Proc.new { nil })
15
+ self.current_model ||= Model.new
14
16
 
15
17
  if Symbol === val || String === val
16
18
  collections = [:page, :store, :params, :controller]
17
19
  if collections.include?(val.to_sym)
18
- @model.cur = self.send(val).cur
20
+ self.current_model = self.send(val)
19
21
  else
20
22
  raise "#{val} is not the name of a valid model, choose from: #{collections.join(', ')}"
21
23
  end
22
24
  elsif val
23
- @model.cur = val.cur
25
+ self.current_model = val
24
26
  else
25
27
  raise "model can not be #{val.inspect}"
26
28
  end
27
29
  end
28
30
 
29
31
  def model
30
- @model
32
+ model = self.current_model
33
+
34
+ # If the model is a proc, call it now
35
+ if model.is_a?(Proc)
36
+ model = model.call
37
+ end
38
+
39
+ return model
31
40
  end
32
41
 
33
42
  def self.new(*args, &block)
@@ -40,11 +49,16 @@ class ModelController
40
49
  return inst
41
50
  end
42
51
 
52
+ attr_accessor :attrs
53
+
43
54
  def initialize(*args)
44
- # Set the instance variable to match any passed in arguments
45
- if args.size > 0
46
- args[0].each_pair do |key, value|
47
- instance_variable_set(:"@#{key}", value)
55
+ if args[0]
56
+ # Assign the first passed in argument to attrs
57
+ self.attrs = args[0]
58
+
59
+ # If a model attribute is passed in, we assign it directly
60
+ if attrs.respond_to?(:model)
61
+ self.model = attrs.locals[:model]
48
62
  end
49
63
  end
50
64
  end
@@ -87,10 +101,10 @@ class ModelController
87
101
  end
88
102
 
89
103
  def controller
90
- @controller ||= ReactiveValue.new(Model.new)
104
+ @controller ||= Model.new
91
105
  end
92
106
 
93
107
  def method_missing(method_name, *args, &block)
94
- return @model.send(method_name, *args, &block)
108
+ return model.send(method_name, *args, &block)
95
109
  end
96
110
  end
@@ -9,7 +9,7 @@ class Object
9
9
  # Provides the same functionality as ||, but since ReactiveValue's only
10
10
  # work with method calls, we provide .or as a convience.
11
11
  def or(other)
12
- if self.true?
12
+ if self && !self.nil?
13
13
  return self
14
14
  else
15
15
  return other
@@ -19,25 +19,13 @@ class Object
19
19
  # Provides the same functionality as &&, but since ReactiveValue's only
20
20
  # work with method calls, we provide .and as a convience
21
21
  def and(other)
22
- if self.true?
22
+ if self && !self.nil?
23
23
  return other
24
24
  else
25
25
  return self
26
26
  end
27
27
  end
28
28
 
29
- def try(*a, &b)
30
- if a.empty? && block_given?
31
- yield self
32
- else
33
- __send__(*a, &b)
34
- end
35
- end
36
-
37
- def deep_cur
38
- self.cur
39
- end
40
-
41
29
  def html_inspect
42
30
  inspect.gsub('<', '&lt;').gsub('>', '&gt;')
43
31
  end