volt 0.2.3

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 (116) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +21 -0
  3. data/.rspec +2 -0
  4. data/.ruby-version +1 -0
  5. data/Gemfile +37 -0
  6. data/Guardfile +9 -0
  7. data/LICENSE.txt +22 -0
  8. data/Rakefile +23 -0
  9. data/Readme.md +34 -0
  10. data/VERSION +1 -0
  11. data/bin/volt +4 -0
  12. data/docs/GETTING_STARTED.md +7 -0
  13. data/docs/GUIDE.md +33 -0
  14. data/lib/volt.rb +15 -0
  15. data/lib/volt/benchmark/benchmark.rb +25 -0
  16. data/lib/volt/cli.rb +34 -0
  17. data/lib/volt/console.rb +19 -0
  18. data/lib/volt/controllers/model_controller.rb +29 -0
  19. data/lib/volt/extra_core/array.rb +10 -0
  20. data/lib/volt/extra_core/blank.rb +88 -0
  21. data/lib/volt/extra_core/extra_core.rb +7 -0
  22. data/lib/volt/extra_core/numeric.rb +9 -0
  23. data/lib/volt/extra_core/object.rb +36 -0
  24. data/lib/volt/extra_core/string.rb +29 -0
  25. data/lib/volt/extra_core/stringify_keys.rb +7 -0
  26. data/lib/volt/extra_core/true_false.rb +44 -0
  27. data/lib/volt/extra_core/try.rb +31 -0
  28. data/lib/volt/models.rb +5 -0
  29. data/lib/volt/models/array_model.rb +37 -0
  30. data/lib/volt/models/model.rb +210 -0
  31. data/lib/volt/models/model_wrapper.rb +23 -0
  32. data/lib/volt/models/params.rb +67 -0
  33. data/lib/volt/models/url.rb +192 -0
  34. data/lib/volt/page/url_tracker.rb +36 -0
  35. data/lib/volt/reactive/array_extensions.rb +13 -0
  36. data/lib/volt/reactive/event_chain.rb +126 -0
  37. data/lib/volt/reactive/events.rb +283 -0
  38. data/lib/volt/reactive/object_tracker.rb +99 -0
  39. data/lib/volt/reactive/object_tracking.rb +15 -0
  40. data/lib/volt/reactive/reactive_array.rb +222 -0
  41. data/lib/volt/reactive/reactive_tags.rb +64 -0
  42. data/lib/volt/reactive/reactive_value.rb +368 -0
  43. data/lib/volt/reactive/string_extensions.rb +34 -0
  44. data/lib/volt/router/routes.rb +83 -0
  45. data/lib/volt/server.rb +121 -0
  46. data/lib/volt/server/binding_setup.rb +2 -0
  47. data/lib/volt/server/channel_handler.rb +31 -0
  48. data/lib/volt/server/component_handler.rb +88 -0
  49. data/lib/volt/server/if_binding_setup.rb +29 -0
  50. data/lib/volt/server/request_handler.rb +16 -0
  51. data/lib/volt/server/scope.rb +43 -0
  52. data/lib/volt/server/source_map_server.rb +31 -0
  53. data/lib/volt/server/template_parser.rb +452 -0
  54. data/lib/volt/store/mongo.rb +5 -0
  55. data/lib/volt/templates/attribute_binding.rb +110 -0
  56. data/lib/volt/templates/base_binding.rb +37 -0
  57. data/lib/volt/templates/channel.rb +48 -0
  58. data/lib/volt/templates/content_binding.rb +35 -0
  59. data/lib/volt/templates/document_events.rb +80 -0
  60. data/lib/volt/templates/each_binding.rb +115 -0
  61. data/lib/volt/templates/event_binding.rb +51 -0
  62. data/lib/volt/templates/if_binding.rb +74 -0
  63. data/lib/volt/templates/memory_test.rb +26 -0
  64. data/lib/volt/templates/page.rb +146 -0
  65. data/lib/volt/templates/reactive_template.rb +38 -0
  66. data/lib/volt/templates/render_queue.rb +5 -0
  67. data/lib/volt/templates/sub_context.rb +23 -0
  68. data/lib/volt/templates/targets/attribute_section.rb +33 -0
  69. data/lib/volt/templates/targets/attribute_target.rb +18 -0
  70. data/lib/volt/templates/targets/base_section.rb +14 -0
  71. data/lib/volt/templates/targets/binding_document/base_node.rb +3 -0
  72. data/lib/volt/templates/targets/binding_document/component_node.rb +112 -0
  73. data/lib/volt/templates/targets/binding_document/html_node.rb +11 -0
  74. data/lib/volt/templates/targets/dom_section.rb +147 -0
  75. data/lib/volt/templates/targets/dom_target.rb +11 -0
  76. data/lib/volt/templates/template_binding.rb +159 -0
  77. data/lib/volt/templates/template_renderer.rb +50 -0
  78. data/spec/models/event_chain_spec.rb +129 -0
  79. data/spec/models/model_spec.rb +340 -0
  80. data/spec/models/old_model_spec.rb +109 -0
  81. data/spec/models/reactive_array_spec.rb +262 -0
  82. data/spec/models/reactive_tags_spec.rb +35 -0
  83. data/spec/models/reactive_value_spec.rb +336 -0
  84. data/spec/models/string_extensions_spec.rb +57 -0
  85. data/spec/router/routes_spec.rb +24 -0
  86. data/spec/server/template_parser_spec.rb +50 -0
  87. data/spec/spec_helper.rb +20 -0
  88. data/spec/store/mongo_spec.rb +4 -0
  89. data/spec/templates/targets/binding_document/component_node_spec.rb +18 -0
  90. data/spec/templates/template_binding_spec.rb +98 -0
  91. data/templates/.gitignore +12 -0
  92. data/templates/Gemfile.tt +8 -0
  93. data/templates/app/.empty_directory +0 -0
  94. data/templates/app/home/config/routes.rb +1 -0
  95. data/templates/app/home/controllers/index_controller.rb +5 -0
  96. data/templates/app/home/css/.empty_directory +0 -0
  97. data/templates/app/home/models/.empty_directory +0 -0
  98. data/templates/app/home/views/index/about.html +9 -0
  99. data/templates/app/home/views/index/home.html +7 -0
  100. data/templates/app/home/views/index/index.html +28 -0
  101. data/templates/config.ru +4 -0
  102. data/templates/public/css/ansi.css +0 -0
  103. data/templates/public/css/bootstrap-theme.css +459 -0
  104. data/templates/public/css/bootstrap.css +7098 -0
  105. data/templates/public/css/jumbotron.css +79 -0
  106. data/templates/public/fonts/glyphicons-halflings-regular.eot +0 -0
  107. data/templates/public/fonts/glyphicons-halflings-regular.svg +229 -0
  108. data/templates/public/fonts/glyphicons-halflings-regular.ttf +0 -0
  109. data/templates/public/fonts/glyphicons-halflings-regular.woff +0 -0
  110. data/templates/public/index.html +25 -0
  111. data/templates/public/js/bootstrap.js +0 -0
  112. data/templates/public/js/jquery-2.0.3.js +8829 -0
  113. data/templates/public/js/sockjs-0.2.1.min.js +27 -0
  114. data/templates/spec/spec_helper.rb +20 -0
  115. data/volt.gemspec +41 -0
  116. metadata +412 -0
