Accessibility

Table of Contents

ActionScript collections and functional programming

The Versatile array

What's called an "array" in ActionScript is generally called a "list" or some other kind of higher-level collection in other languages, because an ActionScript Array object is very full-featured.

Creating arrays

You've already seen the square-bracket syntax for creating arrays, which I prefer because it is shorter and cleaner (and is the same as Python). You can also create and initialize array objects using constructor syntax:

<?xml version="1.0" encoding="utf-8"?>
<mx:Application name="CreatingArrays" xmlns:mx="http://www.adobe.com/2006/mxml">
<mx:creationComplete>
include "../../Mindview/includes/show.as"
var a1:Array = new Array()  // Zero elements
a1.setName("a1")
a1.show()
var a2:Array = new Array(7) // Seven elements
a2.setName("a2")
a2.show()
a2[4] = 11 // Assign to zero-indexed location 4
a2.show()
var a3:Array = new Array('d', 'e', 'f') // List syntax
a3.setName("a3")
a3.show()
// One way to create multidimensional arrays:
var a4:Array = new Array(a1, a2, a3)
a4.setName("a4")
a4.show()
t.show("a4[1][4]: " + a4[1][4] + " a4[2][2]: " + a4[2][2])
// Using String's split() to make an array of characters:
var a5:Array = "abcdefghijklmnopqrstuvwxyz".split('')
a5.setName("a5")
a5.show()
</mx:creationComplete>
</mx:Application>
Here's the output:
a1: []
a2: [,,,,,,]
a2: [,,,,11,,]
a3: [d,e,f]
a4: [[],[,,,,11,,],[d,e,f]]
a4[1][4]: 11 a4[2][2]: f
a5: [a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z]

Adding, removing, concat, slicing & splicing

Arrays have the kinds of tools you expect for inserting and removing elements. push() and pop() insert and remove elements from the end, and unshift() and shift() insert and remove elements from the beginning. splice() modifies the array in place by simultaneously removing and inserting elements at any point in the array. concat() creates a new array and adds elements to the end, while slice() creates a new array containing a section of the original:

<?xml version="1.0" encoding="utf-8"?>
<mx:Application name="AddRemoveEtc" xmlns:mx="http://www.adobe.com/2006/mxml">
<mx:creationComplete>
include "../../Mindview/includes/show.as"
var a:Array = [1,2,3,4,5,6,7]
a.setName("a")
a.show()
a.push('x') // End of the array
a.show("after push")
a.pop()
a.show("after pop")
a.unshift('y') // Beginning of the array
a.show("after unshift")
a.shift()
a.show("after shift")
// Splice adds and removes from the original array:
a.splice(2, 3, 'a', 'b', 'c', 'd')
a.show("after splice")
// concat() and slice() create new arrays:
var b:Array = a.concat("foo", "bar", "baz")
b.setName("b")
b.show("concatted")
var c:Array = a.slice(1,4)
c.setName("c")
c.show("sliced")
a.show("unchanged")
</mx:creationComplete>
</mx:Application>

Here's the output:

a: [1,2,3,4,5,6,7]
after push a: [1,2,3,4,5,6,7,x]
after pop a: [1,2,3,4,5,6,7]
after unshift a: [y,1,2,3,4,5,6,7]
after shift a: [1,2,3,4,5,6,7]
after splice a: [1,2,a,b,c,d,6,7]
concatted b: [1,2,a,b,c,d,6,7,foo,bar,baz]
sliced c: [2,a,b]
unchanged a: [1,2,a,b,c,d,6,7]

Reversing and searching

The reverse() method reverses the order of an array in-place. indexOf() searches for the argument and returns the array index of the first occurrence; lastIndexOf() searches backwards from the end:

<?xml version="1.0" encoding="utf-8"?>
<mx:Application name="ReverseAndSearch" xmlns:mx="http://www.adobe.com/2006/mxml">
<mx:creationComplete>
include "../../Mindview/includes/show.as"
var a:Array = "abcdefghijklmnopqrstuvwxyz".split("")
a.setName("a")
a.show()
a.reverse()
a.show("reversed")
// Search using strict equality (===)
a[10] = 'spam'
var i:int = a.indexOf('spam')
t.show("i = a.indexOf('spam'): " + i + ", a[i]: " + a[i])
// Search from the end:
a[20] = 'spam'
a.show()
i = a.lastIndexOf('spam')
t.show("i = a.lastIndexOf('spam'): " + i + ", a[i]: " + a[i])
</mx:creationComplete>
</mx:Application>

