Search function throwing error #1009 - null object reference - apache-flex

Simple to understand problem, hoping for similarly easy solution:
When I type in my search query, I sometimes get the "Error #1009 Cannot access a property or method of a null object reference" on the dataField = new ArrayCollection(result.data); line.
AS3:
private function getSearch():void
{
//status = "Loading data";
selectStmt = new SQLStatement();
selectStmt.sqlConnection = conn;
var sql:String = "SELECT [Index], Title, CAST(Picture AS ByteArray) AS Picture FROM Data WHERE Title LIKE #searchTarget";
selectStmt.parameters["#searchTarget"] = "%" + searchTarget + "%";
selectStmt.text = sql;
selectStmt.addEventListener(SQLEvent.RESULT, selectResult2);
selectStmt.addEventListener(SQLErrorEvent.ERROR, selectError);
selectStmt.execute();
targetRecordId = pngIndex;
}
private function selectResult2(event:SQLEvent):void
{
//status = "Data loaded";
selectStmt.removeEventListener(SQLEvent.RESULT, selectResult);
selectStmt.removeEventListener(SQLErrorEvent.ERROR, selectError);
var result:SQLResult = selectStmt.getResult();
dataField = new ArrayCollection(result.data);
if (result.data != null) {
pngIndex = result.data.Index;
pngTitle = result.data.Title;
pngByteArray = result.data.Picture;
targetRecordId = pngIndex;
}
}
mxml:
<s:List id="myList"
x="0" y="40"
width="100%" height="100%"
labelField="Title"
dataProvider="{dataField}"
change="myList_changeHandler(event)"
>
</s:List>
Things I've tried (including the permutations of these solutions):
1) Moving the error code inside the SelectResult2 method
2) Adding an if (result.data == null) method
3) Using Array instead of ArrayCollection (I found someone who found that this worked for their project on some forum somewhere)
4) Adding a timer function to try and limit how often the db will get searched. (although I think this was the best solution, I think I will have to try this one again)
Note, the error occurs, from what I can tell, largely as a result of entering search characters too quickly
Thank you for your help.

1. Use Responders
You're using the same event handler function for each call to the database. Hence when the function gets executed it might have lost the reference to its original statement (because a new call was initiated in the meantime). That's why you get those nullpointers on the result object.
So ditch the event listeners and use a Responder instead, like so:
var stmt:SQLStatement = new SQLStatement();
stmt.sqlConnection = connection;
stmt.text = query;
var resp:Responder = new Responder(onResult, onFail);
stmt.execute(-1, resp);
The 'onResult' function will take a SQLResult object as a single argument and you no laonger have to reference the original statement to get that result.
2. Delay
Use a Timer, yes. But don't use it to make the database call on an interval. You wouldn't know when to stop making those calls, would you? Use it to detect whether the user is still typing or not: when it takes longer than - say - 300ms. for one KeyboardEvent.KEY_UP to come after another, then fire the call to the database. Once.
3. Use a threshold
Don't start querying with just one letter. Use a threshold of at least 2-3 characters. You won't get any relevant suggestions with one character anyway. How high the threshold should be, depends on the size of the collection to search.
4. Filter an ArrayCollection
Ignore all of the above. An easier solution might be to just load all the records you want to search into an ArrayCollection and use filterFunction to show only the records that match a certain String. This method will do fine as long as the collection is not gigantic (like tens of thousands of records) and is way faster to implement.

first, how about
if(result == null)
return;
Also, the timer is a good idea, usually about half a second, that way your database isn't being hammered while the user is typing.
private var timer:Timer = new Timer(500, 1);
protected function textChangeHandler():void{
timer.reset();
timer.addEventListener(TimerEvent.Timer, getSearch);//Could be moved to a creation complete
timer.start();
}
error handle and remove/add events as appropriate. You could also disable the search until the last one has completed, which in combination with the timer shouldn't be intrusive to the user.

Related

HierarchicalCollectionView: One time sort?

