Retrieve Last 2 Results from Service Call in Flex AS3 PHP - apache-flex

I'm a Flex newbie and I've searched both StackOverflow and Google'd but can't seem to figure out this (simple) problem with Flex/ActionScript 3 Asynchronous programming. I have a PHP service (Zend) that inserts a row into the table. What I would like to do is be able to call the service twice consecultively with different row values, and get back the new IDs (primary keys) returned by the service. Then I display the new IDs as 2 Alerts.
The problem is when I can the service twice in a row, I only get an Alert.show() for the last and most recent service call. I don't know how to access the result of the first.
I recognize I can reconfigure my Flex code and PHP service to accept an array of objects and just send a single PHP service call for both and receive back an array object with the results, but I'm also trying to get a better understanding in general how AsyncToken is working and how to access old results that I need. Is each use of {serviceResult}.token overwriting my previous result?
<s:NavigatorContent xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx"
// ...
Here is my related code where I want to set up 2 default folders for new users "Home" and "temp":
// imports
import mx.collections.ArrayCollection;
import mx.controls.Alert;
import mx.events.CloseEvent;
import mx.events.FlexEvent;
import mx.rpc.AsyncToken;
import mx.rpc.IResponder;
import mx.rpc.events.FaultEvent;
import mx.rpc.events.ResultEvent;
import mx.rpc.remoting.mxml.RemoteObject;
import mx.rpc.Responder;
import spark.events.IndexChangeEvent;
import valueObjects.Folders;
// When the user clicks "Save" button, 2 new folders are created for a new user:
protected function pendingUserSaveButton_clickHandler(event:MouseEvent):void
{
// Create 2 initial user folders: Home and temp-taiwan
var t1Folders:Folders = new Folders();
t1Folders.users_email = tempUser.email;
t1Folders.name = t1Folders.description = "Home";
createFoldersFunction( t1Folders ); // assume returns folder ID = 100
var t2Folders:Folders = new Folders();
t2Folders.users_email = tempUser.email;
t2Folders.name = t2Folders.description = "temp";
createFoldersFunction( t2Folders ); // assume returns folder ID = 101
}
and here are my event handlers, and I want an Alert box for each new ID to pop up:
protected function createFoldersFunction(item:Folders):void
{
createFoldersResult.token = foldersService.createFolders(item);
}
protected function createFoldersResult_resultHandler(event:ResultEvent):void
{
Alert.show("Folder #" + ((event.result as int) as String) + " created");
// Currently, I only get Alert saying "Folder #101 created".
// I want to see 2 Alerts - one for #100 and another for #101
}
and here are my mx codes for callresponder and service:
<s:CallResponder id="createFoldersResult"
result="createFoldersResult_resultHandler(event)"/>
<foldersservice:FoldersService id="foldersService"
fault="Alert.show(event.fault.faultString + '\n' + event.fault.faultDetail)"
showBusyCursor="true"/>
Only '101' (the result of second service call) is triggering an Alert. Why is this?
Thank you!

Your code is overriding the token that createFoldersResult should respond to. A proper code would be:
protected function createFoldersFunction(item:Folders):void
{
var token:AsyncToken = foldersService.createFolders(item);
var responder:Responder = new Responder(createFoldersResult_resultHandler, someFaultHandler)
token.addResponder(responder);
}
another option would be set up the result handler for createFolders directly in the mxml, so your foldersservice:FoldersService would be:
<foldersservice:FoldersService id="foldersService"
fault="Alert.show(event.fault.faultString + '\n' + event.fault.faultDetail)"
showBusyCursor="true">
<mx:method name="createFolders" result="createFoldersResult_resultHandler(event)"/>
</foldersservice:FoldersService>
then you don't need createFoldersFunction, you can call foldersService.createFolders directly.

Related

Need a cq5 example

