I'm quite new to flex/actionscript and I was wondering if there is an equivalent for php's (and other languages) FILE and LINE identifiers?
Basicly I want to do some custom error logging and would like to something like:
var mymessage:String = 'Oops, a hiccup occured at ' + __FILE__ + ', line: ' + __LINE__;
Where file and line would ofcourse be substituted for their values at compile time.
Is this possible?
It's not directly possible, but there's a fairly usable workaround for personal testing
var stackTrace:String = new Error().getStackTrace();
if (stackTrace) {
var mymessage:String = "Oops, a hiccup occurred " + stackTrace.split("\n")[1];
}
Adjust your abuse of getStackTrace to taste.
To add to Cory's answer to the above. First add:
-define=CONFIG::debugging,true
to your library's compiler settings (next to the "-locale en_US" in "Additional Compiler Arguments"). Then use this quickie library:
package ddd
{
public class Stack
{
protected static function str(val:*):String
{
if( val == null ) return "<null>";
if( val == undefined ) return "<undefined>";
return val.toString();
}
protected static var removeAt :RegExp = /^\s*at\s*/i;
protected static var matchFile:RegExp = /[(][)][\[][^:]*?:[0-9]+[\]]\s*$/i;
protected static var trimFile :RegExp = /[()\[\]\s]*/ig;
/* Must maintain number of stack levels, so that _stack can assume the 4th line of getStackTrace */
private static function _stack( msg:String="", ...params ):String
{
var s :String = new Error().getStackTrace();
var func:String = "??";
var file:String = "??";
var args:String = null;
if(s)
{
func = s.split("\n")[4];
func = func.replace( removeAt, "" );
var farr:Array = func.match( matchFile );
if( farr != null && farr.length > 0 ) file = farr[0].replace( trimFile, "" );
func = func.replace( matchFile, "" );
}
for each( var param:* in params )
{
args = ( args == null ? "" : args.concat(",") );
args = args.concat( str(param) );
}
return func + "(" + (args==null?"":args) + ")" + ( (msg!=null && msg!="") ? ":"+msg : "" ) + " at " + file;
}
/* Must maintain number of stack levels, so that _stack can assume the 4th line of getStackTrace */
public static function stack( msg:String="", ...params ):String
{
params.unshift( msg );
return _stack.apply( null, params );
}
/* Must maintain number of stack levels, so that _stack can assume the 4th line of getStackTrace */
public static function pstack( msg:String="", ...params ):void
{
CONFIG::debugging {
params.unshift(msg);
trace( _stack.apply( null, params ) );
}
}
}
}
And then you can just call:
Stack.pstack();
inside any function to print the stack location at that point, which looks like this:
package::classname/function() at /wherever/src/package/classname.mxml:999
Just remember to turn debugging to false before compiling for production, and all that will be left is an empty pstack call that does nothing - the guts will be conditional-compiled out.
IMHO the line or file doesn't add to much information in Flex. I usually output class and method name and as my methods tend to be short, it usually is clear where something occurred.
If you find yourself with methods that are hundreds of lines long, you should rethink your coding style.
Related
I am using foresight.js to load hi-res images for retina devices. Foresight attempts to replace lo-res images with 2x-pixel density images. Since foresight attempts to replace lo-res images before the page has rendered, it is not possible for me to use the GD image resizing methods in the template for my resized images. So, I am allowing the SS 3.1 cms user to upload one large image and having the system re-size it after upload - leaving a 1x and 2x image in the assets folder.
My question is how can I set a custom validation error message if the cms user does not upload a large enough image?
Here is the code that resizes the image on upload.
class ResampledImage extends Image {
static $default_lores_x = 250;
static $default_lores_y = 250;
static $default_hires_x = 500;
static $default_hires_y = 500;
static $default_assets_dir = 'Uploads';
static $hires_flag = '2x';
function getLoResX() {
return ( static::$lores_x ) ? static::$lores_x : self::$default_lores_x;
}
function getLoResY() {
return ( static::$lores_y ) ? static::$lores_y : self::$default_lores_y;
}
function getHiResX() {
return ( static::$hires_x ) ? static::$hires_x : self::$default_hires_x;
}
function getHiResY() {
return ( static::$hires_y ) ? static::$hires_y : self::$default_hires_y;
}
function getAssetsDir() {
return ( static::$assets_dir ) ? static::$assets_dir : self::$default_assets_dir;
}
function onAfterUpload() {
$this->createResampledImages();
}
function onAfterWrite() {
$this->createResampledImages();
}
function createResampledImages() {
$extension = strtolower($this->getExtension());
if( $this->getHeight() >= $this->getHiResX() || $this->getWidth() >= $this->getHiResY() ) {
$original = $this->getFullPath();
$resampled = $original. '.tmp.'. $extension;
$orig_title = $this->getTitle();
$path_to_hires = Director::baseFolder() . '/' . ASSETS_DIR . '/' . $this->getAssetsDir();
$hires = $path_to_hires . '/' . $orig_title . self::$hires_flag . '.' . $extension;
$gd_lg = new GD($original);
$gd_sm = new GD($original);
if ( $gd_lg->hasImageResource() ) {
$gd_lg = $gd_lg->resizeRatio($this->getHiResX(), $this->getHiResY());
if ( $gd_lg )
$gd_lg->writeTo($hires);
}
if($gd_sm->hasImageResource()) {
$gd_sm = $gd_sm->resizeRatio($this->getLoResX(), $this->getLoResY());
if($gd_sm) {
$gd_sm->writeTo($resampled);
unlink($original);
rename($resampled, $original);
}
}
}
}
Looking at UploadField::setFileEditValidator() it appears that I can designate a method on my extended Image class to use as a Validator so that I can check for $this->getWidth() and $this->getHeight() and return an error if they are not large enough.
Is this possible?
I tried adding the following method to ResampledImage, but this was unsuccessful:
function MyValidator() {
$valid = true;
if ( $this->getHeight() < $this->getHiResX() || $this->getWidth() < $this->getHiResY() ) {
$this->validationError("Thumbnail",'Please upload a larger image');
$valid = false;
}
return $valid;
}
I think the fileEditValidator is acutally used after the image has been uploaded and is for the EditForm when displayed/edited.
Seems that what you are looking for is validate the Upload. You can set a custom Upload_Validator with setValidator($validator) on your UploadField.
So what I would try is create a custom validator class (maybe named CustomUploadValidator) that extends Upload_Validator (source can be found in the Upload.php file in the framework). So, something along those lines:
$myValidator = new CustomUploadValidator();
$uploadField->setValidator($myValidator);
In your custom validator class maybe create a method isImageLargeEnough() which you would call in the validate() method:
public function validate() {
if(!$this->isImageLargeEnough()) {
$this->errors[] = 'Image size is not large enough';
return false;
}
return parent::validate();
}
In your isImageLargeEnough() you can access the uploaded image through $this->tmpFile. So maybe do something like:
public function isImageLargeEnough()
{
$imageSize = getimagesize( $this->tmpFile["tmp_name"] );
if ($imageSize !== false)
{
if ( $imageSize[0] < 500 || $imageSize[1] < 500 )
{
return false;
}
}
return true;
}
Here the min width/height are hard coded to 500, but you can probably implement a setMinImageSizes method that stores those on a variable in your custom validator class. which could be called like $uploadField->getValidator()->setMinImageSize(543, 876);
None of this is actually tested, but hopefully it can give you some pointers on what to look for.
In order to send a POST request I need to enumerate all properties of a given object. This object may or may not be dynamic. I'm looking for the most elegant solution. This is what I've got so far:
function createURLVariables(params:Object):URLVariables
{
// Workaround: Flash Player performs a GET if no params are passed
params ||= {forcePost: true};
var vars:URLVariables = new URLVariables();
var propertyName:String;
var propertyList:XMLList = describeType(params)..variable;
var propertyListLength:int = propertyList.length();
// A dynamic object won't return properties in this fashion
if (propertyListLength > 0)
{
for (var i:int; i < propertyListLength; i++)
{
propertyName = propertyList[i].#name;
vars[propertyName] = params[propertyName];
}
}
else
{
for (propertyName in params)
vars[propertyName] = params[propertyName];
}
return vars;
}
One potential problem is that this won't return properties for getters (accessors).
I took the following approach in the as3corelib JSON Encoder. You'll have to modify this to suit your needs, but it should give you an idea to work from. Note that there is some recursion in here (the convertToString call, which you might not need:
/**
* Converts an object to it's JSON string equivalent
*
* #param o The object to convert
* #return The JSON string representation of <code>o</code>
*/
private function objectToString( o:Object ):String
{
// create a string to store the object's jsonstring value
var s:String = "";
// determine if o is a class instance or a plain object
var classInfo:XML = describeType( o );
if ( classInfo.#name.toString() == "Object" )
{
// the value of o[key] in the loop below - store this
// as a variable so we don't have to keep looking up o[key]
// when testing for valid values to convert
var value:Object;
// loop over the keys in the object and add their converted
// values to the string
for ( var key:String in o )
{
// assign value to a variable for quick lookup
value = o[key];
// don't add function's to the JSON string
if ( value is Function )
{
// skip this key and try another
continue;
}
// when the length is 0 we're adding the first item so
// no comma is necessary
if ( s.length > 0 ) {
// we've already added an item, so add the comma separator
s += ","
}
s += escapeString( key ) + ":" + convertToString( value );
}
}
else // o is a class instance
{
// Loop over all of the variables and accessors in the class and
// serialize them along with their values.
for each ( var v:XML in classInfo..*.(
name() == "variable"
||
(
name() == "accessor"
// Issue #116 - Make sure accessors are readable
&& attribute( "access" ).charAt( 0 ) == "r" )
) )
{
// Issue #110 - If [Transient] metadata exists, then we should skip
if ( v.metadata && v.metadata.( #name == "Transient" ).length() > 0 )
{
continue;
}
// When the length is 0 we're adding the first item so
// no comma is necessary
if ( s.length > 0 ) {
// We've already added an item, so add the comma separator
s += ","
}
s += escapeString( v.#name.toString() ) + ":"
+ convertToString( o[ v.#name ] );
}
}
return "{" + s + "}";
}
is it posible to have callable objects on ActionScript? For example:
class Foo extends EventDispatcher
{
Foo() { super(); }
call(world:String):String
{
return "Hello, " + world;
}
}
And later...
var foo:Foo = new Foo();
trace( foo("World!") ); // Will NOT work
Why would you need to do this? (I'm not criticising, just interested!) Functions in AS3 are themselves first-class citizens, and can be passed around as arguments.
e.g.
public function main(foo:Function):void
{
trace(foo("World!")); // Will work, assuming foo = function(str:String):String {...}
}
No, only functions/methods can be called in this way. If the only reason is you want to type fewer characters, then you should shorten the length of the instance names and method names.
One option is to use a closure:
public function Foo():Function {
var bar:String;
return function (world:String):String {
var msg:String;
if (bar) {
msg = bar + ' says "Hello, ' + world + '"';
} else {
msg = "Hello, " + world;
}
bar = world;
return msg;
}
}
...
var foo = Foo();
trace( foo("World!") );
This is a much simplified case of the larger pattern of implementing objects as functions. As such, it's more useful in languages that support FP but not OOP, but does technically give you a callable "object". The syntax may be a little off, but:
public function createFoo(barInit, ...):Function {
var slots = {
greeter: barInit, ...
};
var methods = {
'get': function(name) { return slots[name]; }
'set': function(name, value) { slots[name] = value; }
greet: function(whom) {
var msg = slots.greeter + ' says "Hello, ' + whom + '"'
slots.greeter = whom;
return msg;
},
...
};
return function (method:String):* {
args = Array.splice.call(arguments, 1);
return methods[method].apply(null, args);
}
}
var foo = createFoo('Kermit');
trace(foo('greet', "World"));
trace(foo('greet', "Sailor"));
You probably don't want to do it in AS.
As others had said, you can't have callable objects. However, if for some reason you want to have stateful functions, you can achieve it with help of static class variables and package level functions. For example:
// com/example/foo/Helper.as
package com.example.foo {
public class Helper {
private static var _instance:Foo;
public static var data:String;
public static function get instance():Helper
{
if(!_instance) { _instance = new Helper(); }
return _instance;
}
}
}
// com/example/foo/hello.as
package com.example.foo {
public function hello(world:String):void
{
if(Helper.instance.data)
{
trace("Bye, " + Helper.instance.data);
}
trace("Hello, " + world);
Helper.instance.data = world;
}
}
When used, it will print different things.
hello("World!"); // traces "Hello, World!"
hello("People"); // traces "Bye, World!" and "Hello, People"
note: both the constructor and the method declaration miss the keywords public function to even compile, but I suppose that's not the original code. :)
the answer is: you can't.
my question is: what do you want to accomplish?
Functions are the only callable values. And Functions are primitives in ActionScript, much as ints, or Booleans, so there is no meaningful way to extend them.
If you want it to be an object, do it the Java way, defining an ICallable interface, and actually call a method, or just really use a function. closures provide the most simple and flexible possibility to create stateful functions, if that is what you want.
edit: well, you can do this (as an example):
private var fooInst:Foo = new Foo();
protected var foo:Function = fooInst.call;
and then the following workst as you wish:
<mx:Label text="{foo('Whatever')}"/>
its maybe even a little more flexible, although you lose the benefits of strict typing.
greetz
back2dos
I want to check in my function if a passed argument of type object is empty or not. Sometimes it is empty but still not null thus I can not rely on null condition. Is there some property like 'length'/'size' for flex objects which I can use here.
Please help.
Thanks in advance.
If you mean if an Object has no properties:
var isEmpty:Boolean = true;
for (var n in obj) { isEmpty = false; break; }
This is some serious hack but you can use:
Object.prototype.isEmpty = function():Boolean {
for(var i in this)
if(i != "isEmpty")
return false
return true
}
var p = {};
trace(p.isEmpty()); // true
var p2 = {a:1}
trace(p2.isEmpty()); // false
You can also try:
ObjectUtil.getClassInfo(obj).properties.length > 0
The good thing about it is that getClassInfo gives you much more info about the object, eg. you get the names of all the properties in the object, which might come in handy.
If object containes some 'text' but as3 doesn't recognize it as a String, convert it to string and check if it's empty.
var checkObject:String = myObject;
if(checkObject == '')
{
trace('object is empty');
}
Depends on what your object is, or rather what you expect it to have. For example if your object is supposed to contain some property called name that you are looking for, you might do
if(objSomeItem == null || objSomeItem.name == null || objSomeItem.name.length == 0)
{
trace("object is empty");
}
or if your object is actually supposed to be something else, like an array you could do
var arySomeItems = objSomeItem as Array;
if(objSomeItem == null || arySomeItems == null || arySomeItems.length == 0)
{
trace("object is empty");
}
You could also use other ways through reflection, such as ObjectUtil.getClassInfo, then enumerate through the properties to check for set values.... this class help:
import flash.utils.describeType;
import flash.utils.getDefinitionByName;
public class ReflectionUtils
{
/** Returns an Array of All Properties of the supplied object */
public static function GetVariableNames(objItem:Object):Array
{
var xmlPropsList:XMLList = describeType(objItem)..variable;
var aryVariables:Array = new Array();
if (xmlPropsList != null)
{
for (var i:int; i < xmlPropsList.length(); i++)
{
aryVariables.push(xmlPropsList[i].#name);
}
}
return aryVariables;
}
/** Returns the Strongly Typed class of the specified library item */
public static function GetClassByName($sLinkageName:String):Class
{
var tObject:Class = getDefinitionByName($sLinkageName) as Class;
return tObject;
}
/** Constructs an instance of the speicified library item */
public static function ConstructClassByName($sLinkageName:String):Object
{
var tObject:Class = GetClassByName($sLinkageName);
//trace("Found Class: " + tMCDefinition);
var objItem:* = new tObject();
return objItem;
}
public static function DumpObject(sItemName:String, objItem:Object):void
{
trace("*********** Object Dump: " + sItemName + " ***************");
for (var sKey:String in objItem)
{
trace(" " + sKey +": " + objItem[sKey]);
}
}
//}
}
Another thing to note is you can use a simple for loop to check through an objects properties, thats what this dumpobject function is doing.
You can directly check it as follow,
var obj:Object = new Object();
if(obj == null)
{
//Do something
}
I stole this from a similar question relating to JS. It requires FP 11+ or a JSON.as library.
function isEmptyObject(obj){
return JSON.stringify(obj) === '{}';
}
can use use the hasProperty method to check for length
var i:int = myObject.hasProperty("length") ? myObject.length: 0;
I'm attempting to search a combobox based on text entered via a keyboard event. The search is working and the correct result is being selected but I can't seem to get the scrollToIndex to find the correct item which should be the found result (i). It's scrolling to the last letter entered which I believe is the default behavior of a combobox. I think I'm referring to the event target incorrectly. Newbie tearing my hair out. Can you help? Thank you. Here's the function:
private function textin(event:KeyboardEvent):void
{
var combo:ComboBox = event.target as ComboBox;
var source:XMLListCollection = combo.dataProvider as XMLListCollection;
str += String.fromCharCode(event.charCode);
if (str=="") {
combo.selectedIndex = 0;
}
for (var i:int=0; i<source.length; i++) {
if ( source[i].#name.match(new RegExp("^" + str, "i")) ) {
combo.selectedIndex = i;
event.target.scrollToIndex(i);
break;
}
}
}
Control:
<mx:ComboBox keyDown="textin(event);" id="thislist" change="processForm();" dataProvider="{xmllist}"/>
If event.target is a mx.control.ComboBox then it doesn't have a scrollToIndex method, which is a method defined in mx.controls.ListBase, which the ComboBox doesn't inherit from. Check the api reference for the ComboBox. What exactly is the result you a you are trying to achieve here? If you set the selected index of a ComboBox it should display the item at that index.
EDIT: Try getting replacing event.target.scrollToIndex(i) (which should throw an error anyway) and replace it with event.stopImmediatePropagation(). This should prevent whatever the default key handler is from firing and overriding your event handler.
Here is a solution, based on Kerri's code and Ryan Lynch's suggestions. The credit goes to then.
It's working for me, so I will leave the complete code here for the future generations. :)
import com.utils.StringUtils;
import flash.events.KeyboardEvent;
import flash.events.TimerEvent;
import flash.utils.Timer;
import mx.collections.ArrayCollection;
import mx.controls.ComboBox;
public class ExtendedComboBox extends ComboBox
{
private var mSearchText : String = "";
private var mResetStringTimer : Timer;
public function ExtendedComboBox()
{
super();
mResetStringTimer = new Timer( 1000 );
mResetStringTimer.addEventListener( TimerEvent.TIMER, function() : void { mResetStringTimer.stop(); mSearchText = ""; } );
}
override protected function keyDownHandler( aEvent : KeyboardEvent ):void
{
if( aEvent.charCode < 32 )
{
super.keyDownHandler( aEvent );
return;
}
var lComboBox : ComboBox = aEvent.target as ComboBox;
var lDataProvider : ArrayCollection = lComboBox.dataProvider as ArrayCollection;
mSearchText += String.fromCharCode( aEvent.charCode );
if ( StringUtils.IsNullOrEmpty( mSearchText ) )
{
lComboBox.selectedIndex = 0;
aEvent.stopImmediatePropagation();
return;
}
if( mResetStringTimer.running )
mResetStringTimer.reset();
mResetStringTimer.start();
for ( var i : int = 0; i < lDataProvider.length; i++ )
{
if ( lDataProvider[i].label.match( new RegExp( "^" + mSearchText, "i") ) )
{
lComboBox.selectedIndex = i;
aEvent.stopImmediatePropagation();
break;
}
}
}
}
This solution expects an ArrayCollection as the dataProvider and a field named "label" to do the searching. You can create a variable to store the name of the field, and use it like this:
lDataProvider[i][FIELD_NAME_HERE].match( new RegExp( "^" + mSearchText, "i") )
Have fun!