PageRenderTime 38ms CodeModel.GetById 9ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/src/mx/binding/Binding.as

https://github.com/artman/Flow
ActionScript | 520 lines | 222 code | 62 blank | 236 comment | 46 complexity | a45871b0c25f3e16d0d4932ea13cdef9 MD5 | raw file
  1. ////////////////////////////////////////////////////////////////////////////////
  2. //
  3. // ADOBE SYSTEMS INCORPORATED
  4. // Copyright 2003-2007 Adobe Systems Incorporated
  5. // All Rights Reserved.
  6. //
  7. // NOTICE: Adobe permits you to use, modify, and distribute this file
  8. // in accordance with the terms of the license agreement accompanying it.
  9. //
  10. ////////////////////////////////////////////////////////////////////////////////
  11. package mx.binding
  12. {
  13. import mx.core.mx_internal;
  14. import flash.utils.Dictionary;
  15. use namespace mx_internal;
  16. [ExcludeClass]
  17. /**
  18. * @private
  19. */
  20. public class Binding
  21. {
  22. // Certain errors are normal during binding execution, so we swallow them.
  23. // 1507 - invalid null argument
  24. // 2005 - argument error (null gets converted to 0)
  25. mx_internal static var allowedErrors:Object = generateAllowedErrors();
  26. mx_internal static function generateAllowedErrors():Object
  27. {
  28. var o:Object = {};
  29. o[1507] = 1;
  30. o[2005] = 1;
  31. return o;
  32. }
  33. //--------------------------------------------------------------------------
  34. //
  35. // Constructor
  36. //
  37. //--------------------------------------------------------------------------
  38. /**
  39. * Create a Binding object
  40. *
  41. * @param document The document that is the target of all of this work.
  42. *
  43. * @param srcFunc The function that returns us the value
  44. * to use in this Binding.
  45. *
  46. * @param destFunc The function that will take a value
  47. * and assign it to the destination.
  48. *
  49. * @param destString The destination represented as a String.
  50. * We can then tell the ValidationManager to validate this field.
  51. *
  52. * @langversion 3.0
  53. * @playerversion Flash 9
  54. * @playerversion AIR 1.1
  55. * @productversion Flex 3
  56. */
  57. public function Binding(document:Object, srcFunc:Function,
  58. destFunc:Function, destString:String,
  59. srcString:String = null)
  60. {
  61. super();
  62. this.document = document;
  63. this.srcFunc = srcFunc;
  64. this.destFunc = destFunc;
  65. this.destString = destString;
  66. this.srcString = srcString;
  67. if (this.srcFunc == null)
  68. {
  69. this.srcFunc = defaultSrcFunc;
  70. }
  71. if (this.destFunc == null)
  72. {
  73. this.destFunc = defaultDestFunc;
  74. }
  75. _isEnabled = true;
  76. isExecuting = false;
  77. isHandlingEvent = false;
  78. hasHadValue = false;
  79. uiComponentWatcher = -1;
  80. BindingManager.addBinding(document, destString, this);
  81. }
  82. //--------------------------------------------------------------------------
  83. //
  84. // Variables
  85. //
  86. //--------------------------------------------------------------------------
  87. /**
  88. * @private
  89. * Internal storage for isEnabled property.
  90. */
  91. mx_internal var _isEnabled:Boolean;
  92. /**
  93. * @private
  94. * Indicates that a Binding is enabled.
  95. * Used to disable bindings.
  96. */
  97. mx_internal function get isEnabled():Boolean
  98. {
  99. return _isEnabled;
  100. }
  101. /**
  102. * @private
  103. */
  104. mx_internal function set isEnabled(value:Boolean):void
  105. {
  106. _isEnabled = value;
  107. if (value)
  108. {
  109. processDisabledRequests();
  110. }
  111. }
  112. /**
  113. * @private
  114. * Indicates that a Binding is executing.
  115. * Used to prevent circular bindings from causing infinite loops.
  116. */
  117. mx_internal var isExecuting:Boolean;
  118. /**
  119. * @private
  120. * Indicates that the binding is currently handling an event.
  121. * Used to prevent us from infinitely causing an event
  122. * that re-executes the the binding.
  123. */
  124. mx_internal var isHandlingEvent:Boolean;
  125. /**
  126. * @private
  127. * Queue of watchers that fired while we were disabled.
  128. * We will resynch with our binding if isEnabled is set to true
  129. * and one or more of our watchers fired while we were disabled.
  130. */
  131. mx_internal var disabledRequests:Dictionary;
  132. /**
  133. * @private
  134. * True as soon as a non-null or non-empty-string value has been used.
  135. * We don't auto-validate until this is true
  136. */
  137. private var hasHadValue:Boolean;
  138. /**
  139. * @private
  140. * This is no longer used in Flex 3.0, but it is required to load
  141. * Flex 2.0.0 and Flex 2.0.1 modules.
  142. */
  143. public var uiComponentWatcher:int;
  144. /**
  145. * @private
  146. * It's possible that there is a two-way binding set up, in which case
  147. * we'll do a rudimentary optimization by not executing ourselves
  148. * if our counterpart is already executing.
  149. */
  150. public var twoWayCounterpart:Binding;
  151. /**
  152. * @private
  153. * If there is a twoWayCounterpart, hasHadValue is false, and
  154. * isTwoWayPrimary is true, then the twoWayCounterpart will be
  155. * executed first.
  156. */
  157. public var isTwoWayPrimary:Boolean;
  158. /**
  159. * @private
  160. * True if a wrapped function call does not throw an error. This is used by
  161. * innerExecute() to tell if the srcFunc completed successfully.
  162. */
  163. private var wrappedFunctionSuccessful:Boolean;
  164. //--------------------------------------------------------------------------
  165. //
  166. // Properties
  167. //
  168. //--------------------------------------------------------------------------
  169. /**
  170. * All Bindings hang off of a document for now,
  171. * but really it's just the root of where these functions live.
  172. *
  173. * @langversion 3.0
  174. * @playerversion Flash 9
  175. * @playerversion AIR 1.1
  176. * @productversion Flex 3
  177. */
  178. mx_internal var document:Object;
  179. /**
  180. * The function that will return us the value.
  181. *
  182. * @langversion 3.0
  183. * @playerversion Flash 9
  184. * @playerversion AIR 1.1
  185. * @productversion Flex 3
  186. */
  187. mx_internal var srcFunc:Function;
  188. /**
  189. * The function that takes the value and assigns it.
  190. *
  191. * @langversion 3.0
  192. * @playerversion Flash 9
  193. * @playerversion AIR 1.1
  194. * @productversion Flex 3
  195. */
  196. mx_internal var destFunc:Function;
  197. /**
  198. * The destination represented as a String.
  199. * This will be used so we can signal validation on a field.
  200. *
  201. * @langversion 3.0
  202. * @playerversion Flash 9
  203. * @playerversion AIR 1.1
  204. * @productversion Flex 3
  205. */
  206. mx_internal var destString:String;
  207. /**
  208. * The source represented as a String.
  209. *
  210. * @langversion 3.0
  211. * @playerversion Flash 9
  212. * @playerversion AIR 1.1
  213. * @productversion Flex 4
  214. */
  215. mx_internal var srcString:String;
  216. /**
  217. * @private
  218. * Used to suppress calls to destFunc when incoming value is either
  219. * a) an XML node identical to the previously assigned XML node, or
  220. * b) an XMLList containing the identical node sequence as the previously assigned XMLList
  221. */
  222. private var lastValue:Object;
  223. //--------------------------------------------------------------------------
  224. //
  225. // Methods
  226. //
  227. //--------------------------------------------------------------------------
  228. private function defaultDestFunc(value:Object):void
  229. {
  230. var chain:Array = destString.split(".");
  231. var element:Object = document;
  232. var i:uint = 0;
  233. if (chain[0] == "this")
  234. {
  235. i++;
  236. }
  237. while (i < (chain.length - 1))
  238. {
  239. element = element[chain[i++]];
  240. }
  241. element[chain[i]] = value;
  242. }
  243. private function defaultSrcFunc():Object
  244. {
  245. return document[srcString];
  246. }
  247. /**
  248. * Execute the binding.
  249. * Call the source function and get the value we'll use.
  250. * Then call the destination function passing the value as an argument.
  251. * Finally try to validate the destination.
  252. *
  253. * @langversion 3.0
  254. * @playerversion Flash 9
  255. * @playerversion AIR 1.1
  256. * @productversion Flex 3
  257. */
  258. public function execute(o:Object = null):void
  259. {
  260. if (!isEnabled)
  261. {
  262. if (o != null)
  263. {
  264. registerDisabledExecute(o);
  265. }
  266. return;
  267. }
  268. if (twoWayCounterpart && !twoWayCounterpart.hasHadValue && twoWayCounterpart.isTwoWayPrimary)
  269. {
  270. twoWayCounterpart.execute();
  271. hasHadValue = true;
  272. return;
  273. }
  274. if (isExecuting || (twoWayCounterpart && twoWayCounterpart.isExecuting))
  275. {
  276. // If there is a twoWayCounterpart already executing, that means that it is
  277. // assigning something of value so even though we won't execute we should be
  278. // sure to mark ourselves as having had a value so that future executions will
  279. // be correct. If isExecuting is true but we re-entered, that means we
  280. // clearly had a value so setting hasHadValue is safe.
  281. hasHadValue = true;
  282. return;
  283. }
  284. try
  285. {
  286. isExecuting = true;
  287. wrapFunctionCall(this, innerExecute, o);
  288. }
  289. catch(error:Error)
  290. {
  291. if (allowedErrors[error.errorID] == null)
  292. throw error;
  293. }
  294. finally
  295. {
  296. isExecuting = false;
  297. }
  298. }
  299. /**
  300. * @private
  301. * Take note of any execute request that occur when we are disabled.
  302. */
  303. private function registerDisabledExecute(o:Object):void
  304. {
  305. if (o != null)
  306. {
  307. disabledRequests = (disabledRequests != null) ? disabledRequests :
  308. new Dictionary(true);
  309. disabledRequests[o] = true;
  310. }
  311. }
  312. /**
  313. * @private
  314. * Resynch with any watchers that may have updated while we were disabled.
  315. */
  316. private function processDisabledRequests():void
  317. {
  318. if (disabledRequests != null)
  319. {
  320. for (var key:Object in disabledRequests)
  321. {
  322. execute(key);
  323. }
  324. disabledRequests = null;
  325. }
  326. }
  327. /**
  328. * @private
  329. * Note: use of this wrapper needs to be reexamined. Currently there's at least one situation where a
  330. * wrapped function invokes another wrapped function, which is unnecessary (i.e., only the inner function
  331. * will throw), and also risks future errors due to the 'wrappedFunctionSuccessful' member variable
  332. * being stepped on. Leaving alone for now to minimize pre-GMC volatility, but should be revisited for
  333. * an early dot release.
  334. * Also note that the set of suppressed error codes below is repeated verbatim in Watcher.wrapUpdate.
  335. * These need to be consolidated and the motivations for each need to be documented.
  336. */
  337. protected function wrapFunctionCall(thisArg:Object, wrappedFunction:Function, object:Object = null, ...args):Object
  338. {
  339. wrappedFunctionSuccessful = false;
  340. try
  341. {
  342. var result:Object = wrappedFunction.apply(thisArg, args);
  343. wrappedFunctionSuccessful = true;
  344. return result;
  345. }
  346. /*catch(itemPendingError:ItemPendingError)
  347. {
  348. itemPendingError.addResponder(new EvalBindingResponder(this, object));
  349. if (BindingManager.debugDestinationStrings[destString])
  350. {
  351. trace("Binding: destString = " + destString + ", error = " + itemPendingError);
  352. }
  353. }
  354. catch(rangeError:RangeError)
  355. {
  356. if (BindingManager.debugDestinationStrings[destString])
  357. {
  358. trace("Binding: destString = " + destString + ", error = " + rangeError);
  359. }
  360. }*/
  361. catch(error:Error)
  362. {
  363. // Certain errors are normal when executing a srcFunc or destFunc,
  364. // so we swallow them:
  365. // Error #1006: Call attempted on an object that is not a function.
  366. // Error #1009: null has no properties.
  367. // Error #1010: undefined has no properties.
  368. // Error #1055: - has no properties.
  369. // Error #1069: Property - not found on - and there is no default value
  370. // We allow any other errors to be thrown.
  371. if ((error.errorID != 1006) &&
  372. (error.errorID != 1009) &&
  373. (error.errorID != 1010) &&
  374. (error.errorID != 1055) &&
  375. (error.errorID != 1069))
  376. {
  377. throw error;
  378. }
  379. else
  380. {
  381. if (BindingManager.debugDestinationStrings[destString])
  382. {
  383. trace("Binding: destString = " + destString + ", error = " + error);
  384. }
  385. }
  386. }
  387. return null;
  388. }
  389. /**
  390. * @private
  391. * true iff XMLLists x and y contain the same node sequence.
  392. */
  393. private function nodeSeqEqual(x:XMLList, y:XMLList):Boolean
  394. {
  395. var n:uint = x.length();
  396. if (n == y.length())
  397. {
  398. for (var i:uint = 0; i < n && x[i] === y[i]; i++)
  399. {
  400. }
  401. return i == n;
  402. }
  403. else
  404. {
  405. return false;
  406. }
  407. }
  408. /**
  409. * @private
  410. */
  411. private function innerExecute():void
  412. {
  413. var value:Object = wrapFunctionCall(document, srcFunc);
  414. if (BindingManager.debugDestinationStrings[destString])
  415. {
  416. trace("Binding: destString = " + destString + ", srcFunc result = " + value);
  417. }
  418. if (hasHadValue || wrappedFunctionSuccessful)
  419. {
  420. // Suppress binding assignments on non-simple XML: identical single nodes, or
  421. // lists over identical node sequences.
  422. // Note: outer tests are inline for efficiency
  423. if (!(lastValue is XML && lastValue.hasComplexContent() && lastValue === value) &&
  424. !(lastValue is XMLList && lastValue.hasComplexContent() && value is XMLList &&
  425. nodeSeqEqual(lastValue as XMLList, value as XMLList)))
  426. {
  427. destFunc.call(document, value);
  428. // Note: state is not updated if destFunc throws
  429. lastValue = value;
  430. hasHadValue = true;
  431. }
  432. }
  433. }
  434. /**
  435. * This function is called when one of this binding's watchers
  436. * detects a property change.
  437. *
  438. * @langversion 3.0
  439. * @playerversion Flash 9
  440. * @playerversion AIR 1.1
  441. * @productversion Flex 3
  442. */
  443. public function watcherFired(commitEvent:Boolean, cloneIndex:int):void
  444. {
  445. if (isHandlingEvent)
  446. return;
  447. try
  448. {
  449. isHandlingEvent = true;
  450. execute(cloneIndex);
  451. }
  452. finally
  453. {
  454. isHandlingEvent = false;
  455. }
  456. }
  457. }
  458. }