How can I determine a function's argument count at runtime in Flex 3? - apache-flex

I want to pass an optional data parameter to some callbacks, but only to callbacks that support a single parameter; right now, I have a moderately-sized code base of callbacks that cannot accept a parameter at all. How can I check what parameters a Function object supports?

Function is an Object.
Every function has a read-only property named length that stores the number of parameters defined for the function.
Use it.

If your function is declared in a class use the function describeType it will return an XML you can parse and look at your function name with his arguments

The arguments array is an array of all the parameters passed into a function. Maybe that is what you are looking for?
function traceArgArray(x:int):void
{
for (var i:uint = 0; i < arguments.length; i++)
{
trace(arguments[i]);
}
}
Example taken from livedocs.adobe.com

Related

Flex How To Call A Function With A Variable Number Of Parameters?

Let's Say I Have This Class:
package{
import flash.display.Sprite;
public class Main extends Sprite{
public function Main(){
trace(getAverage(1,2,3));
trace(getAverage(1,2,3,4));
trace(getAverage(1,2,3,4,5));
}
public function getAverage (...numbers) {
var total = 0;
for (var i = 0; i < numbers.length; i++) {
total += numbers [i];
}
return total / numbers.length;
}
}
}
How do I accomplish the "opposite" of this? Namely, how could I now CALL 'getAverage' with a dynamic number of paraemters?
For instance, if I wanted to do something LIKE:
var r:int=Math.random()*6;
var a:Array=new Array();
for (i:int=0;i<r;i++) {
a[i]=Math.random()*22;
}
// Now I have 'r' Number Of Parameters Stored In 'a'
// How Do I Call getAverage, with all the values in 'a'??
// getAverage(a) isn't right, is it?
// I'm looking for something similar to getAverage(a[0],a[1],a[...]);
var av:Number=getAverage(???);
What I want to know, is if I have a function that takes a variable number of arguments, that's great, but how can I CALL IT with a variable number of arguments, when that number isn't known at runtime? Possibly it's impossible... I'm just not sure, since 'callLater' seems to be able to take an array and generate a dynamic number of parameters from it somehow...
NOTE: Answers consisting solely of "Why Do You Want To Do This?", will be downvoted.
P.S. This IS NOT about calculating Averages! I REALIZE There Are Way Simpler Ways Of Doing All Of This! (I could just write getAverage to accept a single array as its only parameter) The Above is just an EXAMPLE to Illustrate my Question. HOW TO PASS A DYNAMIC NUMBER OF PARAMETERS TO A FUNCTION?
Is this what you're looking for?
var av:Number = getAverage.apply(null, a);
Dave is correct. You can use the apply method of a function to pass in an Array of arguments.
Here is a better explanation of how it works and what the arguments of apply are:
http://www.adobe.com/livedocs/flash/9.0/ActionScriptLangRefV3/Function.html
Also note that you can use the call method to do the same thing but using ...args (comma-delimited list) instead, but apply would be more suitable to your situation.
The problem with your question is that the arguments object is already an Array and using (...args) already provides you with a dynamic way to pass any number of arguments you require. Sorry about the previous answer, wasn't thinking straight...
You can create an array or an object with those parameters and pass that object to that function. That's just normal.
Flash has a rather strong introspection capabilities. So, instead of passing a number of objects, you just pass a single dynamic object with any number of attributes you need:
var ob:Object={arg1:"value1", arg2:8};
var arg:String="arg4";
ob["arg3"]=8;
ob[arg]=18;
trace (ob.hasOwnProperty("arg1"));
trace (ob.arg3);
trace (ob.arg4);
That should cover just about any use case you might need. The downside is that this allows for some rather clever and hard to trace bugs. :-)

Create and define Vector