I have an AdvancedDataGrid that relies on a HierarchicalCollectionView as it's dataProvider. What I'd like to do is sort the data when it is first loaded, but then disable the sort so that anything that is added after the initial load doesn't cause the grid to auto-sort again. I tried something like this:
this._myDataProvider = new HierarchicalCollectionView(
new HierarchicalData(this._model.rootTasks));
var mySort:Sort = new Sort();
mySort.fields = [new SortField("startDate")];
this._tasksDataProvider.sort = taskSorting;
this._tasksDataProvider.refresh();
this._tasksDataProvider.sort = null;
But setting the sort to null just leaves the data unsorted. I guess what I'm asking is: how can I sort the underlying hierarchical data since it seems setting the sort property will keep it dynamically sorting. Thanks for any help you can provide.
Personally, I would change the sort order when you're getting the data. Either it's done on the server side or when you parse the data (ie. in your model). You can do a one time sort using Array with sortOn.
you can
1. sort the original data with sort function,
2. clone content and put it to a new collection with no sort (be careful do and make a manual clone),
3. just use the new data collection.
I had the same kind of problem until I realized that the sorting with Sort object does not change the "physical" ordering of the items within the Collection, so when you remove the Sort, the next refresh reverts the view to the actual "physical" ordering.
Similarily as stated above, I solved by it by cloning the sub-collections into sorted order this way:
public static function buildPositionSort():Sort
{
var dataSortField:SortField = new SortField();
dataSortField.name = "position";
dataSortField.numeric = true;
dataSortField.descending = false;
var sort:Sort = new Sort();
sort.fields = [ dataSortField ];
return sort;
}
/**
* This method is used to create a clone of ArrayCollection, because sorting does not
* actually change the physical ordering of the items.
*/
public static function createSortedArrayCollectionCopy(source:ArrayCollection):ArrayCollection
{
var target:ArrayCollection = new ArrayCollection();
source.sort = buildPositionSort();
source.refresh();
for each (var item:Object in source)
{
if (item.children != null) item.children = createSortedArrayCollectionCopy(item.children);
target.addItem(item);
}
return target;
}

Factory Method implementation in actionscript

Hey folks, i ve got this issue implementing the Factory method.
Following is the snippet of the the main chart class which calls ChartFactory's method to attain the proper object. I Type Cast chartobject so as to be able to call the Show method;i m apprehensive about that as well.
container = new VBox();
container.percentWidth = 100;
container.percentHeight = 100;
super.media.addChild(container);
chartObject = new ChartBase();
chartObject = ChartFactory.CreateChartObject(chartType);
IChart(chartObject).Show(o);
container.addChild(chartObject);
legend = new Legend();
legend.dataProvider = IChart(chartObject);
container.addChild(legend);
Following is the snippet of ChartFactory's method:
public static function CreateChartObject(subType:String):ChartBase
{
switch(subType)
{
case ChartFactory.AREA_CHART:
return new AreaCharts();
break;
case ChartFactory.COLUMN_CHART:
return new ColumnCharts();
break;
case ChartFactory.PIE_CHART:
return new PieCharts();
break;
default:
throw new ArgumentError(subType + ": Chart type is not recognized.");
}
}
And following is Show method of one of the several Charts type classes: AreaCharts, PieCharts etc. All of which implements IChart Interface.
public function Show(o:ObjectProxy):void
{
var grids:GridLines;
var stroke:SolidColorStroke;
var horizontalAxis:CategoryAxis;
var verticalAxis:LinearAxis;
var horizontalAxisRenderer:AxisRenderer;
var verticalAxisRenderer:AxisRenderer;
grids = new GridLines();
if(WidgetStylesheet.instance.LineChart_ShowGrid)
grids.setStyle("gridDirection", "both");
else
grids.setStyle("gridDirection", "");
stroke = new SolidColorStroke(WidgetStylesheet.instance.LineChart_GridLineColor, WidgetStylesheet.instance.LineChart_GridLineThickness);
grids.setStyle("horizontalStroke", stroke);
grids.setStyle("verticalStroke", stroke);
horizontalAxis = new CategoryAxis();
horizontalAxis.categoryField = o.LargeUrl.Chart.xField;
horizontalAxis.title = o.LargeUrl.Chart.xAxisTitle.toString();
verticalAxis = new LinearAxis();
verticalAxis.title = o.LargeUrl.Chart.yAxisTitle.toString();
horizontalAxisRenderer = new AxisRenderer();
horizontalAxisRenderer.axis = horizontalAxis;
horizontalAxisRenderer.setStyle("tickLength", 0);
horizontalAxisRenderer.setStyle("showLine", false);
horizontalAxisRenderer.setStyle("showLabels", true);
horizontalAxisRenderer.setStyle("fontSize", WidgetStylesheet.instance.ComputeChartAxisFontSize(o.HeadlineFontSize));
verticalAxisRenderer = new AxisRenderer();
verticalAxisRenderer.axis = verticalAxis;
verticalAxisRenderer.setStyle("tickLength", 0);
verticalAxisRenderer.setStyle("showLine", false);
verticalAxisRenderer.setStyle("fontSize", WidgetStylesheet.instance.ComputeChartAxisFontSize(o.HeadlineFontSize));
this.series = this.m_createSeries(o);
this.horizontalAxis = horizontalAxis;
this.horizontalAxisRenderers = [horizontalAxisRenderer];
this.verticalAxis = verticalAxis;
this.verticalAxisRenderers = [verticalAxisRenderer];
this.backgroundElements = [grids];
}
I'm afraid that there is more than one issue with this code. Unfortunately it is not obvious why your chart doesn't show up so you may apply some of advices below and use debugger to analyse the issue.
There is no point in creating ChartBase instance if you are going to change value of chartObject reference in the next line
chartObject = new ChartBase();
chartObject = ChartFactory.CreateChartObject(chartType);
If the API of your charts is IChart your factory should return IChart instead of casting.
public static function CreateChartObject(subType:String):IChart
Make sure that you are returning instances of the correct class from the factory. i.e. that you are returning your subclass of standard PieChart. Generally it's not the best idea to extend the class keeping the same name and just changing the package.
Once again, if you are not sure if the program enters some function use the Flash Builder debugger to check this. I can't imagine development without debugger.
Some thoughts:
you call the Show method, pass it some object but nowhere in that method is any child added to a displayObject. What exactly is Show supposed to do?
a lot of member variables in your classes start with UpperCase. The compiler can easily confuse those with class names, in case your classes are named the same. Bad practice to start variable and function names with capitals.
If your casting an instance to another class or interface fails, you will get a runtime error. Those are easy to debug using the Flash Builder debugger.
Hey ppl..
i found out wat wnt wrng..as olwys it wa "I".
I ve a habit of mkin mock ups secluded from the main project n dn integrate it. So in mock up i hd used an xml whch hd a format slightly diff dn d one being used in the main project.
N i hd a conditional chk to return from the prog if certain value doesnt match, n due to faulty xml i did'nt.
So this more a lexical error than a logical one.
Sorry n Thanx evryone for responding.