Here's the output:

a: [a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z]
reversed a: [z,y,x,w,v,u,t,s,r,q,p,o,n,m,l,k,j,i,h,g,f,e,d,c,b,a]
i = a.indexOf('spam'): 10, a[i]: spam
a: [z,y,x,w,v,u,t,s,r,q,spam,o,n,m,l,k,j,i,h,g,spam,e,d,c,b,a]
i = a.lastIndexOf('spam'): 20, a[i]: spam

Basic sorting

Basic sorting uses built-in comparisons. You can also specify variations on the basic sort using flags, which can be ORed together:

<?xml version="1.0" encoding="utf-8"?>
<mx:Application name="Sorting" xmlns:mx="http://www.adobe.com/2006/mxml">
<mx:creationComplete>
include "../../Mindview/includes/show.as"
var a:Array = "aBcDeFgHiJkLmNoPqRsTuVwXyZ".split("")
a.setName("a")
a.show()
a.sort()
a.show("After sorting")
a.sort(Array.CASEINSENSITIVE)
a.show("After case-insensitive sort")
a.sort(Array.CASEINSENSITIVE | Array.DESCENDING)
a.show("After case-insensitive, descending sort")
var b:Array = [4,5,6,7,8,9,10]
b.setName("b")
b.show()
b.sort()
b.show("After sorting")
b.sort(Array.NUMERIC)
b.show("After numeric sort")
b.sort(Array.NUMERIC | Array.DESCENDING)
b.show("After numeric, descending sort")
var c:Array = "ABCDEFGHIJK".split("").reverse()
c.setName("c")
c.show()
var csorted:Array = c.sort(Array.RETURNINDEXEDARRAY)
c.show("After indexed array sort")
csorted.setName("csorted")
csorted.show()
</mx:creationComplete>
</mx:Application>

Most of these sorts happen in-place. Here's the output:

a: [a,B,c,D,e,F,g,H,i,J,k,L,m,N,o,P,q,R,s,T,u,V,w,X,y,Z]

After sorting a:

[B,D,F,H,J,L,N,P,R,T,V,X,Z,a,c,e,g,i,k,m,o,q,s,u,w,y]

After case-insensitive sort a:

[a,B,c,D,e,F,g,H,i,J,k,L,m,N,o,P,q,R,s,T,u,V,w,X,y,Z]

After case-insensitive, descending sort a:

[Z,y,X,w,V,u,T,s,R,q,P,o,N,m,L,k,J,i,H,g,F,e,D,c,B,a]

b: [4,5,6,7,8,9,10]

After sorting b:

[10,4,5,6,7,8,9]

After numeric sort b:

 [4,5,6,7,8,9,10]

After numeric, descending sort b:

 [10,9,8,7,6,5,4]

c:

 [K,J,I,H,G,F,E,D,C,B,A]

After indexed array sort c:

 [K,J,I,H,G,F,E,D,C,B,A]

csorted:

 [10,9,8,7,6,5,4,3,2,1,0]

Complex sorting

In order to demonstrate the more complex sorting functionality, we need a list of objects with multiple fields. The following Person class contains a static createArray() method that randomly generates any number of Person objects:

package com.mindviewinc.test {
    [Bindable] // Create Bindable Value Objects
    public class Person {
        public var first:String
        public var last:String
        public var street:String
        public var city:String
        public var state:String
        public var zip:String
        public function Person(first:String, last:String, street:String, city:String, state:String, zip:String) {
            this.first = first
            this.last = last
            this.street = street
            this.city = city
            this.state = state
            this.zip = zip
        }
        private static var values:Array = [
            ["Bob", "Betty", "John", "Jane", "Alfred", "Annette", "Sam", "Hank", "Hillary"],
            ["Smith", "Miller", "Johnson", "Jones", "Lee", "Adams", "Jefferson"],
            ["123 Elm Ave.", "100 Main St.", "123 Broadway", "123 Center St."],
            ["Springfield", "Oak Grove", "Fairview", "Mount Pleasant", "Centerville", "Riverside", "Greenwood"],
            ["IL", "UT", "KS", "PA", "IN", "ID", "WA", "OR"]
        ]
        private static function selectRandom(offset:int):String {
            return values[offset][Math.round(Math.random() * (values[offset].length - 1))]
        }
        private static function makeZip(): String {
            var result:String = ""
            for(var i:int = 0; i < 5; i++)
                 result += String(Math.round(Math.random() * 9))
            return result
        }
        public static function createPerson():Person {
            return new Person(selectRandom(0), selectRandom(1), selectRandom(2),
                selectRandom(3), selectRandom(4), makeZip())
        }
        public static function createArray(size:int):Array {
            var result:Array = new Array(size)
            for(var i:int = 0; i < size; i++)
                result[i] = createPerson()
            return result
        }
        public function toString():String {
            return first + " " + last + ", " + street + ", " + city  + ", " + state + " " + zip
        }
    }
}

