Flex: Accessing SQLite entry using 'getItemAt()' - apache-flex

I have an array populated with input from an SQLite database. When I use this array as the dataprovider for a s:list then it will display all the objects in the array that I specify.
I would like to access some of the objects in the array, and have been trying to use getItemAt(). At the moment I can return the first object in the array, but changing the index (to getItemAt(1) for example), it returns nothing.
Ultimately I am aiming to populate an FX:Model with data entered elsewhere in the application and inserted into the SQLite database. The model then acts as the dataprovider for a pie chart. I can pass the first entry of the database on to the model/pie chart, but can't access others.
Some relevant sections of code are as follows. Trouble shooting tips would be appreciated.
[Bindable]
private var GHGsource:ArrayCollection;
And then:
GHGsource = new ArrayCollection();
}
for each ( var o:Object in event.data )
{
GHGsource.addItem(o);
}
FullList.dataProvider = GHGsource;
}
}
Model setup:
<fx:Declarations>
<fx:Model id="GHG" >
<data>
<row>
<input>Enteric methane</input>
<Value> {GHGsource.getItemAt(0).answer} </Value>
List setup:
<s:List id="FullList">
<s:itemRenderer>
<fx:Component>
<s:IconItemRenderer labelFunction="returnQuestion" messageFunction="returnAnswer">
<fx:Script>
<![CDATA[
private function returnQuestion(item:Object):String
{
return "Question: " + item.question;
}
private function returnAnswer(item:Object):String
{
var response:String = "";
if ( !item.answer || item.answer == "" )
{
response = "(no response)";
} else {
response = item.answer;
}
return response;
}
]]>
</fx:Script>
</s:IconItemRenderer>
</fx:Component>
</s:itemRenderer>
</s:List>
This application is based on the database structures set out in Daniel Koestler's survey ape application.
Perhaps an insight into how the s:List component accesses objects in an array will help here?
Additional detail:
Running in debug mode it seems as though that the objects are not binding correctly to the arraycollection.
warning: unable to bind to property 'xxx' on class 'Object' (class is not an IEventDispatcher)
I have tried to follow guides on the following links, but have not been sucessfull.
link1
link2

Ok, got it.
GHGsource = new ArrayCollection();
}
for each ( var o:Object in event.data )
{
GHGsource.addItem(o);
}
FullList.dataProvider = GHGsource;
}
}
Becomes:
[Bindable]
private var passingArray:Array;
And:
GHGsource = new ArrayCollection();
passingArray = new Array();
}
for each ( var o:Object in event.data )
{
GHGsource.addItem(o);
passingArray[o] = new ObjectProxy(event.data[o]);
}
// not needed as the list is no longer needed - FullList.dataProvider = GHGsource;
}
}
Then this works:
<fx:Declarations>
<fx:Model id="GHG" >
<data>
<row>
<input>Enteric methane</input>
<Value> {GHGsource.getItemAt(3).answer} </Value>

Related

What can be used instead of tree.dataProvider in adobe flex?

