How to communicate between my model and flex component? - actionscript-3

I'm trying to get my Actionscript 3.0 model that links to an SQLite database using Probertson's SQLRunner class to talk to my flex component; I'm really unsure of how best to accomplish this. I have worked off a few examples, but I don't know the simplest way to tell my component the results of the SQL query. Anyone have any recommendations?
Here is some of the code, to give you an idea of what I'm working with right now.
Component
<fx:Declarations>
<model:Patient id="editedPatient" FirstName="{FirstName.text}" />
</fx:Declarations>
<fx:Script>
<![CDATA[
/*imports*/
protected var _patient:Patient;
public function get patient():Patient
{
return _patient;
}
[Bindable]
public function set patient(value:Patient):void
{
_patient = value;
}
private function creationCompleteHandler(event:FlexEvent):void{
_patient.getPatient(currentUser);
}
protected function save_clickHandler(event:MouseEvent):void
{
_patient.update(editedPatient);
}
]]>
</fx:Script>
<s:TextInput id="FirstName" text="{patient.FirstName}" />
<s:Button id="save" label="save" click="save_clickHandler(event)" />
Model
public function getPatient(PatientId:int):void {
var stmt:String = new String();
stmt = "SELECT * FROM Patient WHERE PatientID= #PatiendId;";
sqlRunner.execute(stmt, {PatientId:PatientId}, loadPatient_result, Patient);
}
private function loadPatient_result(result:SQLResult):void
{
if (result.data != null && result.data.length > 0)
{
var Patient:Patient = result.data[0];
}
}

There's a few things you can do...
First, I would create a Model that follows the Singleton pattern so you can bind to any data changes in any view, or component.
Second, I would then update that singleton'd model with in the loadPatient_result method you call.
If you want to de-couple from the result and the component, you could dispatch a custom event manually that contains the patient record, have the component listen for that kind of event and update itself accordingly. Or have that view listen for that event and update accordingly really.
You're on the right track. I think Singleton is what you need.

Related

Adobe/Apache Flex: Modify View in an ActionScript class

I have a WindowedApplication in Apache/Adobe Flex 4 which currently consists of one view (the view defined in the WindowedApplication MXML).
In that application I have an object which listens to data coming from a network. When data is available a method is called on that object and it shall update my view by changing the text of a label.
I do not have a reference to the view in the network listener object though. How can I get it?
This is part of my MXML where I define my view.
<fx:Script source="./ViewCodeBehind.as"/>
<!-- ommited stuff -->
<s:Label id="errorLabel"
text=""
fontSize="14"/>
<!-- Stuff in between -->
<s:Button label="Get Status"
click="getStatus();"/>
The code which is called when the button is clicked:
public function getStatus(): void
{
var networkGateway: NetworkGateway = new NetworkGatewayImpl();
networkGateway.getConnectionStatus();
}
And the NetworkGatewayImpl
public class NetworkGatewayImpl implements NetworkGateway
{
public function NetworkGatewayImpl()
{
}
public function getConnectionStatus(): void
{
// Start asynchronous network call
// when error occurs onNetworkError() is called
}
private function onNetworkError(): void
{
// Set "errorLabel" here: How?
}
}
Essentially I want to know some ways to update "errorLabel" from the NetworkGatewayImpl.
Based on your code, there could be multiple ways to solve this. Easiest way (as per me) would be to dispatch an event from the NetworkGatewayImpl class and listen to it on the instance you have created in the view class. So sample code would look like this:
public function getStatus(): void
{
var networkGateway: NetworkGateway = new NetworkGatewayImpl();
networkGateway.addEventListener("networkError", onNetworkError);
networkGateway.getConnectionStatus();
}
private function onNetworkError(e:Event):void
{
networkGateway.removeEventListener("networkError", onNetworkError);
this.errorLabel.text = "Your Text Here";
}
Dispatch your event like this from your NetworkGatewayImpl class:
private function onNetworkError(): void
{
this.dispatchEvent("networkError");
}
You will have to ensure that your NetworkGatewayImpl also implements the IEventDispatcher interface to be able to dispatch events.
Also, best practice would be to create a custom Event class (extending the Event class) and use constants instead of the literal 'networkError'
Hope this helps.

Flex Datagrid labelFunction query