In the constructor, this is necessary to distinguish the arguments from the fields.

By using the [Bindable] decorator on the class, the objects of this class become Bindable Data Objects, which means that all the fields in the objects become bindable and thus automatically updateable. We'll use this feature later in the article.

Although all the fields are public for simplicity, they could also be exposed as properties. Even nicer, you can transparently change them from public fields into properties without changing any of the client code, because the syntax remains the same.

For convenience, here is a class that can be used as an MXML component to hold an array containing a group of Person objects:

package com.mindviewinc.test {
    import mx.core.UIComponent
    public class People extends UIComponent {
        public var quantity:Number
        [Bindable] public var array:Array
        protected override function commitProperties():void {
            super.commitProperties();
            array = Person.createArray(quantity)
        }
    }
}

The commitProperties() event is called after the properties have been assigned from their MXML declarations, so quantity will be the correct value at that time.

We want to display Person objects in a DataGrid, and to avoid repetition every time we want to control the order the columns are displayed, we'll create an MXML component that inherits from DataGrid:

<?xml version="1.0" encoding="utf-8"?>
<mx:DataGrid name="PeopleGrid" height="100%" editable="true" xmlns:mx="http://www.adobe.com/2006/mxml">
    <mx:columns>
        <mx:DataGridColumn dataField="first"/>
        <mx:DataGridColumn dataField="last"/>
        <mx:DataGridColumn dataField="street"/>
        <mx:DataGridColumn dataField="city"/>
        <mx:DataGridColumn dataField="state"/>
        <mx:DataGridColumn dataField="zip"/>
    </mx:columns>
</mx:DataGrid>

The columns group establishes the display order for the data columns. As a simple test, we display a list of Person objects in a PeopleGrid:

<?xml version="1.0" encoding="utf-8"?>
<mx:Application name="PersonTest" xmlns:mx="http://www.adobe.com/2006/mxml" xmlns:test="com.mindviewinc.test.*">
<test:People id="people" quantity="40"/>
<test:PeopleGrid dataProvider="{people.array}"/>
</mx:Application>

Notice that you can sort on a particular column by clicking on the header for that column.

Next you can see various kinds of complex sorting using sortOn() to sort on one or more fields, and a sort function:

<?xml version="1.0" encoding="utf-8"?>
<mx:Application name="ComplexSorting" xmlns:mx="http://www.adobe.com/2006/mxml">
<mx:creationComplete>
<![CDATA[
include "../../Mindview/includes/show.as"
import com.mindviewinc.test.Person
var people:Array = Person.createArray(10)
people.show = function(msg:String = ""):void {
    if(msg.length)
       t.show(msg + ":")
    for each(var p:* in people)
       t.show(p)
    t.show("========================================")
}
people.setPropertyIsEnumerable("show", false)
people.show()
people.sort()
people.show("Ordinary sort")
people.sortOn('last')
people.show("sortOn('last')")
people.sortOn(['last', 'street', 'zip'])
people.show("sortOn(['last', 'street', 'zip'])")
people.sortOn(['last', 'zip'], [Array.DESCENDING, Array.NUMERIC | Array.DESCENDING])
people.show("sortOn(['last', 'zip'], [Array.DESCENDING, Array.NUMERIC | Array.DESCENDING]")
function sortOnContinentalDivide(a:Person, b:Person):Number {
    // Create set behavior for "in" testing:
    var east:Object = {IL:0, KS:0, PA:0, IN:0}
    var west:Object = {UT:0, ID:0, WA:0, OR:0}
    if(a.state in west && b.state in east)
        return 1
    if(a.state in east && b.state in west)
        return -1
    return 0
}
people.sort(sortOnContinentalDivide)
people.show("sortOnContinentalDivide")
]]>
</mx:creationComplete>
</mx:Application>

An ordinary call to sort() just sorts on the initial field in Person, which is first. sortOn() not only allows you to choose a single field to sort on, it allows you to choose multiple fields; the first field in the list is the primary sorting field, the second is the secondary field, and so on. In addition, you can add a parallel array of sorting flags -- each element in the second array affects the corresponding element in the first array.