I have my .mxml as below:
<fx:Declarations>
<fx:XMLList id="data">
<node>
<node label="Inbox">
<node label="Marketing"/>
<node label="Product Management"/>
<node label="Personal"/>
</node>
<node label="Outbox">
<node label="Professional"/>
<node label="Private"/>
</node>
<node label="Spam">kushal</node>
<node label="Sent"/>
</node>
</fx:XMLList>
</fx:Declarations>
<mx:VBox>
<mx:Button label="Search by name" click="findByName()" />
<mx:Tree id="tree" width="500" height="500"
showRoot="false" dataProvider="{data}"
labelField="#label" />
</mx:VBox>
I'm trying to click findByName() on click of a button:
which is:
private function findByName():void
{
var mixml:XMLList = new XMLList(data);
var searchStr:String = "Outbox";
//child.expandChildrenOf(myXML[0], false);
//mixml=data;
searchResult= mixml.node.(#label==searchStr);
var xn:XML = searchResult[searchResultIndex];
Alert.show("xn"+ xn);
searchResultIndex = 0;
if (searchResult[searchResultIndex] != undefined)
var xmlNode:XML = searchResult[searchResultIndex];
while (xmlNode.parent() != null) {
Alert.show("xmlNodeBefore"+ xmlNode);
xmlNode = xmlNode.parent();
Alert.show("xmlNodeAfter"+ xmlNode);
//checkpoint 1
tree.expandItem(xmlNode, true, false);
tree.selectedItem = searchResult[searchResultIndex];
Alert.show(" tree.selectedItem " + tree.selectedItem );
}
}
If here instead of data
I use tree.dataProvider then this code doesn't work, can somebody tell me why?
Your code does not work when you use tree.dataProvider because it's of the type XMLListCollection, which is a wrapper on top of the XMLList object. Flex internally transforms the XMLList into a XMLListCollection, because XMLListCollection has capabilities of providing change notifications via events, which XMLList does not.
So in your code, if you use tree.dataProvider at the following line :
var mixml:XMLList = new XMLList(tree.dataProvider);
You are actually trying to type cast and convert an XMLListCollection into a XMLList, which is why it does not work.
Interestingly, the XMLListCollection has public properties (like most collections in Flex) which can be used to access the underlying XMLList source from which it is made up of. So, to access the data from the tree, one correct way would be:
var mixml:XMLList = new XMLList(tree.dataProvider.source);
I have tried this in your code and it seems to do the same job as your above code.
You can read more about this here :
http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/mx/collections/XMLListCollection.html#source
Hope this explanation works and it answers your question overall.
Cheers.
If you check out the setter for Tree.dataProvider you can see that it modifies/translates the value that get's passed in.
This line of code will cast to null because dataProvider changes xml based data into XmlListCollection.
var mixml:XMLList = new XMLList(data);
Here is the setter for reference.
override public function set dataProvider(value:Object):void
{
// in all cases save off the original
if (_rootModel)
_rootModel.removeEventListener(
CollectionEvent.COLLECTION_CHANGE,
collectionChangeHandler);
// handle strings and xml
if (typeof(value)=="string")
value = new XML(value);
else if (value is XMLNode)
value = new XML(XMLNode(value).toString());
else if (value is XMLList)
value = new XMLListCollection(value as XMLList);
if (value is XML)
{
_hasRoot = true;
var xl:XMLList = new XMLList();
xl += value;
_rootModel = new XMLListCollection(xl);
}
//if already a collection dont make new one
else if (value is ICollectionView)
{
_rootModel = ICollectionView(value);
if (_rootModel.length == 1)
_hasRoot = true;
}
else if (value is Array)
{
_rootModel = new ArrayCollection(value as Array);
}
//all other types get wrapped in an ArrayCollection
else if (value is Object)
{
_hasRoot = true;
// convert to an array containing this one item
var tmp:Array = [];
tmp.push(value);
_rootModel = new ArrayCollection(tmp);
}
else
{
_rootModel = new ArrayCollection();
}
//flag for processing in commitProps
dataProviderChanged = true;
invalidateProperties();
}

ArayList as dataProvider for a List: The index 0 is out of range 0

Does anybody please have an idea, why do I get the runtime error:
RangeError: Error #1125: The index 0 is out of range 0.
........
at Popup/update()[Popup.mxml:80]
at PopupTest/showPopup()[PopupTest.mxml:45]
at PopupTest/___btn_click()[PopupTest.mxml:52]
when calling the function:
private function showPopup(event:MouseEvent):void {
_popup.update(new Array('Pass' ,
'6♠', '6♣', '6♦', '6♥', '6 x',
'7♠', '7♣', '7♦', '7♥', '7 x',
'8♠', '8♣', '8♦', '8♥', '8 x',
'9♠', '9♣', '9♦', '9♥', '9 x',
'10♠', '10♣', '10♦', '10♥', '10 x'), true, 80);
}
As if my _list would have no entries at all (but why? I do assign _data.source=args) and thus the _list.ensureIndexIsVisible(0) call would fail at the line 80:
<?xml version="1.0" encoding="utf-8"?>
<s:Panel xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx"
width="220" height="200"
initialize="init(event)">
<fx:Script>
<![CDATA[
import mx.collections.ArrayList;
import mx.events.FlexEvent;
import mx.utils.ObjectUtil;
private static const FORCE:uint = 20;
[Bindable]
private var _data:ArrayList = new ArrayList();
private var _timer:Timer = new Timer(1000, 120);
private function init(event:FlexEvent):void {
_timer.addEventListener(TimerEvent.TIMER, timerUpdated);
_timer.addEventListener(TimerEvent.TIMER_COMPLETE, timerCompleted);
}
public function close():void {
_timer.reset();
_data.source = null;
visible = false;
}
private function timerUpdated(event:TimerEvent=null):void {
var seconds:int = _timer.repeatCount - _timer.currentCount;
title = 'Your turn! (' + seconds + ')';
// show panel for cards too
if (seconds < FORCE)
visible = true;
}
private function timerCompleted(event:TimerEvent=null):void {
title = 'Your turn!';
close();
}
public function update(args:Array, bidding:Boolean, seconds:int):void {
if (seconds <= 0) {
close();
return;
}
// nothing has changed
if (ObjectUtil.compare(_data.source, args, 0) == 0)
return;
_data.source = args;
if (args == null || args.length == 0) {
close();
return;
}
if (seconds < FORCE || bidding)
visible = true;
_timer.reset();
title = 'Your turn! (' + seconds + ')';
_list.ensureIndexIsVisible(0); // the line 80
_timer.repeatCount = seconds;
_timer.start();
}
]]>
</fx:Script>
<s:VGroup paddingLeft="10" paddingTop="10" paddingRight="10" paddingBottom="10" gap="10" width="100%" height="100%">
<s:List id="_list" dataProvider="{_data}" width="100%" height="100%" fontSize="24" itemRenderer="RedBlack" />
</s:VGroup>
</s:Panel>
the reason
You are adding the new array allright, but then the List starts creating ItemRenderers based on the items that are in that array. This takes some time and happens asynchronously. In the meantime you're saying "show me item 1", but the ItemRenderer for item 1 doesn't exist yet. It will very soon, but not right now. That's why you get an indexoutofrange error.
the solution
You have to be sure the List is done creating ItemRenderers before you call that method. The easiest way to solve this situation - though definitely not the cleanest - is to just wait until the next render cycle by using the infamous callLater().
callLater(_list.ensureIndexIsVisible, [0]);
This essentially saying: wait for the next render cycle and then call ensureIndexIsVisible() on _list with parameter 0.
(On a side note: if you really only want index 0 this whole thing is rather pointless, because I think a List scrolls back to the top when its dataprovider is changed anyway)
a cleaner solution
You can listen on the List for the RendererExistenceEvent#RENDERER_ADD event. This will be dispatched whenever a new ItemRenderer was added to the list and it holds a reference to the item's index in the List, the data and the ItemRenderer itself. However in your case we only need the 'index'. Whenever an ItemRenderer is added at index 0 we'll scroll back to the top:
_list.addEventListener(RendererExistenceEvent.RENDERER_ADD, onRendererAdded);
private function onRendererAdded(event:RendererExistenceEvent):void {
if (event.index == 0) myList.ensureIndexIsVisible(0);
}
This will immediately scroll to the top when the first ItemRenderer is added and doesn't need to wait until all of them are ready.

Flex 3: Can anybody see why this dictionary isn't working?

So, in my main mxml, i have a variable defined as such:
[Bindable] public var studentsListDict:Dictionary = new Dictionary;
I also have the following imported:
import flash.utils.Dictionary;
I then have an HTTPService that imports an XML file:
<mx:HTTPService id="studentsHttp" url="students.xml" resultFormat="e4x" makeObjectsBindable="true" result="createStudentsCollection(event)" />
The createStudentsCollection function is as follows:
private function createStudentsCollection(e:ResultEvent):void
{
var xmlList:XMLList = XML(e.result).student;
var dupString:String = "|";
var tempArray:Array = new Array;
studentsListDict = new Dictionary;
for (var i:int = 0; i < xmlList.length(); i++)
{
if (dupString.indexOf(String("|" + xmlList[i].name) + "|") == -1)
{
tempArray = new Array;
tempArray[0] = xmlList[i].name.#id;
tempArray[1] = xmlList[i].name;
tempArray[2] = xmlList[i].year;
tempArray[3] = xmlList[i].track;
studentsListAC.addItem(tempArray);
studentsListDict[tempArray[0]] = tempArray;
dupString += "|" + xmlList[i].name + "|";
getLen(studentsListDict);
}
}
}
Then, to ensure the items were correctly put into the dictionary, i have the following function:
public static function getLen(d:Dictionary):int
{
var i:int = 0;
for (var key:Object in d)
{
Alert.show(String(key + "\n" + d[key]));
i++;
}
return i;
}
This creates pop up alerts that show that everything was loaded correctly into the dictionary.
Later on, in a child, I call a function that tries to use the dictionary, and i get a return of "undefined".
Here's the function that searches based on key, and returns a value from the array within:
public function getStudentName(sID:Number):String
{
return studentsListDict[sID][1];
}
Unfortunately, the getStudentName function simply returns undefined every time.
If anybody can see something I'm missing, it'd be greatly appreciated.
Thanks,
Brds
EDIT
It wasn't working b/c you can't have numbers as keys in a dictionary. Simply casting them to a string during the declaration and look up seems to work just fine.
Here is some documentation on dictionary keys..
It looks like you're code is setting it as a string and then accessing it as a number. I suspect that is the root of your problem You can try something like this:
public function getStudentName(sID:Number):String
{
return studentsListDict[sID.toString()][1];
}
It is actually perfectly acceptable to use numbers as Keys to a Dictionary. The Dictionary apparently turns the number and a string value of that number to the same key. Here is a sample:
<?xml version="1.0" encoding="utf-8"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx" minWidth="955" minHeight="600" creationComplete="application1_creationCompleteHandler(event)">
<fx:Script>
<![CDATA[
import mx.events.FlexEvent;
public var dict : Dictionary;
protected function application1_creationCompleteHandler(event:FlexEvent):void
{
dict = new Dictionary();
dict["0"] = "hi";
dict["4"] = "hola";
dict["17"] = "bye";
dict["32"] = "adios";
dict[32] = "adios 2";
dict[3.2] = "adios 3";
dict[50] = "Audio ";
dict["50"] = "Audio 2";
trace(dict["0"]);
trace(dict["4"]);
trace(dict["17"]);
trace(dict["32"]);
trace(dict[32]);
trace(dict[3.2]);
trace(dict[50]);
trace(dict["50"]);
}
]]>
</fx:Script>
</s:Application>
I think it's because getStudentName is using a Number as a key, while createStudentCollection is using a string. In this case, because the keys are numbers/strings, you can simply use an Object: var studentsListDict:Object = {}; — it will automatically coerce all the keys to strings.
Also, as an asside: new Dictionary(); more standard, and it's better form in ActionScript to use [] (eg, var foo:Array = []) than new Array(). That way you can put stuff in the array at the same time:
var tempArray:Array = [
xmlList[i].name.#id,
xmlList[i].name,
…
];

Compiler warns me that binding will not work but why I run the application it does work!

The Flex application below generates the compiler warning: Data binding will not be able to detect assignments to 'dp'. This seems correct since the variable 'dp' is not a bindable property (there is no [Bindable] metadata tag). I have added a button which appends items to the back of 'dp' when it is clicked. Although the compiler warns me that I will not see changes to 'dp', the list shows the new item every time the button is clicked!
I do not understand why I can see new items appear in the list. Can someone explain why this still works although 'dp' is not bindable?
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical" minWidth="955" minHeight="600">
<mx:Script>
<![CDATA[
import mx.collections.ArrayCollection;
import mx.rpc.events.ResultEvent;
private var arrayData:Array = [
{name:"banana", cat:"fruit", cost:0.99},
{name:"bread", cat:"bakery", cost:1.99},
{name:"orange", cat:"fruit", cost:0.52},
{name:"donut", cat:"bakery", cost:0.33},
{name:"apple", cat:"fruit", cost:1.05}];
private var dp:ArrayCollection = new ArrayCollection(arrayData);
private function onButtonClick(event:MouseEvent):void
{
var obj:Object = new Object();
obj.name="test";
obj.cat="testcat";
obj.cost=666;
dp.addItem(obj);
}
]]>
</mx:Script>
<mx:HorizontalList dataProvider="{dp}" labelField="name" columnWidth="100" width="80%" height="50"/>
<mx:Button label="Click me" click="onButtonClick(event)" />
The compiler is correct in it's warning.
The compiler is warning you that assignments that change the value of dp from the initial ArrayCollection you specified to another ArrayCollection won't be detected.
However, if you leave the value of dp alone, and only change the contents of it, then your <HorizontalList /> will continue to work.
This may seem trivial, but it's an important distinction, and one that can lead to some very confusing bugs further down the road in your application.
Assignments to the variable dp will not be detected. However, changes to the ArrayCollections list will, because they dispatch a CollectionChangeEvent.
eg:
private var dp:ArrayCollection = new ArrayCollection();
private function test():void
{
// Here, we don't change the value of dp directly,
// instead we just modify it's list.
// The DataGroup will show the strings One,Two
dp.addItem("One")
dp.addItem("Two")
// Here, we change the actual value of dp, by assigning a
// new ArrayCollection to it.
// This change would not be detected, and the list would continue to show
// the contents of the previous value.
// Additionally, the label will show the string "Length: 2",
// even though the length is clearly now 3.
dp = new ArrayCollection();
dp.addItem("Tahi");
dp.addItem("Rua");
dp.addItem("Toru");
}
<s:DataGroup dataProvider="{dp}" />
<s:Label text="Length: {dp.length}" />
Try to use:
[Bindable("__NoChangeEvent__")]
private var dp:ArrayCollection = new ArrayCollection(arrayData);
What about adding elements in list see the code of ListBase:
public function set dataProvider(value:Object):void
{
if (collection)
{
collection.removeEventListener(CollectionEvent.COLLECTION_CHANGE, collectionChangeHandler);
}
if (value is Array)
{
collection = new ArrayCollection(value as Array);
}
else if (value is ICollectionView)
{
collection = ICollectionView(value);
}
else if (value is IList)
{
collection = new ListCollectionView(IList(value));
}
else if (value is XMLList)
{
collection = new XMLListCollection(value as XMLList);
}
else if (value is XML)
{
var xl:XMLList = new XMLList();
xl += value;
collection = new XMLListCollection(xl);
}
else
{
// convert it to an array containing this one item
var tmp:Array = [];
if (value != null)
tmp.push(value);
collection = new ArrayCollection(tmp);
}
// get an iterator for the displaying rows. The CollectionView's
// main iterator is left unchanged so folks can use old DataSelector
// methods if they want to
iterator = collection.createCursor();
collectionIterator = collection.createCursor(); //IViewCursor(collection);
// trace("ListBase added change listener");
collection.addEventListener(CollectionEvent.COLLECTION_CHANGE, collectionChangeHandler, false, 0, true);
clearSelectionData();
var event:CollectionEvent = new CollectionEvent(CollectionEvent.COLLECTION_CHANGE);
event.kind = CollectionEventKind.RESET;
collectionChangeHandler(event);
dispatchEvent(event);
itemsNeedMeasurement = true;
invalidateProperties();
invalidateSize();
invalidateDisplayList();
}
So take a look at line:
collection.addEventListener(CollectionEvent.COLLECTION_CHANGE, collectionChangeHandler, false, 0, true);

How to access the array elements

var count:uint = 0;
var textInputs:Array /* of TextInputs */ = [];
for(var i:String in columnsData){
textInputs[count] = new TextInput();
addChild(textInputs[count]);
count++;
}
here how can i access the first, second of Array of textinputs in the form of string or any thing to further proceed
I am not sure if I understood what your question is, but you're not setting the value for text inputs:
I prefer it the following way:
//make the array an instance variable instead of local var
//so that it can be accessed from other functions if required.
public var textInputs:Array = [];
for(var str:String in columnsData)
{
var ti:TextInput = new TextInput();
ti.text = str;
addChild(ti);
textInputs.push(ti);
}
You can access the values using textInputs[0].text etc.
if(textInput != null){
for(var i:Number = 0; i < textInputs.length; i++)
trace(textInputs[i].text);
}
(getChildAt(0) as TextInput).text //first
(getChildAt(1) as TextInput).text //second
If your sprite contains something other than TextInput at indices 0 & 1 you'll get null reference exception.
<?xml version="1.0"?>
<!-- dpcontrols\UseIList.mxml -->
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx"
initialize="initData();">
<s:layout>
<s:VerticalLayout/>
</s:layout>
<fx:Script>
<![CDATA[
import mx.collections.*;
// The data provider is an Array of Strings
public var myArray:Array = ["AZ", "MA", "MZ", "MN", "MO", "MS"];
// Declare an ArrayList that represents the Array.
[Bindable]
public var myAL:ArrayList;
//Initialize the ArrayList.
public function initData():void {
myAL = new ArrayList(myArray);
}
// The function to change the collection, and therefore
// the Array.
public function changeCollection():void {
// Get the original collection length.
var oldLength:int=myAL.length;
// Remove the invalid first item, AZ.
var removedItem:String=String(myAL.removeItemAt(0));
// Add ME as the second item. (ILists used 0-based indexing.)
myAL.addItemAt("ME", 1);
// Add MT at the end of the Array and collection.
myAL.addItem("MT");
// Change the third item from MZ to MI.
myAL.setItemAt("MI", 2);
// Get the updated collection length.
var newLength:int=myAL.length;
// Get the index of the item with the value ME.
var addedItemIndex:int=myAL.getItemIndex("ME");
// Get the fifth item in the collection.
var index4Item:String=String(myAL.getItemAt(4));
// Display the information in the TextArea control.
ta1.text="Start Length: " + oldLength + ". New Length: " +
newLength;
ta1.text+=".\nRemoved " + removedItem;
ta1.text+=".\nAdded ME at index " + addedItemIndex;
ta1.text+=".\nThe item at index 4 is " + index4Item + ".";
// Show that the base Array has been changed.
ta1.text+="\nThe base Array is: " + myArray.join();
}
]]>
</fx:Script>
<s:ComboBox id="myCB" dataProvider="{myAL}"/>
<s:TextArea id="ta1" height="75" width="300"/>
<s:Button label="rearrange list" click="changeCollection();"/>
</s:Application>

Resources