Flex & PHP - Creating WoW Armory Search Widget

Skill

Flex & PHP - Creating WoW Armory Search Widget

Posted in:

One of the neat things about World of Warcraft is the amount of data freely available. Between sources like Thottbot, Wowhead, and WoW Armory you can find information on just about anything in the game. The WoW Armory is even more interesting because it sends all of its data to the browser in xml. This makes it really easy to consume this data through web requests. This tutorial is about building a small application that searches the Armory for items.

So below you can see the application we are going to build. It is relatively simple and allows you to search for items that have the term entered in the text input in their name. It displays the icon for the item, the name of the item, and the level. The name is a link out to the item's page on the armory website. You can also check out the source for this tutorial by right clicking on the demo.

Get Adobe Flash player

The first thing we need to do is build a small PHP proxy script that will relay the search results from the Armory. The reason we have to build a proxy script is because the Armory doesn't have any cross domain rules, therefore we are not allowed to directly access the site from Flex.

To get the information from the Armory we use PHP's cURL library. We do a simple request using the term passed in the GET variable named term. The one important factor to make sure you set is the user agent of the request. This makes sure we actually receive xml from the Armory.

<?php
  $url = "http://www.wowarmory.com/search.xml?" .
         "searchQuery={$_GET['term']}&searchType=items";
  $ch = curl_init();
  $useragent="Mozilla/5.0 (Windows; U; Windows NT 5.1; " .
             "en-US; rv:1.8.1.1) Gecko/20061204 Firefox/2.0.0.1";
  curl_setopt ($ch, CURLOPT_URL, $url);
  curl_setopt ($ch, CURLOPT_RETURNTRANSFER, 1);
  curl_setopt ($ch, CURLOPT_USERAGENT, $useragent);
  $f = curl_exec($ch);
  curl_close($ch);
  echo $f;
?>

Now let's get down to some Flex code. The first thing we are going to do is setup the basic interface. It has a label, text input, search button, and a datagrid. Nothing out of the ordinary here. We have a few columns in our datagrid, the one doesn't have any headerText set because it is going to show the item's image. You may also notice none of the columns have the dataField property set, this is because we are going to use some custom item renderers and also a label function.

<?xml version="1.0" encoding="utf-8"?>
<mx:Application
 xmlns:mx="http://www.adobe.com/2006/mxml"
 layout="absolute"
 width="250" height="300">
  <mx:Label x="5" y="4" text="Search WoW Items"
   fontSize="14" fontWeight="bold"/>
  <mx:TextInput x="5" y="31" width="161" id="searchTerm"/>
  <mx:Button x="175" y="31" label="Search" width="70"/>
  <mx:DataGrid x="5" y="61" width="240" height="235"
   rowHeight="25" fontSize="9">
    <mx:columns>
      <mx:DataGridColumn headerText="" width="25"/>
      <mx:DataGridColumn headerText="Item Name"/>
      <mx:DataGridColumn headerText="Level" width="45"/>
    </mx:columns>
  </mx:DataGrid>
</mx:Application>

Next up, adding a little bit of functionality. The first bit of functionality we are going to add is retrieving the data from the Armory, through our proxy. This is done using a HTTPService to handle the request. The service has the url property set equal to the path of our php script. This needs to be running on the same server or on a server with proper cross domain permissions. We are also going to pass the term parameter that was mentioned in the PHP script. To handle the result or fault we add a couple event handlers. When we receive the results we want them parsed into an object, therefore we set the resultFormat to object.

<mx:HTTPService
 id="wowsearch"
 url="/sites/default/phpExamples/576/wowarmory.php"
 method="GET" resultFormat="object"
 result="handleResult(event)" fault="handleFault(event)"
 showBusyCursor="true">
  <mx:request>
    <term>{term}</term>
  </mx:request>
</mx:HTTPService>

This is all well and good but we need the functions created to handle the results or fault. To do this we need to add a Script block. The functions handleResult and handleFault are then added.

