source: Dev/trunk/src/client/dojox/mvc/sync.js @ 536

Last change on this file since 536 was 483, checked in by hendrikvanantwerpen, 11 years ago

Added Dojo 1.9.3 release.

File size: 10.5 KB
Line 
1define([
2        "dojo/_base/lang",
3        "dojo/_base/config",
4        "dojo/_base/array",
5        "dojo/has"
6], function(lang, config, array, has){
7        var mvc = lang.getObject("dojox.mvc", true);
8        /*=====
9        mvc = {};
10        =====*/
11       
12        /*=====
13        dojox.mvc.sync.converter = {
14                // summary:
15                //              Class/object containing the converter functions used when the data goes between data binding source (e.g. data model or controller) to data binding origin (e.g. widget).
16
17                format: function(value, constraints){
18                        // summary:
19                        //              The converter function used when the data comes from data binding source (e.g. data model or controller) to data binding origin (e.g. widget).
20                        // value: Anything
21                        //              The data.
22                        // constraints: Object
23                        //              The options for data conversion, which is: mixin({}, dataBindingTarget.constraints, dataBindingOrigin.constraints).
24                },
25
26                parse: function(value, constraints){
27                        // summary:
28                        //              The converter function used when the data comes from data binding origin (e.g. widget) to data binding source (e.g. data model or controller).
29                        // value: Anything
30                        //              The data.
31                        // constraints: Object
32                        //              The options for data conversion, which is: mixin({}, dataBindingTarget.constraints, dataBindingOrigin.constraints).
33                }
34        };
35
36        dojox.mvc.sync.options = {
37                // summary:
38                //              Data binding options.
39
40                // bindDirection: Number
41                //              The data binding bindDirection, choose from: dojox.mvc.Bind.from, dojox.mvc.Bind.to or dojox.mvc.Bind.both.
42                bindDirection: dojox/mvc.both,
43
44                // converter: dojox/mvc/sync.converter
45                //              Class/object containing the converter functions used when the data goes between data binding source (e.g. data model or controller) to data binding origin (e.g. widget).
46                converter: null,
47
48                // equals: Function
49                //              The function to check if there really was a change when source/target dojo/Stateful indicates so.
50                //              Should take two arguments, and should return true when those two are considered equal.
51                equals: null
52        };
53        =====*/
54
55        has.add("mvc-bindings-log-api", (config["mvc"] || {}).debugBindings);
56
57        var sync;
58
59        if(has("mvc-bindings-log-api")){
60                function getLogContent(/*dojo/Stateful*/ source, /*String*/ sourceProp, /*dojo/Stateful*/ target, /*String*/ targetProp){
61                        return [
62                                [target.canConvertToLoggable || !target.declaredClass ? target : target.declaredClass, targetProp].join(":"),
63                                [source.canConvertToLoggable || !source.declaredClass ? source : source.declaredClass, sourceProp].join(":")
64                        ];
65                }
66        }
67
68        function equals(/*Anything*/ dst, /*Anything*/ src){
69                // summary:
70                //              Returns if the given two values are equal.
71
72                return dst === src
73                 || typeof dst == "number" && isNaN(dst) && typeof src == "number" && isNaN(src)
74                 || lang.isFunction((dst || {}).getTime) && lang.isFunction((src || {}).getTime) && dst.getTime() == src.getTime()
75                 || (lang.isFunction((dst || {}).equals) ? dst.equals(src) : lang.isFunction((src || {}).equals) ? src.equals(dst) : false);
76        }
77
78        function copy(/*Function*/ convertFunc, /*Object?*/ constraints, /*Function*/ equals, /*dojo/Stateful*/ source, /*String*/ sourceProp, /*dojo/Stateful*/ target, /*String*/ targetProp, /*Anything*/ old, /*Anything*/ current, /*Object?*/ excludes){
79                // summary:
80                //              Watch for change in property in dojo/Stateful object.
81                // description:
82                //              Called when targetProp property in target is changed. (This is mainly used as a callback function of dojo/Stateful.watch())
83                //              When older value and newer value are different, copies the newer value to sourceProp property in source.
84                // convertFunc: Function
85                //              The data converter function.
86                // constraints: Object?
87                //              The data converter options.
88                // equals: Function
89                //              The function to check if there really was a change when source/target dojo/Stateful indicates so.
90                //              Should take two arguments, and should return true when those two are considered equal.
91                // source: dojo/Stateful
92                //              The dojo/Stateful of copy source.
93                // sourceProp: String
94                //              The property of copy source, specified in data binding. May be wildcarded.
95                // target: dojo/Stateful
96                //              The dojo/Stateful of copy target.
97                // targetProp: String
98                //              The property of copy target, being changed. For wildcard-based data binding, this is used as the property to be copied.
99                // old: Anything
100                //              The older property value.
101                // current: Anything
102                //              The newer property value.
103                // excludes: Object?
104                //              The list of properties that should be excluded from wildcarded data binding.
105
106                // Bail if there is no change in value,
107                // or property name is wildcarded and the property to be copied is not in source property list (and source property list is defined),
108                // or property name is wildcarded and the property to be copied is in explicit "excludes" list
109                if(equals(current, old)
110                 || sourceProp == "*" && array.indexOf(source.get("properties") || [targetProp], targetProp) < 0
111                 || sourceProp == "*" && targetProp in (excludes || {})){ return; }
112
113                var prop = sourceProp == "*" ? targetProp : sourceProp;
114                if(has("mvc-bindings-log-api")){
115                        var logContent = getLogContent(source, prop, target, targetProp);
116                }
117
118                try{
119                        current = convertFunc ? convertFunc(current, constraints) : current;
120                }catch(e){
121                        if(has("mvc-bindings-log-api")){
122                                console.log("Copy from" + logContent.join(" to ") + " was not done as an error is thrown in the converter.");
123                        }
124                        return;
125                }
126
127                if(has("mvc-bindings-log-api")){
128                        console.log(logContent.reverse().join(" is being copied from: ") + " (Value: " + current + " from " + old + ")");
129                }
130
131                // Copy the new value to source
132                lang.isFunction(source.set) ? source.set(prop, current) : (source[prop] = current);
133        }
134
135        var directions = {
136                // from: Number
137                //              Data binding goes from the source to the target
138                from: 1,
139
140                // to: Number
141                //              Data binding goes from the target to the source
142                to: 2,
143
144                // both: Number
145                //              Data binding goes in both directions (dojox/mvc/Bind.from | dojox/mvc/Bind.to)
146                both: 3
147        }, undef;
148
149        sync = function(/*dojo/Stateful*/ source, /*String*/ sourceProp, /*dojo/Stateful*/ target, /*String*/ targetProp, /*dojox/mvc/sync.options*/ options){
150                // summary:
151                //              Synchronize two dojo/Stateful properties.
152                // description:
153                //              Synchronize two dojo/Stateful properties.
154                // source: dojo/Stateful
155                //              Source dojo/Stateful to be synchronized.
156                // sourceProp: String
157                //              The property name in source to be synchronized.
158                // target: dojo/Stateful
159                //              Target dojo/Stateful to be synchronized.
160                // targetProp: String
161                //              The property name in target to be synchronized.
162                // options: dojox/mvc/sync.options
163                //              Data binding options.
164                // returns:
165                //              The handle of data binding synchronization.
166
167                var converter = (options || {}).converter, converterInstance, formatFunc, parseFunc;
168                if(converter){
169                        converterInstance = {source: source, target: target};
170                        formatFunc = converter.format && lang.hitch(converterInstance, converter.format);
171                        parseFunc = converter.parse && lang.hitch(converterInstance, converter.parse);
172                }
173
174                var _watchHandles = [],
175                 excludes = [],
176                 list,
177                 constraints = lang.mixin({}, source.constraints, target.constraints),
178                 bindDirection = (options || {}).bindDirection || mvc.both,
179                 equals = (options || {}).equals || sync.equals;
180
181                if(has("mvc-bindings-log-api")){
182                        var logContent = getLogContent(source, sourceProp, target, targetProp);
183                }
184
185                if(targetProp == "*"){
186                        if(sourceProp != "*"){ throw new Error("Unmatched wildcard is specified between source and target."); }
187                        list = target.get("properties");
188                        if(!list){
189                                list = [];
190                                for(var s in target){ if(target.hasOwnProperty(s) && s != "_watchCallbacks"){ list.push(s); } }
191                        }
192                        excludes = target.get("excludes");
193                }else{
194                        list = [sourceProp];
195                }
196
197                if(bindDirection & mvc.from){
198                        // Start synchronization from source to target (e.g. from model to widget). For wildcard mode (sourceProp == targetProp == "*"), the 1st argument of watch() is omitted
199                        if(lang.isFunction(source.set) && lang.isFunction(source.watch)){
200                                _watchHandles.push(source.watch.apply(source, ((sourceProp != "*") ? [sourceProp] : []).concat([function(name, old, current){
201                                        copy(formatFunc, constraints, equals, target, targetProp, source, name, old, current, excludes);
202                                }])));
203                        }else if(has("mvc-bindings-log-api")){
204                                console.log(logContent.reverse().join(" is not a stateful property. Its change is not reflected to ") + ".");
205                        }
206
207                        // Initial copy from source to target (e.g. from model to widget)
208                        array.forEach(list, function(prop){
209                                // In "all properties synchronization" case, copy is not done for properties in "exclude" list
210                                if(targetProp != "*" || !(prop in (excludes || {}))){
211                                        var value = lang.isFunction(source.get) ? source.get(prop) : source[prop];
212                                        copy(formatFunc, constraints, equals, target, targetProp == "*" ? prop : targetProp, source, prop, undef, value);
213                                }
214                        });
215                }
216
217                if(bindDirection & mvc.to){
218                        if(!(bindDirection & mvc.from)){
219                                // Initial copy from source to target (e.g. from model to widget)
220                                array.forEach(list, function(prop){
221                                        // In "all properties synchronization" case, copy is not done for properties in "exclude" list
222                                        if(targetProp != "*" || !(prop in (excludes || {}))){
223                                                // Initial copy from target to source (e.g. from widget to model), only done for one-way binding from widget to model
224                                                var value = lang.isFunction(target.get) ? target.get(targetProp) : target[targetProp];
225                                                copy(parseFunc, constraints, equals, source, prop, target, targetProp == "*" ? prop : targetProp, undef, value);
226                                        }
227                                });
228                        }
229
230                        // Start synchronization from target to source (e.g. from widget to model). For wildcard mode (sourceProp == targetProp == "*"), the 1st argument of watch() is omitted
231                        if(lang.isFunction(target.set) && lang.isFunction(target.watch)){
232                                _watchHandles.push(target.watch.apply(target, ((targetProp != "*") ? [targetProp] : []).concat([function(name, old, current){
233                                        copy(parseFunc, constraints, equals, source, sourceProp, target, name, old, current, excludes);
234                                }])));
235                        }else if(has("mvc-bindings-log-api")){
236                                console.log(logContent.join(" is not a stateful property. Its change is not reflected to ") + ".");
237                        }
238                }
239
240                if(has("mvc-bindings-log-api")){
241                        console.log(logContent.join(" is bound to: "));
242                }
243
244                var handle = {};
245                handle.unwatch = handle.remove = function(){
246                        for(var h = null; h = _watchHandles.pop();){
247                                h.unwatch();
248                        }
249                        if(has("mvc-bindings-log-api")){
250                                console.log(logContent.join(" is unbound from: "));
251                        }
252                };
253                return handle; // dojo/handle
254        };
255
256        lang.mixin(mvc, directions);
257
258        // lang.setObject() thing is for back-compat, remove it in 2.0
259        return lang.setObject("dojox.mvc.sync", lang.mixin(sync, {equals: equals}, directions));
260});
Note: See TracBrowser for help on using the repository browser.