In general, sortOn() will handle your complex sorting needs, but if it doesn't, you can take complete control by providing a sorting function to the sort() method. This function must return 1, -1 or zero depending on whether the first argument is greater than, less than or equal to the second argument, respectively. In the example above, the sort is based on whether the state is on the east or west side of the continental divide.

Notice the definitions for east and west. These are associative arrays, which you'll learn about shortly. The keys, to the left of the colon, are automatically turned into strings by the compilers. The values are all zero because they are unused in this case. The reason for creating these associative arrays is ActionScript's in keyword, which tells you whether an argument is one of the keys in an associative array. The ability to ask whether an object is part of a group of others is set behavior, so by creating the associative arrays we are conveniently able to ask whether a state is in the east or the west. It's also possible to create associative arrays with objects rather than strings as keys; see later in this article.

Here's the output:

Annette Adams, 123 Elm Ave., Riverside, WA 26323
Annette Miller, 123 Broadway, Springfield, WA 41491
Alfred Jones, 123 Elm Ave., Fairview, UT 43821
Sam Lee, 123 Broadway, Springfield, UT 22643
Hillary Jones, 123 Center St., Riverside, WA 42253
John Miller, 123 Broadway, Oak Grove, ID 26276
John Adams, 123 Elm Ave., Centerville, WA 22253
Sam Adams, 123 Center St., Mount Pleasant, KS 50456
Betty Miller, 123 Broadway, Riverside, IN 05445
Annette Jones, 123 Elm Ave., Fairview, PA 26193
========================================
Ordinary sort:
Alfred Jones, 123 Elm Ave., Fairview, UT 43821
Annette Adams, 123 Elm Ave., Riverside, WA 26323
Annette Jones, 123 Elm Ave., Fairview, PA 26193
Annette Miller, 123 Broadway, Springfield, WA 41491
Betty Miller, 123 Broadway, Riverside, IN 05445
Hillary Jones, 123 Center St., Riverside, WA 42253
John Adams, 123 Elm Ave., Centerville, WA 22253
John Miller, 123 Broadway, Oak Grove, ID 26276
Sam Adams, 123 Center St., Mount Pleasant, KS 50456
Sam Lee, 123 Broadway, Springfield, UT 22643
========================================
sortOn('last'):
Sam Adams, 123 Center St., Mount Pleasant, KS 50456
Annette Adams, 123 Elm Ave., Riverside, WA 26323
John Adams, 123 Elm Ave., Centerville, WA 22253
Annette Jones, 123 Elm Ave., Fairview, PA 26193
Hillary Jones, 123 Center St., Riverside, WA 42253
Alfred Jones, 123 Elm Ave., Fairview, UT 43821
Sam Lee, 123 Broadway, Springfield, UT 22643
John Miller, 123 Broadway, Oak Grove, ID 26276
Betty Miller, 123 Broadway, Riverside, IN 05445
Annette Miller, 123 Broadway, Springfield, WA 41491
========================================
sortOn(['last', 'street', 'zip']):
Sam Adams, 123 Center St., Mount Pleasant, KS 50456
John Adams, 123 Elm Ave., Centerville, WA 22253
Annette Adams, 123 Elm Ave., Riverside, WA 26323
Hillary Jones, 123 Center St., Riverside, WA 42253
Annette Jones, 123 Elm Ave., Fairview, PA 26193
Alfred Jones, 123 Elm Ave., Fairview, UT 43821
Sam Lee, 123 Broadway, Springfield, UT 22643
Betty Miller, 123 Broadway, Riverside, IN 05445
John Miller, 123 Broadway, Oak Grove, ID 26276
Annette Miller, 123 Broadway, Springfield, WA 41491
========================================
sortOn(['last', 'zip'], [Array.DESCENDING, Array.NUMERIC | Array.DESCENDING]:
Annette Miller, 123 Broadway, Springfield, WA 41491
John Miller, 123 Broadway, Oak Grove, ID 26276
Betty Miller, 123 Broadway, Riverside, IN 05445
Sam Lee, 123 Broadway, Springfield, UT 22643
Alfred Jones, 123 Elm Ave., Fairview, UT 43821
Hillary Jones, 123 Center St., Riverside, WA 42253
Annette Jones, 123 Elm Ave., Fairview, PA 26193
Sam Adams, 123 Center St., Mount Pleasant, KS 50456
Annette Adams, 123 Elm Ave., Riverside, WA 26323
John Adams, 123 Elm Ave., Centerville, WA 22253
========================================
sortOnContinentalDivide:
Sam Adams, 123 Center St., Mount Pleasant, KS 50456
Annette Jones, 123 Elm Ave., Fairview, PA 26193
Betty Miller, 123 Broadway, Riverside, IN 05445
John Miller, 123 Broadway, Oak Grove, ID 26276
Alfred Jones, 123 Elm Ave., Fairview, UT 43821
Annette Miller, 123 Broadway, Springfield, WA 41491
Sam Lee, 123 Broadway, Springfield, UT 22643
Hillary Jones, 123 Center St., Riverside, WA 42253
Annette Adams, 123 Elm Ave., Riverside, WA 26323
John Adams, 123 Elm Ave., Centerville, WA 22253
========================================

Copying arrays

Like Java, ActionScript is reference-based. If you point two references at the same object, any changes in the object will show up through both references:

<?xml version="1.0" encoding="utf-8"?>
<mx:Application name="CopyingArrays"             xmlns:mx="http://www.adobe.com/2006/mxml">
<mx:creationComplete>
include "../../Mindview/includes/show.as"
function display():void {
 t.show("a: " + formatArray(a))
 t.show("b: " + formatArray(b))
}
var a:Array = ['a', 'b', 'c']
var b:Array = a
display()
a[1] = 'x'
display()
b = a.slice()
b[1] = 'b'
display()
var c:Array = b.concat()
c[2] = 'z'
display()
t.show("c: " + formatArray(c))
</mx:creationComplete>
</mx:Application>
This produces the following output:
a: [a,b,c]
b: [a,b,c]
a: [a,x,c]
b: [a,x,c]
a: [a,x,c]
b: [a,b,c]
a: [a,x,c]
b: [a,b,c]
c: [a,b,z]

Changing a[1] changes it for both a and b when the two references are aliased to the same array object. But using either slice() or concat() with no arguments produces a shallow copy* of the array, which means that the array is duplicated, but the objects the array points to are not.

Deep copy

A deep copy means that not only are references copied, but all the objects the references are pointing to must also be copied. To see the effect of shallow vs. deep copy, it's helpful to have a class that automatically generates a unique field:

package com.mindviewinc.test {
    public class AutoID {
        private static var count:Number = 'A'.charCodeAt()
        public var id:String = String.fromCharCode(count++)
        public function toString():String { return id }
    }
}

Using a static variable, this class automatically creates a different id for each new object, and produces that id whenever you ask for a string representation of the object. Notice that the override keyword is not used when defining toString() even though you normally need to invoke override when providing a new definition for an existing method. This is an artifact left over from prototype days, and it will cause you some consternation if you forget and try to use override.

The following example demonstrates the issue with shallow copy, and then shows the solution suggested in the ActionScript documentation (which comes with Flex Builder):

<?xml version="1.0" encoding="utf-8"?>
<mx:Application name="ShallowCopy" xmlns:mx="http://www.adobe.com/2006/mxml">
<mx:creationComplete>
include "../../Mindview/includes/show.as"
import com.mindviewinc.test.AutoID
function display():void {
    t.show("a: " + formatArray(a))
    t.show("b: " + formatArray(b))
}
var a:Array = [new AutoID(), new AutoID(), new AutoID(), new AutoID()]
var b:Array = a.slice() // Shallow copy
display()
a[3].id = 'X'
display() // Objects being pointed to are the same
 
// Solution suggested in ActionScript docs:
import flash.utils.ByteArray
function deepCopy(source:Object):* {
    var myBA:ByteArray = new ByteArray()
    myBA.writeObject(source)
    myBA.position = 0
    return(myBA.readObject())
}
// The bits are there, but it loses the type information:
b = deepCopy(a)
a[3].id = 'Y'
display()
for(var i:* in b)
    t.show('b[i].id: ' +  b[i].id)
</mx:creationComplete>
</mx:Application>

The deepCopy() function creates a deep copy by serializing the array into an instance of the ByteArray class, and then reading the array back into a new array.

The result of deepCopy() isn't quite right, as you can see from the output:

a: [A,B,C,D]
b: [A,B,C,D]
a: [A,B,C,X]
b: [A,B,C,X]
a: [A,B,C,Y]
b: [[object Object],[object Object],[object Object],[object Object]]
b[i].id: A
b[i].id: B
b[i].id: C
b[i].id: X

Although the id fields are there, this approach loses the type information about what the object is.