I am new to Adobe cq5. Went through many online blogs and tutorials but could not get much. Can any one provide a Adobe cq5 application example with detailed explanation that can store and retrieve data in JCR.
Thanks in advance.
Here's a snippet for CQ 5.4 to get you started. It inserts a content page and text (as a parsys) at an arbitrary position in the content hierarchy. The position is supplied by a workflow payload, but you could write something that runs from the command line and use any valid CRX path instead. The advantage of making it a process step is that you get a session established for you, and the navigation to the insert point has been taken care of.
import java.text.SimpleDateFormat;
import java.util.Date;
import javax.jcr.Node;
import javax.jcr.RepositoryException;
import org.apache.sling.jcr.resource.JcrResourceConstants;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Properties;
import org.apache.felix.scr.annotations.Property;
import org.apache.felix.scr.annotations.Service;
import org.osgi.framework.Constants;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.day.cq.workflow.WorkflowException;
import com.day.cq.workflow.WorkflowSession;
import com.day.cq.workflow.exec.WorkItem;
import com.day.cq.workflow.exec.WorkflowData;
import com.day.cq.workflow.exec.WorkflowProcess;
import com.day.cq.workflow.metadata.MetaDataMap;
import com.day.cq.wcm.api.NameConstants;
#Component
#Service
#Properties({
#Property(name = Constants.SERVICE_DESCRIPTION,
value = "Makes a new tree of nodes, subordinate to the payload node, from the content of a file."),
#Property(name = Constants.SERVICE_VENDOR, value = "Acme Coders, LLC"),
#Property(name = "process.label", value = "Make new nodes from file")})
public class PageNodesFromFile implements WorkflowProcess {
private static final Logger log = LoggerFactory.getLogger(PageNodesFromFile.class);
private static final String TYPE_JCR_PATH = "JCR_PATH";
* * *
public void execute(WorkItem workItem, WorkflowSession workflowSession, MetaDataMap args)
throws WorkflowException {
//get the payload
WorkflowData workflowData = workItem.getWorkflowData();
if (!workflowData.getPayloadType().equals(TYPE_JCR_PATH)) {
log.warn("unusable workflow payload type: " + workflowData.getPayloadType());
workflowSession.terminateWorkflow(workItem.getWorkflow());
return;
}
String payloadString = workflowData.getPayload().toString();
//the text to be inserted
String lipsum = "Lorem ipsum...";
//set up some node info
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("d-MMM-yyyy-HH-mm-ss");
String newRootNodeName = "demo-page-" + simpleDateFormat.format(new Date());
SimpleDateFormat simpleDateFormatSpaces = new SimpleDateFormat("d MMM yyyy HH:mm:ss");
String newRootNodeTitle = "Demo page: " + simpleDateFormatSpaces.format(new Date());
//insert the nodes
try {
Node parentNode = (Node) workflowSession.getSession().getItem(payloadString);
Node pageNode = parentNode.addNode(newRootNodeName);
pageNode.setPrimaryType(NameConstants.NT_PAGE); //cq:Page
Node contentNode = pageNode.addNode(Node.JCR_CONTENT); //jcr:content
contentNode.setPrimaryType("cq:PageContent"); //or use MigrationConstants.TYPE_CQ_PAGE_CONTENT
//from com.day.cq.compat.migration
contentNode.setProperty(javax.jcr.Property.JCR_TITLE, newRootNodeTitle); //jcr:title
contentNode.setProperty(NameConstants.PN_TEMPLATE,
"/apps/geometrixx/templates/contentpage"); //cq:template
contentNode.setProperty(JcrResourceConstants.SLING_RESOURCE_TYPE_PROPERTY,
"geometrixx/components/contentpage"); //sling:resourceType
Node parsysNode = contentNode.addNode("par");
parsysNode.setProperty(JcrResourceConstants.SLING_RESOURCE_TYPE_PROPERTY,
"foundation/components/parsys");
Node textNode = parsysNode.addNode("text");
textNode.setProperty(JcrResourceConstants.SLING_RESOURCE_TYPE_PROPERTY,
"foundation/components/text");
textNode.setProperty("text", lipsum);
textNode.setProperty("textIsRich", true);
workflowSession.getSession().save();
}
catch (RepositoryException e) {
log.error(e.toString(), e);
workflowSession.terminateWorkflow(workItem.getWorkflow());
return;
}
}
}
I have posted further details and discussion.
A few other points:
I incorporated a timestamp into the name and title of the content
page to be inserted. That way, you can run many code and test cycles
without cleaning up your repository, and you know which test was the
most recently run. Added bonus: no duplicate file names, no
ambiguity.
Adobe and Day have been inconsistent about providing constants for
property values, node types, and suchlike. I used the constants that
I could find, and used literal strings elsewhere.
I did not fill in properties like the last-modified date. In code for
production I would do so.
I found myself confused by Node.setPrimaryType() and
Node.getPrimaryNodeType(). The two methods are only rough
complements; the setter takes a string but the getter returns a
NodeType with various info inside it.
In my original version of this code, I read the text to be inserted from a file, rather than just using the static string "Lorem ipsum..."
Once you've worked through this example, you should be able to use the Abobe docs to write code that reads data back from the CRX.
If you want to learn how to write a CQ application that can store and query data from the CQ JRC, see this article:
http://scottsdigitalcommunity.blogspot.ca/2013/02/querying-adobe-experience-manager-data.html
This provides a step by step guide and walks you right through the entire processes - including building the OSGi bundle using Maven.
FRom the comments above - I see reference to BND file. You should stay away from CRXDE to create OSGi and use Maven.

