source: Dev/trunk/src/client/dojox/mvc/_atBindingMixin.js @ 532

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

Added Dojo 1.9.3 release.

File size: 9.4 KB
Line 
1define([
2        "dojo/_base/array",
3        "dojo/_base/lang",
4        "dojo/_base/declare",
5        "dojo/has",
6        "dojo/Stateful",
7        "./resolve",
8        "./sync"
9], function(array, lang, declare, has, Stateful, resolve, sync){
10        if(has("mvc-bindings-log-api")){
11                function getLogContent(/*dojo/Stateful*/ target, /*String*/ targetProp){
12                        return [target._setIdAttr || !target.declaredClass ? target : target.declaredClass, targetProp].join(":");
13                }
14
15                function logResolveFailure(target, targetProp){
16                        console.warn(targetProp + " could not be resolved" + (typeof target == "string" ? (" with " + target) : "") + ".");
17                }
18        }
19
20        function getParent(/*dijit/_WidgetBase*/ w){
21                // summary:
22                //              Returns parent widget having data binding target for relative data binding.
23                // w: dijit/_WidgetBase
24                //              The widget.
25
26                // Usage of dijit/registry module is optional. Return null if it's not already loaded.
27                var registry;
28                try{
29                        registry = require("dijit/registry");
30                }catch(e){
31                        return;
32                }
33                var pn = w.domNode && w.domNode.parentNode, pw, pb;
34                while(pn){
35                        pw = registry.getEnclosingWidget(pn);
36                        if(pw){
37                                var relTargetProp = pw._relTargetProp || "target", pt = lang.isFunction(pw.get) ? pw.get(relTargetProp) : pw[relTargetProp];
38                                if(pt || relTargetProp in pw.constructor.prototype){
39                                        return pw; // dijit/_WidgetBase
40                                }
41                        }
42                        pn = pw && pw.domNode.parentNode;
43                }
44        }
45
46        function bind(/*dojo/Stateful|String*/ source, /*String*/ sourceProp, /*dijit/_WidgetBase*/ target, /*String*/ targetProp, /*dojox/mvc/sync.options*/ options){
47                // summary:
48                //              Resolves the data binding literal, and starts data binding.
49                // source: dojo/Stateful|String
50                //              Source data binding literal or dojo/Stateful to be synchronized.
51                // sourceProp: String
52                //              The property name in source to be synchronized.
53                // target: dijit/_WidgetBase
54                //              Target dojo/Stateful to be synchronized.
55                // targetProp: String
56                //              The property name in target to be synchronized.
57                // options: dojox/mvc/sync.options
58                //              Data binding options.
59
60                var _handles = {}, parent = getParent(target), relTargetProp = parent && parent._relTargetProp || "target";
61
62                function resolveAndBind(){
63                        _handles["Two"] && _handles["Two"].unwatch();
64                        delete _handles["Two"];
65
66                        var relTarget = parent && (lang.isFunction(parent.get) ? parent.get(relTargetProp) : parent[relTargetProp]),
67                         resolvedSource = resolve(source, relTarget),
68                         resolvedTarget = resolve(target, relTarget);
69
70                        if(has("mvc-bindings-log-api") && (!resolvedSource || /^rel:/.test(source) && !parent)){ logResolveFailure(source, sourceProp); }
71                        if(has("mvc-bindings-log-api") && (!resolvedTarget || /^rel:/.test(target) && !parent)){ logResolveFailure(target, targetProp); }
72                        if(!resolvedSource || !resolvedTarget || (/^rel:/.test(source) || /^rel:/.test(target)) && !parent){ return; }
73                        if((!resolvedSource.set || !resolvedSource.watch) && sourceProp == "*"){
74                                if(has("mvc-bindings-log-api")){ logResolveFailure(source, sourceProp); }
75                                return;
76                        }
77
78                        if(sourceProp == null){
79                                // If source property is not specified, it means this handle is just for resolving data binding target.
80                                // (For dojox/mvc/Group and dojox/mvc/Repeat)
81                                // Do not perform data binding synchronization in such case.
82                                lang.isFunction(resolvedTarget.set) ? resolvedTarget.set(targetProp, resolvedSource) : (resolvedTarget[targetProp] = resolvedSource);
83                                if(has("mvc-bindings-log-api")){
84                                        console.log("dojox/mvc/_atBindingMixin set " + resolvedSource + " to: " + getLogContent(resolvedTarget, targetProp));
85                                }
86                        }else{
87                                // Start data binding
88                                _handles["Two"] = sync(resolvedSource, sourceProp, resolvedTarget, targetProp, options); // dojox/mvc/sync.handle
89                        }
90                }
91
92                resolveAndBind();
93                if(parent && /^rel:/.test(source) || /^rel:/.test(target) && lang.isFunction(parent.set) && lang.isFunction(parent.watch)){
94                        _handles["rel"] = parent.watch(relTargetProp, function(name, old, current){
95                                if(old !== current){
96                                        if(has("mvc-bindings-log-api")){ console.log("Change in relative data binding target: " + parent); }
97                                        resolveAndBind();
98                                }
99                        });
100                }
101                var h = {};
102                h.unwatch = h.remove = function(){
103                        for(var s in _handles){
104                                _handles[s] && _handles[s].unwatch();
105                                delete _handles[s];
106                        }
107                };
108                return h;
109        }
110
111        var mixin = {
112                // summary:
113                //              The mixin for dijit/_WidgetBase to support data binding.
114
115                // dataBindAttr: String
116                //              The attribute name for data binding.
117                dataBindAttr: "data-mvc-bindings",
118
119                _dbpostscript: function(/*Object?*/ params, /*DomNode|String*/ srcNodeRef){
120                        // summary:
121                        //              See if any parameters for this widget are dojox/mvc/at handles.
122                        //              If so, move them under this._refs to prevent widget implementations from referring them.
123
124                        var refs = this._refs = (params || {}).refs || {};
125                        for(var prop in params){
126                                if((params[prop] || {}).atsignature == "dojox.mvc.at"){
127                                        var h = params[prop];
128                                        delete params[prop];
129                                        refs[prop] = h;
130                                }
131                        }
132
133                        var dbParams = new Stateful(),
134                         _self = this;
135                        dbParams.toString = function(){ return '[Mixin value of widget ' + _self.declaredClass + ', ' + (_self.id || 'NO ID') + ']'; };
136                        dbParams.canConvertToLoggable = true;
137                        this._startAtWatchHandles(dbParams);
138                        for(var prop in refs){
139                                if(dbParams[prop] !== void 0){
140                                        (params = params || {})[prop] = dbParams[prop];
141                                }
142                        }
143                        this._stopAtWatchHandles();
144                },
145
146                _startAtWatchHandles: function(/*dojo/Stateful*/ bindWith){
147                        // summary:
148                        //              Establish data bindings based on dojox/mvc/at handles.
149                        // bindWith: dojo/Stateful
150                        //              The dojo/Stateful to bind properties with.
151
152                        this.canConvertToLoggable = true;
153
154                        var refs = this._refs;
155                        if(refs){
156                                var atWatchHandles = this._atWatchHandles = this._atWatchHandles || {};
157
158                                // Clear the cache of properties that data binding is established with
159                                this._excludes = null;
160
161                                // First, establish non-wildcard data bindings
162                                for(var prop in refs){
163                                        if(!refs[prop] || prop == "*"){ continue; }
164                                        atWatchHandles[prop] = bind(refs[prop].target, refs[prop].targetProp, bindWith || this, prop, {bindDirection: refs[prop].bindDirection, converter: refs[prop].converter, equals: refs[prop].equalsCallback});
165                                }
166
167                                // Then establish wildcard data bindings
168                                if((refs["*"] || {}).atsignature == "dojox.mvc.at"){
169                                        atWatchHandles["*"] = bind(refs["*"].target, refs["*"].targetProp, bindWith || this, "*", {bindDirection: refs["*"].bindDirection, converter: refs["*"].converter, equals: refs["*"].equalsCallback});
170                                }
171                        }
172                },
173
174                _stopAtWatchHandles: function(){
175                        // summary:
176                        //              Stops data binding synchronization handles as widget is destroyed.
177
178                        for(var s in this._atWatchHandles){
179                                this._atWatchHandles[s].unwatch();
180                                delete this._atWatchHandles[s];
181                        }
182                },
183
184                _setAtWatchHandle: function(/*String*/ name, /*Anything*/ value){
185                        // summary:
186                        //              Called if the value is a dojox/mvc/at handle.
187                        //              If this widget has started, start data binding with the new dojox/mvc/at handle.
188                        //              Otherwise, queue it up to this._refs so that _dbstartup() can pick it up.
189
190                        if(name == "ref"){
191                                throw new Error(this + ": 1.7 ref syntax used in conjuction with 1.8 dojox/mvc/at syntax, which is not supported.");
192                        }
193
194                        // Claen up older data binding
195                        var atWatchHandles = this._atWatchHandles = this._atWatchHandles || {};
196                        if(atWatchHandles[name]){
197                                atWatchHandles[name].unwatch();
198                                delete atWatchHandles[name];
199                        }
200
201                        // Claar the value
202                        this[name] = null;
203
204                        // Clear the cache of properties that data binding is established with
205                        this._excludes = null;
206
207                        if(this._started){
208                                // If this widget has been started already, establish data binding immediately.
209                                atWatchHandles[name] = bind(value.target, value.targetProp, this, name, {bindDirection: value.bindDirection, converter: value.converter, equals: value.equalsCallback});
210                        }else{
211                                // Otherwise, queue it up to this._refs so that _dbstartup() can pick it up.
212                                this._refs[name] = value;
213                        }
214                },
215
216                _setBind: function(/*Object*/ value){
217                        // summary:
218                        //              Sets data binding described in data-mvc-bindings.
219
220                        var list = eval("({" + value + "})");
221                        for(var prop in list){
222                                var h = list[prop];
223                                if((h || {}).atsignature != "dojox.mvc.at"){
224                                        console.warn(prop + " in " + dataBindAttr + " is not a data binding handle.");
225                                }else{
226                                        this._setAtWatchHandle(prop, h);
227                                }
228                        }
229                },
230
231                _getExcludesAttr: function(){
232                        // summary:
233                        //              Returns list of all properties that data binding is established with.
234
235                        if(this._excludes){
236                                return this._excludes;  // String[]
237                        }
238                        var list = [];
239                        for(var s in this._atWatchHandles){
240                                if(s != "*"){ list.push(s); }
241                        }
242                        return list; // String[]
243                },
244
245                _getPropertiesAttr: function(){
246                        // summary:
247                        //              Returns list of all properties in this widget, except "id".
248                        // returns: String[]
249                        //               The list of all properties in this widget, except "id"..
250
251                        if(this.constructor._attribs){
252                                return this.constructor._attribs; // String[]
253                        }
254                        var list = ["onClick"].concat(this.constructor._setterAttrs);
255                        array.forEach(["id", "excludes", "properties", "ref", "binding"], function(s){
256                                var index = array.indexOf(list, s);
257                                if(index >= 0){ list.splice(index, 1); }
258                        });
259                        return this.constructor._attribs = list; // String[]
260                }
261        };
262
263        mixin[mixin.dataBindAttr] = ""; // Let parser treat the attribute as string
264
265        var _atBindingMixin = declare("dojox/mvc/_atBindingMixin", null, mixin);
266        _atBindingMixin.mixin = mixin; // Keep the plain object version
267        return _atBindingMixin;
268});
Note: See TracBrowser for help on using the repository browser.