I'm building a mobile app with Flash Builder 4.6 and AIR.
My Main.mxml class -- a ViewNavigatorApplication -- references a splash screen set to a minimum time of 6 seconds. After the splashscreen disappears, there's a period of a couple of seconds during which the content area of the screen is just blank white -- and that's when I need the Busy Indicator to appear.
EDIT: Additional Code, as requested:
In the Main.mxml class:
protected function viewnavigatorAppInitializeHandler(event:FlexEvent):void
{
//checks various criteria to determine which data to load, ie which service call
// to make. When that is determined, code calls setUpModel().
}
private function setUpModel():void {
Model.Instance.initialize(); //adds listeners in the model,
//makes service call. Data is returned and parsed in the event handler.
//APPLICATION_MODEL_LOADED event is then dispatched, and handled here
Model.Instance.addEventListener( Model.APPLICATION_MODEL_LOADED, modelReadyHandler );
}
private function modelReadyHandler(e:Event):void
{
//Busy Indicator only appears *after* the HomeListView is loaded.
//I need it to appear *while* these calls are being made.
navigator.pushView(HomeListView); //this is my First View
}
So I think the question is: what is it that is being displayed before HomeListView? If it's Main.mxml -- the ViewNavigatorApplication -- then how do I avoid the 'not assignable' error?
In HomeListView:
[Bindable] private var categoryList:ArrayCollection = new ArrayCollection();
private function viewActivateHandler():void
{
//the service call has been made, and data is ready to be loaded into the control
categories = Model.Instance.Categories;
I've tried this inside HomeListView:
<s:Group width="100%" height="100%">
<s:BusyIndicator
id="busy"
visible="true" symbolColor="blue"
horizontalCenter="0" verticalCenter="0"
rotationInterval="100" />
</s:Group>
It works. But the problem is it only appears once HomeListView has loaded. I thought that the blank white was HomeListView, just before the list appeared. But it isn't.
So then I tried putting the above code into the Main.mxml file. But I got the following error:
'spark.components.Group' is not assignable to the default property, 'navigationStack', of type 'spark.components.supportClasses.NavigationStack'.
So where can I put this BusyIndicator so that it covers the blank space before HomeListView appears?
I've figured out a way to do this.
From the documentation:
"Unlike Application, ViewNavigatorApplication is not meant to accept UIComponent objects as children. Instead, all visual components should be children of the views managed by the application."
That is why I was getting the error. So for my first View, I created a simple View called HolderView which contains the BusyIndicator:
<?xml version="1.0" encoding="utf-8"?>
<s:View xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark" title="HolderView">
<s:Group width="100%" height="100%">
<s:BusyIndicator
id="busy"
visible="true" symbolColor="blue"
horizontalCenter="0" verticalCenter="0"
rotationInterval="100" />
</s:Group>
Then, from my Main.mxml file, I first load that HolderView.
private function setUpModel():void {
Model.Instance.initialize();
navigator.pushView(HolderView);
Model.Instance.addEventListener(Model.APPLICATION_MODEL_LOADED, modelReadyHandler );
}
Once the data has come back -- on the Model.APPLICATION_MODEL_LOADED event listener -- that is when I will load HomeListView:
private function modelReadyHandler(e:Event):void
{
navigator.pushView(HomeListView);
}
This works. Hope it helps someone with same problem!
Related
I am working with Flex 4.1 on Linux but I suppose this problem must be common to all versions of Flex.
I am working on a Flex client front-end which connects to a Java back-end (in Tomcat) via BlazeDS. All of this works OK 99.9% of the time. However, from time to time (let's say, if the client browser has been open for several hours), the user gets a pop-up message saying "Send failed". It seems to be a harmless transient error -- if the user closes the pop-up, the front-end carries on as usual and seems to continue to work as expected.
Is there a way to have the "send failed" message written to a log file instead of opening a pop-up message?
As a separate issue, if anyone has seen occasional, transient "send failed" errors, I would be interested to hear about that. If you have some solution to make them go away, that would be terrific.
SOLUTION 1 : Use simple trace() feature
Install the debugger version of Flash Player
Set TraceOutputFileEnable to 1 in your mm.cfg file
the log of your application will in "flashlog.txt" file (see Log file location)
Use trace("message") to log your messages to "flashlog.txt" file.
SOLUTION 2 : Use logging API:
<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"
creationComplete="initLogging();"
height="600">
<s:layout>
<s:VerticalLayout/>
</s:layout>
<fx:Script>
<![CDATA[
import mx.collections.ArrayCollection;
import mx.logging.targets.*;
import mx.logging.*;
[Bindable]
public var myData:ArrayCollection;
private function initLogging():void {
/* Create a target. */
var logTarget:TraceTarget = new TraceTarget();
/* Log only messages for the classes in the mx.rpc.* and
mx.messaging packages. */
logTarget.filters=["mx.rpc.*","mx.messaging.*"];
/* Log all log levels. */
logTarget.level = LogEventLevel.ALL;
/* Add date, time, category, and log level to the output. */
logTarget.includeDate = true;
logTarget.includeTime = true;
logTarget.includeCategory = true;
logTarget.includeLevel = true;
/* Begin logging. */
Log.addTarget(logTarget);
}
]]>
</fx:Script>
<fx:Declarations>
<!-- HTTPService is in the mx.rpc.http.* package -->
<mx:HTTPService id="srv"
url="../assets/trace_example_data.xml"
useProxy="false"
result="myData=ArrayCollection(srv.lastResult.data.result)"/>
</fx:Declarations>
<mx:LineChart id="chart" dataProvider="{myData}" showDataTips="true">
<mx:horizontalAxis>
<mx:CategoryAxis categoryField="month"/>
</mx:horizontalAxis>
<mx:series>
<mx:LineSeries yField="apple" name="Apple"/>
<mx:LineSeries yField="orange" name="Orange"/>
<mx:LineSeries yField="banana" name="Banana"/>
</mx:series>
</mx:LineChart>
<s:Button id="b1" click="srv.send();" label="Load Data"/>
</s:Application>
The proposed UX I am trying to achieve is this:
user clicks menu item (via a listBase subclass: e.g. ButtonBar or TabBar)
prevent initial selection
validate if user needs to address issues (e.g. unsaved data on a form, etc.)
if valid, take selection and set the listBase to that selectedIndex, otherwise present warnings to user and cancel out the selection process altogether
This does not work as you'd expect. Utilizing the IndexChangeEvent.CHANGING type and the preventDefault works to kill the selection, but at step 4, when I am programmatically setting the selectedIndex of the listBase, it then tries to redispatch the CHANGING event (this despite what the API docs claim).
Here is a sample application src code if you'd like to try this for yourself. I look forward to your comments & solutions.
Thanks.
J
http://snipt.org/vUji3#expand
<?xml version="1.0" encoding="utf-8"?>
<s:Application minWidth="955" minHeight="600"
xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:mx="library://ns.adobe.com/flex/mx"
xmlns:s="library://ns.adobe.com/flex/spark">
<fx:Script>
<![CDATA[
import flash.utils.setTimeout;
import mx.core.mx_internal;
import spark.events.IndexChangeEvent;
use namespace mx_internal;
[Bindable]
private var doPreventDefault:Boolean;
[Bindable]
private var delayMS:uint = 500;
private function buttonbar_changingHandler( event:IndexChangeEvent ):void
{
// TODO Auto-generated method stub
if ( doPreventDefault )
{
event.preventDefault();
setTimeout( delayedLogic, delayMS, event.newIndex );
}
}
private function delayedLogic( index:int ):void
{
//disabling this results in an endless loop of trying to set the selected index
// doPreventDefault = false;
//this should NOT be hitting the changing handler since we're supposed to be dispatching a value commit event instead.
bb.setSelectedIndex( index, false );
}
]]>
</fx:Script>
<fx:Declarations>
<!-- Place non-visual elements (e.g., services, value objects) here -->
</fx:Declarations>
<s:layout>
<s:VerticalLayout horizontalAlign="center"/>
</s:layout>
<s:ButtonBar id="bb"
changing="buttonbar_changingHandler(event)">
<s:dataProvider>
<s:ArrayList>
<fx:String>btn 0</fx:String>
<fx:String>btn 1</fx:String>
<fx:String>btn 2</fx:String>
</s:ArrayList>
</s:dataProvider>
</s:ButtonBar>
<s:CheckBox label="preventDefault?"
selected="#{ doPreventDefault }"/>
<s:NumericStepper maximum="5000" minimum="500"
stepSize="500" value="#{ delayMS }"/>
</s:Application>
Looking at the SDK, the IndexChangeEvent.CHANGING event is actually preventable - despite the documentation here says that cancelable is false, so my bad on that (although ASDoc went a little sideways), however things get a little interesting from here.
In ListBase #1296 this is only ever dispatched from the commitSelection(dispatchEvents:Boolean = true) method. In ButtonBarBase:dataProvider_changeHandler() is the only place that specifically calls to not dispatch the event, but in ListBase, it's called in commitProperties #939 when there is a proposedSelectionIndex.
So from your code above, if you are trying to set the selection - this is going to call the commitSelection, which I believe is causing the call stack issue. The Timer delay is just going to exacerbate the issue, since at 500ms the UI will have gone through its invalidation cycle at least once, meaning the commitSelection will be executed again because of an invalidateProperties flag is being set from the proprosedSelectionIndex eventually stemming from setSelectedIndex #729
So how to fix this.
I would look at only doing the prevent if the validation fails, otherwise allow it to proceed as normal. If it does fail, call the prevent, set an errorString or equivalent, but don't attempt to change the selection.
[edit] Read RiaStar's comment, and I just concurred with the same 'solution'.
I'm using a local shared object as means to transfer data between two swf's running at the same time.
so = getLocal("mySO");
// set the value from a user input, and set it to 'connected'
so.setProperty(txtName.text, "connected")
// every few seconds check if the other SWF started and modified value
onTimer
{
var data:Object = so.data
// breakpoint and inspect the data with debugger
}
The last commented line is where the problem is. In debugger, no changes to data are detected, but when inspecting .sol file with .minerva, I see the changes from both SWF's. So, although shared object is modified by SWF 2, the SWF 1 does not see these changes. Is this how's it supposed to be?
ps: I know I could use LocalConnection for communicating between two running SWF's but still want to know about the SO if there's some kind of a limitation.
UPDATE:
Run the compiled Flex app from disk twice, put the "swf2" into textfield of the second one. Press start on both. The first one that is started never detects that the second one was connected.
<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;
private var timer:Timer;
private var so:SharedObject;
protected function application1_creationCompleteHandler(event:FlexEvent):void
{
so = SharedObject.getLocal("mySO");
timer = new Timer(1000);
timer.addEventListener(TimerEvent.TIMER, onTimer);
}
protected function button1_clickHandler(event:MouseEvent):void
{
so.setProperty(txtSwf.text, 'connected');
so.flush();
timer.start();
}
protected function onTimer(event:TimerEvent):void
{
so = SharedObject.getLocal("mySO");
txtLog.text += so.data["swf1"] + "\n";
txtLog.text += so.data["swf2"] + "\n";
txtLog.text += "------------ \n";
}
]]>
</fx:Script>
<fx:Declarations>
<!-- Place non-visual elements (e.g., services, value objects) here -->
</fx:Declarations>
<s:TextInput id="txtSwf" x="10" y="50" text="swf1"/>
<s:Button x="170" y="50" label="Start" click="button1_clickHandler(event)"/>
<s:TextArea id="txtLog" x="10" y="154" width="279" height="112" />
</s:Application>
You need to force the reading of the file, but not of the data which client already has :/
so = getLocal("mySO");
// set the value from a user input, and set it to 'connected'
so.setProperty(txtName.text, "connected")
// every few seconds check if the other SWF started and modified value
onTimer
{
so = getLocal("mySO"); // read the so file.
var data:Object = so.data
}
Phew, found an answer. Jevgeenij gave me a lead to forcing the re-read of the shared object, but his code didn't work exactly. what I needed to add was this.
so = getLocal("mySO");
// set the value from a user input, and set it to 'connected'
so.setProperty(txtName.text, "connected")
// every few seconds check if the other SWF started and modified value
onTimer
{
so = getLocal("mySO"); // read the so file.
var data:Object = so.data;
so = null // MAKE IT NULL AND HOPE IT'S GC'D BEFORE NEXT TIMER
}
The trick was to set it to null after use, only then it is forcefully re-read and values written from other SWF instance picked up.
I'm building a multiplayer game which when connecting to the server, the server sends back a list of available rooms (each room has MaxPlayers,MinRank,TableId,TableName,Password) so everytime I recieve these 5 strings, I create an instance of Mxml UI Component I have created and fill it with the relevant details.
In the main.MXML i've added an AS3 script variable to hold the GameInstances object i've created when rcvd data back from the server:
private var gameInstances:ArrayCollection = new ArrayCollection();
GameInstance.mxml is a component that has UI components in it and AS3 script to set some data.
When rcving data from the server in main.mxml :
var gameInstance:GameInstance = new GameInstance();
gameInstance.setTablePlayers(rcvdMsg[1]);
gameInstance.setTableMinRank(rcvdMsg[2]);
gameInstance.setTableId(rcvdMsg[3]);
gameInstance.setTableName(rcvdMsg[4]);
gameInstance.setTablePassword(rcvdMsg[5]);
gameInstances.addItem(gameInstance);
gameInstances holds objects of that mxml component.
How do I show this component visualy on the main.mxml?
I have a in main.mxml that I want visually show inside it the GameInstance objects.
This is how GameInstance.mxml looks like, I want the s:List to hold for each game a UI object like that(to show it ofcourse)
OK, if you using the gameInstances arraycollection as a data provider for tableList then you need to do a few things.
First, you need to make your gameInstances arraycollection [Bindable]
Then, you need to add data to the array collection as code you posted shows you are.
Next, you have to Create a custom item renderer for your tableList.
Finally, when you are done changing data/adding/removing objects from the gameInstances arraycollection you need to gameInstances.refresh();
[EDIT]
create a file named myListRenderer.mxml and put this code in it
<?xml version="1.0" encoding="utf-8"?>
<mx:VBox xmlns:mx="http://www.adobe.com/2006/mxml">
<mx:Script>
<![CDATA[
override protected function commitProperties():void{
// this is where you assign your values
// this function gets called every-time the list scrolls
super.commitProperties();
players.text = this.data.setTablePlayers
minRank.text = this.data.setTableMinRank;
tableId.text = this.data.setTableId;
tableName.text = this.data.setTableName;
tablePassword.text = this.data.setTablePassword;
}
]]>
</mx:Script>
<mx:Label id="players" />
<mx:Label id="minRank" />
<mx:Label id="tableId" />
<mx:Label id="tableName" />
<mx:Label id="tablePassword" />
</mx:VBox>
If I understand the question, when you create components in ActionScript, you have to add them to a container before they'lls how up in the UI. If you have an array of components, you can just loop over that array and add each component as a child on a container. The main application file is a container, so you can do something like this:
for each(var myComp:UIComponent in myArrayList){
addChild(myComp);
}
It would be unusual to take this approach. Usually you just add the component to the parent container as you create them.
Once I've called DragManager.acceptDrag is there any way to "unaccept" the drag? Say that I have a view which can accept drag and drop, but only in certain areas. Once the user drags over one of these areas I call DragManager.acceptDrag(this) (from a DragEvent.DRAG_OVER handler), but if the user then moves out of this area I'd like to change the status of the drag to not accepted and show the DragManager.NONE feedback. However, neither calling DragManager.acceptDrag(null) nor DragManager.showFeedback(DragManager.NONE) seems to have any effect. Once I've accepted the drag an set the feedback type I can't seem to change it.
Just to make it clear: the areas where the user should be able to drop are not components or even display objects, in fact they are just ranges in the text of a text field (like the selection). Had they been components of their own I could have solved it by making each of them accept drag events individually. I guess I could create proxy components that float over the text to emulate it, but I'd rather not if it isn't necessary.
I've managed to get it working in both AIR and the browser now, but only by putting proxy components on top of the ranges of text where you should be able to drop things. That way I get the right feedback and drops are automatically unaccepted on drag exit.
This is the oddest thing about D&D in AIR:
DragManager.doDrag(initiator, source, event, dragImage, offsetX, offsetY);
In browser-based Flex, offsetX and offsetY should be negative (so says the documentation, and it works fine). However, when running exactly the same code in AIR you have to make the offsets positive. The same numbers, but positive. That is very, very weird.
I've tested some more and what #maclema works, but not if you run in AIR. It seems like drag and drop in AIR is different. It's really, really weird because not only is the feedback not showing correctly, and it's not possible to unaccept, but the coordinates are also completely off. I just tried my application in a browser instead of AIR and dragging and dropping is completely broken.
Also, skipping the dragEnter handler works fine in AIR, but breaks everything when running in a browser.
Are you using only the dragEnter method? If you are trying to reject the drag while still dragging over the same component you need to use both the dragEnter and dragOver methods.
Check out this example:
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute">
<mx:Script>
<![CDATA[
import mx.core.DragSource;
import mx.managers.DragManager;
import mx.events.DragEvent;
private function onDragEnter(e:DragEvent):void {
if ( e.target == lbl ) {
if ( e.localX < lbl.width/2 ) {
trace("accept");
DragManager.acceptDragDrop(this);
}
else {
DragManager.acceptDragDrop(null);
}
}
}
private function doStartDrag(e:MouseEvent):void {
if ( e.buttonDown ) {
var ds:DragSource = new DragSource();
ds.addData("test", "text");
DragManager.doDrag(btn, ds, e);
}
}
]]>
</mx:Script>
<mx:Label id="lbl" text="hello world!" left="10" top="10" dragEnter="onDragEnter(event)" dragOver="onDragEnter(event)" />
<mx:Button id="btn" x="47" y="255" label="Button" mouseMove="doStartDrag(event)"/>
</mx:Application>
If you don't need native drag and drop in AIR, you can get the Flex drag and drop behavior by subclassing WindowedApplication and setting the DragManager. See this post on the Adobe Jira for more info: https://bugs.adobe.com/jira/browse/SDK-13983
You are misunderstanding the concept. Your "unaccept" is achieved by implementing the dragOverHandler and signaling that the data is not wanted.
Here is the basic concept:
register the dragEnterHandler or override the already registered method.
function dragEnterHandler(event: DragEvent):void {
if (data suites at least one location in this component)
DragManager.acceptDragDrop(this);
}
This enables your container to receive further messages (dragOver/dragExit). But this is NOT the location to decide which kind of mouse cursor should be displayed.
Without DragManager.acceptDragDrop(this); the other handlers aren't called.
register the dragOverHandler or override the already registered method.
function dragOverHandler(event: DragEvent):void {
if (data suites at least no location in this component) {
DragManager.showFeedback(DragManager.NONE);
return;
}
... // handle other cases and show the cursor / icon you want
}
Calling DragManager.showFeedback(DragManager.NONE); does the trick to display the "unaccept".
register the dragExitHandler or override the already registered method.
function dragOverHandler(event: DragEvent):void {
// handle the recieved data as you like.
}
ok, I see the problem now. Rather than null, try setting it to the dragInitiator.
Check this out.
<?xml version="1.0" encoding="utf-8"?>
<mx:WindowedApplication xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute">
<mx:Script>
<![CDATA[
import mx.controls.Alert;
import mx.events.DragEvent;
import mx.managers.DragManager;
import mx.core.DragSource;
private function doStartDrag(e:MouseEvent):void {
if ( e.buttonDown && !DragManager.isDragging ) {
var ds:DragSource = new DragSource();
ds.addData("test", "test");
DragManager.doDrag(btn, ds, e);
}
}
private function handleDragOver(e:DragEvent):void {
if ( e.localX < cvs.width/2 ) {
//since null does nothing, lets just set to accept the drag
//operation, but accept it to the dragInitiator
DragManager.acceptDragDrop(e.dragInitiator);
}
else {
//accept drag
DragManager.acceptDragDrop(cvs);
DragManager.showFeedback( DragManager.COPY );
}
}
private function handleDragDrop(e:DragEvent):void {
if ( e.dragSource.hasFormat("test") ) {
Alert.show("Got a drag drop!");
}
}
]]>
</mx:Script>
<mx:Canvas x="265" y="66" width="321" height="245" backgroundColor="#FF0000" id="cvs" dragOver="handleDragOver(event)" dragDrop="handleDragDrop(event)">
</mx:Canvas>
<mx:Button id="btn" x="82" y="140" label="Drag Me" mouseDown="doStartDrag(event)"/>
</mx:WindowedApplication>
Yes, drag and drop is different in AIR. I HATE that! It takes a lot of playing around to figure out how to get things to work the same as custom dnd that was built in flex.
As for the coordinates, maybe play around with localToContent, and localToGlobal methods. They may help in translating the coordinates to something useful.
Good luck. I will let you know if I think of anything else.