@@ -0,0 +1,7 @@
1
+ require 'volt/extra_core/array'
2
+ require 'volt/extra_core/object'
3
+ require 'volt/extra_core/blank'
4
+ require 'volt/extra_core/stringify_keys'
5
+ require 'volt/extra_core/string'
6
+ require 'volt/extra_core/numeric'
7
+ require 'volt/extra_core/true_false'
@@ -0,0 +1,9 @@
1
+ class Numeric
2
+ def in_units_of(unit)
3
+ if self == 1
4
+ return "1 #{unit}"
5
+ else
6
+ return "#{self} #{unit}s"
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,36 @@
1
+ class Object
2
+ # Setup a default pretty_inspect
3
+ # alias_method :pretty_inspect, :inspect
4
+
5
+ def instance_values
6
+ Hash[instance_variables.map { |name| [name[1..-1], instance_variable_get(name)] }]
7
+ end
8
+
9
+ # Provides the same functionality as ||, but since ReactiveValue's only
10
+ # work with method calls, we provide .or as a convience.
11
+ def or(other)
12
+ if self.true?
13
+ return self
14
+ else
15
+ return other
16
+ end
17
+ end
18
+
19
+ # Provides the same functionality as &&, but since ReactiveValue's only
20
+ # work with method calls, we provide .and as a convience
21
+ def and(other)
22
+ if self.true?
23
+ return other
24
+ else
25
+ return self
26
+ end
27
+ end
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
+ end
@@ -0,0 +1,29 @@
1
+ class String
2
+ # TODO: replace with better implementations
3
+ # NOTE: strings are currently immutable in Opal, so no ! methods
4
+ def camelize
5
+ self.split("_").map {|s| s.capitalize }.join("")
6
+ end
7
+
8
+ def underscore
9
+ self.scan(/[A-Z][a-z]*/).join("_").downcase
10
+ end
11
+
12
+ def pluralize
13
+ # TODO: Temp implementation
14
+ if self[-1] != 's'
15
+ return self + 's'
16
+ else
17
+ return self
18
+ end
19
+ end
20
+
21
+ def singularize
22
+ # TODO: Temp implementation
23
+ if self[-1] == 's'
24
+ return self[0..-2]
25
+ else
26
+ return self
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,7 @@
1
+ class Object
2
+ def stringify_keys
3
+ self.each_with_object({}) { |(key, value), hash|
4
+ hash[key.to_s] = value
5
+ }
6
+ end
7
+ end
@@ -0,0 +1,44 @@
1
+ # The true?/false? predicates are convience methods since if ..reactive_value.. does
2
+ # not correctly evaluate. The reason for this is that ruby currently does not allow
3
+ # anything besides nil and false to be falsy.
4
+
5
+ class Object
6
+ def true?
7
+ true
8
+ end
9
+
10
+ def false?
11
+ false
12
+ end
13
+ end
14
+
15
+ class FalseClass
16
+ def true?
17
+ false
18
+ end
19
+
20
+ def false?
21
+ true
22
+ end
23
+ end
24
+
25
+ class NilClass
26
+ def true?
27
+ false
28
+ end
29
+
30
+ def false?
31
+ true
32
+ end
33
+ end
34
+
35
+ # Opal only has a single class for true/false
36
+ class Boolean
37
+ def true?
38
+ self
39
+ end
40
+
41
+ def false?
42
+ self
43
+ end
44
+ end
@@ -0,0 +1,31 @@
1
+ class Object
2
+
3
+ class TryProxy
4
+ def initialize(original)
5
+ @original = original
6
+ end
7
+
8
+ def method_missing(method_name, *args, &block)
9
+ if @original.respond_to?(method_name)
10
+
11
+ else
12
+ NilProxy.new
13
+ end
14
+ end
15
+ end
16
+
17
+ class NilProxy
18
+ def method_missing(method_name, *args, &block)
19
+ if @original.respond_to?(method_name)
20
+
21
+ else
22
+ NilProxy.new
23
+ end
24
+ end
25
+ end
26
+
27
+ def try
28
+
29
+ end
30
+
31
+ end
@@ -0,0 +1,5 @@
1
+ require 'volt/extra_core/extra_core'
2
+ require 'volt/reactive/reactive_value'
3
+ require 'volt/models/model'
4
+ require 'volt/models/params'
5
+
@@ -0,0 +1,37 @@
1
+ require 'volt/models/model_wrapper'
2
+
3
+ class ArrayModel < ReactiveArray
4
+ include ModelWrapper
5
+
6
+ def initialize(array=[], parent=nil, path=nil)
7
+ @parent = parent
8
+ @path = path
9
+
10
+ array = wrap_values(array)
11
+
12
+ super(array)
13
+ end
14
+
15
+ def attributes
16
+ self
17
+ end
18
+
19
+ # Make sure it gets wrapped
20
+ def <<(*args)
21
+ args = wrap_values(args)
22
+
23
+ super(*args)
24
+ end
25
+
26
+ # Make sure it gets wrapped
27
+ def inject(*args)
28
+ args = wrap_values(args)
29
+ super(*args)
30
+ end
31
+
32
+ # Make sure it gets wrapped
33
+ def +(*args)
34
+ args = wrap_values(args)
35
+ super(*args)
36
+ end
37
+ end
@@ -0,0 +1,210 @@
1
+ require 'volt/models/model_wrapper'
2
+ require 'volt/models/array_model'
3
+ require 'volt/reactive/object_tracking'
4
+
5
+ class NilMethodCall < NoMethodError
6
+ def true?
7
+ false
8
+ end
9
+
10
+ def false?
11
+ true
12
+ end
13
+ end
14
+
15
+ class Model
16
+ include ReactiveTags
17
+ include ModelWrapper
18
+ include ObjectTracking
19
+
20
+ attr_accessor :attributes
21
+ attr_reader :parent, :path
22
+
23
+ def nil?
24
+ attributes.nil?
25
+ end
26
+
27
+ def false?
28
+ attributes.false?
29
+ end
30
+
31
+ def true?
32
+ attributes.true?
33
+ end
34
+
35
+ def initialize(attributes={}, parent=nil, path=nil, class_paths=nil)
36
+ self.attributes = wrap_values(attributes)
37
+ @parent = parent
38
+ @path = path
39
+ end
40
+
41
+ # Pass the comparison through
42
+ def ==(val)
43
+ attributes == val
44
+ end
45
+
46
+ # Pass through needed
47
+ def !
48
+ !attributes
49
+ end
50
+
51
+ tag_method(:delete) do
52
+ destructive!
53
+ end
54
+ def delete(*args)
55
+ __clear_element(args[0])
56
+ attributes.delete(*args)
57
+ trigger_by_attribute!('changed', args[0])
58
+ end
59
+
60
+ tag_all_methods do
61
+ destructive! do |method_name|
62
+ method_name[0] == '_' && method_name[-1] == '='
63
+ end
64
+ pass_reactive! do |method_name|
65
+ method_name[0] == '_' && method_name[-1] == '='
66
+ end
67
+ end
68
+ def method_missing(method_name, *args, &block)
69
+ if method_name[0] == '_'
70
+ if method_name[-1] == '='
71
+ # Assigning an attribute with =
72
+ assign_attribute(method_name, *args, &block)
73
+ else
74
+ read_attribute(method_name)
75
+ end
76
+ else
77
+ # Call method directly on attributes. (since they are
78
+ # not using _ )
79
+ attributes.send(method_name, *args, &block)
80
+ end
81
+ end
82
+
83
+ # Do the assignment to a model and trigger a changed event
84
+ def assign_attribute(method_name, *args, &block)
85
+ self.expand!
86
+ # Assign, without the =
87
+ attribute_name = method_name[0..-2].to_sym
88
+
89
+ value = args[0]
90
+ __assign_element(attribute_name, value)
91
+
92
+ attributes[attribute_name] = wrap_value(value)
93
+ trigger_by_attribute!('changed', attribute_name)
94
+ end
95
+
96
+ # When reading an attribute, we need to handle reading on:
97
+ # 1) a nil model, which returns a wrapped error
98
+ # 2) reading directly from attributes
99
+ # 3) trying to read a key that doesn't exist.
100
+ def read_attribute(method_name)
101
+ # Reading an attribute, we may get back a nil model.
102
+ method_name = method_name.to_sym
103
+
104
+ if attributes == nil
105
+ # The method we are calling is on a nil model, return a wrapped
106
+ # exception.
107
+ return return_undefined_method(method_name)
108
+ elsif attributes && attributes.has_key?(method_name)
109
+ # Method has the key, look it up directly
110
+ return attributes[method_name]
111
+ else
112
+ return new_model(nil, self, method_name)
113
+ end
114
+ end
115
+
116
+ def return_undefined_method(method_name)
117
+ # Methods called on nil capture an error so the user can know where
118
+ # their nil calls are. This error can be re-raised at a later point.
119
+ begin
120
+ raise NilMethodCall.new("undefined method `#{method_name}' for #{self.to_s}")
121
+ rescue => e
122
+ result = e
123
+
124
+ # Cleanup backtrace around ReactiveValue's
125
+ # TODO: this could be better
126
+ result.backtrace.reject! {|line| line['lib/models/model.rb'] || line['lib/models/live_value.rb'] }
127
+ end
128
+ end
129
+
130
+ def new_model(*args)
131
+ Model.new(*args)
132
+ end
133
+
134
+ def trigger_by_attribute!(event_name, attribute, *passed_args)
135
+ trigger_by_scope!(event_name, *passed_args) do |scope|
136
+ method_name, *args, block = scope
137
+
138
+ # TODO: Opal bug
139
+ args ||= []
140
+
141
+ # Any methods without _ are not directly related to one attribute, so
142
+ # they should all trigger
143
+ !method_name || method_name[0] != '_' || (method_name == attribute.to_sym && args.size == 0)
144
+ end
145
+ end
146
+
147
+ # If this model is nil, it makes it into a hash model, then
148
+ # sets it up to track from the parent.
149
+ def expand!
150
+ if attributes.nil?
151
+ self.attributes = {}
152
+ if @parent
153
+ @parent.expand!
154
+
155
+ @parent.attributes[@path] = self
156
+ end
157
+ end
158
+ end
159
+
160
+ tag_method(:<<) do
161
+ destructive!
162
+ pass_reactive!
163
+ end
164
+ # Initialize an empty array and append to it
165
+ def <<(value)
166
+ @parent.expand!
167
+ result = @parent.send(@path)
168
+
169
+ if result.nil?
170
+ # If this isn't a model yet, instantiate it
171
+ @parent.send(:"#{@path}=", ArrayModel.new([], @parent, @path))
172
+ result = @parent.send(@path)
173
+
174
+ # Add the new item
175
+ result << value
176
+ end
177
+
178
+ return result
179
+ end
180
+
181
+ def inspect
182
+ "<#{self.class.to_s}:#{@path} #{attributes.inspect}>"
183
+ end
184
+
185
+
186
+ private
187
+ # Clear the previous value and assign a new one
188
+ def __assign_element(key, value)
189
+ __clear_element(key)
190
+ __track_element(key, value)
191
+ end
192
+
193
+ # TODO: Somewhat duplicated from ReactiveArray
194
+ def __clear_element(key)
195
+ # Cleanup any tracking on an index
196
+ # TODO: is this send a security risk?
197
+ # puts "TRY TO CLEAR: #{key} - #{@reactive_element_listeners && @reactive_element_listeners.keys.inspect}"
198
+ if @reactive_element_listeners && @reactive_element_listeners[key]
199
+ @reactive_element_listeners[key].remove
200
+ @reactive_element_listeners.delete(key)
201
+ end
202
+ end
203
+
204
+ def __track_element(key, value)
205
+ puts "TRACK: #{key} - #{value.inspect}"
206
+ __setup_tracking(key, value) do |event, key, args|
207
+ trigger_by_attribute!(event, key, *args)
208
+ end
209
+ end
210
+ end
@@ -0,0 +1,23 @@
1
+ module ModelWrapper
2
+ # For cretain values, we wrap them to make the behave as a
3
+ # model.
4
+ def wrap_value(value)
5
+ if value.cur.is_a?(Array)
6
+ value = ArrayModel.new(value, self, nil)
7
+ elsif value.cur.is_a?(Hash)
8
+ value = Model.new(value)
9
+ end
10
+
11
+ return value
12
+ end
13
+
14
+ def wrap_values(values)
15
+ if values.cur.is_a?(Array)
16
+ values = values.map {|v| wrap_value(v) }
17
+ elsif values.cur.is_a?(Hash)
18
+ values = Hash[values.map {|k,v| [k, wrap_value(v)] }]
19
+ end
20
+
21
+ return values
22
+ end
23
+ end