How to handle calls to multiple functions in the same web service?

This is a super newbie question on Flex. While I am a seasoned programmer, this is my first ever Flex application; please bear with me for my Flex codes.
I have a web service written in ColdFusion. In this web service, there are two functions; one is to return all the questions in a quiz and the other one is to return all the answer selections to the questions in a quiz.
[Bindable]
private var questionArray:ArrayCollection;
private var cfquiz:RemoteObject;
private function loadQuestions():void {
currentQuestionCounter = 0;
btnPrev.enabled = false;
btnNext.enabled = false;
cfquiz = new RemoteObject("ColdFusion");
cfquiz.source = "CFCertExam.cfquiz";
cfquiz.addEventListener(ResultEvent.RESULT, resultHandler);
}
private function resultHandler(event:ResultEvent):void {
questionArray = event.result as ArrayCollection;
txt1Questions.htmlText = questionArray.getItemAt(currentQuestionCounter).Question_Text;
btnNext.enabled = true;
}
I have the codes above. loadQuestions is called at creationComplete to retrieve the questions. Things are working fine. What I want to do is to call another function within the same web service, returnAnswers, to return the answer options for a question. Since I have cfquiz associated to the web service already, I was using cfquiz to call returnAnswers. However, there is an event listener associated to cfquiz already, resultHandler is being called when returnAnswers comes back with the results.
My questions are, first, is it possible to check which function returns the results within resultHandler? If so, how? And second, what is the best way to handle calls to multiple functions within the same web service?
Thanks in advance,
Monte
Yes you can. You need to specify a handler function for each method which in turn calls a different webservice.
The better way to do this is using AsyncToken and AsyncResponder instead of addEventListener, as following code.
tokenA = cfquiz.methodA();
tokenA.addResponder(new AsyncResponder(onResultForMethodA, onFaultMethodA));
tokenB = cfquiz.methodA();
tokenB.addResponder(new AsyncResponder(onResultForMethodB, onFaultMethodB));
tokenC = cfquiz.methodA();
tokenC.addResponder(new AsyncResponder(onResultForMethodC, onFaultMethodC));
or
tokenA = cfquiz.methodA();
var responderA:IResponder = new AsyncResponder(onResult, onFault, "methodA");
tokenB = cfquiz.methodB();
var responderB:IResponder = new AsyncResponder(onResult, onFault, "methodB");
tokenA.addResponder(responderA);
tokenB.addResponder(responderB);
private function onResult(evt:ResultEvent, token:Object):void {
if(token == "methodA" ) {
//logic for methodA
}
if(token == "methodB" ) {
//logic for methodB
}
}
I'm a little confused as I can't see where you are calling the actual web service function, for example from these examples, I am expecting to see:
cfquiz = new RemoteObject("ColdFusion");
cfquiz.source = "CFCertExam.cfquiz";
cfquiz.addEventListener(ResultEvent.RESULT, resultHandler);
cfquiz.myCFCFunctionCall(); /* where is this? */
Anyway, AFAIK you can create a new instance of the remote object and set that up to have it's own event listener.
Hope that helps.