<mx:Script>
 <![CDATA[
   import mx.messaging.messages.ErrorMessage;
   import mx.collections.ArrayCollection;
   import mx.controls.Alert;
   import mx.rpc.events.FaultEvent;
   import mx.rpc.events.ResultEvent;

   [Bindable]
   private var items:ArrayCollection;
   
   private function handleResult(e:ResultEvent):void
   {
     items = e.result.page.armorySearch.searchResults.items.item;
   }
   
   private function handleFault(e:FaultEvent):void
   {
     Alert.show((e.message as ErrorMessage).faultString);
   }
 ]]>
</mx:Script>

There is a lot done in these few lines. The first thing we need is a bindable variable for the items returned. Then in the handleResult function we take the results and set them to our items variable. In order to get the item ArrayCollection I checked the xml source from the search page over at the Armory. The handleFault simply displays the error message in an alert.

To actually search we need to call send on the HTTPService, we are going to do this in a function named search. The search function is below. We also need to add a second variable named term, which we will bind to the text input.

[Bindable]
private var term:String;

private function search():void
{
  wowsearch.send();
}

To bind the term variable we add a Binding mxml tag and set the source to the text in our text input and destination to the term variable.

<mx:Binding source="searchTerm.text" destination="term" />

We also need to hook up our button to call the search function. I set up the text input to call search when the Enter button is pressed inside the box or when the button is clicked. The updated items are below.

<mx:TextInput x="5" y="31" width="161" id="searchTerm" enter="search()"/>
<mx:Button x="175" y="31" label="Search" width="70" click="search()"/>

So, we can now enter the text we want to search for, search for it, and retrieve results. Finally, we need to display the results. Before actually showing the data we should hook up our datagrid to the items collection by setting the dataProvider property. Here is the updated opening datagrid tag.

<mx:DataGrid x="5" y="61" width="240" height="235"
 dataProvider="{items}" rowHeight="25" fontSize="9">

Going right down the line we start with displaying the image for the item. This is done using a custom item renderer, ItemImage. I created a custom component based off of HBox, I chose this container for ease of setting the horizontal and vertical alignment. The entire code for the ItemImage follows.

<mx:HBox xmlns:mx="http://www.adobe.com/2006/mxml"
 horizontalAlign="center" verticalAlign="middle"
 width="100%" height="100%">
  <mx:Image source="http://www.wowarmory.com/wow-icons/_images/21x21
   /{this.data.icon}.png" />

</mx:HBox>

Yeah, that thing is pretty complicated. Again to figure out the property on the item object to look at I checked out the source for the item search page over at the Armory. The image location I figured out by simply checking out the location of a few of the images on their page. Now to get this guy working we need to set the itemRenderer property on the column.

<mx:DataGridColumn headerText="" itemRenderer="ItemImage"
 width="25"/>

The next piece to display is the name of the item. We are also going to use a custom item renderer for this guy. This one is named ItemName and is a custom component based off of a LinkButton. It is slightly more complicated. I will go over it after the code.

<?xml version="1.0" encoding="utf-8"?>
<mx:LinkButton
 xmlns:mx="http://www.adobe.com/2006/mxml"
 width="100%" height="100%" click="handleLinkClick()">
  <mx:Script>
   <![CDATA[
     import flash.net.navigateToURL;
     
     private function handleLinkClick():void
     {
       var url:String = "http://www.wowarmory.com/item-info.xml?" + data.url;
       navigateToURL(new URLRequest(url), "_blank");
     }
   ]]>
 </mx:Script>
  <mx:label>{data.name}</mx:label>
</mx:LinkButton>

Really the only quirk here is the handleLinkClick function. This function runs when the button is clicked and will open a new tab/window with the item Armory page. This relies on the navigateToUrl function which has been around in actionscript for ages. We also set the label text. To use the item renderer we again update the column.

<mx:DataGridColumn headerText="Item Name" itemRenderer="ItemName"/>