Custom component containing filterFunction problem when using multiple instances

I have a main app that is using two instances of a custom MXML DropDownList component.
I included all the logic and queries within the custom component to query MySQL and fill an ArrayCollection with the result.
In my first DropDownList, I want to show all the available currencies avilable in my database.
In the second DropDownList, I only want to show the CAD and USD currencies using a filterFunction.
I don't know why, but once the filterFunction is applied to the first element, the second act like they are sharing the same currenciesList variable (THIS IS MY PROBLEM).
[Bindable] for currenciesList is required to bind to my aSyncListView.
public for currenciesList is required in order to be used in the main app.
And no matter if my variable is public or private, I have the same bug... Please view the output at the end of this message.
The call in my main app look like :
<mx:Form>
<formElems:DropDownListCurrencies id="product_cost_price_curr"
currencyCadUsdOnly="true"/>
<formElems:DropDownListCurrencies id="product_price_curr"/>
</mx:Form>
Now my custom component :
<fx:Script>
<![CDATA[
import classes.SharedFunctions;
import mx.collections.ArrayCollection;
import mx.controls.Alert;
import mx.events.FlexEvent;
import mx.rpc.events.ResultEvent;
[Bindable]
public var currenciesList:ArrayCollection;
public var currencyCadUsdOnly:Boolean = false;
protected function dropdownlist1_creationCompleteHandler(event:FlexEvent):void
{
getAllCurrenciesResult.token = currenciesService.getAllCurrencies();
// DEBUG just to show the id of the component
trace('id:' + this.id + ' (getAllCurrencies)');
}
protected function getAllCurrenciesResult_resultHandler(event:ResultEvent):void
{
currenciesList = getAllCurrenciesResult.lastResult;
// DEBUG before filterFunction
trace('id:' + this.id + ', currencyCadUsdOnly:' + currencyCadUsdOnly + ', currenciesList.length:' + currenciesList.length + ' (BEFORE filterFunction)');
if (currencyCadUsdOnly == true) {
currenciesList.filterFunction = filterCadUsdOnly;
currenciesList.refresh();
}
// DEBUG after filterFunction
trace('id:' + this.id + ', currencyCadUsdOnly:' + currencyCadUsdOnly + ', currenciesList.length:' + currenciesList.length + ' (AFTER filterFunction)');
}
protected function filterCadUsdOnly(obj:Object):Boolean
{
return (obj.code == 'CAD' || obj.code == 'USD');
}
]]>
</fx:Script>
<fx:Declarations>
<s:CallResponder id="getAllCurrenciesResult" result="getAllCurrenciesResult_resultHandler(event)"/>
<currenciesservice:CurrenciesService id="currenciesService" fault="SharedFunctions.showError(event.fault.faultString, event.fault.faultDetail)" showBusyCursor="true"/>
</fx:Declarations>
<s:AsyncListView list="{currenciesList}"/>
Finally let's have a look at the console output. I'm expecting the ArrayList to have a length of 7 on creation of the second component... :
id:product_prices_curr (getAllCurrencies)
id:product_cost_price_curr (getAllCurrencies)
id:product_prices_curr, currencyCadUsdOnly:true, currenciesList.length:7 (BEFORE filterFunction)
id:product_prices_curr, currencyCadUsdOnly:true, currenciesList.length:2 (AFTER filterFunction)
id:product_cost_price_curr, currencyCadUsdOnly:false, currenciesList.length:2 (BEFORE filterFunction)
id:product_cost_price_curr, currencyCadUsdOnly:false, currenciesList.length:2 (AFTER filterFunction)
THANKS FOR THE HELP!
Whenever you need to have the same list in multiple places with differing filters, what you need is a ListCollectionView. That way you can apply a filter to it and you won't affect the original list. It's as easy as:
var secondList:ListCollectionView = new ListCollectionView(originalList);
And your secondList can have any filter you like without affecting the original list, with the added benefit of updating when items are added or removed from the originalList.
See here: mx.collections.ListCollectionView
Try to change your code to be the following:
if (currencyCadUsdOnly == true) {
currenciesList = new ArrayCollection(currenciesList.source);
currenciesList.filterFunction = filterCadUsdOnly;
currenciesList.refresh();
}
Hope this helps!