I'm looking for method to create Vector and push some values without defining variable Vector. For example:
I have function:
public function bla(data:Vector.<Object>):void { ... }
this function expects Vector as parameter. I can pass parameters this way
var newVector:Vector.<Object> = new Vector.<Object>();
newVector.push("bla1");
newVector.push("bla2");
bla(newVector);
Can I do it in one line in Flex? I'm looking for something like:
bla(new Vector.<Object>().push("bla1").push("bla2"));
I've also tried this:
bla(function():Vector.<Object> { var result:Vector.<Object> = new Vector.<Object>(2, true); result.push("bla1"); result.push("bla2"); return result; });
But it complains:
1067: Implicit coercion of a value of type Function to an unrelated type __AS3__.vec:Vector.<Object>...
Thanks
You can't chain Vector.push() calls as they return uint's -- the new vector length.
The coercion problem, on the other hand, happens because you are passing a function to the bla function, which expects a Vector.<Object>.
You could fix that easily:
bla((function():Vector.<Object> {
var result:Vector.<Object> = new Vector.<Object>(2, true);
result.push("bla1");
result.push("bla2");
return result; })()
);
However, there's already a top level function in AS3 that helps you creating vectors.
The Vector() function expects either an Array or a Vector and returns a Vector. So, for example, you could use:
bla(Vector.<Object>(['bla1', 'bla2']));
Visit the AS3 Reference for more info.
EDIT: I forgot to mention that the fix on the function approach was simply adding a () to it, meaning we actually called the anonymous function and passed it's return to the bla function.
Just want to mention push takes multiple arguments, and each one is pushed onto the stack sequentially:
function getVector():Vector.<String>
{
var newVector:Vector.<String> = new Vector.<String>();
newVector.push("blah1","blah2","blah3","blah4");
return newVector;
}
I'm not really sure why you'd need to do it one line. You could always write a wrapper class if you happen to do this often. The wrapper class could have a push method that returns a reference to the original object so you can use the first method you wanted.
You could also write a helper function which created a new vector and added the elements to the vector and then returned the vector.
Is there a particular need for wanting this on one line?
You are not able to do this:
bla(new Vector.<Object>().push("bla1").push("bla2"));
because the "push" method returns the length of the Vector. So what this means is that you are trying to push the String "bla2" onto the int 1. This won't work!
And your next example is passing a function to the bla method, not calling that function and passing the returned Vector.
Also you are saying the Vector type is "Object" but you are passing in Strings. You should do this:
Vector.<String>
You could do something like this:
function getVector():Vector.<String>
{
var newVector:Vector.<String> = new Vector.<String>();
newVector.push("bla1");
newVector.push("bla2");
return newVector;
}
bla( getVector() );

Passing optional arguments (...rest) through to another method that takes optional arguments in AS3

I have a "format" method that works in a similar manner to the C# String.Format method, with the following signature:
In a class named StringTools:
/**
* Formats a string, based on C# String.Format method.
* #param raw A string with numbered tokens, such as "{0}, {1}"
* #param rest Values that replace the numbered tokens in raw.
*/
public static function format(raw:String, ...rest:*):String;
StringTools.format("{0}, {1}", "Hello", "World") returns the string "Hello, World" as expected. Now, I'm trying to get my logging class to use this method, but I'm having trouble passing the optional variables through. The signature of the method in the logging class is:
public static function infof(raw:String, ...rest:*):String;
If I pass "rest" directly into StringTools.format(raw, rest), it's passed in as an array, and not as a series of parameters, so if I call it liks this: infof("{0}, {1}", "Hello", "World"), I get the string "Hello,World, {1}", since it replaces the first token with the entire array of values.
I also tried constructing an arguments array, and calling the method like this:
var collectArgs:Array = [raw];
for (var i:Number = 0; i < rest.length; i++)
{
collectArgs.push(rest[i]);
}
var callFunction:Function = StringTools.format.call;
trace(callFunction.apply(null, collectArgs));
However, this traces "World,6". So, it looks like the parameters are shifted. So, I tried initializing collectArgs as [null, raw], and I get "Hello World,6. The number is {1}" again.
Am I doing something wrong? What is the correct way to pass optional parameters from one method that expects optional parameters to another method that expects optional parameters?
Thanks!
I think you are on the right lines using apply. This seems to do illustrate the behaviour you want:
static function f1(raw:String, ...rest:*):void
{
trace("f1: "+raw+" "+rest);
rest.unshift(raw);
f2.apply(null, rest);
}
static function f2(raw:String, ...rest:*):void
{
trace("f2: "+raw+" "+rest);
}
function passSomeArguments():void
{
f1("A",1,2,3);
}
EDIT: You need to pass 'null' as the 1st parameter to apply because the first parameter is what is considered to be 'this' when the function is called. Since the functions are static (and in any case have no dependency on 'this') you can pass null, but you must pass something.
You could also do something like this (of course this is not best implementation for the string formatting):
public static function format(raw:String, ...rest:*):String {
if (rest[0] is Array && rest.length == 1) {
rest = rest[0];
}
var r:RegExp = /(\{\d+\})/g;
var matches:Array = raw.match(r);
for (var i:Number = 0; i < rest.length; i++) {
raw = raw.replace(matches[i], rest[i]);
}
return raw;
}
Then your infof function would just look like this:
public static function infof(raw:String, ...rest:*):void {
var formatted = StringTools.format(raw, rest);
}
As mentioned in my comment, if you remove the call method from the end of you callFunction setter, then you do not need to supply null as the first argument. See http://livedocs.adobe.com/ to understand what the call method actually does, and what the first parameter is for.
As #stephen mentioned, it is a lot simpler to unshift your raw var onto the rest array, rather than building up a new one.
Actually, just found that it's my problem. It should work fine using the argument collection method described, as long as the first element in the arguments array is null. I'm not sure why null is necessary, but it works fine this way.

