Here's one more for XML integration, but this time using more components offered in Flash CS3. This article will focus on the how easy Flash components integrate with XML. This way uses ActionScript 3.0 and not a XML data connector. This way is for implementing a need for searchable items where internet access is not desired or implied.
First we should plan out how this will integrate.
Why MSAccess? Why not some other relational database management system (RDBMS) such as MySQL?
Usability: It's easy for the client to keep track of information in a database tool they're familiar with. In this case, it's Microsoft Access.
Domain: This application is for use on a distrubuted CD/DVD that will enable the user to search through data efficiently. Since most RDBMS require a server and/or internet access, this pseudo-database will just run from flash and filter an XML file.
Since MSAccess can export external data as XML, ActionScript can read that file and place it in the datagrid.
Sorry, if you're reading this article thinking, "How can flash read a MS Access database?" I have one answer. Maybe VB is your answer or maybe even Silverlight. Flash does not read MSAccess databases unless there exists some JDBC/ODBC driver to read. But why do that when MS Access exports XML that flash can read? Anyways, on to the application!
var strHelper = new StringHelper(); // used for String::trim(); this isn't built into AS3
A plethora (yay thesaurus!) of import statements for you. On to make the DataGrid!
Making the DataGrid
var dp:DataProvider;
/** * Instantiate the DataGrid */ var myDataGrid:DataGrid = new DataGrid(); myDataGrid.horizontalScrollPolicy = ScrollPolicy.ON; myDataGrid.setSize(600, 300); myDataGrid.setStyle("cellRenderer", AlternatingRowColors); myDataGrid.move(10,185); myDataGrid.rowCount = 4;//datagrid should only have 12 rows addChild(myDataGrid);//add to dataGrid_mc not root
/** * Loading an external XML file */ var xmlDP:XML; var xmlListData:XML; var xmlLoader:URLLoader = new URLLoader(); xmlLoader.addEventListener(Event.COMPLETE, dataCompleteHandler); xmlLoader.addEventListener(ProgressEvent.PROGRESS, onProgress); xmlLoader.addEventListener(IOErrorEvent.IO_ERROR, catchIOError); xmlLoader.load(new URLRequest("Cars.xml"));
/** * Once the XML file is done loading it will set the datagrid to read the * dataprovider set by the xml file. */ function dataCompleteHandler(event:Event):void { var ldr:URLLoader = event.currentTarget as URLLoader; xmlDP = new XML(ldr.data);
dp = new DataProvider(xmlDP);//instantiate dataprovider myDataGrid.dataProvider = dp;//assign dataprovider to datagrid
setList(xmlDP.Cars.Make, makeList); setList(xmlDP.Cars.Model, modelList); setList(xmlDP.Cars.Year, yearList, true); //setList(xmlDP.Cars.HP, hpList, true); //range values instead of actual values in grid hpList.addItem({label:"All", data:"All"});
hpList.addItem({label:"Under 100", data:"Under 100"}); hpList.addItem({label:"100 - 149", data:"100 - 149"}); hpList.addItem({label:"150 - 199", data:"150 - 199"}); hpList.addItem({label:"200 - 249", data:"200 - 249"}); hpList.addItem({label:"250 to 299", data:"250 to 299"}); hpList.addItem({label:"300 or more", data:"300 or more"});
hpList.allowMultipleSelection = true;
setList(xmlDP.Cars.Options.Value, optionList) }
/** If a value is added to the DataGrid then it is added to it's list accordingly. * There is no way to search for a value that is NOT in the DataGrid. */ function setList(newXMLList:XMLList, list:List, isNumeric:Boolean = false):void { var dp_array:Array = new Array(); for each (var item:XML in newXMLList) { var found:Boolean = false; for(var i:int = 0; i < dp_array.length; i++) { if(item[0] == dp_array[i].label) found = true; } if( ! found) dp_array.push({label:item, data:item}); }
//crude way to add "All" to the top after sorting, there probably is a method //to add a value to the front of the array, but this works. dp_array = dp_array.reverse(); dp_array.push({label:"All", data:"All"}); dp_array = dp_array.reverse();
list.allowMultipleSelection = true; list.dataProvider = new DataProvider(dp_array); }
/** * Show that XML file is not found. */ function catchIOError(event:IOErrorEvent) { trace(event.text); }
/** * Show progress of XML loading */ function onProgress(e:ProgressEvent):void { var percent:Number = (e.bytesLoaded / e.bytesTotal) * 100; trace("Loading..." + percent.toString(10) + "%"); }
The Exported XML
If you have Microsoft Office Access 2007, download the zip file and open up the .accdb file. I didn't use a .mdb file but that can also be used to export XML. All you really need to know is that XML has to be used instead of an actual database file. If you're using MySQL just use php to show the database as XML.
Back to the .accdb file, once opened you'll see how to constrain the Options field to certain values. This is what MSAccess2007 exports.
6 Nissan Maxima 2009
4 Wheel Disc Brakes
Power Doorlocks
Power Windows
Sun roof
290
1 Toyota Celica GT-S 2000
4 Wheel Disc Brakes
Power Doorlocks
Power Windows
Sun roof
180
2 Toyota Celica ST 1996
Power Doorlocks
Power Windows
105
3 Nissan 350Z 2009
4 Wheel Disc Brakes
Power Doorlocks
Power Windows
Sun roof
306
4 Honda Civic Si 2000
4 Wheel Disc Brakes
Power Doorlocks
Power Windows
Sun roof
160
5 Honda S2000 2009
4 Wheel Disc Brakes
Power Doorlocks
Power Windows
237
Creating Inputs, Lists, and Labels
All this code really does is instantiate labels, inputs, and lists, which could actually be drawn on the stage with less code, but I have already written the code.
//--------------------------- var textInput:TextInput = new TextInput(); textInput.move(10, 150); textInput.width = 220;
//Press the enter key to apply filter (search) textInput.addEventListener("enter",changeHandler); //Everytime the input box is changed, apply filter (search) //(can affect performance) textInput.addEventListener(Event.CHANGE, changeHandler);
textInput.tabIndex = 0; addChild(textInput);
var textInputLabel:Label = new Label(); textInputLabel.text = "Search grid"; textInputLabel.move(textInput.x + 5, textInput.y - 20); textInputLabel.autoSize = TextFieldAutoSize.LEFT; addChild(textInputLabel);
//--------------------------- var makeList:List = new List(); makeList.move(10,40); makeList.width = 100; makeList.rowCount = 4; makeList.addEventListener(Event.CHANGE, changeHandler); makeList.tabIndex = 1; addChild(makeList);
var optionLabel:Label = new Label(); optionLabel.htmlText = "Options"; optionLabel.move(optionList.x + 5, optionList.y - 20); optionLabel.autoSize = TextFieldAutoSize.LEFT; addChild(optionLabel);
//---------------------------
Array Filters (for DataProviders)
There was one filter for the text input already written in the livedocs on adobe.com. The others that I wrote were derived from this in order to derive more filters using the lists. Each filter is a different example of how a filter can be used.
/* Handler function for the TextInput component instance. This function converts the data provider (dp) to an array using the DataProvider class's toArray() method, and then filters the newly created array using the Array class's filter() method. Finally, the data grid's data provider property is set to the contents of the filtered array. */ function changeHandler(event:Event):void { //apply the name filter var arr:Array = dp.toArray(); var filteredArr:Array = arr.filter(textInputFilter); filteredArr = filteredArr.filter(makeFilter); filteredArr = filteredArr.filter(modelFilter); filteredArr = filteredArr.filter(yearFilter); filteredArr = filteredArr.filter(optionFilter); filteredArr = filteredArr.filter(hpFilter); myDataGrid.dataProvider = new DataProvider(filteredArr); trace(myDataGrid.dataProvider.length + " entries relative."); }
//comment provided by Adobe.com /* This function is called by the changeHandler() function and is used to filter the contents of an array. This function takes the current contents of the TextInput component instance and compares the contents against the current item in the array. If the strings match, the nameFilter() method returns true and the current item is added to the new array. If the strings do not match, the method returns false and the item is not added. */
/* My comment: It is able to add delimiters for the search. In order to have it be a keyword search there must be an explode function, called Split. This function returns an Array of all the words separated by the delimiter. There could also be special cases where 'OR' or 'AND' can be used as delimiters for more advanced searching.
for more information refer to: http://livedocs.adobe.com/flash/9.0/ActionScriptLangRefV3/String.html */ function textInputFilter(obj:Object, idx:int, arr:Array):Boolean { if(textInput.text == "") return true; //nothing to search
//This will make one string out of every piece of data from the XML file. Not from //the DataGrid. This just filters the DataProvider of the DataGrid to show results if(obj.Make) { var searchable:String = obj.Name;
//get all values from one row of values var newXMLList:XMLList = xmlDP.Cars.(ID == obj.ID).Options.Value; if(newXMLList) { var optionString:String = ""; //create one long string of options separated by spaces for each (var item:XML in newXMLList) optionString += " " + item; searchable += " " + optionString; }
if (searchable.toLowerCase().search(textInput.text.toLowerCase()) >= 0) { return true; // search value has been found! } } return false; //value not found }
function makeFilter(obj:Object, idx:int, arr:Array):Boolean { //if no items are selected in the list if(makeList.selectedItems.length == 0) return true; //if "ALL" is selected in the list if(makeList.selectedItems[0].data == "All") return true;
//if the row actually has something in the Make field if(obj.Make) { //take the selected items var searchTerms:Array = makeList.selectedItems; //and the Make var searchable:String = obj.Make; //and search the selected items for the Make! for each(var searchTerm:Object in searchTerms) if(searchable == searchTerm.data) return true; //search term found! //nothings found! } return false; }
function hpFilter(obj:Object, idx:int, arr:Array):Boolean { if(hpList.selectedItems.length <= 0) return true; // no items selected if(hpList.selectedItems[0].data == "All") return true;
if(obj.HP) { var hp:Number = obj.HP; var selections = hpList.selectedItems;
for (var i:int = 0; i { var txt1:String = selections[i].data; trace(hp + " : " + selections[i].data); switch (txt1) { case "Under 100" : if (hp < 100) return true; break; case "100 - 149" : if (hp >= 100 && hp < 150) return true; break; case "150 - 199" : if (hp >= 150 && hp < 200) return true; break; case "200 - 249" : if (hp >= 200 && hp < 250) return true; break; case "250 to 299" : if (hp >= 250 && hp < 300) return true; break; case "300 or more" : if (hp >= 300) return true; break;
case "All" : return true; } } } return false; // hp year field DNE }
/** These items aren't actually in the datagrid but they still can be searched for * using the options in the list. */ function optionFilter(obj:Object, idx:int, arr:Array):Boolean { if(optionList.selectedItems.length == 0) return true; //create a list of all key elements at each car's (search by ID) var newXMLList:XMLList = xmlDP.Cars.(ID == obj.ID).Options.Value;
if(newXMLList) { var optionString:String = "";
//create one long string of key elements separated by commas for each (var item:XML in newXMLList) { if (optionString != "") optionString += ", "; optionString += item; }
function yearFilter(obj:Object, idx:int, arr:Array):Boolean { /* //if no items are selected in the list if(yearList.selectedItems.length == 0) return true; //if "ALL" is selected in the list if(yearList.selectedItems[0].data == "All") return true;
//if the row actually has something in the year field if(obj.Year) { //take the selected items var searchTerms:Array = yearList.selectedItems; //and the year var searchable:String = obj.Year; //and search the selected items for the year! for each(var searchTerm:Object in searchTerms) if(searchable == searchTerm.data) return true; //search term found! } return false; //nothings found!
// will return true or false if any string in selections is in searchable function searchStringInArray(searchTerms:Array, searchable:String, omniString:String = "All"):Boolean { if(searchTerms.length > 0) { if(searchTerms[0].data == omniString) return true;
if(searchable) { for each(var searchTerm:Object in searchTerms) if(searchable.search(strHelper.trim(searchTerm.data," ")) >= 0) return true; //search term found! } } else return true; // nothing is selected return false; // nothing has been found }
how would you make a video play in flash when a cell is clicked in the datagrid? im guessing a function but would you know the exact code and where it gets placed in the AS??
Is there a way to work with several xml-
files; here for instance one for cars,
one for truck, one for motorbikes.
Depending on what the visitor is looking
for, the data inside the xml-files in question should be read into the
datagrid and the queries set for All.
The visitor can 'jump' between each of
the sections (eg by using a combobox
and the grid gets filled up with the
new data from the other xml-file
immediately.
Really top notch tutorial, congratulations. I'm trying to take it a bit further but I need a bit of help. How can I use the filter to load a corresponding image into a UIloader - I think the problem I'm having is that logically the image can only appear when the user has filtered down to only one unique result in the data grid, but I can't see what the value of the result would be in action script, or what to set an event listener for.
then I suppose it is a simple question of: -loader.source=that value+".png"
If you don't think this is the way please tell me what you think
Thanks in advance and keep up the good work - we need you!