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.
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.
Source Files:
this example not working for me..
You have the same problem of a Script I do in Flex.
When I do a search and returns only one row, the script dosnt work.
try bethor search and then bethor's p
the script hangs up and doesnt return the One row.
\^\^ looking for solution...
Find Solution, Worked for me:
if (event.result.rows.row is ArrayCollection) { resObj= event.result }else{ resObj= new ArrayCollection(ArrayUtil.toArray(event.result)); }
It's working but.. need php code too please publish PHP and XML codes too
regards