Extending Mate

In the weather widget example, we use the Yahoo! Astra Web API that contains utilities classes that make it simpler to retrieve weather data from Yahoo! Weather. This API has a class called WeatherService. To retrieve weather information, you simply write the following code:

var weatherService:WeatherService = new WeatherService();
weatherService.getWeather(location, unit);

When the weather service returns with the information, it dispatches an event:

WeatherResultEvent.WEATHER_LOADED

When it encounters an error, it dispatches an error event:

WeatherErrorEvent.INVALID_LOCATION

But we would like to be able to handle these two results as a list of handlers in our resultHandlers block by making a call to the weather service and receiving each type of event in its own handlers list, much like we do when calling a WebService.

Pseudo code for this would look like this:

<WeatherLoader><!-- calls weather service -->
   
   <resultHandlers>
      <!-- handle the result here -->
   </resultHandlers>
   
   <faultHandlers>
      <!-- handle the fault here -->
   </faultHandlers>

</WeatherLoader>

Creating this wrapper for the WeatherService class lets us include it in our EventHandlers and MessageHandlers and let us easily handle those results and faults.

The WeatherService also needs the location for which we want to retrieve the weather information and the unit of measure we want to use (ºC or ºF). To pass that information we can either enter that information in the <WeatherLoader> tag itself or by using the <Properties> inner tag. Because the location information will be changing depending on user interaction (ie: if the user enters his/her zip code in the text input), we should use the <Properties> inner tag because it will allow us to retrieve that data from the event that triggered the call.

The pseudo code for that will look like this:

<WeatherLoader location="92614" ><!-- calls weather service -->
   
   <resultHandlers>
      <!-- handle the result here -->
   </resultHandlers>
   
   <faultHandlers>
      <!-- handle the fault here -->
   </faultHandlers>

</WeatherLoader>

Implementing the tag

Our tag will be called WeatherLoader. We first need to create a class with that name that will extend from AbstractServiceInvoker and will implement IAction. We need to implement IAction to be able to place the tag inside EventHandlers blocks. While it is not required to extend from AbstractServiceInvoker, doing so will provide us with the result and fault inner-handlers.

This class will also need public properties for the data we needed: the location and the unit of measure. Those properties will be populated by the Properties tag.

package com.asfusion.weather.mate.extensions
{
	import com.asfusion.mate.actions.*;
	import com.yahoo.webapis.weather.WeatherService;
	import com.yahoo.webapis.weather.events.*;
	
	public class WeatherLoader extends AbstractServiceInvoker 
	implements IAction
	{
		public var location:String;
		public var unit:String;
		public var url:String = 'http://weather.yahooapis.com/forecastrss';
		// the service that we are wrapping
		private var weatherService:WeatherService ;
	}
}

The constructor

In the class constructor we'll instantiate the weather service we are wrapping. We'll also set the property "currentInstance" to point to "this". currentInstance is defined in the class AbstractAction. It is the object that will receive the property values specified in the <Properties> tag. Because our class has the location and unit properties, we'll set the currentInstance to our own class instance.

public function WeatherLoader() {

            weatherService = new WeatherService();

            currentInstance = this;   

}

The run method

Every handler needs a "run" method that gets called when the handlers list is executing each of its tags. We'll override this method to do what we want our class to do when our tag is called. In this method we will make the call to the WeatherService. In this method will also create the inner handlers for the result and fault.

Inner handlers blocks are created when an event is dispatched. In the case of a RemoteObject, for example, the RemoteObject dispatches the ResultEvent when the result gets back. When that event is dispatched, the tags contained in resultHandlers tag are executed. Those events can contain data. The ResultEvent contains a result object with the contents of what the server returned. In our resultHandlers, we access that object by using the {resultObject} smart object. But we could also access that event directly by using the {currentEvent} smart object.

Because we are now creating a custom tag for our own events, we need to specify what event will trigger the resultHandlers execution and what event will trigger the faultHandlers execution. We also need to specify who is dispatching those events.

The WeatherService class dispatches aWeatherResultEvent.WEATHER_LOADED when the result gets back, and a WeatherErrorEvent.INVALID_LOCATION when there is an error. We'll use those two events to trigger our result and fault inner handlers executions. We do so by creating those inner handlers as follows:

this.createInnerHandlers(scope, WeatherResultEvent.WEATHER_LOADED, resultHandlers);

and

this.createInnerHandlers(scope, WeatherErrorEvent.INVALID_LOCATION , faultHandlers);

The method createInnerHandlers is defined by theAbtractServiceInvoker class. The scope argument contains the scope within which this tag is called (the scope is defined by the EventHandler tag that contains this action item). The third argument is the inner handlers block that should be started when the event is received. The names "resultHandlers" and "faultHandlers" are defined by the AbstractServiceInvoker class, but you could create your own.

At the end of the method, we make the call to the WeatherService:

weatherService.getWeather(location, unit);

The complete code for the run method would look like this:

override protected function run(scope:IScope):void {

// specify that the dispatcher of the result and error event is the weatherService object 

        innerHandlersDispatcher = weatherService;

            if (this.resultHandlers && resultHandlers.length > 0){

                this.createInnerHandlers(scope, WeatherResultEvent.WEATHER_LOADED, resultHandlers);

            }

            if (this.faultHandlers && faultHandlers.length > 0){

                this.createInnerHandlers(scope, WeatherErrorEvent.INVALID_LOCATION , faultHandlers);

            }

            weatherService.getWeather(location, unit);

}

Using the tag

We can include our tag in an EventHandlers list, getting the location and unit information from the event that was dispatched. When we receive the weather result, we call WeatherManager.setWeather() method. This method receives the Weather object that was retrieved by the service. The event that triggers the resultHandlers is of type WeatherResultEvent.WEATHER_LOADED as we specified in our run method of our custom tag class. This event contains a Weather object in the "data" property. To access that data, we need to use the SmartObject "currentEvent", which represents the WeatherResultEvent that triggered the inner handlers.

The code:

<EventHandlers type="{WeatherEvent.GET}">
   
   <extensions:WeatherLoader> <!-- make the call to the service -->
      <Properties location="{event.location}" unit="{event.unit}" />
      
      <extensions:resultHandlers>
      
      <!-- receive the results contained in the currentEvent.data property (WeatherResultEvent contains a data property) -->
         <MethodInvoker generator="{WeatherManager}" method="setWeather" arguments="{currentEvent.data}" />
         
      </extensions:resultHandlers>
         
      <extensions:faultHandlers>
      
         <!-- receive an error event. The error event contains a data property that contains the reason for the error -->
         <MethodInvoker generator="{WeatherManager}" method="handleFault" arguments="{currentEvent.data}" />
         
      </extensions:faultHandlers>
      
   </extensions:WeatherLoader>
      
</EventHandlers>

3 responses

  1. Just a note: setting currentInstance on the constructor will make it only work once. You should set currentInstance on the prepare() method.
  2. Thanks, David. Your comment helped me to solve the problem.
  3. This is great, but what about updating the weather widget after it is initially built? I tried just writing a method to call

    weatherService.getWeather(location, unit);

    But apparently the event handlers never pick up these subsequent calls. Are they handlers removed after the initial events are consumed? How to I force the widget to update itself?

Comments now closed