[483] | 1 | define([ |
---|
| 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 | }); |
---|