Is asp.net caching my sql results?

I have the following method in an App_Code/Globals.cs file:
public static XmlDataSource getXmlSourceFromOrgid(int orgid)
{
XmlDataSource xds = new XmlDataSource();
var ctx = new SensusDataContext();
SqlConnection c = new SqlConnection(ctx.Connection.ConnectionString);
c.Open();
SqlCommand cmd = new SqlCommand(String.Format("select orgid, tekst, dbo.GetOrgTreeXML({0}) as Subtree from tblOrg where OrgID = {0}", orgid), c);
var rdr = cmd.ExecuteReader();
rdr.Read();
StringBuilder sb = new StringBuilder();
sb.AppendFormat("<node orgid=\"{0}\" tekst=\"{1}\">",rdr.GetInt32(0),rdr.GetString(1));
sb.Append(rdr.GetString(2));
sb.Append("</node>");
xds.Data = sb.ToString();
xds.ID = "treedata";
rdr.Close();
c.Close();
return xds;
}
This gives me an XML-structure to use with the asp.net treeview control (I also use the CssFriendly extender to get nicer code)
My problem is that if I logon on my pc with a code that gives me access on a lower level in the tree hierarchy (it's an orgianization hierarchy), it somehow "remembers" what level i logon at. So when my coworker tests from her computer with another code, giving access to another place in the tree, she get's the same tree as me.
(The tree is supposed to show your own level and down.)
I have added a html-comment to show what orgid it passes to the function, and the orgid passed is correct. So either the treeview caches something serverside, or the sqlquery caches it's result somehow...
Any ideas?
Sql function:
ALTER function [dbo].[GetOrgTreeXML](#orgid int)
returns XML
begin RETURN
(select org.orgid as '#orgid',
org.tekst as '#tekst',
[dbo].GetOrgTreeXML(org.orgid)
from tblOrg org
where (#orgid is null and Eier is null) or Eier=#orgid
for XML PATH('NODE'), TYPE)
end
Extra code as requested:
int orgid = int.Parse(Session["org"].ToString());
string orgname = context.Orgs.Where(q => q.OrgID == orgid).First().Tekst;
debuglit.Text = String.Format("<!-- Id: {0} \n name: {1} -->", orgid, orgname);
var orgxml = Globals.getXmlSourceFromOrgid(orgid);
tvNavtree.DataSource = orgxml;
tvNavtree.DataBind();
Where "debuglit" is a asp:Literal in the aspx file.
EDIT:
I have narrowed it down. All functions returns correct values. It just doesn't bind to it. I suspect the CssFriendly adapter to have something to do with it.
I disabled the CssFriendly adapter and the problem persists...
Stepping through it in debug it's correct all the way, with the stepper standing on "tvNavtree.DataBind();" I can hover the pointer over the tvNavtree.Datasource and see that it actually has the correct data. So something must be faulting in the binding process...
I would normally suspect the issue is with orgid that is getting passed in to your method, but you say that you have checked to make sure the right code is being passed. Just to confirm, show us the code that assigns the value to that.
Additionally, there are a few problems with your code, SQL injection risk being one of them. orgid is an int, offering some protection, but if at some point orgid is changed to require characters by your organization, a developer may just change the data type to string, suddenly opening up the app to SQL injection. You should remove the String.Fotmat, and use a parameterized query instead.
I found the problem. The XmlDataSource component has a cache function, which by default is enabled. When I disabled this, everything works nicely.

ActionScript 3 Object to name value string

In a Flex application I am trying to turn an Object into a QueryString such as name1=value1&name2=value2... But I am having trouble getting the names of the Objects children. How do I enumerate the names instead of the values?
Thanks
I'm guessing you're doing a for each(in) loop. Just do a normal for(in) loop and you'll get the names instead of the values:
for(var name:String in obj) {
var value:* = obj[name];
// do whatever you need
}
Ok, first off, if you need that query string to actually query a server, you don't really need to get it yourself as this code will query the server for you
protected function callSerivce():void
{
var o:Object = new Object();
o.action = "loadBogusData";
o.val1 = "dsadasd";
service.send(o);
}
<mx:HTTPService id="service" url="http://www.somewhere.com/file.php" method="GET" showBusyCursor="true"/>
Will make a call to the server like this: http://www.somewhere.com/file.php?action=loadBogusData&val1=dsadasd
But in case you really want to analyze the object by hand, try using ObjectUtil.getClassInfo, it returns a lot of information including all the fields (read more on LiveDocs).

Resources