The very last thing to do is display the item level. This is achieved using a label function on the last column. We set the labelFunction property equal to the function, displayLevel, that we are going use to return the string for the cell.

<mx:DataGridColumn headerText="Level" labelFunction="displayLevel"
 width="45"/>

The function will get the level of the item by looping through the filter parameter on the item and checking to see it the name of the filter is itemLevel and if so returns the value associated with it. Again this was all found by examining the xml returned by the Armory.

private function displayLevel(item:Object, col:DataGridColumn):String
{
  if(item) {
    for each(var o:Object in item.filter) {
      if(o.name == "itemLevel") {
        return o.value.toString();
      }
    }  
  }
 
  return "";
}

That pretty much wraps up this tutorial, all that is left is to share the completed code for our main application file.

<?xml version="1.0" encoding="utf-8"?>
<mx:Application
 xmlns:mx="http://www.adobe.com/2006/mxml"
 layout="absolute"
 width="250" height="300">
  <mx:Script>
   <![CDATA[
     import com.adobe.viewsource.ViewSource;
     import mx.controls.dataGridClasses.DataGridColumn;
     import mx.messaging.messages.ErrorMessage;
     import mx.collections.ArrayCollection;
     import mx.controls.Alert;
     import mx.rpc.events.FaultEvent;
     import mx.rpc.events.ResultEvent;
     
     [Bindable]
     private var term:String;
     
     [Bindable]
     private var items:ArrayCollection;
     
     private function search():void
     {
       wowsearch.send();
     }
     
     private function handleResult(e:ResultEvent):void
     {
       items = e.result.page.armorySearch.searchResults.items.item;
     }
     
     private function handleFault(e:FaultEvent):void
     {
       Alert.show((e.message as ErrorMessage).faultString);
     }
     
     private function displayLevel(item:Object, col:DataGridColumn):String
     {
       if(item) {
         for each(var o:Object in item.filter) {
           if(o.name == "itemLevel") {
             return o.value.toString();
           }
         }  
       }
       
       return "";
     }
   ]]>
 </mx:Script>
  <mx:Binding source="searchTerm.text" destination="term" />
  <mx:HTTPService
   id="wowsearch"
   url="/sites/default/phpExamples/576/wowarmory.php"
   method="GET" resultFormat="object"
   result="handleResult(event)" fault="handleFault(event)"
   showBusyCursor="true">
    <mx:request>
      <term>{term}</term>
    </mx:request>
  </mx:HTTPService>
  <mx:Label x="5" y="4" text="Search WoW Items"
   fontSize="14" fontWeight="bold"/>
  <mx:TextInput x="5" y="31" width="161" id="searchTerm" enter="search()"/>
  <mx:Button x="175" y="31" label="Search" width="70" click="search()"/>
  <mx:DataGrid x="5" y="61" width="240" height="235"
   dataProvider="{items}" rowHeight="25" fontSize="9">
    <mx:columns>
      <mx:DataGridColumn headerText="" itemRenderer="ItemImage"
       width="25"/>
      <mx:DataGridColumn headerText="Item Name" itemRenderer="ItemName"/>
      <mx:DataGridColumn headerText="Level" labelFunction="displayLevel"
       width="45"/>
    </mx:columns>
  </mx:DataGrid>
</mx:Application>

As final words I should say I have no idea how long any of this will work because it relies on the architecture of the WoW Armory site. It doesn't matter too much though because this is meant to be a teaching tool and the information in this tutorial will still be relevant. Enough about that, if anyone has any questions about the tutorial feel free to drop a comment or put up a post in our forums.

Anonymous
04/21/2009 - 06:56

this example not working for me..

reply

Add Comment

Put code snippets inside language tags:
[language] [/language]

Examples:
[javascript] [/javascript]
[actionscript] [/actionscript]
[csharp] [/csharp]

See here for supported languages.

Javascript must be enabled to submit anonymous comments - or you can login.

Sponsors