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,5 @@
1
+ require 'mongo'
2
+
3
+ mongo_client = Mongo::MongoClient.new("localhost", 27017)
4
+
5
+ db = mongo_client.db("test1")
@@ -0,0 +1,110 @@
1
+ require 'volt/templates/base_binding'
2
+ require 'volt/templates/targets/attribute_target'
3
+
4
+ class AttributeBinding < BaseBinding
5
+ def initialize(target, context, binding_name, attribute_name, getter)
6
+ # puts "New Attribute Binding: #{binding_name}, #{attribute_name}, #{getter}"
7
+ super(target, context, binding_name)
8
+
9
+ @attribute_name = attribute_name
10
+ @getter = getter
11
+
12
+ setup
13
+ end
14
+
15
+ def setup
16
+
17
+ # Find the source for the content binding
18
+ @value = value_from_getter(@getter)
19
+
20
+ # Run the initial update (render)
21
+ update
22
+
23
+ @update_listener = @value.on('changed') { update }
24
+
25
+ # Bind so when this value updates, we update
26
+ case @attribute_name
27
+ when 'value'
28
+ element.on('input.attrbind') { changed }
29
+ when 'checked'
30
+ element.on('change.attrbind') {|event| changed(event) }
31
+ end
32
+ end
33
+
34
+ def changed(event=nil)
35
+ case @attribute_name
36
+ when 'value'
37
+ current_value = element.value
38
+ # puts "NEW VAL: #{current_value} : #{@value.inspect}"
39
+ else
40
+ current_value = element.is(':checked')
41
+ end
42
+
43
+ @value.cur = current_value
44
+ end
45
+
46
+ def element
47
+ Element.find('#' + binding_name)
48
+ end
49
+
50
+ def update
51
+ if @attribute_target
52
+ value = @attribute_target.to_html
53
+ else
54
+ value = @value.cur
55
+ end
56
+
57
+ if @attribute_name == 'checked'
58
+ update_checked
59
+ return
60
+ end
61
+
62
+ if value.is_a?(NilMethodCall) || value.nil?
63
+ value = ''
64
+ end
65
+
66
+ self.value = value
67
+ end
68
+
69
+ def value=(val)
70
+ case @attribute_name
71
+ when 'value'
72
+ element.value = val
73
+ else
74
+ element[@attribute_name] = val
75
+ end
76
+ end
77
+
78
+ def update_checked
79
+ value = @value.cur
80
+
81
+ if value.is_a?(NilMethodCall) || value.nil?
82
+ value = false
83
+ end
84
+
85
+ if value
86
+ element['checked'] = 'checked'
87
+ else
88
+ element.remove_attr('checked')
89
+ end
90
+
91
+ end
92
+
93
+ def remove
94
+ # Unbind events, leave the element there since attribute bindings
95
+ # aren't responsible for it being there.
96
+ case @attribute_name
97
+ when 'value'
98
+ element.off('input.attrbind')
99
+ when 'checked'
100
+ element.off('change.attrbind')
101
+ end
102
+
103
+ if @update_listener
104
+ @update_listener.remove
105
+ @update_listener = nil
106
+ end
107
+ end
108
+
109
+
110
+ end
@@ -0,0 +1,37 @@
1
+ class BaseBinding
2
+ attr_accessor :target, :context, :binding_name
3
+
4
+ def initialize(target, context, binding_name)
5
+ @target = target
6
+ @context = context
7
+ @binding_name = binding_name
8
+
9
+ @@binding_number ||= 10000
10
+ end
11
+
12
+ def section
13
+ @section ||= target.section(@binding_name)
14
+ end
15
+
16
+ def remove
17
+ section.remove
18
+ end
19
+
20
+ def remove_anchors
21
+ section.remove_anchors
22
+ end
23
+
24
+ def queue_update
25
+ if Volt.server?
26
+ # Run right away
27
+ update
28
+ else
29
+
30
+ end
31
+ end
32
+
33
+ def value_from_getter(getter)
34
+ # Evaluate the getter proc in the context
35
+ return @context.instance_eval(&getter)
36
+ end
37
+ end
@@ -0,0 +1,48 @@
1
+ # The channel is the connection between the front end and the backend.
2
+
3
+ require 'volt/reactive/events'
4
+ require 'json'
5
+
6
+ class Channel
7
+ include Events
8
+
9
+ def initialize
10
+ @socket = nil
11
+ %x{
12
+ this.socket = new SockJS('http://localhost:3000/channel');//, {reconnect: true});
13
+
14
+ this.socket.onopen = function() {
15
+ console.log('open');
16
+ self['$trigger!']("open");
17
+ };
18
+
19
+ this.socket.onmessage = function(message) {
20
+ console.log('received: ', message);
21
+ self['$message_received'](message.data);
22
+ };
23
+ }
24
+ end
25
+
26
+ def message_received(message)
27
+ message = JSON.parse(message)
28
+ puts "Got #{message.inspect}"
29
+
30
+ trigger!('message', message)
31
+ end
32
+
33
+ def send(message)
34
+ # TODO: Temp: wrap message in an array, so we're sure its valid JSON
35
+ message = JSON.dump([message])
36
+ %x{
37
+ //message = window.JSON.parse(message);
38
+ console.log('send: ', message);
39
+ this.socket.send(message);
40
+ }
41
+ end
42
+
43
+ def close
44
+ %x{
45
+ this.socket.close();
46
+ }
47
+ end
48
+ end
@@ -0,0 +1,35 @@
1
+ require 'volt/templates/base_binding'
2
+
3
+ class ContentBinding < BaseBinding
4
+ def initialize(target, context, binding_name, getter)
5
+ # puts "new content binding: #{getter}"
6
+ super(target, context, binding_name)
7
+
8
+ # Find the source for the content binding
9
+ @value = value_from_getter(getter)
10
+
11
+ # Run the initial render
12
+ update
13
+
14
+ @changed_listener = @value.on('changed') { update }
15
+ end
16
+
17
+ def update
18
+ value = @value.cur.or('')
19
+
20
+ # Exception values display the exception as a string
21
+ value = value.to_s
22
+
23
+ # Update the text in this section
24
+ section.text = value
25
+ end
26
+
27
+ def remove
28
+ @changed_listener.remove
29
+ @changed_listener = nil
30
+
31
+ super
32
+ end
33
+
34
+
35
+ end
@@ -0,0 +1,80 @@
1
+ class DocumentEvents
2
+ def initialize
3
+ @events = {}
4
+ end
5
+
6
+ def add(event, binding, handler)
7
+ # Track each document event based on the event, element id, then binding.object_id
8
+ unless @events[event]
9
+ # We haven't defined an event of type event yet, lets attach it to the
10
+ # document.
11
+
12
+ @events[event] = {}
13
+
14
+ that = self
15
+
16
+ %x{
17
+ $('body').on(event, function(e) {
18
+ that.$handle(event, e, e.originalEvent.target);
19
+ });
20
+ }
21
+
22
+ end
23
+
24
+ # puts "Register: #{event} - #{binding.binding_name} - #{binding.object_id}"
25
+
26
+ @events[event][binding.binding_name] ||= {}
27
+ @events[event][binding.binding_name][binding.object_id] = handler
28
+ end
29
+
30
+ def handle(event_name, event, target)
31
+ # puts "Handle: #{event_name} on #{target}"
32
+ element = Element.find(target)
33
+
34
+ loop do
35
+ # Lookup the handler, make sure to not assume the group
36
+ # exists.
37
+ # TODO: Sometimes the event doesn't exist, but we still get
38
+ # an event.
39
+ handlers = @events[event_name]
40
+ handlers = handlers[element.id] if handlers
41
+
42
+ if handlers
43
+ handlers.values.each do |handler|
44
+ # Call each handler for this object
45
+ handler.call(event)
46
+ end
47
+ end
48
+
49
+ if element.size == 0
50
+ break
51
+ else
52
+ element = element.parent
53
+ end
54
+ end
55
+
56
+ nil
57
+ end
58
+
59
+ def remove(event, binding)
60
+ # Remove the event binding
61
+ @events[event][binding.binding_name].delete(binding.object_id)
62
+
63
+ # if there are no more handlers for this binding_name (the html id), then
64
+ # we remove the binding name hash
65
+ if @events[event][binding.binding_name].size == 0
66
+ @events[event].delete(binding.binding_name)
67
+ end
68
+
69
+ # if there are no more handlers in this event, we can unregister the event
70
+ # from the document
71
+ if @events[event].size == 0
72
+ @events.delete(event)
73
+
74
+ # Remove the event from the body
75
+ %x{
76
+ $('body').unbind(event);
77
+ }
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,115 @@
1
+ require 'volt/templates/base_binding'
2
+
3
+ class EachBinding < BaseBinding
4
+ def initialize(target, context, binding_name, getter, variable_name, template_name)
5
+ # puts "New EACH Binding"
6
+
7
+ super(target, context, binding_name)
8
+
9
+ @item_name = variable_name
10
+ @template_name = template_name
11
+
12
+ # Find the source for the content binding
13
+ @value = value_from_getter(getter)
14
+
15
+ @templates = []
16
+
17
+ # Run the initial render
18
+ update
19
+
20
+ @added_listener = @value.on('added') { |position, item| puts "ADDED" ; item_added(position) }
21
+ @changed_listener = @value.on('changed') { puts "CHANGED" ; reload }
22
+ @removed_listener = @value.on('removed') { |position| puts "REMOVED at #{position.inspect}" ; item_removed(position) }
23
+ end
24
+
25
+ # When a change event comes through, its most likely upstream, so the whole
26
+ # array might have changed. In this case, just reload the whole thing
27
+ # TODO: Track to make sure the changed event isn't being called too often (it is currently)
28
+ def reload
29
+ # Remove all of the current templates
30
+ if @templates
31
+ @templates.each do |template|
32
+ template.remove
33
+ template.remove_anchors
34
+ end
35
+ end
36
+
37
+ @templates = []
38
+
39
+ # Run update again to rebuild
40
+ update
41
+ end
42
+
43
+ def item_removed(position)
44
+ position = position.cur
45
+ @templates[position].remove
46
+ @templates[position].remove_anchors
47
+ @templates.delete_at(position)
48
+
49
+ value_obj = @value.cur
50
+
51
+ if value_obj
52
+ size = value_obj.size - 1
53
+ else
54
+ size = 0
55
+ end
56
+
57
+ # puts "Position: #{position} to #{size}"
58
+
59
+ # Removed at the position, update context for every item after this position
60
+ position.upto(size) do |index|
61
+ @templates[index].context.locals[:index].cur = index
62
+ end
63
+ end
64
+
65
+ def item_added(position)
66
+ # puts "ADDED AT #{position}"
67
+ binding_name = @@binding_number
68
+ @@binding_number += 1
69
+
70
+ # Setup new bindings in the spot we want to insert the item
71
+ section.insert_anchor_before_end(binding_name)
72
+
73
+ index = ReactiveValue.new(position)
74
+ value = @value[index]
75
+
76
+ item_context = SubContext.new({@item_name => value, :index => index, :parent => @value}, @context)
77
+
78
+ @templates << TemplateRenderer.new(@target, item_context, binding_name, @template_name)
79
+ end
80
+
81
+ def update(item=nil)
82
+ if item
83
+ values = [item]
84
+ else
85
+ values = @value.cur
86
+ return if values.is_a?(Model) || values.is_a?(Exception)
87
+ values = values.attributes
88
+ end
89
+
90
+ # TODO: Switch to #each?
91
+ values.each_with_index do |value,index|
92
+ item_added(index)
93
+ end
94
+ end
95
+
96
+ # When this each_binding is removed, cleanup.
97
+ def remove
98
+ # puts "Remove Each"
99
+ @added_listener.remove
100
+ @added_listener = nil
101
+
102
+ @changed_listener.remove
103
+ @changed_listener = nil
104
+
105
+ @removed_listener.remove
106
+ @removed_listener = nil
107
+
108
+ @templates.each(&:remove)
109
+ @templates = nil
110
+
111
+ super
112
+ end
113
+
114
+
115
+ end
@@ -0,0 +1,51 @@
1
+ require 'volt/templates/base_binding'
2
+
3
+ # TODO: We need to figure out how we want to wrap JS events
4
+ class JSEvent
5
+ attr_reader :js_event
6
+ def initialize(js_event)
7
+ @js_event = js_event
8
+ end
9
+
10
+ def key_code
11
+ `this.js_event.keyCode`
12
+ end
13
+
14
+ def stop
15
+ # puts "STOPPING"
16
+ # `this.js_event.stopPropagation();`
17
+ `this.js_event.preventDefault();`
18
+ end
19
+ end
20
+
21
+
22
+ class EventBinding < BaseBinding
23
+ attr_accessor :context, :binding_name
24
+ def initialize(target, context, binding_name, event_name, call_proc)
25
+ @target = target
26
+ @context = context
27
+ @binding_name = binding_name
28
+ @event_name = event_name
29
+
30
+ handler = Proc.new do |js_event|
31
+ event = JSEvent.new(js_event)
32
+ event.stop if event_name == 'submit'
33
+
34
+ # Call the proc the user setup for the event in context,
35
+ # pass in the wrapper for the JS event
36
+ result = @context.instance_exec(event, &call_proc)
37
+ end
38
+
39
+ @listener = $page.events.add(event_name, self, handler)
40
+ end
41
+
42
+ def element
43
+ Element.find('#' + binding_name)
44
+ end
45
+
46
+ # Remove the event binding
47
+ def remove
48
+ # puts "REMOVE EL FOR #{@event}"
49
+ $page.events.remove(@event_name, self)
50
+ end
51
+ end