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