PDA

View Full Version : Dynamically importing/instantiating classes


RR_QQ
05-16-2008, 12:24 AM
I HAD POSTED THIS IN AS3 FORUM BY ACCIDENT BUT MOVED IT HERE

If anyone has been paying attention I have been trying to construct a custom DataGrid and for the most part I have succeeded in making my code truly reusable UNTIL the point that I needed to dynamically generate class instances. I find out that my dynamic classes cannot be instantiated UNLESS I create an instance of that class type somewhere in my code, ex:


private var forceCompilation:Array = new Array(OrderEdit);


So there is one line that is not dynamic whatsoever. But if I want to do the above I must do this:


// import place where OrderEdit is
import templates.Orders.*;


Now that's TWO lines that throw my reusable code out the door. So my question is this: is it IMPOSSIBLE to create a plugin that has no single line hard coded and truly dynamic?

So in other words is there a way that through strings I pass to my application I can construct an import statement like:


//pseudocode:
// importVar = "templates.Orders.*";
import magically_translate_to_class_path(importVar);



And through strings I can construct an array with elements such as:


//pseudocode:
// dynamicClassArray = Array(0=>"OrderEdit");
private var forceCompilation:Array = new Array();
for (i=0; i<dynamicClassArray.length; i++) {
forceCompilation.push(magically_translate_to_class _reference(dynamicClassArray[i]));
}


And then finally, I need to be able to pass variables to and from my dynamically generated class that I open up in a PopUP HOWEVER I tried translating this:


var pop1:ArrayEntryForm = ArrayEntryForm(PopUpManager.createPopUp(this, ArrayEntryForm, true));
// Set TitleWindow properties.
pop1.title = "Select File Type";
pop1.showCloseButton = true;

// Set properties of the ArrayEntryForm custom component.
pop1.targetComponent = ti1;
pop1.myArray = doctypes;
PopUpManager.centerPopUp(pop1);


This is the code I found to open up a class dynamically (which turns out is not exactly dynamic for the reasons stated above) - reed the comments in caps to see what I did/tried:


var myClassName:String = "templates." + event.templateName;
try {
var ClassReference:Class = getDefinitionByName(myClassName) as Class;
//var sdf:OrderEdit2;
// THIS WORKS
var myInstanceObject:* = new ClassReference();

// THIS LINE I USED TO REPLICATE THE EXAMPLE TO BE ABLE TO PASS VARIABLES TO MY POP UP WITHOUT SUCCESS
// HAVE NO IDEA WHAT ELSE TO TRY - error #1067 Implicit coercion value type Class to unrelated type
// mx.core:IFlexDisplayObject
//var myInstanceObject:* = ClassReference(PopUpManager.addPopUp(ClassReferenc e, this, true));

} catch( e:Error ) {
Alert.show("Could not instantiate the class " + myClassName + ". Please ensure that the class name is a valid name and that the Actionscript 3 class or MXML file exists within the project parameters.");
return;
}



Can anyone please provide some insight??? I'm at a total loss here and I refuse to believe that I just CANNOT create a truly dynamic application - that it MUST contain hardcoded sections....

Thank you!

creynders
05-16-2008, 07:20 AM
So in other words is there a way that through strings I pass to my application I can construct an import statement like:


//pseudocode:
// importVar = "templates.Orders.*";
import magically_translate_to_class_path(importVar);



No. Importing happens at compile-time not at run-time. Therefore it is impossible. The flash player would have to contain an actionscript compiler for that to be possible.
If you want to use a certain class that class needs to be compiled in the swf and therefore it needs to be referenced somewhere at least once.

I'm a bit confused though, I don't quite understand why you think that is such a problem, nor why it would "throw my reusable code out the door"?

What you're trying to do is bad OOP, since you'd want to use those classes without strictly typing them. That's the reason why you're getting the error BTW. The "addPopUp" method expects an instance of a class that implements IFlexDisplayObject, but the compiler has no way of knowing whether the class instance of ClassReference really implements that required interface.

What exactly is it you're trying to achieve?

RR_QQ
05-16-2008, 05:16 PM
Thank you for the reply, first of all. I was afraid no one would be able to at least comment. Ok this is the reason I am trying to create dynamic classes: I have a custom DataGrid, the last two columns of which will be an Edit column and a Delete column both of which are LinkButton itemrederers that will popup forms when clicked with content depending on which LinkButton was clicked, one form will have input components to edit and the other one will have delete record functionality.

I am using this DataGrid custom component in several different record set types. For example I am using it to display records of my Orders table. When the 'Edit' LinkButton is clicked a form pops up with the appropriate order input elements. I have set up a directory in src/templates/ called Orders (src/templates/Orders) and inside this directory I have "OrderEdit.mxml". From my DataGrid component I have an event listener for when the Edit LinkButton is clicked that creates the popup. However I don't do


