Overview

My personal collection of ActionScript/Flex utility functions.

Using utils

I use the utils package by adding utils/src to Flex's build path:

$ cat SomeProject/.actionScriptProperties | grep utils
<compilerSourcePathEntry kind="1" linkType="1" path="../utils/src/"/>

This is, admittedly, suboptimal... But I haven't gotten around to turning it into a proper library project yet.

Most useful functions

  • assert(...): Exactly what you think ;)

  • ignoreArgs(function, ...args): Returns a function which, when called, will ignore any arguments it is passed, and call 'function' with 'args'. For example:

    function doStuff():void { ... stuff is done ... }
    ignoreArgs(doStuff)(1, 2, 3)
    
    reloadButton.addEventListener(MouseEvent.CLICK, ignoreArgs(reloadPage))
    sprite.addEventListener(MouseEvent.CLICK, ignoreArgs(addWidget, someWidget))
    
  • assertEqual(a, b): a shortcut for assertThat(a, equalTo(b))

  • getprop(prop): return a function will will accept one object and retun the result of getting 'prop' on that object. Especially useful when combined with 'map'. For example:

    getprop("foo")({ foo: 42 }) => 42
    getprop("name")(somePerson) => "David"
    
  • callprop(prop, ...args): similar to 'getprop', except the property will be called with 'args'. For example:

    callprop("replace", " ", "")("a b c") => "abc"
    callprop("toString")(somePerson) => "[object Person]"
    
  • map(function, iterable): returns an array containing the result of applying 'fuction' to every item in 'iterable'. For example:

    map(getprop("name"), people) => ["David", "Marguerite"]
    map(callprop("toUpperCase"), names) => ["DAVID", "MARGUERITE"]
    
  • chainListener(instance, chain, event, callback): Add an event listener on instance[chain[0]][chain[1]]... waiting for 'event', and automatically add/remove that event listener as the 'chain' is changed (note: the Flex ChangeWatcher class is used for this, so the normal rules about what changes will/won't be noticed apply). For example:

    chainListener(this, ["peopleArrayCollection"],
                  CollectionEvent.COLLECTION_CHANGED,
                  handlePeopleArrayChange)
    
  • printf(str, ...args): A port of a JavaScript printf library which can handle most standard printf format specifiers:

    printf("Hello, %s!", "world") => "Hello, world!"
    printf("%0.02f", 0.5) => "0.50"
    
  • curried(function, ...args): Return a curried, or partial, function. See, eg, the Wikipedia page for more information. Some examples

    add(1, 2) => 3 add1 = curried(add, 1) add1(3) => 4 add1(5) => 6

    addEventListener("someEvent", curried(handleSomeEvent, someToken))

  • chain(...iterables): Modeled after Python's itertools.chain. For example:

    chain([1, 2], [3, 4]).toArray() => [1, 2, 3, 4]
    
    <!-- note: 'chain' will dispatch correct 'collectionChanged' events if
         at all possible (eg, if an ArrayCollection inside the chain
         changes) -->
    <mx:List dataProvider="{chain(someThing, arrayCollectionOfThings)}" />
    

Contributing to utils

Contributions are welcome, provided they are accompanied by unit tests.

All functions

(ps: sorry for the crappy format... asdoc appears to be buggy, so I'll using this little script instead)

src/utils/all.as

/**
 * Returns true iff all the elements of 'iterable' are true.
 */
public function all(iterable:*):Boolean {

src/utils/any.as

/**
 * Returns true iff any element of 'iterable' is true.
 */
public function any(iterable:*):Boolean {

src/utils/AsArray.as

/**
 * Cast 'obj' to 'Array', throwing an exception otherwise.
 */
public function AsArray(obj:*):Array {

src/utils/assert.as

/**
 * Generate a stack trace an throw an AssertionError if 'expression' evaluates
 * false.
 * If 'messageFormatAndArgs' are supplied, they will be passed to printf and
 * used for the error message.
 */
public function assert(expression:*, ...messageFormatAndArgs):void {

src/utils/assertEqual.as

/**
 * A shortcut for assertThat(a, equalTo(b)).
 */
public function assertEqual(a:*, b:*):void {

src/utils/AssertionError.as

/**
 * Thrown by 'assert' if an assertion fails.
 */
public class AssertionError extends Error {

src/utils/assertNotEqual.as

/**
 * A shortcut for assertThat(a, not(equalTo(b))).
 */
public function assertNotEqual(a:*, b:*):void {

src/utils/async/AsyncError.as

/**
 * Encapsulates all errors returned by AsyncResult.
 */
public class AsyncError extends Error {

src/utils/async/AsyncResult.as

/**
 * A convinent interface for accessing the result of asynchronous calls.
 *
 * There are two primary functions:
 * - 'complete(callback[, error callback])': 'callback' will be called
 *     when the AsyncResult gets a result (or, if an 'error callback' is
 *     supplied, that will be called on an error).
 * - 'gotResult(result:Object)': notifies 'complete' callbacks of a result
 *     (or 'gotError', which notifies error callbacks).
 *
 * As with LoadedAsynchronously, order is not important; if 'gotResult' is
 * called before 'complete', the complete callback will be called right away
 * with the result.
 *
 * AsyncResult also allows results to be chained, where the result of the
 * first 'complete' callback is passed to the next, and so on. This chaining
 * is "intelligent" - if any result is an instance of "AsyncResult" or
 * "LoadedAsynchronously", it will not continue until the AsyncResult returns
 * a result or the LoadedAsynchronously is compleatly loaded.
 *
 * Some examples:
 *
 * AsyncResult, as it is commonly used with services:
 * >>> var r:AsyncResult = g().styleService.byId("some_style");
 * >>> r.complete(
 * ...     function(result:DesignOption):void {
 * ...         trace("style loaded");
 * ...     }, function(error:AsyncError):void {
 * ...         trace("error loading style.");
 * ... });
 *
 * A simple chaining example:
 * >>> function add1(x) { return x + 1; };
 * >>> function add2(x) { return x + 2; };
 * >>> r = new AsyncResult();
 * >>> r.complete(add1).complete(add2);
 * >>> r.complete(function(result) { trace("got: " + result); });
 * >>> r.gotResult(0)
 * got: 3
 *
 * "Intelligently" chaining together multiple AsyncResults:
 * >>> function getThumbnailUrl():AsyncResult {
 * ...     var r:AsyncResult = new AsyncResult();
 * ...     var token:AsyncToken = remoteObject.getThumbnailUrl();
 * ...     token.addResponder(r.getResponder);
 * ...     return r;
 * ... }
 * >>> function getThumbnail(url:String):AsyncResult {
 * ...     var r:AsyncResult = new AsyncResult();
 * ...     FakeImageLoader.loadImage(url);
 * ...     FakeImageLoader.onResult = function(image:Image):void {
 * ...         r.gotResult(image);
 * ...     };
 * ...     return r;
 * ... }
 * >>> function showImage(image:Image):void {
 * ...     this.image = image;
 * ... }
 * >>> getThumbnailUrl().complete(getThumbnail).complete(showImage);
 * >>> // This will load the thumbnail URL, pass that URL to 'getThumbnail',
 * >>> // then when 'getThumbnail' loads the thumbnail, it will be passed
 * >>> // to 'showImage' to be displayed.
 *
 * For more examples, see TestAsyncResult.as (especially the
 * 'testCombinedChaining' * and 'testResultBeforeCallbackRegistered' tests)
 * and the services in 'global.services.*'.
 */
public class AsyncResult {

src/utils/async/DeferredLoader.as

/**
 * Mechanism allowing other components to wait for a class to finish loading
 * before loading themselves.
 *
 * For example, the FurnitureDB depends on the StylesDB, so it can call:
 *          g().stylesDB.afterLoadSuccess(function():void {
 *                  loadSuccessful();
 *          });
 * If 'stylesDB' is fully loaded (ie, it has called 'loadSuccessful'), the
 * DeferredLoader will immidiatly call the callback. Otherwise, when
 * 'loadSuccessful' is called, all supplied callbacks will be executed.
 */
public /* abstract */ class DeferredLoader extends EventDispatcher {

src/utils/async/LoadedAsynchronously.as

/**
 * Implemented by classes which require asynchronous calls to load compleatly.
 * See comments on 'LoadedAsynchronouslyImpl.as'.
 */
public interface LoadedAsynchronously {

src/utils/async/LoadedAsynchronouslyImpl.as

/**
 * The 'LoadedAsynchronously' interface is implemented by classes which
 * require asynchronous calls to load compleatly.
 * For example, Theme depends on styles, which much be loaded asynchronously.
 *
 * There are two main functions in the "public" API:
 * - onLoadComplete(callback): queues 'callback' to be called (with no
 *     arguments) once the instance has loaded compleatly. If the instance
 *     is already loaded, 'callback' will be called right away.
 * - onLoadComplete(callback): queues 'callback' to be callled (with one
 *     argument, an instance of "AsyncError") if there is an error. If an
 *     error has already been encountered, 'callback' is called right away.
 *
 * And there are three main "internal" functions (ie, to be used by the class
 * which is doing the loading):
 * - asyncLoadBegin: signals that an asynchronous load has been started.
 * - asyncLoadComplete: signals that an asynchronous load has successfully
 *     completed.
 * - asyncLoadError: signals that an asynchronous load has failed.
 *
 * Note: when using this interface, it is important to remember that every
 * call to 'asyncLoadBegin' /must/ have a corresponding call to
 * 'asyncLoad{Complete,Error}', and every * 'asyncLoad{Complete,Error}'
 * /must/ have a corresponding 'asyncLoadBegin'.
 *
 * For examples, see 'TestLoadedAsync.as'.
 */

import flash.utils.getQualifiedClassName;

import utils.async.AsyncError;
import utils.async.AsyncResult;

import utils.ignoreArgs;
import utils.logger.Logger;

protected static var loadLogger:Logger = new Logger("LoadedAsynchronously");

protected var _loadingLevel:int = 0;
protected var _loadingState:String = STATE_NOT_LOADED;

public static const STATE_NOT_LOADED:String = "not loaded";
public static const STATE_LOADING:String = "loading";
public static const STATE_LOADED:String = "loaded";
public static const STATE_ERROR:String = "error";

public function asyncLoadState():String {
    return _loadingState;
}

/**
 * Called to signify that a dependency of this instance has started to load
 * asynchronously. 'asyncLoadComplete' should be called if that resource
 * is loaded successfully, and 'asyncLoadError' should be called if it
 * encounters an error.
 * For example:
 * >>> x = new SomethingLoadedAsynchronously();
 * >>> x.asyncLoadBegin();
 * >>> loadImage("images/foo.jpg").complete(function(result:*):void {
 * ...     x.image = result;
 * ...     x.asyncLoadComplete();
 * ... }, function(error:AsyncError):void {
 * ...     x.asyncLoadError(error);
 * ... });
 */
public function asyncLoadBegin():void {
    loadLogger.debug("{0}.loadLevel = {1} + 1",
                                     getQualifiedClassName(this), _loadingLevel);
    // todo: what happens when we get an asyncLoadBegin when we are in the
    // error state?
    if (_loadingState == STATE_LOADED)
            throw Error("Cannot begin a load after the load has completed.");
    if (_loadingState == STATE_NOT_LOADED)
            _loadingState = STATE_LOADING;
    _loadingLevel += 1;
}

/**
 * Called when a dependency of this instance has been successfully loaded
 * and applied to this instance.
 */
public function asyncLoadComplete():void {
    if (_loadingState == STATE_LOADED)
            throw Error("Cannot call 'asyncLoadComplete' after load has completed.");

    loadLogger.debug("{0}.loadLevel = {1} - 1",
                                     getQualifiedClassName(this), _loadingLevel);

    _loadingLevel -= 1;

    if (_loadingLevel == 0) {
            _loadingState = STATE_LOADED;
            for each (var callback:Function in _loadCompleteCallbacks)
                    callback();
    }

    if (_loadingLevel < 0)
            throw Error("Oh no! 'asyncLoadComplete' without 'asyncLoadBegin'!");
}

protected var _loadError:AsyncError;

/**
 * Called when a dependency of this instance encounters an error.
 */
public function asyncLoadError(error:AsyncError):void {
    _loadingState = STATE_ERROR;
    _loadError = error;
    for each (var callback:Function in _loadErrorCallbacks)
            callback(error);
}

protected var _loadCompleteCallbacks:Array = [];
protected var _loadErrorCallbacks:Array = [];

/**
 * Calls 'callback' with no arguments when this instance has
 * successfully finished loading.
 */
public function onLoadComplete(callback:Function):void {
    if (_loadingState == STATE_LOADED) {
            callback();
    } else if (_loadingState == STATE_ERROR) {
            // do nothing
    } else {
            _loadCompleteCallbacks.push(callback);
    }
}

/**
 * Calls 'callback' with an instance of AsyncError when this instance
 * encounters a load error.
 */
public function onLoadError(callback:Function):void {
    if (_loadingState == STATE_ERROR) {
            callback(_loadError);
    } else if (_loadingState == STATE_LOADED) {
            // do nothing
    } else {
            _loadErrorCallbacks.push(callback);
    }
}

/**
 * A shortcut which handles automatically calling loadBegin and
 * loadComplete/loadError when loading an AsyncResult:
 * >>> x = new SomethingLoadedAsynchronously();
 * >>> x.asyncLoad(g().someService.getStuff(), function(stuff:Stuff):void {
 * ...     x.stuff = stuff;
 * ... });
 */
public function asyncLoad(result:AsyncResult, callback:Function):void {
    asyncLoadBegin();
    result.complete(callback);
    result.complete(ignoreArgs(asyncLoadComplete), asyncLoadError);
}

/**
 * A shortcut which says "I'm loaded after 'child' is loaded".
 */
public function asyncLoadCompleteAfter(child:LoadedAsynchronously):void {

src/utils/async/ServiceProxy.as

/**
 * 'Service' is an abstract base class for all service proxies.
 *
 * Service proxies provide three things:
 * - Methods which proxy calls to server-side methods
 * - Methods which simulate ("mock") server-side methods
 * - Data used for the mock methods and testing
 *
 * (ideally these last two responsibilities would be moved elsewhere, but it
 * turns out that it's quite a bit easier to leave them here, so that's what
 * I've done for the time being)
 *
 * The proxy methods, as their name suggests, create a Flash interface for
 * methods on the remote server. For example, when the StyleDisplay wants to
 * get an instance of 'style', it calls the 'loadById' method of
 * 'StyleService':
 *     g().style.loadByid(styleID).complete(function(style:DesignOption):void {
 *         part.setStyle(style);
 *     });
 * Which make a service call to load the style data, turns it into an
 * instance of DesignOption, and returns it (via AsyncResult) to the caller:
 *     var result:AsyncResult = this.call("loadByid", [ styleID ]);
 *     result.complete(function(data:Object):DesignOption {
 *          return loadStyleFromObject(data);
 *     });
 *
 * The mock methods are standalone implementations of server-side methods
 * which are used so that testing and development can be done without the
 * need of a server.
 * For example, this could be the mock implementation of 'loadByid':
 *     function _mock_loadById(id:String):Object {
 *         assert(_mockStyleData[id], "No style with id '" + id + "'");
 *         return _mockStyleData[id];
 *     }
 * The mock methods are called automatically by 'Service.call' if
 * 'g().mockServiceCalls' has been set.
 *
 * Finally, the mock data is used by the mock methods and tests.
 *
 * See the 'ConversationService' class in 'TestService.as' for an example
 * implementation and more documentation.
 */
public /* abstract */ class ServiceProxy extends DeferredLoader {

src/utils/asyncrule/AsyncRule.as

/**
 * The AsyncRule is used to make async testing easier.
 * In FlexUnit-speak, a Rule is a low-level wrapper around each test case (see
 * http://docs.flexunit.org/index.php?title=Rule for more information).
 *
 * The AsyncRule should be used like this:
 * public class TestLoadingStuff {
 *     [Rule]
 *     public var asyncRule:AsyncRule = new AsyncRule();
 *
 *     [Test]
 *     public function testLoadStuff():void {
 *         // A call to '.begin()' tells the 'asyncRule' that this test case
 *         // will require async facilities (which will be provided by the
 *         // AsyncRuleToken which is returned).
 *         var async:AsyncRuleToken = asyncRule.begin();
 *
 *         function handleComplete(event:Event):void {
 *             assertEqual(event.target.data, "Hello, world!");
 *             // The 'AsyncRuleToken.complete()' method should be called when
 *             // there are no more asynchronous callbacks expected (ie, when
 *             // the test is complete).
 *             async.complete();
 *         }
 *
 *         var loader:URLLoader = new URLLoader();
 *         loader.load(new URLRequest("http://example.com/"));
 *
 *         // Any functions which will be called asynchronously should be
 *         // wrapped using 'async.wrap(func)'.
 *         // Note: the wrapped function will behave mostly like the original
 *         // function (for example, `async.wrap(func)(42) == func(42)`),
 *         // but not exactly - see the documentation on 'AsyncRuleToken.wrap'.
 *         loader.addEventListener(Event.COMPLETE, async.wrap(handleComplete));
 *         // The 'async.fail' function can be used to quickly fail.
 *         // See the documentation on 'AsyncRuleToken.fail' for more.
 *         loader.addEventListener(IOErrorEvent.IO_ERROR, async.fail);
 * }
 */
public class AsyncRule extends MethodRuleBase implements IMethodRule {

src/utils/asyncrule/AsyncRuleToken.as

/**
 * Helper token for AsyncRule.
 * See documentation on AsyncRule and on the public methods here for examples.
 */
public class AsyncRuleToken {

src/utils/beginDrag.as

/**
 * Begin a drag by adding the appropriate listeners to 'stage'.
 *
 * The 'moveCallback' will be called as the mouse moves and the
 * 'completeCallback' will be called when the drag is complete.
 * The callbacks will be called with an instance of 'DragStatus'.
 *
 * For example:
 * >>> beginDrag(stage, function(status:DragStatus):void {
 * ...    trace("move at:", status.stageX, status.stageY);
 * ... }, function():void {
 * ...    trace("drag completed");
 * ...});
 * >>>
 */
public function beginDrag(stage:DisplayObjectContainer, moveCallback:Function,

src/utils/BindableObject.as

/**
 * Similar to ObjectProxy, but possibly buggier.
 */
[Bindable] // Added to make code-hinting happy
[Bindable("propertyChange")]
dynamic public class BindableObject extends Proxy implements IEventDispatcher, IUID {

src/utils/BooleanMap2D.as

/**
 * A 2D boolean map.
 */
public class BooleanMap2D {

src/utils/callprop.as

/**
 * Return a closure which will accept one argument an call 'property' with
 * arguments 'args' on that argument.
 *
 * >>> callprop("toUpperCase")("hello")
 * "HELLO"
 */
public function callprop(property:String, ...args):Function {

src/utils/canWatch.as

/**
 * Checks that every portion of 'chain' is watchable by ChangeWatcher.
 * If the optional 'unwatchable' argument is supplied, it will be filled
 * with the portion of 'chain' that can be watched, and the first property
 * which cannot.
 * Note: 'ChangeWatcher.canWatch' does not accept a chain - it only
 * accepts a string.
 *
 * >>> canWatch(this, ["someArray", "length"])
 * false
 * >>> unwatchable = []
 * >>> canWatch(this, ["someArray", "length"], unwatchable)
 * false
 * >>> unwatchable
 * ["someArray", "length"]
 * >>> canWatch(this, ["someArrayCollection", "length"])
 * true
 */
public function canWatch(host:Object, chain:Object,

src/utils/cartesian_product.as

/**
 * Return the cartesian product of ...iterables.
 * cartesian_product([1, 2]) => [ [1], [2] ]
 * cartesian_product([1, 2], ["a", "b"]) => [ [1, "a"], [1, "b"], ... ]
 */
public function cartesian_product(...iterables):Array {

src/utils/chain.as

/**
 * Returns a read-only view onto a collection of objects. For example:
 * >>> chain(0, [1, 2, 3]).getItemAt(0)
 * 0
 * >>> chain(0, [1, 2, 3]).getItemAt(2)
 * 2
 * >>> chain([0, 1], [2, 3]).getItemAt(0)
 * 0
 * >>> chain([0, 1], [2, 3]).getItemAt(2)
 * 2
 */
public function chain(...iterables):IList {

src/utils/chainGet.as

/**
 * Get instance[chain[0]][chain[1]][...] if possible, but if
 * some part of the chain is undefined, return 'dflt'.
 * >>> chainGet({ a: "b" }, "a")
 * "b"
 * >>> chainGet({ a: "b" }, "c")
 * undefined
 * >>> chainGet({ a: "b" },  [ "a", "toString" ])()
 * "b"
 */
public function chainGet(instance:*, chain:Object, dflt:*=undefined):* {

src/utils/chainListener.as

/**
 * Attach an event listener to host[chain[0]][chain[1]]..., adding and
 * removing it as portions of thd change are changed.
 *
 * >>> chainListener(this, ["someProperty", "someArrayCollection"],
 * ...               CollectionEvent.COLLECTION_CHANGE,
 * ...               handleCollectionChanged)
 */
public function chainListener(host:Object, chain:Object,

src/utils/compose.as

/**
 * compose will reture a closure such that:
 *     compose(f, g)(x) == f(g(x))
 */
public function compose(...fs):Function {

src/utils/controls/CollapsiblePanel.as

/**
* Source: http://lab.arc90.com/
*/

package utils.controls
{
import flash.events.Event;
import flash.events.MouseEvent;
import flash.utils.getQualifiedClassName;

import mx.containers.Panel;
import mx.controls.Button;
import mx.core.EdgeMetrics;
import mx.core.FlexVersion;
import mx.core.ScrollPolicy;
import mx.core.UITextField;
import mx.core.mx_internal;
import mx.effects.Resize;
import mx.events.EffectEvent;
import mx.logging.ILogger;
import mx.logging.Log;
import mx.styles.CSSStyleDeclaration;
import mx.styles.StyleManager;
import mx.styles.StyleProxy;

use namespace mx_internal;

//--------------------------------------
//  Events
//--------------------------------------

/**
 *  Dispatched when the user collapses the panel.
 *
 *  @eventType minimize
 */
[Event(name="minimize", type="flash.events.Event")]

/**
 *  Dispatched when the user expands the panel.
 *
 *  @eventType restore
 */
[Event(name="restore", type="flash.events.Event")]

//--------------------------------------
//  Styles
//--------------------------------------

/**
 *  The collapse button disabled skin.
 *
 *  @default CollapseButtonDisabled
 */
[Style(name="collapseButtonDisabledSkin", type="Class", inherit="no")]

/**
 *  The collapse button down skin.
 *
 *  @default CollapseButtonDown
 */
[Style(name="collapseButtonDownSkin", type="Class", inherit="no")]

/**
 *  The collapse button over skin.
 *
 *  @default CollapseButtonOver
 */
[Style(name="collapseButtonOverSkin", type="Class", inherit="no")]

/**
 *  The collapse button up skin.
 *
 *  @default CollapseButtonUp
 */
[Style(name="collapseButtonUpSkin", type="Class", inherit="no")]

/**
 *  The collapse button default skin.
 *
 *  @default null
 */
[Style(name="collapseButtonSkin", type="Class", inherit="no", states="up, over, down, disabled")]

/**
 *  The collapse effect duration.
 *
 *  @default 250
 */
[Style(name="collapseDuration", type="Number", inherit="no")]

public class CollapsiblePanel extends Panel

src/utils/controls/FixedTree.as

/**
 * This class fixes a bug in the Tree class where the 'disclosure' arrow is
 * shown, even if an item doesn't have any children.
 */
public class FixedTree extends Tree {

src/utils/controls/HelpfulPopupMenu.as

/**
 * A more helpful version of the PopupMenu, providing a 'selectedItem'
 * and some other nicities.
 */
public class HelpfulPopupMenu extends MyPopUpMenuButton {

src/utils/controls/MyPopUpMenuButton.as

/**
 *  Dispatched when a user selects an item from the pop-up menu.
 *
 *  @eventType mx.events.MenuEvent.ITEM_CLICK
 */
[Event(name="itemClick", type="mx.events.MenuEvent")]

/**
 *  The name of a CSS style declaration used by the dropdown menu.
 *  This property allows you to control the appearance of the dropdown menu.
 *  The default value sets the <code>fontWeight</code> to <code>normal</code> and
 *  the <code>textAlign</code> to <code>left</code>.
 *
 *  @default "popUpMenu"
 */
[Style(name="popUpStyleName", type="String", inherit="no")]

//--------------------------------------
//  Excluded APIs
//--------------------------------------

[Exclude(name="toggle", kind="property")]
[Exclude(name="selectedDisabledIcon", kind="style")]
[Exclude(name="selectedDisabledSkin", kind="style")]
[Exclude(name="selectedDownIcon", kind="style")]
[Exclude(name="selectedDownSkin", kind="style")]
[Exclude(name="selectedOverIcon", kind="style")]
[Exclude(name="selectedOverSkin", kind="style")]
[Exclude(name="selectedUpIcon", kind="style")]
[Exclude(name="selectedUpSkin", kind="style")]

//--------------------------------------
//  Other metadata
//--------------------------------------

//[IconFile("PopUpMenuButton.png")]

[RequiresDataBinding(true)]

/**
 *  The PopUpMenuButton control creates a PopUpButton control with a main
 *  sub-button and a secondary sub-button.
 *  Clicking on the secondary (right) sub-button drops down a menu that
 *  can be popluated through a <code>dataProvider</code> property.
 *  Unlike the Menu and MenuBar controls, the PopUpMenuButton control
 *  supports only a single-level menu. This means that the menu cannot contain
 *  cascading submenus.
 *
 *  <p>The main sub-button of the PopUpMenuButton control can have a
 *     text label, an icon, or both on its face.
 *     When a user selects an item from the drop-down menu or clicks
 *     the main button of the PopUpMenuButton control, the control
 *     dispatches an <code>itemClick</code> event.
 *     When a user clicks the main button of the
 *     control, the control also dispatches a <code>click</code> event.
 *     You can customize the look of a PopUpMenuButton control.</p>
 *
 *  <p>The PopUpMenuButton control has the following sizing
 *     characteristics:</p>
 *     <table class="innertable">
 *        <tr>
 *           <th>Characteristic</th>
 *           <th>Description</th>
 *        </tr>
 *        <tr>
 *           <td>Default size</td>
 *           <td>Sufficient to accommodate the label and any icon on
 *               the main button, and the icon on the pop-up button.
 *               The control does not reserve space for the menu.</td>
 *        </tr>
 *        <tr>
 *           <td>Minimum size</td>
 *           <td>0 pixels.</td>
 *        </tr>
 *        <tr>
 *           <td>Maximum size</td>
 *           <td>10000 by 10000.</td>
 *        </tr>
 *     </table>
 *
 *  @mxml
 *
 *  <p>The <code>&lt;mx:PopUpMenuButton&gt;</code> tag inherits all of the tag
 *  attributes of its superclass, and adds the following tag attributes:</p>
 *
 *  <pre>
 *  &lt;mx:PopUpMenuButton
 *    <strong>Properties</strong>
 *    dataDescriptor="<i>instance of DefaultDataDescriptor</i>"
 *    dataProvider="undefined"
 *    labelField="null"
 *    labelFunction="undefined"
 *    showRoot="false|true"
 *    &nbsp;
 *    <strong>Event</strong>
 *    change=<i>No default</i>
 *  /&gt;
 *  </pre>
 *
 *  @includeExample examples/PopUpButtonMenuExample.mxml
 *
 *  @see mx.controls.Menu
 *  @see mx.controls.MenuBar
 *
 *  @tiptext Provides ability to pop up a menu and act as a button
 *  @helpid 3441
 */
public class MyPopUpMenuButton extends PopUpButton

src/utils/controls/popUp/IPopUp.as

/**
 * Interface used for popups.
 */
public interface IPopUp {

src/utils/controls/popUp/PopUp.as

/**
 * A more helpful popup.
 */
public class PopUp extends Panel implements IPopUp  {

src/utils/controls/popUp/PopUpUtils.as

/**
 * Helpful popup-related functions.
 */
public class PopUpUtils{

src/utils/controls/RotatableLabel.as

/**
* Use this Label class instead of the standard
* mx.controls.Label to allow text rotation
* (using the 'rotation' property) without using embedded fonts!
*/
public class RotatableLabel extends mx.controls.Label {

src/utils/controls/spinner/Spinner.as

/**
 * Creates a spinning "loader" component that is sort of an indeterminate progress bar.
 * @author jhawkes
 *
 */
public class Spinner extends UIComponent {

src/utils/controls/spinner/Tick.as

/**
 * A spinner tick.
 */
public class Tick extends Sprite {

src/utils/create.as

/**
 * Create an instance of class 'cls', passing 'args' to the constructor.
 * 'kwargs' is treated as a set of properties which are applied to the instance.
 * For example:
 *     var b:Button = create(Button, { label: "my button!" });
 * is identical to:
 *     <mx:Button id="b" label="my button!" />
 * And:
 *     var e:DynamicEvent = create(DynamicEvent, ["type"], { foo: "bar" });
 * is identical to:
 *     var e:DynamicEvent = new DynamicEvent("type");
 *     e.foo = bar;
 * NB: 'create(Cls, { ... })' is identical to 'create(Cls, undefined, { ... })'
 */
public function create(cls:Class, args:*=undefined, kwargs:*=undefined):* {

src/utils/curried.as

/**
 * Return a curried, or partial, function.
 * For example, if:
 *   add(2, 3) => 5
 *   add(1, 2, 3) => 6
 * then:
 *   var add2 = curried(add, 2);
 *   add2(3) => 5
 *   add2(1, 3) => 6
 * and:
 *   var add5 = curried(add, 2, 3);
 *   add5(1) => 6
 *   add5() => 5
 */
public function curried(f:Function, ... boundArgs):Function {

src/utils/Dict.as

/**
 * A subclass of Dictionary which implements IExternalizable.
 * Should be identical in every way to Dictionary, except that the current
 * implementation does not support weak refs. This is because IExternalizable
 * pass arguments to the constructor and 'weakKeys' can only be set in the
 * constructor.
 */
[RemoteClass]
public dynamic class Dict extends Dictionary implements IExternalizable {

src/utils/DragDropCallback.as

/**
 * Use the 'setupTarget' method to setup a UIComponent to accept drag-drops
 * of format 'format', then call 'callback' with the data.
 */
public class DragDropCallback {

src/utils/DragStatus.as

/**
 * Reports on the status of a drag which has been started by 'beginDrag'.
 */
public class DragStatus {

src/utils/fadeBetween.as

/**
 * Fade between two colors.
 */
public function fadeBetween(col0:uint, col1:uint, step:int, steps:int):uint {

src/utils/filter.as

/**
 * Return an array containing 'e' for every element 'e' in iterable 'i' iff
 * f(i) returns true.
 * if 'f' is 'null', fitlering is based on the value of the element.
 * >>> filter(function(x) { return x > 5 }, [ 1, 3, 6 ])
 * [6]
 * >>> filter(null, [ 0, 1, null, false, true ])
 * [1, true]
 */
public function filter(f:Function, i:*):Array {

src/utils/forEach.as

/**
 * Apply function 'f' to each element of iterable 'i'.
 */
public function forEach(i:*, f:Function):void {

src/utils/forMap.as

/**
 * Accept a function, 'f', which accepts one argument and reurn a function
 * which will accept three arguments, passing only the first to 'f'.
 */
public function forMap(f:Function):Function {

src/utils/getprop.as

/**
 * Return a closure which will accept one argument and return
 * argument[property].
 *
 * >>> getprop("length")([1,2,3])
 * 3
 */
public function getprop(property:String):Function {

src/utils/getUrlParameters.as

/**
 * Parses GET parameters (see comments for attribution).
 */
public function getUrlParameters():Object {

src/utils/HelpfulDataDescriptor.as

/**
 *  The DefaultDataDescriptor class provides a default implementation for
 *  accessing and manipulating data for use in controls such as Tree and Menu.
 *
 *  This implementation handles e4x XML and object nodes in similar but different
 *  ways. See each method description for details on how the method
 *  accesses values in nodes of various types.
 *
 *  This class is the default value of the Tree, Menu, MenuBar, and
 *  PopUpMenuButton control <code>dataDescriptor</code> properties.
 *
 *  @see mx.controls.treeClasses.ITreeDataDescriptor
 *  @see mx.controls.menuClasses.IMenuDataDescriptor
 *  @see mx.controls.Menu
 *  @see mx.controls.MenuBar
 *  @see mx.controls.PopUpMenuButton
 *  @see mx.controls.Tree
 */
public class HelpfulDataDescriptor implements ITreeDataDescriptor2, IMenuDataDescriptor

src/utils/identity.as

/**
 * The identity function. Returns whatever it is passed.
 * >>> identity(42)
 * 42
 * >>> map(identity, [ null, undefined, {} ])
 * [ null, undefined, {} ]
 * >>>
 */
public function identity(arg:*):* {

src/utils/ignoreArgs.as

/**
 * Accept a function, 'f', and (optionally) a list of arguments and return
 * a new function which will call the original function with the supplied
 * arguments, ignoring any new arguments which are passed.
 */
public function ignoreArgs(f:Function, ...boundArgs):Function {

src/utils/issubclass.as

/**
 * Returns true if 'cls' is a subclass of 'supercls' or if 'cls' implements
 * the interface 'supercls'.
 * Note: when 'cls != supercls', this will be *VERY SLOW*, as it uses
 * 'describeType'.
 */
public function issubclass(cls:Class, supercls:Class):Boolean {

src/utils/JSON.as

/**
 * JSON Encoder (see comments for attribution).
 */
public class JSON {

src/utils/logger/Log.as

/**
 * A more helpful, less insane replacement for mx.logging.*
 */
public class Log {

src/utils/logger/LogEvent.as

/**
 * mx.logging.LogEvent, augmented with a helpful toString method
 * and a 'levelString' method.
 */
public class LogEvent extends mx.logging.LogEvent {

src/utils/logger/Logger.as

/**
 * A better logger.
 */
public class Logger {

src/utils/lstrip.as

/**
 * Remove 'chars' (which defaults to whitespace) from the left of 'str'.
 */
public function lstrip(str:String, chars:String=null):String {

src/utils/map.as

/**
 * Apply function 'f' to each element of iterable 'i' and return an array
 * containing the result.
 */
public function map(f:Function, i:*):Array {

src/utils/max.as

/**
 * Return the maximum of some set.
 * >>> max(1, 2, 3)
 * 3
 * >>> max([3, 2, 1])
 * 3
 * >>> max(1, [5, 4], 3, new ArrayCollection([4]))
 * 5
 * >>> max()
 * ArgumentError("Max needs at least one argument.")
 * >>> max(2, [])
 * 2
 * >>> max([])
 * ArgumentError("Max needs at least one argument.")
 */
public function max(...args):* {

src/utils/memoized.as

/**
 * Return a function who's results will be cached.
 * For now, only deals with funcitons that accept 0 or 1 arguments.
 */
public function memoized(f:Function):Function {

src/utils/min.as

/**
 * Return the minimum of some set.
 * >>> min(1, 2, 3)
 * 1
 * >>> min([3, 2, 1])
 * 1
 * >>> min(1, [5, 4], 3, new ArrayCollection([4]))
 * 1
 * >>> min()
 * ArgumentError("Min needs at least one argument.")
 * >>> min(2, [])
 * 2
 * >>> min([])
 * ArgumentError("Min needs at least one argument.")
 */
public function min(...args):* {

src/utils/nop.as

/**
 * Do nothing. Nothing at all.
 */
public function nop(...args):* {

src/utils/not.as

/**
 * Return a closure which negates its argument.
 *
 * >>> map(not(getprop("foo")), [{foo: true}, {foo: false}])
 * [false, true]
 */
public function not(fun:Function):Function {

src/utils/objKeys.as

/**
 * Return all of the keys in an object.
 * >>> objKeys({a: 1, b: 2})
 * [ "a", "b" ]
 * >>> objKeys(null)
 * []
 */
public function objKeys(obj:*):Array {

src/utils/objVals.as

/**
 * Return all of the values in an object.
 * >>> objKeys({a: 1, b: 2})
 * [ 1, 2 ]
 * >>> objKeys(null)
 * []
 */
public function objVals(obj:*):Array {

src/utils/onceDeferred.as

/**
 * Returns a closure which will, when called, push a function onto the
 * event stack. Until that function has been executed, further invocations
 * will be ignored.
 * >>> printHello = onceDeferred(function() { trace("Hello, world!"); });
 * >>> printHello();
 * >>> printHello();
 * ... after current event handler returns ...
 * Hello, world!
 */
public function onceDeferred(closure:Function, timeout:int=-1):Function {

src/utils/parseDateString.as

/**
 * Excactly as the name suggests - parse a date string.
 * Adapted from a Javascript version written by:
 * Matt Kruse <matt@mattkruse.com>, http://www.mattkruse.com/
 */
public function parseDateString(string:String, format:String):Date {

src/utils/printf.as

/**
 * Good old printf.
 */
public function printf (...arguments):String {

src/utils/range.as

/**
 * Return an array of integers including start, excluding stop.
 * For example:
 *     range(1) => [0]
 *     range(1, 2) => [1]
 *     range(0, 6, 2) => [0, 2, 4]
 * Should be identical to Python's 'range' function.
 */
public function range(a:int, b:*=null, step:int=1):Array {

src/utils/rstrip.as

/**
 * Remove 'chars' (which defaults to whitespace) from the left of 'str'.
 */
public function rstrip(str:String, chars:String=null):String {

src/utils/setdefault.as

/**
 * Get the value of an Object's field, or set that field to 'defaultValue' if
 * the Object doesn't yet have that field.
 * Similar to Python's dict.setdefault function.
 * >>> foo.bar
 * undefined
 * >>> setdefault(foo, "bar", []).push("x")
 * >>> foo.bar
 * ["x"]
 * >>> setdefault(foo, "bar", []).push("y")
 * >>> foo.bar
 * ["x", "y"]
 */
public function setdefault(object:Object, field:*, defaultValue:*):* {

src/utils/setprop.as

/**
 * Like `getprop`, but sets the property.
 */
public function setprop(property:String, value:*):Function {

src/utils/showbbox.as

/**
 * Show a bounding box around a sprite.
 */
public function showbbox(sprite:Sprite, color:uint, width:int=1):void {

src/utils/strip.as

/**
 * Remove 'chars' (which defaults to whitespace) from the left and
 * right of 'str'.
 */
public function strip(str:String, chars:String=null,

src/utils/sum.as

/**
 * Add up all the values in sequence.
 */
public function sum(sequence:*):Number {

src/utils/testlisteners/HTMLTestReporter.as

/**
 * Reports the results of a TextTestListener into the browser, using HTML
 * instad of Flash.
 */
public class HTMLTestReporter {

src/utils/testlisteners/TextTestListener.as

/**
 * A FlexUnit RunListener which emits plain text describing the status
 * of the test run.
 * Note: See the 'injectLogMessage' method, which allows log messages
 * to be injected into the test run then displated if a test fails.
 */
[Event(name="gotText", type="mx.events.DynamicEvent")]
public class TextTestListener extends EventDispatcher implements IRunListener {

src/utils/testrunners/BlockWithArgumentsRunner.as

/**
 * Extends the BlockFlexUnit4ClassRunner, allowing children to specify
 * arguments which will be passed to the test methods.
 * Children returned by 'computeTestMethods' are expected to look something
 * like this:
 *     child = {
 *         method:FrameworkMethod = the method to run,
 *             args:Array = arguments to pass to the method,
 *         description:String = an optional description of the test,
 *     }
 */
public class BlockWithArgumentsRunner extends BlockFlexUnit4ClassRunner {

src/utils/testrunners/InvokeMethodWithArgs.as

/**
 * Similar to the InvokeMethod class, but allows args to be passed
 * to the method being invoked.
 */
public class InvokeMethodWithArgs extends AsyncStatementBase

src/utils/testrunners/ParameterizedRunner.as

/**
 * Parameterize a testcase.
 * (this class is still in development and will have a few rough edges)
 * For example:
 *
 *     [RunWith("utils.testrunners.ParameterizedRunner")]
 *     class AdditionTests {
 *         // Force the MXMLC to link in the ParameterizedRunner class
 *         protected var _forceMXMLCToLinkRunner:ParameterizedRunner;
 *
 *         // Note: parameters must be a public static class variable
 *         public static var numbersToTest:Array = [
 *             [1, 2, 3],
 *             [4, 5, 9],
 *             [-1, 1, 0]
 *         };
 *
 *         [Parameterized("numbersToTest")]
 *         public function testAddition(a:int, b:int, expected:int):void {
 *             assertEqual(a+b, expected);
 *         }
 *     }
 *
 * Will cause three tests to be executed.
 */
public class ParameterizedRunner extends BlockWithArgumentsRunner {

src/utils/Timeout.as

/**
 * A small class to make dealing with timeouts cleaner.
 * For example:
 *     var someTimeout:Timeout = new Timeout(500, onSomeTimeout);
 *     function onSomeTimeout():void {
 *         doStuff();
 *     }
 *
 *     foo.addEventListener(MouseEvent.MOUSE_ENTER, ignoreArgs(someTimeout.start));
 *     foo.addEventListener(MouseEvent.MOUSE_LEAVE, ignoreArgs(someTimeout.abort));
 */
public class Timeout {

src/utils/truncate.as

/**
 * Truncate a string.
 */
public function truncate(string:String, maxLength:int,

src/utils/uniq.as

/**
 * Returns an array containing only the unique elements of 'iterable'.
 * Assumes that 'iterable' is sorted.
 */
public function uniq(iterable:*):Array {

src/utils/updateEvent.as

/**
 * A thin wrapper around PropertyChangeEvent.createUpdateEvent which uses up
 * less screen real-estate.
 */
public function updateEvent(source:Object, property:Object,

src/utils/zip.as

/**
 * zip([1,2,3], ["a","b","c"]) => [ [1, "a"], [2, "b"], [3, "c"] ]
 */
public function zip(...iterables):Array {