Stateful.js 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  1. define(["./_base/declare", "./_base/lang", "./_base/array", "./when"], function(declare, lang, array, when){
  2. // module:
  3. // dojo/Stateful
  4. return declare("dojo.Stateful", null, {
  5. // summary:
  6. // Base class for objects that provide named properties with optional getter/setter
  7. // control and the ability to watch for property changes
  8. //
  9. // The class also provides the functionality to auto-magically manage getters
  10. // and setters for object attributes/properties.
  11. //
  12. // Getters and Setters should follow the format of _xxxGetter or _xxxSetter where
  13. // the xxx is a name of the attribute to handle. So an attribute of "foo"
  14. // would have a custom getter of _fooGetter and a custom setter of _fooSetter.
  15. //
  16. // example:
  17. // | require(["dojo/Stateful", function(Stateful) {
  18. // | var obj = new Stateful();
  19. // | obj.watch("foo", function(){
  20. // | console.log("foo changed to " + this.get("foo"));
  21. // | });
  22. // | obj.set("foo","bar");
  23. // | });
  24. // _attrPairNames: Hash
  25. // Used across all instances a hash to cache attribute names and their getter
  26. // and setter names.
  27. _attrPairNames: {},
  28. _getAttrNames: function(name){
  29. // summary:
  30. // Helper function for get() and set().
  31. // Caches attribute name values so we don't do the string ops every time.
  32. // tags:
  33. // private
  34. var apn = this._attrPairNames;
  35. if(apn[name]){ return apn[name]; }
  36. return (apn[name] = {
  37. s: "_" + name + "Setter",
  38. g: "_" + name + "Getter"
  39. });
  40. },
  41. postscript: function(/*Object?*/ params){
  42. // Automatic setting of params during construction
  43. if (params){ this.set(params); }
  44. },
  45. _get: function(name, names){
  46. // summary:
  47. // Private function that does a get based off a hash of names
  48. // names:
  49. // Hash of names of custom attributes
  50. return typeof this[names.g] === "function" ? this[names.g]() : this[name];
  51. },
  52. get: function(/*String*/name){
  53. // summary:
  54. // Get a property on a Stateful instance.
  55. // name:
  56. // The property to get.
  57. // returns:
  58. // The property value on this Stateful instance.
  59. // description:
  60. // Get a named property on a Stateful object. The property may
  61. // potentially be retrieved via a getter method in subclasses. In the base class
  62. // this just retrieves the object's property.
  63. // example:
  64. // | require(["dojo/Stateful", function(Stateful) {
  65. // | var stateful = new Stateful({foo: 3});
  66. // | stateful.get("foo") // returns 3
  67. // | stateful.foo // returns 3
  68. // | });
  69. return this._get(name, this._getAttrNames(name)); //Any
  70. },
  71. set: function(/*String*/name, /*Object*/value){
  72. // summary:
  73. // Set a property on a Stateful instance
  74. // name:
  75. // The property to set.
  76. // value:
  77. // The value to set in the property.
  78. // returns:
  79. // The function returns this dojo.Stateful instance.
  80. // description:
  81. // Sets named properties on a stateful object and notifies any watchers of
  82. // the property. A programmatic setter may be defined in subclasses.
  83. // example:
  84. // | require(["dojo/Stateful", function(Stateful) {
  85. // | var stateful = new Stateful();
  86. // | stateful.watch(function(name, oldValue, value){
  87. // | // this will be called on the set below
  88. // | }
  89. // | stateful.set(foo, 5);
  90. // set() may also be called with a hash of name/value pairs, ex:
  91. // | stateful.set({
  92. // | foo: "Howdy",
  93. // | bar: 3
  94. // | });
  95. // | });
  96. // This is equivalent to calling set(foo, "Howdy") and set(bar, 3)
  97. // If an object is used, iterate through object
  98. if(typeof name === "object"){
  99. for(var x in name){
  100. if(name.hasOwnProperty(x) && x !="_watchCallbacks"){
  101. this.set(x, name[x]);
  102. }
  103. }
  104. return this;
  105. }
  106. var names = this._getAttrNames(name),
  107. oldValue = this._get(name, names),
  108. setter = this[names.s],
  109. result;
  110. if(typeof setter === "function"){
  111. // use the explicit setter
  112. result = setter.apply(this, Array.prototype.slice.call(arguments, 1));
  113. }else{
  114. // no setter so set attribute directly
  115. this[name] = value;
  116. }
  117. if(this._watchCallbacks){
  118. var self = this;
  119. // If setter returned a promise, wait for it to complete, otherwise call watches immediately
  120. when(result, function(){
  121. self._watchCallbacks(name, oldValue, value);
  122. });
  123. }
  124. return this; // dojo/Stateful
  125. },
  126. _changeAttrValue: function(name, value){
  127. // summary:
  128. // Internal helper for directly changing an attribute value.
  129. //
  130. // name: String
  131. // The property to set.
  132. // value: Mixed
  133. // The value to set in the property.
  134. //
  135. // description:
  136. // Directly change the value of an attribute on an object, bypassing any
  137. // accessor setter. Also handles the calling of watch and emitting events.
  138. // It is designed to be used by descendant class when there are two values
  139. // of attributes that are linked, but calling .set() is not appropriate.
  140. var oldValue = this.get(name);
  141. this[name] = value;
  142. if(this._watchCallbacks){
  143. this._watchCallbacks(name, oldValue, value);
  144. }
  145. return this; // dojo/Stateful
  146. },
  147. watch: function(/*String?*/name, /*Function*/callback){
  148. // summary:
  149. // Watches a property for changes
  150. // name:
  151. // Indicates the property to watch. This is optional (the callback may be the
  152. // only parameter), and if omitted, all the properties will be watched
  153. // returns:
  154. // An object handle for the watch. The unwatch method of this object
  155. // can be used to discontinue watching this property:
  156. // | var watchHandle = obj.watch("foo", callback);
  157. // | watchHandle.unwatch(); // callback won't be called now
  158. // callback:
  159. // The function to execute when the property changes. This will be called after
  160. // the property has been changed. The callback will be called with the |this|
  161. // set to the instance, the first argument as the name of the property, the
  162. // second argument as the old value and the third argument as the new value.
  163. var callbacks = this._watchCallbacks;
  164. if(!callbacks){
  165. var self = this;
  166. callbacks = this._watchCallbacks = function(name, oldValue, value, ignoreCatchall){
  167. var notify = function(propertyCallbacks){
  168. if(propertyCallbacks){
  169. propertyCallbacks = propertyCallbacks.slice();
  170. for(var i = 0, l = propertyCallbacks.length; i < l; i++){
  171. propertyCallbacks[i].call(self, name, oldValue, value);
  172. }
  173. }
  174. };
  175. notify(callbacks['_' + name]);
  176. if(!ignoreCatchall){
  177. notify(callbacks["*"]); // the catch-all
  178. }
  179. }; // we use a function instead of an object so it will be ignored by JSON conversion
  180. }
  181. if(!callback && typeof name === "function"){
  182. callback = name;
  183. name = "*";
  184. }else{
  185. // prepend with dash to prevent name conflicts with function (like "name" property)
  186. name = '_' + name;
  187. }
  188. var propertyCallbacks = callbacks[name];
  189. if(typeof propertyCallbacks !== "object"){
  190. propertyCallbacks = callbacks[name] = [];
  191. }
  192. propertyCallbacks.push(callback);
  193. // TODO: Remove unwatch in 2.0
  194. var handle = {};
  195. handle.unwatch = handle.remove = function(){
  196. var index = array.indexOf(propertyCallbacks, callback);
  197. if(index > -1){
  198. propertyCallbacks.splice(index, 1);
  199. }
  200. };
  201. return handle; //Object
  202. }
  203. });
  204. });