Is there a test to see if an Object is an associative Array?

Is there a test to see if an Object is an associative array?
Thanks.
When using an Object as an associative array, you are simply adding dynamic properties to it, with arbitrary values.
for...in loops iterate over only dynamic properties of an Object, so if you create a for...in loop and it completes one loop, you will know that the Object is an associative array.
http://livedocs.adobe.com/flex/3/langref/statements.html#for..in
function isObjectAssociativeArray(obj:Object):Boolean
{
for (var prop in obj)
{
return true;
}
return false;
}
You can try getQualifiedClassName and see if the return type is "Object". I haven't tested this myself, but it accepts flash primitives (Object, Array, String...) as well as Classes.

Passing in variables ByRef in Actionscript 3

Is it possible to pass a parameter to a method ByRef (or out etc) in ActionScript 3?
I have some globally scoped variables at the top of my class and my method will populate that variable if it's == null.
I'm passing in the variable which needs to be populated but so far my efforts have returned a locally populated variable leaving the globally scoped version of it still null.
The variable being passed to my method varies so I can't hardcode it in my method and simply set it.
ActionScript 3 passes params by reference by default, like Java - except for primitive types. But what you are trying to have it do isn't passing by reference. The parameter passed in is a reference to an object(in the case when it's not a primitive type), which you may well modify inside of the function.
But, to answer your question. Here is a solution:
function populateIfNull(variableName, value){
this[variableName] = this[variableName] || value
}
Which you can use like:
populateIfNull('name', 'Bob')
populateIfNull('age', 20)
AS3 does not have pass by reference (it is similar to Java in this regard, in that it passes references by value).
Something similar can be simulated if you control the client code by wrapping the object in another object:
var myObj = null;
myFun({ a: myObj });
function (param) {
if (param.a == null) {
param.a = "Hello";
}
}
Use objects.
eg:
var myObj : Object = new Object();
var myArr : Array;
myObj.arr = myArr;
function populateViaRef(obj : Object) : void {
obj.arr = new Array();
for(var i : Number = 0; i < 10; i++)
obj.arr[i] = i;
}
populateViaRef(myObj);
for(var i : Number = 0; i < 10; i++)
trace(myObj.arr[i]);
In ActionScript 3.0, all arguments are passed by reference, because all values are stored as objects. However, objects that belong to the primitive data types, which includes Boolean, Number, int, uint, and String, have special operators that make them behave as if they were passed by value.
http://help.adobe.com/en_US/ActionScript/3.0_ProgrammingAS3/WS5b3ccc516d4fbf351e63e3d118a9b90204-7f56.html
In Java arguments are passed by value.
http://javadude.com/articles/passbyvalue.htm

Resources