volt 0.2.3

Sign up to get free protection for your applications and to get access to all the features.
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,74 @@
1
+ require 'volt/templates/base_binding'
2
+
3
+ class IfBinding < BaseBinding
4
+ def initialize(target, context, binding_name, branches)
5
+ getter, template_name = branches[0]
6
+ # puts "New If Binding: #{binding_name}, #{getter.inspect}"
7
+
8
+
9
+ super(target, context, binding_name)
10
+
11
+ @branches = []
12
+ @listeners = []
13
+
14
+ branches.each do |branch|
15
+ getter, template_name = branch
16
+
17
+ if getter.present?
18
+ # Lookup the value
19
+ value = value_from_getter(getter)
20
+
21
+ if value.reactive?
22
+ # Trigger change when value changes
23
+ @listeners << value.on('changed') { update }
24
+ end
25
+ else
26
+ # A nil value means this is an unconditional else branch, it
27
+ # should always be true
28
+ value = true
29
+ end
30
+
31
+ @branches << [value, template_name]
32
+ end
33
+
34
+ update
35
+ end
36
+
37
+ def update
38
+ # Find the true branch
39
+ true_template = nil
40
+ @branches.each do |branch|
41
+ value, template_name = branch
42
+
43
+ # TODO: A bug in opal requires us to check == true
44
+ if value.cur.true? == true
45
+ # This branch is currently true
46
+ true_template = template_name
47
+ break
48
+ end
49
+ end
50
+
51
+ # Change out the template only if the true branch has changed.
52
+ if @last_true_template != true_template
53
+ @last_true_template = true_template
54
+
55
+ if @template
56
+ @template.remove
57
+ @template = nil
58
+ end
59
+
60
+ if true_template
61
+ @template = TemplateRenderer.new(@target, @context, binding_name, true_template)
62
+ end
63
+ end
64
+ end
65
+
66
+ def remove
67
+ # Remove all listeners on any reactive values
68
+ @listeners.each(&:remove)
69
+
70
+ @template.remove if @template
71
+
72
+ super
73
+ end
74
+ end
@@ -0,0 +1,26 @@
1
+ require 'opal'
2
+ require 'volt/models'
3
+
4
+ class Test
5
+ def self.test1
6
+ a = ReactiveValue.new(1)
7
+ listener = a.on('changed') { puts "CHANGED" }
8
+ a.cur = 5
9
+ listener.remove
10
+
11
+ ObjectTracker.process_queue
12
+ end
13
+
14
+ def self.test
15
+ a = ReactiveValue.new(Model.new)
16
+ a._cool = [1,2,3]
17
+
18
+ listener = a._cool.on('added') { puts "ADDED" }
19
+ a._cool << 4
20
+ puts a._cool[3]
21
+
22
+ listener.remove
23
+
24
+ ObjectTracker.process_queue
25
+ end
26
+ end
@@ -0,0 +1,146 @@
1
+ require 'opal'
2
+
3
+ ENV['CLIENT'] = true
4
+
5
+ require 'opal-jquery'
6
+ require 'volt/models'
7
+ require 'volt/models/params'
8
+ require 'volt/controllers/model_controller'
9
+ require 'volt/templates/attribute_binding'
10
+ require 'volt/templates/content_binding'
11
+ require 'volt/templates/each_binding'
12
+ require 'volt/templates/if_binding'
13
+ require 'volt/templates/template_binding'
14
+ require 'volt/templates/template_renderer'
15
+ require 'volt/templates/reactive_template'
16
+ require 'volt/templates/event_binding'
17
+ require 'volt/templates/document_events'
18
+ require 'volt/templates/sub_context'
19
+ require 'volt/templates/targets/dom_target'
20
+ require 'volt/templates/channel'
21
+ require 'volt/router/routes'
22
+ require 'volt/models/url'
23
+ require 'volt/page/url_tracker'
24
+ require 'volt'
25
+ require 'volt/benchmark/benchmark'
26
+ require 'volt/templates/render_queue'
27
+
28
+ class Page
29
+ attr_reader :url, :params, :page, :store, :templates, :routes, :render_queue
30
+
31
+ def initialize
32
+
33
+ # debugger
34
+ puts "------ Page Loaded -------"
35
+ @model_classes = {}
36
+
37
+ # Run the code to setup the page
38
+ @page = ReactiveValue.new(Model.new)#({}, nil, 'page', @model_classes))
39
+ @store = ReactiveValue.new(Model.new)#({}, nil, 'store', @model_classes))
40
+
41
+ @url = ReactiveValue.new(URL.new)
42
+ @params = @url.params
43
+ @url_tracker = UrlTracker.new(self)
44
+
45
+ @events = DocumentEvents.new
46
+ @render_queue = RenderQueue.new
47
+
48
+ # Add event for link clicks to handle all a onclick
49
+ # EventBinding.new(self, )
50
+
51
+ # Setup escape binding for console
52
+ %x{
53
+ $(document).keyup(function(e) {
54
+ if (e.keyCode == 27) {
55
+ Opal.gvars.page.$launch_console();
56
+ }
57
+ });
58
+
59
+ $(document).on('click', 'a', function(event) {
60
+ Opal.gvars.page.$link_clicked($(this).attr('href'));
61
+ event.stopPropagation();
62
+
63
+ return false;
64
+ });
65
+ }
66
+
67
+ # channel
68
+ # channel.on('message') do |message|
69
+ # # puts "GOT: #{message}"
70
+ # # `console.log('got: ', message);`
71
+ # end
72
+ end
73
+
74
+ def link_clicked(url)
75
+ puts "Link Clicked to: #{url}"
76
+ # Skip when href == ''
77
+ return if url.blank?
78
+
79
+ # Normalize url
80
+ # Benchmark.bm(1) do
81
+ @url.parse("http://localhost:3000" + url)
82
+ # end
83
+ end
84
+
85
+ # We provide a binding_name, so we can bind events on the document
86
+ def binding_name
87
+ 'page'
88
+ end
89
+
90
+ def launch_console
91
+ puts "Launch Console"
92
+ end
93
+
94
+ def channel
95
+ @channel ||= Channel.new
96
+ end
97
+
98
+ def events
99
+ @events
100
+ end
101
+
102
+ def add_model(model_name)
103
+ # puts "ADD MODEL: #{model_name.inspect} - #{model_name.camelize.inspect}"
104
+
105
+ @model_classes[["*", "_#{model_name}"]] = Object.const_get(model_name.camelize)
106
+ end
107
+
108
+ def add_template(name, template, bindings)
109
+ # puts "Add Template: #{name}\n#{template.inspect}\n#{bindings.inspect}"
110
+ @templates ||= {}
111
+ @templates[name] = {'html' => template, 'bindings' => bindings}
112
+ # puts "Add Template: #{name}"
113
+ end
114
+
115
+ def add_routes(&block)
116
+ @routes = Routes.new.define(&block)
117
+ @url.cur.router = @routes
118
+ end
119
+
120
+ def start
121
+ # Setup to render template
122
+ Element.find('body').html = "<!-- $CONTENT --><!-- $/CONTENT -->"
123
+
124
+ main_controller = IndexController.new
125
+
126
+ # Setup main page template
127
+ TemplateRenderer.new(DomTarget.new, main_controller, 'CONTENT', 'home/index/index/body')
128
+
129
+ # Setup title listener template
130
+ title_target = AttributeTarget.new
131
+ title_target.on('changed') do
132
+ title = title_target.to_html
133
+ `document.title = title;`
134
+ end
135
+ TemplateRenderer.new(title_target, main_controller, "main", "home/index/index/title")
136
+
137
+ @url_tracker.url_updated(true)
138
+ end
139
+ end
140
+
141
+ $page = Page.new
142
+
143
+ # Call start once the page is loaded
144
+ Document.ready? do
145
+ $page.start
146
+ end
@@ -0,0 +1,38 @@
1
+ class ReactiveTemplate
2
+ include Events
3
+
4
+ def initialize(context, template_path)
5
+ @template_path = template_path
6
+ @target = AttributeTarget.new
7
+ @template = TemplateRenderer.new(@target, context, "main", template_path)
8
+ end
9
+
10
+ def event_added(event, scope_provider, first)
11
+ if first && !@template_listener
12
+ @template_listener = @target.on('changed') { update }
13
+ end
14
+ end
15
+
16
+ def event_removed(event, last)
17
+ if last && @template_listener
18
+ @template_listener.remove
19
+ @template_listener = nil
20
+ end
21
+ end
22
+
23
+ # Render the template and get the current value
24
+ def cur
25
+ @target.to_html
26
+ end
27
+
28
+ # TODO: improve
29
+ def skip_current_queue_flush
30
+ true
31
+ end
32
+
33
+
34
+ def update
35
+ trigger!('changed')
36
+ end
37
+
38
+ end
@@ -0,0 +1,5 @@
1
+ class RenderQueue
2
+ def initialize
3
+ # @queue
4
+ end
5
+ end
@@ -0,0 +1,23 @@
1
+ # A sub context takes in a hash of local variables that should be available
2
+ # in front of the current context. It basically proxies the local variables
3
+ # first, then failing those proxies the context.
4
+
5
+ class SubContext
6
+ attr_reader :locals
7
+
8
+ def initialize(locals, context=nil)
9
+ @locals = locals.stringify_keys
10
+ @context = context
11
+ end
12
+
13
+ def method_missing(method_name, *args, &block)
14
+ method_name = method_name.to_s
15
+ if @locals[method_name]
16
+ return @locals[method_name]
17
+ elsif @context
18
+ return @context.send(method_name, *args, &block)
19
+ end
20
+
21
+ raise NoMethodError.new("undefined method `#{method_name}' for \"#{self.inspect}\":#{self.class.to_s}")
22
+ end
23
+ end
@@ -0,0 +1,33 @@
1
+ # AttributeSection provides a place to render templates that
2
+ # will be placed as text into an attribute.
3
+
4
+ require 'volt/templates/targets/base_section'
5
+
6
+ class AttributeSection
7
+ def initialize(target, binding_name)
8
+ @target = target
9
+ @binding_name = binding_name
10
+ # puts "init attr section on #{binding_name}"
11
+ end
12
+
13
+ def text=(text)
14
+ set_content_and_rezero_bindings(text, {})
15
+ end
16
+
17
+ # Takes in our html and bindings, and rezero's the comment names, and the
18
+ # bindings. Returns an updated bindings hash
19
+ def set_content_and_rezero_bindings(html, bindings)
20
+ if @binding_name == 'main'
21
+ @target.html = html
22
+ else
23
+ @target.find_by_binding_id(@binding_name).html = html
24
+ end
25
+
26
+ return bindings
27
+ end
28
+
29
+ def remove
30
+ node = @target.find_by_binding_id(@binding_name)
31
+ node.remove
32
+ end
33
+ end
@@ -0,0 +1,18 @@
1
+ require 'volt/templates/targets/base_section'
2
+ require 'volt/templates/targets/attribute_section'
3
+ require 'volt/templates/targets/binding_document/component_node'
4
+ require 'volt/templates/targets/binding_document/html_node'
5
+
6
+ # AttributeTarget's provide an interface that can render bindings into
7
+ # a string that can then be used to update a attribute binding.
8
+
9
+ class AttributeTarget < ComponentNode
10
+ # TODO: improve
11
+ def skip_current_queue_flush
12
+ true
13
+ end
14
+
15
+ def section(*args)
16
+ return AttributeSection.new(self, *args)
17
+ end
18
+ end
@@ -0,0 +1,14 @@
1
+ # Class to describe the interface for sections
2
+ class BaseSection
3
+ def remove
4
+ raise "not implemented"
5
+ end
6
+
7
+ def remove_anchors
8
+ raise "not implemented"
9
+ end
10
+
11
+ def insert_anchor_before_end
12
+ raise "not implemented"
13
+ end
14
+ end
@@ -0,0 +1,112 @@
1
+ require 'volt/templates/targets/binding_document/html_node'
2
+ require 'volt/reactive/events'
3
+
4
+ # Component nodes contain an array of both HtmlNodes and ComponentNodes.
5
+ # Instead of providing a full DOM API, component nodes are the branch
6
+ # nodes and html nodes are the leafs. This is all we need to produce
7
+ # the html from templates outside of a normal dom.
8
+ class ComponentNode < BaseNode
9
+ include Events
10
+
11
+ attr_accessor :parent, :binding_id, :nodes
12
+ def initialize(binding_id=nil, parent=nil)
13
+ @nodes = []
14
+ @binding_id = binding_id
15
+ @parent = parent
16
+
17
+ @change_listener = on('changed') do
18
+ if @parent
19
+ @parent.trigger!('changed')
20
+ end
21
+ end
22
+ end
23
+
24
+ # TODO: improve
25
+ def skip_current_queue_flush
26
+ true
27
+ end
28
+
29
+ def text=(text)
30
+ self.html = text
31
+ end
32
+
33
+ def html=(html)
34
+ parts = html.split(/(\<\!\-\- \$\/?[0-9]+ \-\-\>)/).reject {|v| v == '' }
35
+
36
+ # Clear current nodes
37
+ @nodes = []
38
+
39
+ current_node = self
40
+
41
+ parts.each do |part|
42
+ case part
43
+ when /\<\!\-\- \$[0-9]+ \-\-\>/
44
+ # Open
45
+ binding_id = part.match(/\<\!\-\- \$([0-9]+) \-\-\>/)[1].to_i
46
+
47
+ sub_node = ComponentNode.new(binding_id, current_node)
48
+ current_node << sub_node
49
+ current_node = sub_node
50
+ when /\<\!\-\- \$\/[0-9]+ \-\-\>/
51
+ # Close
52
+ # binding_id = part.match(/\<\!\-\- \$\/([0-9]+) \-\-\>/)[1].to_i
53
+
54
+ current_node = current_node.parent
55
+ else
56
+ # html string
57
+ current_node << HtmlNode.new(part)
58
+ end
59
+ end
60
+
61
+ trigger!('changed')
62
+ end
63
+
64
+ def <<(node)
65
+ @nodes << node
66
+ end
67
+
68
+ def to_html
69
+ str = []
70
+ @nodes.each do |node|
71
+ str << node.to_html
72
+ end
73
+
74
+ return str.join('')
75
+ end
76
+
77
+ def find_by_binding_id(binding_id)
78
+ if @binding_id == binding_id
79
+ return self
80
+ end
81
+
82
+ @nodes.each do |node|
83
+ if node.cur.is_a?(ComponentNode)
84
+ val = node.find_by_binding_id(binding_id)
85
+ return val if val
86
+ end
87
+ end
88
+
89
+ return nil
90
+ end
91
+
92
+ def remove
93
+ @nodes = []
94
+
95
+ trigger!('changed')
96
+ end
97
+
98
+ def remove_anchors
99
+ raise "not implemented"
100
+
101
+ @parent.nodes.delete(self)
102
+
103
+ @change_listener.remove
104
+ @change_listener = nil
105
+
106
+ trigger!('changed')
107
+ end
108
+
109
+ def inspect
110
+ "<ComponentNode:#{@binding_id} #{@nodes.inspect}>"
111
+ end
112
+ end