var popUp:OrderEdit = new OrderEdit();
PopUpManager.addPopUp(popUp, this, true);
PopUpManager.centerPopUp(popUp);


What I do is pass a template name dynamically through the DataGrid's dataProvider in a index 'edit_template_name' => 'OrderEdit' and I test for this in the LinkButton Edit itemrenderer. I put this in a custome event and dispatch it to the DataGrid main application and I do this:


var myClassName:String = event.templateName;
try {
var ClassReference:Class = getDefinitionByName(myClassName) as Class;
var myInstanceObject:* = new ClassReference();
//var myInstanceObject:* = ClassReference(PopUpManager.addPopUp(ClassReferenc e, this, true));

} catch( e:Error ) {
Alert.show("Could not instantiate the class " + myClassName + ". Please ensure that the class name is a valid name and that the Actionscript 3 class or MXML file exists within the project parameters.");
return;
}

PopUpManager.addPopUp(myInstanceObject, this, true);
PopUpManager.centerPopUp(myInstanceObject);


I also have a Customer page where I do the same thing described above except I pass in the template name as "CustomerEdit" that will try to open up CustomerEdit.xml that is located in src/templates/Customers/.

I will have other places where I will use this and I just DID NOT what to go in to my main program to do this each time I used the DataGrid in a new place:

1. Need it in Orders diplay, open main app and add this:
import templates.Orders.*;
private var forceCompilation:Array = new Array(OrderEdit);

2. Now I realize that I will use it also in Customers display, open main app and add this:
import templates.Customers.*;
// update the forceCompilation array
private var forceCompilation:Array = new Array(OrderEdit, CustomerEdit);

3. Now I realize that I will use it also in Payments display, open main app and add this:
import templates.Payments.*;
// update the forceCompilation array
private var forceCompilation:Array = new Array(OrderEdit, CustomerEdit, PaymentEdit);

etc, etc.

So what you are telling me is that every time that I need to do this I must update my main app???? As you can see I do not know what MXML application I will attempt to open until runtime. If what you say is the case...what is the best way to approach this??

Hope to hear back from you!

RR_QQ
05-16-2008, 08:57 PM
Ok question...HOW would I make this code (OrderEdit.mxml) implement IFlexDisplayObject:


<?xml version="1.0" encoding="utf-8"?>
<mx:TitleWindow xmlns:mx="http://www.adobe.com/2006/mxml"
title="Edit Order"
layout="vertical"
showCloseButton="true"
width="440"
height="352" close="titleWindow_close();" backgroundColor="#e6eeee" borderColor="#ABCAE5" borderAlpha="1">

<mx:Script>
<![CDATA[
import mx.events.CloseEvent;
import mx.managers.PopUpManager;

private function titleWindow_close():void
{
PopUpManager.removePopUp(this);
}

private function onSaveButtonClick(event:Event):void
{
var e:EventDispatcher = new EventDispatcher();
e.dispatchEvent(event);
}
]]>
</mx:Script>
<mx:Canvas width="100%" height="100%">
<mx:TextInput x="0" y="66"/>
<mx:Label x="0" y="10" text="Customer:" fontWeight="bold" fontSize="12"/>
<mx:Label x="0" y="47" text="Order Name:" fontWeight="bold" fontSize="12"/>
<mx:Text x="81" y="12" id="customerName"/>
<mx:Label x="177" y="47" text="Order Description:" fontWeight="bold" fontSize="12"/>
<mx:TextArea x="177" y="67" width="232" height="56"/>
<mx:Label x="0" y="152" text="Cost:" fontWeight="bold" fontSize="12"/>
<mx:Text x="43" y="152" id="cost"/>
<mx:Label x="177" y="150" text="Started:" fontWeight="bold" fontSize="12"/>
<mx:Label x="319" y="150" text="Ended:" fontWeight="bold" fontSize="12"/>
<mx:Label x="177" y="224" text="Last Updated:" fontWeight="bold" fontSize="12"/>
<mx:Label x="319" y="224" text="Deleted On:" fontWeight="bold" fontSize="12"/>
<mx:Button y="279" label="Save" horizontalCenter="-2"/>
<mx:DateField x="177" y="166" id="dateStarted" text=""/>
<mx:DateField x="319" y="166"/>
<mx:DateField x="177" y="240"/>
<mx:DateField x="319" y="240"/>
</mx:Canvas>

</mx:TitleWindow>


Thank!

creynders
05-16-2008, 10:13 PM
You'll have to change little to make that work.
Instead of passing the classname as a String to the event, pass an instance of the class.
Then in the code at the receiving end cast that instance to IFlexDisplayObject or one of it descendants.

var myTemplate : IFlexDisplayObject = event.templateInstance as IFlexDisplayObject;
PopUpManager.addPopUp(myTemplate, this, true);

RR_QQ
05-16-2008, 10:42 PM
Thank you....by the way, did you look at my post before that one? That's where I explain what I am trying to do. Thank you.