Main.mxl
<s:DataGrid dataProvider="{employeeData}"> // employeeData is an Arraycollection with predefined data
<s:typicalItem>
<s:DataItem firstName="Christopher"
lastName="Winchester"
hireDate="22/12/2013"/>
</s:typicalItem>
<s:columns>
<s:ArrayList>
<s:GridColumn labelFunction="employeeName"
headerText="Name"/>
<s:GridColumn dataField="hireDate"
headerText="Hire Date"
labelFunction="dateFormat"/>
</s:ArrayList>
</s:columns>
</s:DataGrid>
<fx:Script>
<![CDATA[
import mx.collections.ArrayCollection;
import mx.controls.dataGridClasses.DataGridColumn;
import mx.rpc.events.ResultEvent;
[Bindable]
private var employeeData: ArrayCollection;
private function employeeName(item: Object, column: GridColumn): String
{
return item.firstName+" "+item.lastName;
}
]]>
</fx:Script>
A) Can anyone please explain me how does Datagrid internally works with employeeName function? I mean, Iam not even passing 2 parameters for labelFunction, BUT still how does it get called?
B) Why should I use s:ArrayList tag inside s:columns tag?
A) Can anyone please explain me how does Datagrid internally works
with employeeName function? I mean, Iam not even passing 2 parameters
for labelFunction, BUT still how does it get called?
The labelFunction is a property on the GridColumn class. Inside the Gridcolumn there is an itemToString() function which is used to determine what the label should be for that specific instance of the column. right out of the framework code:
/**
* #private
* Common logic for itemToLabel(), itemToDataTip(). Logically this code is
* similar to (not the same as) LabelUtil.itemToLabel().
*/
private function itemToString(item:Object, labelPath:Array, labelFunction:Function, formatter:IFormatter):String
{
if (!item)
return ERROR_TEXT;
if (labelFunction != null)
return labelFunction(item, this);
var itemString:String = null;
try
{
var itemData:Object = item;
for each (var pathElement:String in labelPath)
itemData = itemData[pathElement];
if ((itemData != null) && (labelPath.length > 0))
itemString = (formatter) ? formatter.format(itemData) : itemData.toString();
}
catch(ignored:Error)
{
}
return (itemString != null) ? itemString : ERROR_TEXT;
}
B) Why should I use s:ArrayList tag inside s:columns tag?
Because the data type of the columns property on the DataGrid is an IList; and the ArrayList implements the IList interface. What you're looking at is the MXML way to create and define an ArrayList. You'd use a slightly different approach if you wanted to create the columns in ActionScript.
To answer your first question: the "labelFunction" property is a reference to the function that will be invoked by the DataGrid to format the text of a cell in a column. The function will be called dynamically and the DataGrid will pass in the required parameters. The DataGrid expects a label function to always have this signature. If you fail to do so, you will get a runtime error.
Technically, a function can be called dynamically with the following syntax:
var anObject:MyType;
var methodName:String = "myMethod";
anObject[methodName](param1, param2);
or if you have a Function object
var myFunction:Function;
myFunction(param1, param2);

Trigger an Event when value has changed

I'm trying to trigger an event (function) in an air application when a value has changed (i.e. when a user has logged in), but so far I've no succeeded in triggering my function...
I have following code:
A user class:
[Bindable]
public class User
{
private var m_rssLink:String;
public function get rssLink():String{
return m_rssLink;
}
public function set rssLink(value:String):void{
m_rssLink = value;
}
}
A header component with id header where the user logs in and the user is defined:
<fx:Declarations>
<user:User id="user" />
</fx:Declarations>
And finally the mxml code with the actionscript:
private var loginWatcher:ChangeWatcher;
protected function creationCompleteHandler(event:FlexEvent):void
{
this.loginWatcher = ChangeWatcher.watch(header.user, ["rssLink"], sendRequest);
}
I've tried to trigger the sendRequest function in a lot of ways by now (with [bindable (event="")], with a bindsetter,... ) and so far Google brought me no other options.
Anyone sees where my code fails, or knows a better way to accomplish this?
Well, you have already made a setter property for User. Use that to track user states.
public function set rssLink(value:String):void{
if (m_rsslink==value) return;
m_rssLink = value;
dispatchEvent(...); // use correct event(s) for certain values, then catch
// them in your watcher class. Use FlexEvents if you want.
}

Custom Event from Item Renderer not detected by a class

