There is a client - server basic application. The client uses a simple remoting to comunicate with the server side. The server side could be powered on WebORB, BlazeDS or any other product. The client side is using the FLEX framework. That is it about a technologies stack. Now, let's forget about the server side and just have a look on the following client side
package com.blog.ri
{
import mx.collections.ArrayCollection;
public class MyCollection extends ArrayCollection
{
public function MyCollection(source:Array=null)
{
super(source);
}
}
}
Additionally, let's assume we have the following class and it is mapped to the server side class:
package com.blog.ri
{
[Bindable]
[RemoteClass(alias="com.blog.ri.MyEntity")]
public dynamic class MyEntity
{
private var _myCollection:MyCollection;
public function get myCollection():MyCollection
{
if(_myCollection == null)
_myCollection = new MyCollection();
return _myCollection;
}
public function set myCollection(value:MyCollection):void
{
_myCollection = value;
}
}
}
Also, the server side service expose for clients the void save(MyEntity candidate) method and I implemented it on the client side as it shown below:
package com.blog.ri
{
public class MyService
{
private var _remoteObject:RemoteObject;
public function MyService()
{
var channelSet:ChannelSet = new ChannelSet();
var amfChannel:AMFChannel = new AMFChannel("my-amf", "http://localhost/weborb.aspx");
channelSet.addChannel(amfChannel);
_remoteObject = new RemoteObject("GenericDestination");
_remoteObject.channelSet = channelSet;
_remoteObject.source = "com.blog.ri.MyService";
_remoteObject.getDetailedStatistic.addEventListener("result",onItemSaved);
_remoteObject.addEventListener("fault", onFault);
}
public function save(candidate:MyEntity, responder:IResponder = null ):void
{
var asyncToken:AsyncToken = _remoteObject.save(candidate);
if( responder != null )
asyncToken.addResponder( responder );
}
}
}
Finally, I tried to save a new instance of the MyEntity class in our main mxml file as it shown below:
protected function creationCompleteHandler():void
{
var myService:MyService = new MyService();
var candidate:MyEntity = new MyEntity();
candidate.myCollection = new MyCollection();
myService.save(candidate);
}
That is it. When I run the code, I received the following exception:
ArgumentError: Error #2004: One of the parameters is invalid. at
flash.net::NetConnection/invokeWithArgsArray() at
flash.net::NetConnection/call() at
mx.messaging.channels::NetConnectionChannel/internalSend()[E:\dev\hero_private\frameworks\projects\rpc\src\mx\messaging\channels\NetConnectionChannel.as:281]
at
mx.messaging.channels::AMFChannel/internalSend()[E:\dev\hero_private\frameworks\projects\rpc\src\mx\messaging\channels\AMFChannel.as:364]
at
mx.messaging::Channel/send()[E:\dev\hero_private\frameworks\projects\rpc\src\mx\messaging\Channel.as:1002]
at
mx.messaging.channels::PollingChannel/send()[E:\dev\hero_private\frameworks\projects\rpc\src\mx\messaging\channels\PollingChannel.as:394]
at
mx.messaging::ChannelSet/send()[E:\dev\hero_private\frameworks\projects\rpc\src\mx\messaging\ChannelSet.as:1429]
at
mx.messaging::ChannelSet/channelConnectHandler()[E:\dev\hero_private\frameworks\projects\rpc\src\mx\messaging\ChannelSet.as:1084]
at flash.events::EventDispatcher/dispatchEventFunction() at
flash.events::EventDispatcher/dispatchEvent() at
mx.messaging::Channel/connectSuccess()[E:\dev\hero_private\frameworks\projects\rpc\src\mx\messaging\Channel.as:1148]
at
mx.messaging.channels::AMFChannel/resultHandler()[E:\dev\hero_private\frameworks\projects\rpc\src\mx\messaging\channels\AMFChannel.as:576]
As you can see, I extended the ArrayCollection class and according to the Adobe documentation, the ArrayCollection implements the IExternalizable interface. I decided to localize the problem and created a simple class that implements IExternalizable. Then, I extended this class in some other MyChild class and defined the MyChild property in the MyEntity class. In this case, I received the exception above as well. Is there a problem how I wrote the code or it is a bug within flex?
Thanks for any help.
The question is duplicated in my blog.
You could try:
registerClassAlias( "mx.collections.ArrayCollection", ArrayCollection);
registerClassAlias("flex.messaging.io.ArrayCollection", ArrayCollection);
Try adding an alias for MyCollection.
I got the unhelpful Error #2004 until all of my classes implementing IExternalizable had aliases. In my case it was for persistence (ViewNavigatorApplicationBase.persistNavigatorState = true) in which case I had to make sure I called registerClassAlias early enough in the startup to precede View.deserializeData(). The preinitialize event on the app works ok.
Some of your classes being (de)serialized is probably not linked into Flex project. Try adding following to your main Application/Module file:
private var forceReference:Array = [YourClass1, YourClass2];
Related
I am in the early stages of setting out the architecture for a new project. A data driven Flex UI, to sit within a web page, calling an Amfphp Service for MySQL data to populate elements within the Flex UI.
So many examples of how to approach this are out of date or poorly written so I am looking not only to completely understand the flow of data but to put in place clear, robust practice. Please review my approach and let me know if you feel I could do better.
Say I want to display a List of 'Subjects', let's follow the process I have so far.
Amfphp Service
Having structured and populated a MySQL database, named 'AtlasData', I have developed my initial Amfphp Service which, using the Amfphp Back Office Service Browser, appears to be returning an array of strongly typed 'VoSubject' objects. On my development Mac (MAMP installed) within my 'amfphp/Services/vo folder I have the following 'VoSubject.php' file:
<?php
/**
* Created by IntelliJ IDEA.
* User: Chris
* Date: 04/10/2014
* Time: 18:31
*/
class VoSubject {
/* *
* This Class models one row of the MySQL table. It has one field
* for each row of the Table and a special extra field.
* The extra field is $_explicitType, and its value is the fully qualified
* ActionScript Value Object I intend to use in the Flex application to model the data.
* If you don‚t configure this field correctly, then in the Flex app you
* will not get your strongly typed ActionScript class, but a dynamic object.
* */
public $subjectId;
public $subjectName;
// Explicit ActionScript class
var $_explicitType = "VoSubject";
}
My Amfphp Service currently looks like this (note I have removed some of the methods for brevity):
<?php
require_once ('vo/VoSubject.php');
include ('DbAccess.php');
class AtlasService {
// This simple function can be used to test the service.
public function helloWorld() {
return "Hello World";
}
public function getAllSubjects() {
// Connect to the database using PHP Data Objects (PDO).
try {
/*
* The DbAccess class is a Singleton class.
* Create an instance of this class to access it's methods.
*/
$db = DbAccess::getInstance();
// Create a PHP Data Object.
$pdo = $db->getPDO();
} catch (PDOException $e) {
print "Connection Error!: " . $e->getMessage() . "<br/>";
die();
}
// Retrieve all rows from the AtlasData database 'subjects' Table.
try {
$tsql =
'SELECT s.`subjectId`, s.`subjectName`
FROM Subjects s';
$stmt = $pdo->prepare($tsql);
$stmt->execute();
// Fetch all of the data and place in variable '$results'.
$results = $stmt->fetchAll(PDO::FETCH_CLASS, 'VoSubject');
} catch (PDOException $e) {
print "Error when fetching data: " . $e->getMessage() . "<br/>";
die();
}
// Close the database connection.
$stmt = null;
$pdo = null;
// Return the array.
return $results;
}
}
Using the Amfphp Back Office - Service Browser to call the 'getAllSubjects' function the following is returned:
It would appear that using the code
$results = $stmt->fetchAll(PDO::FETCH_CLASS, 'VoSubject')
has set $results to be an Array of VoSubject objects.
Flex Application
So now I want my Flex application to call the Amfphp Service function 'getAllSubjects.
I am an advocate of View Model Presenter when developing Flex projects which I know will escalate in complexity. In this project's infancy I have created the following:
views - SubjectBar_View (MXML file)
presenters - SubjectBar_Presenter (ActionScript class)
model - Model (ActionScript class)
My SubjectBar_View displays a List of subjects:
<s:List id="subjectList" dataProvider="{presenter.subjects}">
<s:layout>
<s:HorizontalLayout/>
</s:layout>
<s:itemRenderer>
<fx:Component>
<s:ItemRenderer>
<s:HGroup paddingLeft="2">
<s:Label text="{data.subjectName}" width="125"/>
</s:HGroup>
</s:ItemRenderer>
</fx:Component>
</s:itemRenderer>
</s:List>
My SubjectBar_Presenter provides the data source for the List to bind to and this property is set by calling a method in the Model:
package presenters {
import flash.events.Event;
import models.Model;
import mx.collections.ArrayCollection;
import mx.events.FlexEvent;
import vo.VoSubject;
[Bindable]
public class SubjectBar_Presenter {
private var _model:Model = Model.getInstance();
private var _subjects:ArrayCollection;
public function get subjects():ArrayCollection {
return _subjects;
}
public function set subjects(value:ArrayCollection):void {
_subjects = value;
}
// Constructor.
public function SubjectBar_Presenter() {
// Add an eventListener to listen for property changes in the Model.
_model.addEventListener("subjectsChanged", onSubjectsChanged);
}
private function onSubjectsChanged(event:Event):void {
// Update the property.
this.subjects = _model.subjects;
}
public function onCreationComplete(event:FlexEvent):void {
// Get all Subjects from MySQL database.
_model.getAllSubjects();
}
}
}
My Model connects to the server and calls the Amfphp Service function 'getAllSubjects':
package models {
import flash.events.Event;
import flash.events.EventDispatcher;
import flash.net.NetConnection;
import flash.net.Responder;
import mx.collections.ArrayCollection;
// Custom Events.
[Event(name="subjectsChanged", type="flash.events.Event")]
public class Model extends EventDispatcher {
// Event Names.
public static const SUBJECTS_CHANGED:String = "subjectsChanged";
private static var _model:Model;
private var _subjects:ArrayCollection;
private var _netConnectionObject:NetConnection;
private var _responder:Responder = new Responder(handleAllSubjects, null);
public function get subjects():ArrayCollection {
return _subjects;
}
public function set subjects(value:ArrayCollection):void {
_subjects = value;
// Dispatch an event to allow the Detail view to update.
dispatchEvent(new Event(SUBJECTS_CHANGED));
}
public function get netConnectionObject():NetConnection {
return _netConnectionObject;
}
public function set netConnectionObject(value:NetConnection):void {
_netConnectionObject = value;
}
// Constructor.
public function Model(pvt:PrivateClass) {
// Call the 'init' function to carry out any preparatory work.
this.init();
}
// Singleton creator.
public static function getInstance():Model {
if (Model._model == null) {
Model._model = new Model(new PrivateClass());
//trace("Singleton instantiated");
}
else {
//trace("Sorry--already have a Singleton instantiated")
}
return Model._model;
}
private function init():void {
// Call any preparatory functions here.
this.createNetConnection();
}
private function createNetConnection():void {
netConnectionObject = new NetConnection();
//netConnection.connect( [server name] / [project folder] /amfphp);
netConnectionObject.connect("http://localhost/amfphp-2.2.1/amfphp/index.php");
}
private function handleAllSubjects(result:Object):void{
// trace(result.toString());
// The PHP method returns an Array NOT an ArrayCollection.
this.subjects = new ArrayCollection(result as Array);
}
public function getAllSubjects():void {
// Call the AtlasService.
//netConnection.call([Service Name]/[function name]", [Responder], [parameters]);
netConnectionObject.call("AtlasService/getAllSubjects", new Responder(handleAllSubjects, null));
}
}
}
class PrivateClass {
public function PrivateClass() {
//trace("Private class is up");
}
}
In my Flex project 'src' folder I have created a 'vo' folder and created the following 'VoSubject' class to define my Subject Value Object:
package vo {
// This is the ActionScript Value Object class.
// This must match the PHP Value Object class defined within the amfphp/Services/vo folder.
[RemoteClass(alias="VoSubject")]
[Bindable]
public class VoSubject {
public var subjectId:int;
public var subjectName:String;
// Constructor.
public function VoSubject() {
}
}
}
It is the use of this VoSubject class on the Flex side which I am unsure of. Is the line [RemoteClass(alias="VoSubject")] pointing to the php class in my amfphp/Services/vo folder? If so where is this relative to. Should it read [RemoteClass(alias="vo/VoSubject")] because my VoSubject.php class is within a folder named 'vo' within my Services folder?
If I debug my application the List is displayed and populated with the subjectNames. This is great. However it would appear that my subjects data source is an ArrayCollection of objects containing the subjectId and subjectName but not an ArrayCollection of VoSubject objects.
Can someone please explain how I ensure that my 'subjects' data source in my 'subjectBar_Presenter' class is an ArrayCollection of strongly typed VoSubject objects. Additionally if you feel that I could improve my approach I am very willing to learn.
Thank you for getting to the end! I look forward to your thoughts.
Chris
The var $_explicitType = "VoSubject"; should point to your actionscript class, in your case 'vo.VoSubject'. The [RemoteClass(alias="VoSubject")] should match with the explicit type. So in short, both reference the actionscript class.
At first glance your code seems to follow the MVC pattern, which is always good. But I must admit that I quickly skimmed through all the code to find the actual question.
In app config file I have a Signal/Command mapping
signalCommandMap.map(DisconnectUserSignal).toCommand(DisconnectUserCommand);
Then,
I have two connection classes:
public class BaseConnection implements IBaseConnection
{
// When I am trying to inject the signal here:
[Inject] public var disconnectUserSignal:DisconnectUserSignal; // it is always null
_netConnection = new NetConnection();
...
}
and
public class BBConnection extends DefaultConnectionCallback implements IBBConnection
{
// When I am trying to inject the signal here:
[Inject] public var disconnectUserSignal:DisconnectUserSignal; // it works perfectly fine
_baseConnection = new BaseConnection(this);
}
Is there any suggestion of what might be the reason?
Thank you
After going through the robotlegs framework documentation - I found the answer:
I changed the _baseConnection to be an interface, and moved everything from BaseConnection's constructor into init method and now I am injecting it inside my BBConnection.
Here how BBConnection looks now:
[Inject]
public var baseConnection:IBaseConnection;
public function BBConnection()
{
}
[PostConstruct]
public function init():void
{
baseConnection.init(this);
}
Now I can successfully inject Disconnect signal inside base connection.
Source: https://github.com/robotlegs/robotlegs-framework/wiki/common-problems#injected-properties-are-null-in-constructor
Actually in my Flex Application have Some Popup windows and i want take some values in this Popup Window But The Values are Comming NULL
So how to Make a PopUp Window as Global? Because we are Using the values Globally.
Please Give Suggestion...
Edit
I'm Edit with some code...
Main.mxml(Main Aplication), Demo1.mxml(PopUpWindow), Demo2.mxml(PopUpWindow)
Now in Demo1.mxml have variable like...
[Bindable]private var arrayC:ArrayCollection=new ArrayCollection();//Hear Some value their.
NOw i want Use arrayC in Demo2.mxml then ..
public var variable1:Demo1=new Demo1();
var ac:ArrayCollection = new ArrayCollection();
ac = variable1.arrayC;
But hear ac contain Null Value Why?
Then,Now i'm Thinking Demo2.mxml(PopUpWindow) is Converting To Global Scope so it's value Used in Any Where .
Null because of you are tried create new instance so that each instance having their own state.
Also i bet you can't access arrayC ArrayCollection variable declared as private so you can't acccess.
Need to follow few steps
[Bindable]public var arrayC:ArrayCollection=new ArrayCollection(); //Make public variable
Use Singleton Class for you application
package com.foo.bar {
public class Model {
private static var instance : Model;
public function Model( enforcer : SingletonEnforcer ) {}
public static function getInstance() : Model {
if (!instance) {
instance = new Model( new SingletonEnforcer() );
}
return instance;
}
public var arrayC:ArrayCollection = new ArrayCollection();
}
}
class SingletonEnforcer{}
For more details Singleton pattern
private var popup:Demo1;
popup = PopUpManager.createPopUp(this,Demo1,true) as Demo1;
popup.arrayC = Model.getInstance().arrayC; // Here set value from Model class
PopUpManager.centerPopUp(popup);
Suppose you tried to access demo1.mxml arrayC variable in Demo2.mxml
var demo1_arrayC = Model.getInstance().arrayC;
Note that you can access arrayC arraycollection anywhere in your application like Demo2.mxml,Demo3...etc.
But better we have to avoid Singleton Class (unit test diffcult ..etc).
If you are using the values Globally, then no matter what you mean by the "Make a PopUp Window as Global", I strongly suspect that you would be best served by a singleton event dispatcher.
package com.example.demo.models {
import flash.events.IEventDispatcher;
import flash.events.EventDispatcher;
[Bindable]
class MyGlobalStuff extends EventDispatcher {
public var someGlobalValue:*;
private var _instance:MyGlobalStuff;
public function MyGlobalStuff (lock:SingletonLock, target:IEventDispatcher=null) {
super(target);
if(!(lock is SingletonLock)) {
throw(new Error("MyGlobalStuff is a singleton, please do not make foreign instances of it"));
}
}
public static function getInstance():MyGlobalStuff {
if(!_instance) {
_instance = new MyGlobalStuff (new SingletonLock());
}
return _instance;
}
}
}
class SingletonLock{}
The idea is this: that you would bind in your popup to
{myStuffModel.someGlobalValue}
myStuffModel would be initialized in your mxml as:
protected var myStuffModel:MyStuffModel = MyStuffModel.getInstance();
then in any other class throughout your application you can bind to or access the EXACT same data via the singleton model.
I got error message when trying to access a class in the default package from the class in its sub package. Can any one help me to sort this out.
FYI, my package structure is A -> B. I meant folder 'A' as default package and 'B' as sub package.
Thanks in advance.
Just create a object of Class A, and call class instance method, from its object.
var classAObj:A = new A();
classObj.MethodA();
I think what you're looking for is for class B to extend class A. That would look something like this in your code:
package main
{
class B extends A
{
// Code here...
}
}
Having code inside packages does not in general affect functionality, it's more an organizational tool. (Except for the internal keyword.)
how about private, protected and public ? I could not see any explanation in the other answers so here it is.
class A
{
private var _password:String;
public var username:String;
protected var serverURL:String;
public function login():void
{
// some code
callServerForLogin();
}
protected function callServerForLogin():void
{
// some code
}
}
class B extends A
{
public function B()
{
var parentPassword = super._password;
// FAILS because private and accessible only inside class A
var parentUsername = super.username
// all ok in here, public property
var parentServerURL = super.serverURL;
// all ok, because it is protected
// also we can call super.login(); or super.callServerForLogin();
}
// IMPORTANT we are also allowed to override public and protected functions
override public function login():void
{
super.login();
// we call the parent function to prevent loosing functionality;
Alert.show("Login called from class B");
}
override protected function callServerForLogin():void
{
super.callServerForLogin();
// keep also parent logic
Alert.show("calling protected method from B");
}
}
// ---- Now considering you declare an object of type B you can do the following
var bObj:B = new B();
// access public properties and call public functions from both B and A
bObj.username = "superhero";
bObj.login();
// will get compile error for next lines
bObj.serverURL = "host.port";
bObj.callServerForLogin();
Does anyone know of a framework, preferably some way to have the Flex compiler run an extension or perhaps just a build step that we could generate strongly typed proxy classes of our application's data models.
There are 2 main things we want to do with the proxy's:
At runtime we want to lazily parse and instantiate the instance as accessed (similiar to how Java's Hibernate has Lazy proxy objects)
In an editor application we want to implement setter calls so we can track which objects have been modified
The Proxy is really necessary in this situation beyond things like programatically setting up ChangeWatcther's because we need to track Array adds/remove and possibly track "reference" objects so that when a "reference key" is changed we know to save those objects that are referencing it by key
In the first case we want the proxy to basically abstract when that object is loaded from serialized data, but still pass around references of it with the same public properties and data access pattern if it were the real object.
Basically the proxy would instantiate the object the first time a method is called on it.
I know we could use some AS3 byte-code libraries like as3-commons-bytecode.
Or possibly repurposing the GraniteDS Code Generation.
I'd prefer to generate code because it is a deterministic thing and it'd be nice if we could have a way to debug it at runtime easier.
Does anyone know if I could do something like MXMLC does when it generates AS3 code from MXML files.
Also is there anyway to control "when" in the compilation pipeline I can generate code, because we have a lot of data objects using public fields instead of getter/setters, but that are [Bindable] and so if I could generate the proxy based on the generated getter/setter methods that would work.
Here's an example application data object and proxy classes:
[Bindable]
public class PersonDTO implements Serializable {
private var _name:String;
private var _age:Number
public function get age():Number {
return _age;
}
public function set age(a:Number):void {
_age = a;
}
public function get name():String {
return _name;
}
public function set name(n:String):void {
_name = n;
}
public void readObject(data:*) {
//...
}
}
// GENERATED CLASS BASED ON PersonDTO
public class LazyProxy_PersonDTO extends PersonDTO {
private var _instance:PersonDTO = null;
private var _instanceData:*;
private function getInstance():void {
if (_instance == null) {
_instance = new PersonDTO();
_instance.readObject(_instanceData);
}
}
override public function get age():Number {
//Ensure object is instantiated
return getInstance().age;
}
override public function get name():String {
//Ensure object is instantiated
return getInstance().name;
}
}
// GENERATED CLASS BASED ON PersonDTO
public class LogChangeProxy_PersonDTO extends PersonDTO {
//This will be set in the application
public var instance:PersonDTO;
//set by application
public var dirtyWatcher:DirtyWatcherManager;
override public function set age(a:Number):void {
dirtyWatcher.markAsDirty(instance);
instance.age = a;
}
}
Digging a little deeper into AS3-Commons byte code library it looks like they support generating proxy classes and interceptors.
http://www.as3commons.org/as3-commons-bytecode/proxy.html
public class DirtyUpdateInterceptor implements IInterceptor {
public function DirtyUpdateInterceptor() {
super();
}
public function intercept(invocation:IMethodInvocation):void {
if (invocation.kind === MethodInvocationKind.SETTER) {
if (invocation.arguments[0] != invocation.instance[invocation.targetMember]) {
invocation.instance.isDirty = true;
}
}
}
}