flex fusion charts with webservice

I have flex project where i want to call webservice and pass values from it to the xml file to update fusion chart xml
My code is
<fx:Script>
<![CDATA[
import mx.controls.Alert;
import mx.events.FlexEvent;
import mx.rpc.events.FaultEvent;
import mx.rpc.events.ResultEvent;
public var PUENumber:String;
protected var xmlLoader:URLLoader;
[Bindable]
public var avgPUEXml:XML = new XML;
protected function init():void
{
xmlLoader = new URLLoader();
xmlLoader.addEventListener(Event.COMPLETE,setDataXML)
xmlLoader.load(new URLRequest("data/AvgPUE.xml")); //Loading xml file for the chart from the folder
};
protected function setDataXML(event:Event):void
{
avgPUEXml = XML(event.target.data);
avgPUEXml.value = PUENumber; //updating chart xml value
fw.FCDataXML = avgPUEXml.toString();
fw.FCRender();
};
protected function getDC_POWERdataResult_resultHandler(event:ResultEvent):void
{
PUENumber = getDC_POWERdataResult.lastResult; //getting value to update in xml file
init();
}
protected function bordercontainer1_creationCompleteHandler(event:FlexEvent):void
{
getDC_POWERdataResult.token = mGEMWS.getDCPUE("4","715"); //user details to get data from the method
}
]]>
</fx:Script>
<fx:Declarations>
<s:CallResponder id="getDC_POWERdataResult" result="getDC_POWERdataResult_resultHandler(event)"/>
<mgemws:MGEMWS id="mGEMWS" showBusyCursor="true"
fault="Alert.show(event.fault.faultString + '\n' + event.fault.faultDetail)"/>
</fx:Declarations>
<ns1:FusionWidgets id="fw" FCChartType="Bulb" FCDataXML="avgPUEXml" />
in the declaration am calling web service. and on creationCompelete am sending userid details to get data from webservice. now where exactly i need to call init function so that it updates xml file with values coming from web service and display the fusion widget
You don't need the 'setDataXML' handler method. Also you don't need any 'URLLoader' or 'URLRequest' in the 'init' method. FusionCharts supports XML data as well as XML path. So remove the 'setDataXML' and remove all the codes from the 'init' method and add just fw.addEventListener(FCEvent.FCRenderEvent, wgdtRendHandler);
and add below handler method -
protected function wgdtRendHandler(e:FCEvent):void{
e.target.FCSetDataURL='data/AvgPUE.xml';
e.target.removeEventHandler(FCEvent.FCRenderEvent, wgdRendHandler);
}
I think this should work for you. In case you still don't get the updated data in the chart, check the XML file in the 'init' method whether it's updated properly at that point of time.
You can call the init function when the chart finishes loading and is ready for data.
You can do this listening to FCLoadEvent or FCRenderEvent of the gauge.
Please try using :
<ns1:FusionWidgets id="fw" FCChartType="Bulb" FCDataXML="avgPUEXml" FCRenderEvent="init()"/>
or
<ns1:FusionWidgets id="fw" FCChartType="Bulb" FCDataXML="avgPUEXml" FCLoadEvent="init()"/>