I am trying to dispatch a custom event from an item renderer(which is a child of the Main application file / root).
Code in Main.mxml:
<s:List id="movieGrid"itemRenderer="views.MovieRenderer" dataProvider="{new ArrayCollection()}">
</s:List>
<s:Group width="100%" height="100%" bottom="60">
<views:DetailedViewInfo id="detailed" includeIn="MoviePage" />
</s:Group>
Renderer (something clicked):
MyEventDispatcher.Dispatcher.dispatchEvent(new MovieClickEvent(MovieClickEvent.CLICKED, data));
DetailedViewInfo (creation complete):
MyEventDispatcher.Dispatcher.addEventListener(MovieClickEvent.CLICKED, clickHandler);
MyEventDispatcher:
package events
{
import flash.events.EventDispatcher;
public class MyEventDispatcher
{
public static var Dispatcher:EventDispatcher = new EventDispatcher();
}
}
Event:
package events
{
import flash.events.Event;
public class MovieClickEvent extends Event
{
public function MovieClickEvent(type:String, theMovieData:Object, bubbles:Boolean=true, cancelable:Boolean=false)
{
super(type, bubbles, cancelable);
this._result = theMovieData;
}
public function get result():Object
{
return this._result;
}
override public function clone():Event
{
return new MovieClickEvent(type, result, bubbles, cancelable)
}
public static const CLICKED:String = "MovieClickEvent.CLICKED";
private var _result:Object;
}
}
I am able to listen for the event successfully in the Main.mxml but I also need to detect it in a SkinnableContainer - "DetailedViewInfo" that is also a child of Main.mxml:
It his possible at all? I tried importing all related events / classes and same for declarations. It does not work even if I comment out the event listener in Main.mxml. I tried adding a declaration to the item renderer in DetailedViewInfo but that crashes the application with no understandable error.
Could someone explain to me how this should be done? I am using custom events all over the place in my application and hadn't had this happen before. Any help highly appreciated!
It would seem you're adding the event listener after the event was dispatched. I see you have an includeIn statement there: this means the DetailedViewInfo component will not be immediately created, but only when the MoviePage state is entered. The event may be dispatched before the component is created and the event listener attached.
The quick fix for this issue, is to not use includeIn, but set the component's visibility according to the current state:
<views:DetailedViewInfo id="detailed" visible="false" includeInLayout="false"
visible.MoviePage="true" includeInLayout.MoviePage="true" />
However, you may want to review your architecture if you need to resort to this. Unfortunately I can't tell you much more than that, since I don't know your current architecture.

Creating Dynamically Flex Custom ItemRender (Constructor)

am creating some Advanced Datagrid with actionscript.
I have created an actionscript class where I extend the VBox object:
package core
{
import mx.containers.VBox;
import mx.controls.TextInput;
public class customItemRender extends VBox
{
public function customItemRender(_TextInput:TextInput, _TextInput2:TextInput)
{
//TODO: implement function
super.addChild(_TextInput);
super.addChild(_TextInput2);
}
}
}
The problem comes up when I declare de itemrender property on the data grid:
AdvancedDataGridColumn.itemRenderer = new ClassFactory(customItemRender(_TextInput1,_TextInput2));
The compiler wont let me instanciate my customItemRender.
Does any one know if there is an alternative solution to solve the problem?
Thanks in advance for you helps,
Regards Javier
private var _ItemRendere:ClassFactory;
private function get MyItemRendere():ClassFactory
{
if (_ItemRendere == null)
{
_ItemRendere = new ClassFactory();
_ItemRendere.generator = customItemRender;
_ItemRendere.properties = {
_TextInput1:MY_TextInput1_OBJECT,
_TextInput2:MY_TextInput2_OBJECT
};
}
return _ItemRendere;
}
then you can use
AdvancedDataGridColumn.itemRenderer = MyItemRendere;
I've only tried to do this using MXML. In that case, i usually have to wrap the IListItemRenderer instance in mx:Component tags. I'm not exactly sure what is going on programmatically when I do this, but it works. The reason is that the itemRender is actually looking for an instance of IFactory rather than an instance so I suppose to do this strictly using AS you would need to create your own IFactory implementation.
e.g.
<mx:List>
<mx:itemRenderer>
<mx:Component>
<mx:Text />
</mx:Component>
</mx:itemRenderer>
</mx:List>
ClassFactory's constructor has a Class as a parameter, not an instance. You need to call:
new ClassFactory(customItemRender);
and not:
new ClassFactory(new customItemRender(_TextInput1,_TextInput2));
or:
new ClassFactory(customItemRender(_TextInput1,_TextInput2));
Now, since the constructor will not be called with reference to TextInput1 and TextInput2, you'll need to instantiate your own TextInputs in the custom renderer itself. (But this is a good thing, if you continue to call new customItemRender(_TextInput1, _TextInput2), then the two TextInputs will only be added to the LAST instance of customItemRender, and all of the others will not have these two objects ).