7cf13491 by Michael Richards

Merge pull request #240 from mikeric/full-keypath-observers

Full Keypath Observers
2 parents d82996d2 f9acdd9b
...@@ -20,7 +20,7 @@ module.exports = (grunt) -> ...@@ -20,7 +20,7 @@ module.exports = (grunt) ->
20 'src/view.coffee' 20 'src/view.coffee'
21 'src/bindings.coffee' 21 'src/bindings.coffee'
22 'src/parsers.coffee' 22 'src/parsers.coffee'
23 'src/keypath_observer.coffee' 23 'src/observer.coffee'
24 'src/binders.coffee' 24 'src/binders.coffee'
25 'src/adapters.coffee' 25 'src/adapters.coffee'
26 'src/export.coffee' 26 'src/export.coffee'
......
...@@ -8,7 +8,7 @@ describe('Rivets.Binding', function() { ...@@ -8,7 +8,7 @@ describe('Rivets.Binding', function() {
8 el = document.createElement('div'); 8 el = document.createElement('div');
9 el.setAttribute('data-text', 'obj.name'); 9 el.setAttribute('data-text', 'obj.name');
10 10
11 view = rivets.bind(el, {obj: {}}); 11 view = rivets.bind(el, {obj: {name: 'test'}});
12 binding = view.bindings[0]; 12 binding = view.bindings[0];
13 model = binding.model; 13 model = binding.model;
14 }); 14 });
...@@ -40,12 +40,10 @@ describe('Rivets.Binding', function() { ...@@ -40,12 +40,10 @@ describe('Rivets.Binding', function() {
40 rivets.config.preloadData = true; 40 rivets.config.preloadData = true;
41 }); 41 });
42 42
43 it('sets the initial value via the adapter', function() { 43 it('sets the initial value', function() {
44 spyOn(binding, 'set'); 44 spyOn(binding, 'set');
45 spyOn(adapter, 'read');
46 binding.bind(); 45 binding.bind();
47 expect(adapter.read).toHaveBeenCalledWith(model, 'name'); 46 expect(binding.set).toHaveBeenCalledWith('test');
48 expect(binding.set).toHaveBeenCalled();
49 }); 47 });
50 }); 48 });
51 49
......
...@@ -9,8 +9,8 @@ class Rivets.Binding ...@@ -9,8 +9,8 @@ class Rivets.Binding
9 constructor: (@view, @el, @type, @keypath, @options = {}) -> 9 constructor: (@view, @el, @type, @keypath, @options = {}) ->
10 @formatters = @options.formatters || [] 10 @formatters = @options.formatters || []
11 @dependencies = [] 11 @dependencies = []
12 @model = undefined
12 @setBinder() 13 @setBinder()
13 @setObserver()
14 14
15 # Sets the binder to use when binding and syncing. 15 # Sets the binder to use when binding and syncing.
16 setBinder: => 16 setBinder: =>
...@@ -26,18 +26,6 @@ class Rivets.Binding ...@@ -26,18 +26,6 @@ class Rivets.Binding
26 @binder or= @view.binders['*'] 26 @binder or= @view.binders['*']
27 @binder = {routine: @binder} if @binder instanceof Function 27 @binder = {routine: @binder} if @binder instanceof Function
28 28
29 # Sets a keypath observer that will notify this binding when any intermediary
30 # keys are changed.
31 setObserver: =>
32 @observer = new Rivets.KeypathObserver @view, @view.models, @keypath, (obs) =>
33 @unbind true if @key
34 @model = obs.target
35 @bind true if @key
36 @sync()
37
38 @key = @observer.key
39 @model = @observer.target
40
41 # Applies all the current formatters to the supplied value and returns the 29 # Applies all the current formatters to the supplied value and returns the
42 # formatted value. 30 # formatted value.
43 formattedValue: (value) => 31 formattedValue: (value) =>
...@@ -70,10 +58,16 @@ class Rivets.Binding ...@@ -70,10 +58,16 @@ class Rivets.Binding
70 58
71 # Syncs up the view binding with the model. 59 # Syncs up the view binding with the model.
72 sync: => 60 sync: =>
73 @set if @key 61 if @model isnt @observer.target
74 @view.adapters[@key.interface].read @model, @key.path 62 observer.unobserve() for observer in @dependencies
75 else 63 @dependencies = []
76 @model 64
65 if (@model = @observer.target)? and @options.dependencies?.length
66 for dependency in @options.dependencies
67 observer = new Rivets.Observer @view, @model, dependency, @sync
68 @dependencies.push observer
69
70 @set @observer.value()
77 71
78 # Publishes the value currently set on the input element back to the model. 72 # Publishes the value currently set on the input element back to the model.
79 publish: => 73 publish: =>
...@@ -86,42 +80,30 @@ class Rivets.Binding ...@@ -86,42 +80,30 @@ class Rivets.Binding
86 if @view.formatters[id]?.publish 80 if @view.formatters[id]?.publish
87 value = @view.formatters[id].publish value, args... 81 value = @view.formatters[id].publish value, args...
88 82
89 @view.adapters[@key.interface].publish @model, @key.path, value 83 @observer.publish value
90 84
91 # Subscribes to the model for changes at the specified keypath. Bi-directional 85 # Subscribes to the model for changes at the specified keypath. Bi-directional
92 # routines will also listen for changes on the element to propagate them back 86 # routines will also listen for changes on the element to propagate them back
93 # to the model. 87 # to the model.
94 bind: (silent = false) => 88 bind: =>
95 @binder.bind?.call @, @el unless silent 89 @binder.bind?.call @, @el
96 @view.adapters[@key.interface].subscribe(@model, @key.path, @sync) if @key 90 @observer = new Rivets.Observer @view, @view.models, @keypath, @sync
97 @sync() if @view.config.preloadData unless silent 91 @model = @observer.target
98 92
99 if @options.dependencies?.length 93 if @model? and @options.dependencies?.length
100 for dependency in @options.dependencies 94 for dependency in @options.dependencies
101 observer = new Rivets.KeypathObserver @view, @model, dependency, (obs, prev) => 95 observer = new Rivets.Observer @view, @model, dependency, @sync
102 key = obs.key
103 @view.adapters[key.interface].unsubscribe prev, key.path, @sync
104 @view.adapters[key.interface].subscribe obs.target, key.path, @sync
105 @sync()
106
107 key = observer.key
108 @view.adapters[key.interface].subscribe observer.target, key.path, @sync
109 @dependencies.push observer 96 @dependencies.push observer
110 97
111 # Unsubscribes from the model and the element. 98 @sync() if @view.config.preloadData
112 unbind: (silent = false) =>
113 unless silent
114 @binder.unbind?.call @, @el
115 @observer.unobserve()
116
117 @view.adapters[@key.interface].unsubscribe(@model, @key.path, @sync) if @key
118 99
119 if @dependencies.length 100 # Unsubscribes from the model and the element.
120 for obs in @dependencies 101 unbind: =>
121 key = obs.key 102 @binder.unbind?.call @, @el
122 @view.adapters[key.interface].unsubscribe obs.target, key.path, @sync 103 @observer.unobserve()
123 104
124 @dependencies = [] 105 observer.unobserve() for observer in @dependencies
106 @dependencies = []
125 107
126 # Updates the binding's model from what is currently set on the view. Unbinds 108 # Updates the binding's model from what is currently set on the view. Unbinds
127 # the old model first and then re-binds with the new model. 109 # the old model first and then re-binds with the new model.
...@@ -190,7 +172,6 @@ class Rivets.TextBinding extends Rivets.Binding ...@@ -190,7 +172,6 @@ class Rivets.TextBinding extends Rivets.Binding
190 constructor: (@view, @el, @type, @keypath, @options = {}) -> 172 constructor: (@view, @el, @type, @keypath, @options = {}) ->
191 @formatters = @options.formatters || [] 173 @formatters = @options.formatters || []
192 @dependencies = [] 174 @dependencies = []
193 @setObserver()
194 175
195 # A standard routine binder used for text node bindings. 176 # A standard routine binder used for text node bindings.
196 binder: 177 binder:
......
1 # Rivets.KeypathObserver 1 # Rivets.Observer
2 # ---------------------- 2 # ----------------------
3 3
4 # Parses and observes a full keypath with the appropriate adapters. Also 4 # Parses and observes a full keypath with the appropriate adapters. Also
5 # intelligently re-realizes the keypath when intermediary keys change. 5 # intelligently re-realizes the keypath when intermediary keys change.
6 class Rivets.KeypathObserver 6 class Rivets.Observer
7 # Performs the initial parse, variable instantiation and keypath realization. 7 # Performs the initial parse, variable instantiation and keypath realization.
8 constructor: (@view, @model, @keypath, @callback) -> 8 constructor: (@view, @model, @keypath, @callback) ->
9 @parse() 9 @parse()
10 @objectPath = [] 10 @initialize()
11 @target = @realize()
12 11
13 # Parses the keypath using the interfaces defined on the view. Sets variables 12 # Parses the keypath using the interfaces defined on the view. Sets variables
14 # for the tokenized keypath, as well as the end key. 13 # for the tokenized keypath, as well as the end key.
...@@ -25,34 +24,67 @@ class Rivets.KeypathObserver ...@@ -25,34 +24,67 @@ class Rivets.KeypathObserver
25 @tokens = Rivets.KeypathParser.parse path, interfaces, root 24 @tokens = Rivets.KeypathParser.parse path, interfaces, root
26 @key = @tokens.pop() 25 @key = @tokens.pop()
27 26
28 # Updates the keypath. This is called when any intermediate key is changed. 27 initialize: =>
28 @objectPath = []
29 @target = @realize()
30 @set true, @key, @target, @callback if @target?
31
32 # Updates the keypath. This is called when any intermediary key is changed.
29 update: => 33 update: =>
30 unless (next = @realize()) is @target 34 unless (next = @realize()) is @target
31 prev = @target 35 @set false, @key, @target, @callback if @target?
36 @set true, @key, next, @callback if next?
37
38 oldValue = @value()
32 @target = next 39 @target = next
33 @callback @, prev 40 @callback() unless @value() is oldValue
41
42 adapter: (key) =>
43 @view.adapters[key.interface]
44
45 set: (active, key, obj, callback) =>
46 action = if active then 'subscribe' else 'unsubscribe'
47 @adapter(key)[action] obj, key.path, callback
48
49 read: (key, obj) =>
50 @adapter(key).read obj, key.path
51
52 publish: (value) =>
53 if @target?
54 @adapter(@key).publish @target, @key.path, value
55
56 value: =>
57 @read @key, @target if @target?
34 58
35 # Realizes the full keypath, attaching observers for every key and correcting 59 # Realizes the full keypath, attaching observers for every key and correcting
36 # old observers to any changed objects in the keypath. 60 # old observers to any changed objects in the keypath.
37 realize: => 61 realize: =>
38 current = @model 62 current = @model
63 unreached = null
39 64
40 for token, index in @tokens 65 for token, index in @tokens
41 if @objectPath[index]? 66 if current?
42 if current isnt prev = @objectPath[index] 67 if @objectPath[index]?
43 @view.adapters[token.interface].unsubscribe prev, token.path, @update 68 if current isnt prev = @objectPath[index]
44 @view.adapters[token.interface].subscribe current, token.path, @update 69 @set false, token, prev, @update
70 @set true, token, current, @update
71 @objectPath[index] = current
72 else
73 @set true, token, current, @update
45 @objectPath[index] = current 74 @objectPath[index] = current
75
76 current = @read token, current
46 else 77 else
47 @view.adapters[token.interface].subscribe current, token.path, @update 78 unreached ?= index
48 @objectPath[index] = current
49 79
50 current = @view.adapters[token.interface].read current, token.path 80 if prev = @objectPath[index]
81 @set false, token, prev, @update
51 82
83 @objectPath.splice unreached if unreached?
52 current 84 current
53 85
54 # Unobserves any current observers set up on the keys. 86 # Unobserves any current observers set up on the keys.
55 unobserve: => 87 unobserve: =>
56 for token, index in @tokens 88 for token, index in @tokens
57 if obj = @objectPath[index] 89 if obj = @objectPath[index]
58 @view.adapters[token.interface].unsubscribe obj, token.path, @update 90 @set false, token, obj, @update
......