Flex 3 multiple upload progress monitoring

I have a Flex3 application which has to be capable of uploading multiple files and monitoring each files individual progress using a label NOT a progress bar.
My problem is that a generic progress handler for the uploads has no way (that I know of) of indicating WHICH upload it is that is progressing. I know that a file name is available to check but in the case of this app the file name might be the same for multiple uploads.
My question: With a generic progress handler how does one differentiate between 2 multiple uploads with the same file name?
EDIT: answerers may assume that I am a total newb to Flex... because I am.
I use this:
private function _addFileListeners(dispatcher:IEventDispatcher):void {
dispatcher.addEventListener(Event.OPEN, this._handleFileOpen);
dispatcher.addEventListener(Event.SELECT, this._handleFileOpen);
dispatcher.addEventListener(Event.CANCEL, this._handleFileCancel);
dispatcher.addEventListener(ProgressEvent.PROGRESS, this._handleFileProgress);
dispatcher.addEventListener(DataEvent.UPLOAD_COMPLETE_DATA,this._handleFileComplete);
dispatcher.addEventListener(IOErrorEvent.IO_ERROR, this._handleError);
dispatcher.addEventListener(SecurityErrorEvent.SECURITY_ERROR, this._handleError);
}
where "dispatcher" is the file:
for (var i:uint = 0; i < fileList.length; i++) {
file = FileReference(fileList[i]);
this._addFileListeners(file);
this._pendingFiles.push(file);
}
and a sample handler:
private function _handleFileOpen(e:Event):void {
var file:FileReference = FileReference(e.target);
...
}
I'm not sure how you want to differentiate between two files with the same name. In my case, I send the files in a queue. So there's only ever 1 file being uploaded at a time. (pendingFiles).
If you are listening for ProgressEvents, these events have a currentTarget attribute that would have a reference to the object that has registered the event listener.
I'm assuming you know which file-uploading object goes with each object in the first place.
EDIT: Example using FileReference:
import flash.net.FileReference;
import flash.events.ProgressEvent;
import flash.utils.Dictionary;
public var files:Dictionary = new Dictionary(); // This will hold all the FileReference objects
public function loadFile(id:String):void
{
var file:FileReference = new FileReference();
// Listen for the progress event on this FileReference... will call the same function for every progress event
file.addEventListener(ProgressEvent.PROGRESS, onProgress);
// TODO: listen for errors and actually upload a file, etc.
// Add file to the dictionary (as key), with value set to an object containing the id
files[file] = { 'id': id };
}
public function onProgress(event:ProgressEvent):void
{
// Determine which FileReference dispatched thi progress event:
var file:FileReference = FileReference(event.target);
// Get the ID of the FileReference which dispatched this function:
var id:String = files[file].id;
// Determine the current progress for this file (in percent):
var progress:Number = event.bytesLoaded / event.bytesTotal;
trace('File "' + id + '" is ' + progress + '% done uploading');
}
// Load some files:
loadFile('the first file');
loadFile('the second file');
I ended up creating my own class that manages events for each uploading file

