//////////////////////////////////////////////////////////////////////////////// // // JumpShip Framework for AS3 // Copyright 2007 Jamie Scanlon // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS // IN THE SOFTWARE. // // Project: JumpShip Framework - Core // File: JSEnumerable.as // Created by: Jamie Scanlon // //////////////////////////////////////////////////////////////////////////////// // This class provides utility functions for enumerable objects making it easy // to traverse data structures in a Ruby-esque manner. Much of the code was // adapted from the Enumberable object in the Prototype library for JavaScript. // // This class also lets you use [ ] array notation to access the data. // Much of the code for these functions were derived from the Collection data // structure that is used throuout the Flex Framework. The goal here is to // provide a basis for a data structure that is grounded in the standard // prncipals of Ruby / Prototype but also is mindful of the future direction // and common practices of Flash / Flex // // In order to be as flexible as possible, this class also implements // IEventDispatcher. package com.jsjstudios.jumpship.core { //////////////////////////////////////////////////////////////////////////////// // Imports //////////////////////////////////////////////////////////////////////////////// import flash.utils.Proxy; import flash.utils.flash_proxy; import flash.events.IEventDispatcher; import flash.events.EventDispatcher; import flash.events.Event; //////////////////////////////////////////////////////////////////////////////// // // // Class: JSEnumerable // // //////////////////////////////////////////////////////////////////////////////// public class JSEnumerable extends Proxy implements IEventDispatcher { //////////////////////////////////////////////////////////////////////////// // Event Dispatcher proxy //////////////////////////////////////////////////////////////////////////// private var eventDispatcher:EventDispatcher; //////////////////////////////////////////////////////////////////////////// // Aliases //////////////////////////////////////////////////////////////////////////// public var map:Function = collect; //public var find:Function = detect; public var select:Function = findAll; public var member:Function = incl; public var entries:Function = toArray; //////////////////////////////////////////////////////////////////////////// // // Constructor // //////////////////////////////////////////////////////////////////////////// public function JSEnumerable ( ) { eventDispatcher = new EventDispatcher(); } //////////////////////////////////////////////////////////////////////////// // // Public Methods // //////////////////////////////////////////////////////////////////////////// public function each(iterator:Function):* { var index:uint = 0; doEach(function (value:*):* { iterator(value, index++); }); return this; } public function eachSlice(num:uint, iterator:Function):* { var index:uint = -num; var slices:Array = []; var array:Array = toArray(); while ((index += num) < array.length) { slices.push(array.slice(index, index+num)); } return slices.map(iterator); } public function getItemAt(index:int, ... args):* { var myResult:Array = findAll(function (value:*, myIndex:int):* { return myIndex == index; }); if (myResult.length > 0) { return myResult[0]; } else { return null; } } // This function should be defined in the class that inherits from this one // since this class does not refer to any data directly and therefore can't // modify it. public function setItemAt(value:Object, index:int, ... args):* {} public function all(iterator:Function):* { var result:Boolean = true; each(function (value:*,index:int):* { result = result && iterator(value,index); }); return result; } public function any(iterator:Function):* { var result:Boolean = false; each(function (value:*,index:int):* { if (!result) { result = iterator(value,index); } }); return result; } public function collect( iterator:Function = null ):* { if (iterator == null) { iterator = __defaultFunc; } var results:Array = []; each(function (value:*,index:int):* { results.push( iterator(value,index) ); }); return results; } public function detect(iterator:Function):* { var result:* = false; each(function (value:*,index:int):* { if ( iterator(value,index) && !result ) { result = value; } }); return result; } public function findAll(iterator:Function):* { var results:Array = []; each(function (value:*,index:int):* { if ( iterator(value,index) ) { results.push( value ); } }); return results; } public function grep(pattern:RegExp, iterator:Function):* { var results:Array = []; each(function (value:*,index:int):* { var stringValue:String = value.toString(); if ( pattern.test(stringValue) ) { results.push( iterator(value,index) ); } }); return results; } public function incl(object:*):* { var found:Boolean = false; each(function (value:*):* { if ( value == object ) { found = true; } }); return found; } public function inject(memo:*, iterator:Function):* { each(function(value:*, index:int):* { memo = iterator(memo, value, index); }); return memo; } public function invoke(method:*, ... args):* { return map(function(value:*):* { return value[method].apply(value, args); }); } public function max(iterator:Function):* { var result:*; each(function (value:*, index:int):* { value = iterator(value, index); if (result == null || value >= result) { result = value } }); return result; } public function min(iterator:Function):* { var result:*; each(function (value:*, index:int):* { value = iterator(value, index); if (result == null || value < result) { result = value } }); return result; } public function pluck(property:String):* { var results:Array = []; each(function(value:*, index:int):* { results.push(value[property]); }); return results; } public function reject(iterator:Function):* { var results:Array = []; each(function (value:*, index:int):* { if (!iterator(value, index)) { results.push(value); } }); return results; } public function sortBy(iterator:Function):* { var results:Array = []; var temp:Array = map(function(value:*, index:int):* { return {value: value, criteria: iterator(value, index)}; }).sort(function(left:*, right:*):* { var a:* = left.criteria; var b:* = right.criteria; return a < b ? -1 : a > b ? 1 : 0; }); for (var i:int = 0; i < temp.length; i++) { results.push(temp[i].value); } return results; } public function toArray():Array { return map(); } public function zip(... args):* { // } public function size():int { return toArray().length; } //////////////////////////////////////////////////////////////////////////// // // Proxy methods // //////////////////////////////////////////////////////////////////////////// // The following lets you use [ ] array notation to access the data. //attempts to call getItemAt converting the property name into an int override flash_proxy function getProperty(name:*):* { if (name is QName) { name = name.localName; } //else name is String var index:int = -1; try { //if they passed in a number (5.5) it will be floored var n:Number = parseInt(String(name)); if (!isNaN(n)) { index = int(n); } } catch (e:Error) {} //localName was not a number if (index == -1) { var message:String = "unknownProperty"+name+" in JSEnumerable"; throw new Error(message); } else { return getItemAt(index) } } //attempts to call setItemAt converting the property name into an int override flash_proxy function setProperty(name:*, value:*):void { if (name is QName) { name = name.localName; } //else name is String var index:int = -1; try { //if they passed in a number (5.5) it will be floored var n:Number = parseInt(String(name)); if (!isNaN(n)) { index = int(n); } } catch (e:Error) {} //localName was not a number if (index == -1) { var message:String = "unknownProperty"+name+" in JSEnumerable"; throw new Error(message); } else { setItemAt(value, index) } } override flash_proxy function hasProperty(name:*):Boolean { // Check for XML if (name is QName) { name = name.localName; } //else name is String var index:int = -1; try { //if they passed in a number (5.5) it will be floored var n:Number = parseInt(String(name)); if (!isNaN(n)) { index = int(n); } } catch (e:Error) {} //localName was not a number if (index == -1) { return false; } return (index >= 0 && index < length); } override flash_proxy function nextNameIndex(index:int):int { return index < length ? index + 1 : 0; } override flash_proxy function nextName(index:int):String { return (index-1).toString(); } override flash_proxy function nextValue(index:int):* { return getItemAt(index - 1); } override flash_proxy function callProperty(name:*, ... rest):* { return null; } //////////////////////////////////////////////////////////////////////////// // // IEventDispatcher Methods // //////////////////////////////////////////////////////////////////////////// public function addEventListener(type:String, listener:Function, useCapture:Boolean = false, priority:int = 0, useWeakReference:Boolean = false):void { eventDispatcher.addEventListener(type, listener, useCapture, priority, useWeakReference) } public function dispatchEvent(event:Event):Boolean { return eventDispatcher.dispatchEvent(event); } public function hasEventListener(type:String):Boolean { return eventDispatcher.hasEventListener(type); } public function removeEventListener(type:String, listener:Function, useCapture:Boolean = false):void { eventDispatcher.removeEventListener(type, listener, useCapture) } public function willTrigger(type:String):Boolean { return eventDispatcher.willTrigger(type); } /** * Returns the a String description of this model. * @return a String. */ public function toString():String { return "[object JSEnumerable]"; } //////////////////////////////////////////////////////////////////////////// // // Private Methods // //////////////////////////////////////////////////////////////////////////// protected function doEach(iterator:Function):* {}; private function __defaultFunc(value:*, ... args):* { return value; } } // Class End } // Package End