[ Team LiB ] |
Recipe 12.11 Adding Listeners to Custom Classes12.11.1 ProblemYou want your objects of a custom class to generate listener events and support listener objects. 12.11.2 SolutionWrite an addListener( ) method for your custom class. Use the watch( ) method to watch a property. When the property is modified, call all listeners. 12.11.3 DiscussionListeners are a convenient way to automatically trigger methods on one or more objects when a property is modified within the object being listened to. Listeners are available for some classes of objects such as Key, Mouse, and TextField (see Table 12-1). Although ActionScript does not provide listeners for all objects, you can use the watch( ) method to generate listener events in response to property changes. The watch( ) method requires at least two parameters: a string value that is the name of the property to watch and a reference to a callback function to call when the property is changed. (A callback function is simply a function that is called in response to a particular trigger.) The callback function is always automatically passed at least three values: the property name, the old value, and the new value. If the callback function returns true, the new value is assigned to the property, and if it returns false, the old value is assigned to the property. The watch( ) method can also be used for other purposes, such as implementing logic in the callback function to determine which value (old or new) should be assigned to the property. For the purposes of creating listener functionality, your callback function should always return true because you don't want to interfere with the setting of the property value (you only want to detect the change so that your code can trigger a method on the listener objects). To add listener functionality to a class, you also need to create methods for adding and removing listeners. The addListener( ) method takes a reference to an object and adds it to an array property. The removeListener( ) method also takes a reference to an object, which it removes from the array, if found. Here is an example of how to create listener functionality for a custom class and how to use it: // Create a constructor for a class, MyClass. _global.MyClass = function (val) { // Store the value passed to the constructor in a property (myProperty). this.myProperty = val; // Tell the object to watch for any changes to myProperty. When a change occurs, // call the object's propSet( ) method. this.watch("myProperty", this.propSet); }; // The propSet( ) method is the callback function invoked automatically when // myProperty's value changes. The callback is passed the property name // ('myProperty' in this case) plus the old value and the proposed new value. MyClass.prototype.propSet = function (prop, oldVal, newVal) { // Call onMyPropertyChange( ), if it exists, for each listener object. Pass that // method the old value and new value. for (var i = 0; i < this.listeners.length; i++) { this.listeners[i].onMyPropertyChange(oldVal, newVal); } // Return the new value so that it is assigned to the property. This is very // important. Without this line, myProperty becomes undefined. return newVal; }; // The addListener( ) method adds a listener object to an array of listeners. MyClass.prototype.addListener = function (listener) { // If the listeners array property does not exist, create it. if (this.listeners == undefined) { this.listeners = new Array( ); } // Add the new listener to the existing array of listeners. this.listeners.push(listener); }; // The removeListener( ) method removes a listener object from the listeners array. MyClass.prototype.removeListener = function (listener) { // Remove the listener from the listeners array, if it is found. for (var i = 0; i < this.listeners.length; i++) { if (this.listeners[i] == listener) { this.listeners.splice(i, 1); break; } } }; // Create a listener object. It must have a method named onMyPropertyChange( ) to // listen effectively (because that's what the propSet( ) method tries to call). obj = new Object( ); obj.onMyPropertyChange = function (oldVal, newVal) { trace(oldVal + " " + newVal); }; // Create a new instance of MyClass and add obj as a listener on the instance. myClassInstance = new MyClass(6); myClassInstance.addListener(obj); // Each time myProperty is set, the callback function (propSet( )) invokes the // onMyPropertyChange( ) method of each listener object. myClassInstance.myProperty = 24; myClassInstance.myProperty = 42; myClassInstance.myProperty = 36; The preceding code gives you an idea of the process involved in adding listeners to objects. However, it is not as versatile as it could be. The primary issue is that it requires you to define the listener code for every class to which you want to add listeners. There is a better way. All ActionScript classes inherit from the Object class. Therefore, if you add the listener code to the Object class, all classes automatically inherit it. The only real difference when you add the listener code to the Object class is that you cannot rely on having access to each class's constructor as you did in the preceding example. Therefore, you should create another method, startListeners( ), that initiates the watch for a watched property. The startListeners( ) method should accept the name of the property to watch as a parameter. For future use, add the following code to an external Object.as file and include it in any project for which you want to implement custom listener functionality: // The startListeners( ) method calls watch( ) to start watching // a property. If you want an object to listen for multiple // properties, call startListeners( ) for each property. Object.prototype.startListeners = function (propName) { this.watch(propName, this.propSet); }; // The addListener( ) and removeListener( ) methods are the same as the preceding // example, except that they are applied to the Object class. Object.prototype.addListener = function (listener) { if (this.listeners == undefined) { this.listeners = new Array( ); } this.listeners.push(listener); }; Object.prototype.removeListener = function (listener) { for (var i = 0; i < this.listeners.length; i++) { if (this.listeners[i] == listener) { this.listeners.splice(i, 1); break; } } }; // The propSet( ) method is the callback function for the watched properties. This // method is slightly more generic than in the preceding example, insofar as it // allows for any property to be watched. Object.prototype.propSet = function (prop, oldVal, newVal) { // Loop through all the listeners. for (var i = 0; i < this.listeners.length; i++) { // This calls the onPropertyNameChange( ) method on each listener, where // PropertyName is given by the prop parameter. this.listeners[i]["on" + prop + "Change"](oldVal, newVal); } // Return the new value so it is properly assigned to the property. return newVal; }; Here is an example that uses our new listener code: #include "Object.as" // Define a custom class. This time, the class does not need any of the listener code // because it automatically inherits it from the Object class. _global.MyClass = function (val0, val1) { this.myProperty0 = val0; this.myProperty1 = val1; }; // Create a listener object. Define methods named onMyProperty0Change( ) and // onMyProperty1Change( ) so the object can listen for changes to the watched // properties named myProperty0 and myProperty1. obj = new Object( ); obj.onMyProperty0Change = function (oldVal, newVal) { trace("myProperty0: " + oldVal + " " + newVal); }; obj.onMyProperty1Change = function (oldVal, newVal) { trace("myProperty1: " + oldVal + " " + newVal); }; // Create a new instance of MyClass. myClassInstance = new MyClass(6, 9); // Add obj as a listener. Start listeners for myProperty0 and myProperty1. myClassInstance.addListener(obj); myClassInstance.startListeners("myProperty0"); myClassInstance.startListeners("myProperty1"); // The following changes to the properties invoke the listener methods // onMyProperty0Change( ) and onMyProperty1Change( ). myClassInstance.myProperty0 = 24; myClassInstance.myProperty0 = 42; myClassInstance.myProperty1 = 36; /* The results displayed in the Output window are as follows: myProperty0: 6 24 myProperty0: 24 42 myProperty1: 9 36 */ Be aware that Flash cannot watch some properties, so you cannot add listeners in the following cases:
Table 12-2 lists the getter/setter properties that cannot be watched, but it does not list all unwatchable properties. Note that Flash 4 legacy properties such as the _x, _y, _width, and _height movie clip properties also cannot be watched.
12.11.4 See AlsoRecipe 12.10. Also refer to the Object.watch( ) entry in the "Language Reference" section of ActionScript for Flash MX: The Definitive Guide, from which Table 12-2 is reproduced. |
[ Team LiB ] |