Adobe Air/Flex + SQLite database problem

I'm still a newbie to Adobe Air/Flex, and still fairly new with SQL.
I've downloaded this (http://coenraets.org/blog/2008/11/using-the-sqlite-database-access-api-in-air…-part-1/) code and have been looking over it and I'm trying to implement the same idea.
I think it's just something stupid. I'm using Flex Builder. I made a new desktop application project, didn't import anything.
I added a DataGrid object and bound it to an ArrayCollection:
I'm trying to make it so when the program initializes it will load data from a database if it exists, otherwise it'll create a new one.
The problem is, when the application runs, the datagrid is empty. No column headers, no data, nothing. I've tried changing a whole bunch of stuff, I've used the debugger to make sure all the functions are being called like they're supposed to. I don't know what I'm doing wrong. I've compared my code to the before mentioned code, I've looked for tutorials on Google. Anyone know what I'm doing wrong?
<?xml version="1.0" encoding="utf-8"?>
<mx:WindowedApplication xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" width="672" height="446"
applicationComplete="onFormLoaded()"
title="iRecipes">
<mx:Script>
<![CDATA[
import mx.collections.ArrayCollection;
private var sqlConnection:SQLConnection;
[Bindable] private var recipeList:ArrayCollection;
private function onFormLoaded():void
{
sqlConnection = new SQLConnection();
openDataBase();
}
private function openDataBase():void
{
var file:File = File.userDirectory.resolvePath("recipes.db");
sqlConnection.open(file, SQLMode.CREATE);
if(!file.exists)
{
createDatabase();
}
populateRecipeList()
}
private function createDatabase():void
{
var statement:SQLStatement = new SQLStatement();
statement.sqlConnection = sqlConnection;
statement.text = "CREATE TABLE Recipes (recipeId INTEGER PRIMARY KEY AUTOINCREMENT, recipeName TEXT, authorName TEXT)";
statement.execute();
statement.text = "INSERT INTO Recipes (recipeName, authorName) VALUES (:recipeName, :authorName)";
statement.parameters[":recipeName"] = "Soup";
statement.parameters[":authorName"] = "Joel Johnson";
statement.execute();
statement.parameters[":recipeName"] = "Garbage";
statement.parameters[":authorName"] = "Bob Vila";
statement.execute();
}
private function populateRecipeList():void
{
var statement:SQLStatement = new SQLStatement();
statement.sqlConnection = sqlConnection;
statement.text = "SELECT * FROM Recipes";
statement.execute();
recipeList = new ArrayCollection(statement.getResult().data);
}
]]>
</mx:Script>
<mx:DataGrid dataProvider="{recipeList}">
</mx:DataGrid>
</mx:WindowedApplication>
I just tried this out using your code. I made a change and removed the condition as I was getting errors about the table not existing.
//if(!file.exists)
//{
createDatabase();
//}
This got the datagrid showing the correct info. I think that there is something wrong with the way you are initialising the database file. I'm having a look into it at the moment.
Try using
sqlConnection.open(file, SQLMode.CREATE);
instead, for opening the database.
Thanks Feet. With your suggestion, I believe I have figured it out. I changed the if statement to this:
if(!file.exists)
{
sqlConnection.open(file, SQLMode.CREATE);
createDatabase();
}
else
{
sqlConnection.open(file, SQLMode.UPDATE);
}
And it works great. Thanks for your help.

Resources