├── .gitignore ├── libs └── actionjson-1.3.swc ├── src ├── manifest.xml ├── mesh │ ├── mesh_internal.as │ ├── Mesh.as │ ├── core │ │ ├── range │ │ │ ├── from.as │ │ │ ├── RangeBuilder.as │ │ │ ├── IntRange.as │ │ │ ├── CharRange.as │ │ │ └── DateRange.as │ │ ├── inflection │ │ │ ├── pluralize.as │ │ │ ├── singularize.as │ │ │ ├── humanize.as │ │ │ ├── underscore.as │ │ │ ├── camelize.as │ │ │ └── ordinalize.as │ │ ├── reflection │ │ │ ├── reflect.as │ │ │ ├── path.as │ │ │ ├── clazz.as │ │ │ ├── className.as │ │ │ ├── Metadata.as │ │ │ ├── Method.as │ │ │ ├── Property.as │ │ │ ├── Definition.as │ │ │ └── newInstance.as │ │ ├── number │ │ │ ├── round.as │ │ │ ├── random.as │ │ │ └── Fraction.as │ │ ├── string │ │ │ ├── sentenceize.as │ │ │ └── capitalize.as │ │ ├── BatchedListDelegate.as │ │ ├── functions │ │ │ └── closure.as │ │ ├── object │ │ │ ├── inspect.as │ │ │ ├── isEmpty.as │ │ │ ├── merge.as │ │ │ └── copy.as │ │ ├── state │ │ │ ├── TransitionEvent.as │ │ │ ├── StateEvent.as │ │ │ ├── Action.as │ │ │ ├── Transition.as │ │ │ └── State.as │ │ ├── array │ │ │ ├── intersection.as │ │ │ ├── flatten.as │ │ │ └── ArrayProxy.as │ │ ├── IBatchedListDelegate.as │ │ ├── Set.as │ │ ├── BatchedList.as │ │ └── proxy │ │ │ └── DataProxy.as │ ├── model │ │ ├── store │ │ │ ├── DataCache.as │ │ │ ├── ICommitResponder.as │ │ │ ├── Query.as │ │ │ ├── CommitResponder.as │ │ │ ├── Cache.as │ │ │ ├── RecordCache.as │ │ │ ├── Store.as │ │ │ ├── ResultsList.as │ │ │ ├── Data.as │ │ │ └── QueryBuilder.as │ │ ├── IPersistable.as │ │ ├── associations │ │ │ ├── HasOneAssociation.as │ │ │ ├── HasManyAssociation.as │ │ │ └── HasAssociation.as │ │ ├── validators │ │ │ ├── FormatValidator.as │ │ │ ├── ExclusionValidator.as │ │ │ ├── InclusionValidator.as │ │ │ ├── PresenceValidator.as │ │ │ ├── EachValidator.as │ │ │ ├── Validator.as │ │ │ ├── LengthValidator.as │ │ │ └── NumericValidator.as │ │ ├── source │ │ │ ├── AssociationCollectionSnapshot.as │ │ │ ├── IPersistenceResponder.as │ │ │ ├── IRetrievalResponder.as │ │ │ ├── DataSource.as │ │ │ ├── DataSourceRetrievalOperation.as │ │ │ └── Snapshot.as │ │ ├── ID.as │ │ ├── ILoadable.as │ │ └── RecordState.as │ ├── operations │ │ ├── EmptyOperation.as │ │ ├── ParallelOperation.as │ │ ├── ProgressOperationEvent.as │ │ ├── ResultOperationEvent.as │ │ ├── QueueProgress.as │ │ ├── SequentialOperation.as │ │ ├── FinishedOperationEvent.as │ │ ├── OperationQueueEvent.as │ │ ├── Progress.as │ │ ├── OperationEvent.as │ │ ├── Timeout.as │ │ ├── Attempt.as │ │ ├── FaultOperationEvent.as │ │ ├── MethodOperation.as │ │ ├── ServiceOperation.as │ │ ├── FactoryOperation.as │ │ ├── URLLoaderOperation.as │ │ └── FileReferenceUploadOperation.as │ └── view │ │ └── helpers │ │ ├── number │ │ ├── withDelimiter.as │ │ ├── toPercentage.as │ │ └── withPrecision.as │ │ └── text │ │ └── pluralizeByCount.as └── metadata.xml └── tests └── mesh ├── Account.as ├── Order.as ├── model ├── AggregateTestMockRecord.as ├── validators │ ├── FormatValidatorTests.as │ ├── InclusionValidatorTests.as │ ├── ExclusionValidatorTests.as │ ├── PresenceValidatorTests.as │ ├── LengthValidatorTests.as │ └── NumericValidatorTests.as ├── AggregateTests.as ├── ValidationTests.as ├── RecordDirtyTests.as ├── store │ ├── DataTests.as │ └── ResultsListTests.as ├── InitializeAssociatedRecordsTests.as ├── associations │ └── HasOneAssociationTests.as ├── RecordLoadTests.as └── RecordCommitTests.as ├── Customer.as ├── Name.as ├── AsyncTest.as ├── operations ├── MockOperation.as └── MethodOperationTests.as ├── core ├── number │ ├── FractionTests.as │ └── RoundTests.as ├── array │ └── FlattenTests.as ├── inflection │ ├── HumanizeTests.as │ ├── SingularizeTests.as │ ├── PluralizeTests.as │ ├── CamelizeTests.as │ └── UnderscoreTests.as ├── string │ ├── CapitalizeTests.as │ └── SentenceizeTests.as ├── reflection │ ├── MethodTests.as │ └── TypeTests.as ├── proxy │ └── DataProxyTests.as ├── object │ └── CopyTests.as └── state │ └── StateMachineTests.as ├── Person.as └── view └── helpers ├── text └── PluralizeByCountTests.as └── number ├── WithDelimiterTests.as └── WithPrecisionTests.as /.gitignore: -------------------------------------------------------------------------------- 1 | .* 2 | *FlexUnit* 3 | /.* 4 | /bin 5 | /target 6 | **/.* 7 | -------------------------------------------------------------------------------- /libs/actionjson-1.3.swc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danschultz/mesh/HEAD/libs/actionjson-1.3.swc -------------------------------------------------------------------------------- /src/manifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/mesh/mesh_internal.as: -------------------------------------------------------------------------------- 1 | package mesh 2 | { 3 | /** 4 | * An internal namespace used by Mesh. 5 | */ 6 | public namespace mesh_internal = "http://github.com/danschultz/mesh/internal"; 7 | } -------------------------------------------------------------------------------- /src/mesh/Mesh.as: -------------------------------------------------------------------------------- 1 | package mesh 2 | { 3 | /** 4 | * A class that defines the globals used by Mesh. 5 | * 6 | * @author Dan Schultz 7 | */ 8 | public class Mesh 9 | { 10 | 11 | } 12 | } -------------------------------------------------------------------------------- /src/mesh/core/range/from.as: -------------------------------------------------------------------------------- 1 | package mesh.core.range 2 | { 3 | /** 4 | * @copy Range#from() 5 | */ 6 | public function from(value:*):RangeBuilder 7 | { 8 | return Range.from(value); 9 | } 10 | } -------------------------------------------------------------------------------- /src/mesh/core/inflection/pluralize.as: -------------------------------------------------------------------------------- 1 | package mesh.core.inflection 2 | { 3 | /** 4 | * @copy Inflector#pluralize() 5 | */ 6 | public function pluralize(word:String):String 7 | { 8 | return Inflector.inflections().pluralize(word); 9 | } 10 | } -------------------------------------------------------------------------------- /src/mesh/core/inflection/singularize.as: -------------------------------------------------------------------------------- 1 | package mesh.core.inflection 2 | { 3 | /** 4 | * @copy Inflector#singularize() 5 | */ 6 | public function singularize(word:String):String 7 | { 8 | return Inflector.inflections().singularize(word); 9 | } 10 | } -------------------------------------------------------------------------------- /tests/mesh/Account.as: -------------------------------------------------------------------------------- 1 | package mesh 2 | { 3 | import mesh.model.Record; 4 | 5 | [Bindable] 6 | public class Account extends Record 7 | { 8 | [HasOne] 9 | public var customer:Customer; 10 | public var customerId:int; 11 | 12 | public function Account(values:Object=null) 13 | { 14 | super(values); 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /src/mesh/model/store/DataCache.as: -------------------------------------------------------------------------------- 1 | package mesh.model.store 2 | { 3 | /** 4 | * The data cache stores data that was retrieved through a data source. 5 | * 6 | * @author Dan Schultz 7 | */ 8 | public class DataCache extends Cache 9 | { 10 | /** 11 | * Constructor. 12 | */ 13 | public function DataCache() 14 | { 15 | 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /tests/mesh/Order.as: -------------------------------------------------------------------------------- 1 | package mesh 2 | { 3 | import mesh.model.Record; 4 | 5 | [Bindable] 6 | public class Order extends Record 7 | { 8 | [HasOne] 9 | public var customer:Customer; 10 | public var customerId:int; 11 | 12 | public var total:Number; 13 | 14 | public function Order(values:Object=null) 15 | { 16 | super(values); 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /src/mesh/model/IPersistable.as: -------------------------------------------------------------------------------- 1 | package mesh.model 2 | { 3 | import mesh.model.store.ICommitResponder; 4 | 5 | /** 6 | * The IPersistable interface defines an interface for objects that 7 | * can be persisted. 8 | * 9 | * @author Dan Schultz 10 | */ 11 | public interface IPersistable 12 | { 13 | function save(responder:ICommitResponder = null):*; 14 | } 15 | } -------------------------------------------------------------------------------- /src/mesh/core/reflection/reflect.as: -------------------------------------------------------------------------------- 1 | package mesh.core.reflection 2 | { 3 | /** 4 | * Returns either a cached reflection object for the given class or object. 5 | * 6 | * @param classOrObject The class or object to reflect. 7 | * @return A reflection object. 8 | */ 9 | public function reflect(classOrObject:Object):Type 10 | { 11 | return Type.reflect(classOrObject); 12 | } 13 | } -------------------------------------------------------------------------------- /src/mesh/model/associations/HasOneAssociation.as: -------------------------------------------------------------------------------- 1 | package mesh.model.associations 2 | { 3 | import mesh.model.Record; 4 | 5 | public class HasOneAssociation extends HasAssociation 6 | { 7 | /** 8 | * @copy HasAssociation#HasAssociation() 9 | */ 10 | public function HasOneAssociation(owner:Record, property:String, options:Object = null) 11 | { 12 | super(owner, property, options); 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /src/mesh/model/associations/HasManyAssociation.as: -------------------------------------------------------------------------------- 1 | package mesh.model.associations 2 | { 3 | import mesh.model.Record; 4 | 5 | public class HasManyAssociation extends AssociationCollection 6 | { 7 | /** 8 | * @copy AssociationCollection#AssociationCollection() 9 | */ 10 | public function HasManyAssociation(source:Record, property:String, options:Object = null) 11 | { 12 | super(source, property, options); 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /tests/mesh/model/AggregateTestMockRecord.as: -------------------------------------------------------------------------------- 1 | package mesh.model 2 | { 3 | import mesh.Name; 4 | 5 | public class AggregateTestMockRecord extends Record 6 | { 7 | [Bindable] public var name:Name; 8 | [Bindable] public var firstName:String; 9 | [Bindable] public var last:String; 10 | 11 | public function AggregateTestMockRecord(values:Object=null) 12 | { 13 | super(values); 14 | aggregate("name", Name, ["first:firstName", "last"]); 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /src/mesh/core/number/round.as: -------------------------------------------------------------------------------- 1 | package mesh.core.number 2 | { 3 | /** 4 | * Rounds a number to the given precision. 5 | * 6 | * @param number The number to round. 7 | * @param precision The number of fractional digits. 8 | * @return The rounded number. 9 | */ 10 | public function round(number:Number, precision:int = 0):Number 11 | { 12 | precision = Math.pow(10, (precision < 0 ? 0 : precision)); 13 | return Math.round(number * precision) / precision; 14 | } 15 | } -------------------------------------------------------------------------------- /src/mesh/core/string/sentenceize.as: -------------------------------------------------------------------------------- 1 | package mesh.core.string 2 | { 3 | /** 4 | * Capitalizes the first letter of each sentence in the string. 5 | * 6 | * @param sentences The sentences to capitalize. 7 | * @return A capitalized sentence. 8 | */ 9 | public function sentenceize(sentences:String):String 10 | { 11 | return sentences.replace(/(^|[\.|\?|!]\s+)(.)/gm, function():String 12 | { 13 | return arguments[1] + arguments[2].toUpperCase(); 14 | }); 15 | } 16 | } -------------------------------------------------------------------------------- /src/mesh/core/reflection/path.as: -------------------------------------------------------------------------------- 1 | package mesh.core.reflection 2 | { 3 | import flash.utils.getQualifiedClassName; 4 | 5 | /** 6 | * Returns the package name of the given object or class. 7 | * 8 | * @param objectOrClass The object or class to get the package for. 9 | * @return The package that the object or class belongs to. 10 | */ 11 | public function path(objectOrClass:Object):String 12 | { 13 | return getQualifiedClassName(objectOrClass).split("::").shift(); 14 | } 15 | } -------------------------------------------------------------------------------- /src/mesh/core/string/capitalize.as: -------------------------------------------------------------------------------- 1 | package mesh.core.string 2 | { 3 | /** 4 | * Capitalizes the first letter of a string and the first letter after each 5 | * whitespace character. 6 | * 7 | * @param str The string to capitalize. 8 | * @return A capitalized string. 9 | */ 10 | public function capitalize(str:String):String 11 | { 12 | return str.replace(/(^|\s+)(.)/g, function():String 13 | { 14 | return arguments[1] + arguments[2].toUpperCase(); 15 | }); 16 | } 17 | } -------------------------------------------------------------------------------- /tests/mesh/Customer.as: -------------------------------------------------------------------------------- 1 | package mesh 2 | { 3 | import mesh.model.associations.HasManyAssociation; 4 | 5 | [Bindable] 6 | public class Customer extends Person 7 | { 8 | [HasOne] 9 | public var account:Account; 10 | 11 | public var accountId:int; 12 | 13 | [HasMany(inverse="customer", recordType="mesh.Order")] 14 | public var orders:HasManyAssociation; 15 | 16 | public function Customer(properties:Object=null) 17 | { 18 | super(properties); 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /src/mesh/core/inflection/humanize.as: -------------------------------------------------------------------------------- 1 | package mesh.core.inflection 2 | { 3 | import mesh.core.string.sentenceize; 4 | 5 | /** 6 | * First converts the given string to an underscored string, then capitalizes the first 7 | * word and turns underscores into spaces. 8 | * 9 | * @param str The string to humanize. 10 | * @return A humanized string. 11 | */ 12 | public function humanize(str:String):String 13 | { 14 | return sentenceize(underscore(str).replace(/_/g, " ")); 15 | } 16 | } -------------------------------------------------------------------------------- /src/mesh/core/inflection/underscore.as: -------------------------------------------------------------------------------- 1 | package mesh.core.inflection 2 | { 3 | /** 4 | * Converts a camelized string, or string with whitespaces into a lower cased 5 | * string with underscores. 6 | * 7 | * @param str The string to convert. 8 | * @return An underscored string. 9 | */ 10 | public function underscore(str:String):String 11 | { 12 | return camelize(str.replace(/-/g, "_")).replace(/([A-Z]+)([A-Z][a-z])/g, "$1_$2").replace(/([a-z\d])([A-Z])/g, "$1_$2").toLowerCase(); 13 | } 14 | } -------------------------------------------------------------------------------- /src/mesh/core/reflection/clazz.as: -------------------------------------------------------------------------------- 1 | package mesh.core.reflection 2 | { 3 | import flash.utils.getDefinitionByName; 4 | import flash.utils.getQualifiedClassName; 5 | 6 | /** 7 | * Returns the class for the given object. The object can either be an instance of 8 | * a class or a class reference itself. 9 | * 10 | * @param obj The object to get the class for. 11 | * @return The class reference for the object. 12 | */ 13 | public function clazz(obj:Object):Class 14 | { 15 | return getDefinitionByName(getQualifiedClassName(obj)) as Class; 16 | } 17 | } -------------------------------------------------------------------------------- /tests/mesh/Name.as: -------------------------------------------------------------------------------- 1 | package mesh 2 | { 3 | public class Name 4 | { 5 | public function Name(first:String, last:String) 6 | { 7 | _first = first; 8 | _last = last; 9 | } 10 | 11 | public function equals(name:Name):Boolean 12 | { 13 | return first == name.first && last == name.last; 14 | } 15 | 16 | private var _first:String; 17 | public function get first():String 18 | { 19 | return _first; 20 | } 21 | 22 | private var _last:String; 23 | public function get last():String 24 | { 25 | return _last; 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /src/mesh/operations/EmptyOperation.as: -------------------------------------------------------------------------------- 1 | package mesh.operations 2 | { 3 | /** 4 | * An operation that when executed will finish immediately. This operation is useful 5 | * as a null object. 6 | * 7 | * @author Dan Schultz 8 | */ 9 | public class EmptyOperation extends Operation 10 | { 11 | /** 12 | * Constructor. 13 | */ 14 | public function EmptyOperation() 15 | { 16 | super(); 17 | } 18 | 19 | /** 20 | * @inheritDoc 21 | */ 22 | override protected function executeRequest():void 23 | { 24 | finish(true); 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /src/mesh/core/range/RangeBuilder.as: -------------------------------------------------------------------------------- 1 | package mesh.core.range 2 | { 3 | import mesh.core.reflection.newInstance; 4 | 5 | public class RangeBuilder 6 | { 7 | private var _clazz:Class; 8 | private var _from:*; 9 | 10 | public function RangeBuilder(clazz:Class, from:*) 11 | { 12 | _clazz = clazz; 13 | _from = from; 14 | } 15 | 16 | public function to(value:*):Range 17 | { 18 | return newInstance(_clazz, _from, value, false); 19 | } 20 | 21 | public function toButNotIncluding(value:*):Range 22 | { 23 | return newInstance(_clazz, _from, value, true); 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /src/mesh/core/reflection/className.as: -------------------------------------------------------------------------------- 1 | package mesh.core.reflection 2 | { 3 | import flash.utils.getQualifiedClassName; 4 | 5 | /** 6 | * Returns the name of the class for the given object. The returned name does not 7 | * include the package or namespace that the class resides in. The object can either 8 | * be an instance of a class, or the class itself. 9 | * 10 | * @param obj The object to get the class name for. 11 | * @return The name of the class. 12 | */ 13 | public function className(obj:Object):String 14 | { 15 | return getQualifiedClassName(obj).split("::").pop(); 16 | } 17 | } -------------------------------------------------------------------------------- /tests/mesh/AsyncTest.as: -------------------------------------------------------------------------------- 1 | package mesh 2 | { 3 | import flash.events.Event; 4 | import flash.events.EventDispatcher; 5 | 6 | import org.flexunit.async.Async; 7 | 8 | public class AsyncTest extends EventDispatcher 9 | { 10 | public function AsyncTest(testCase:Object, timeout:int, handler:Function) 11 | { 12 | var wrapper:Function = function(event:Event, data:Object = null):void 13 | { 14 | handler(); 15 | }; 16 | addEventListener(Event.COMPLETE, Async.asyncHandler(testCase, wrapper, timeout)); 17 | } 18 | 19 | public function complete():void 20 | { 21 | dispatchEvent( new Event(Event.COMPLETE) ); 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /tests/mesh/operations/MockOperation.as: -------------------------------------------------------------------------------- 1 | package mesh.operations 2 | { 3 | 4 | public class MockOperation extends Operation 5 | { 6 | public function MockOperation() 7 | { 8 | 9 | } 10 | 11 | public function mimicFault(summary:String, detail:String):void 12 | { 13 | fault(summary, detail); 14 | } 15 | 16 | private var _resultThrowsRTE:Boolean; 17 | public function mimicResult(data:Object, throwsRTE:Boolean):void 18 | { 19 | _resultThrowsRTE = throwsRTE; 20 | result(data); 21 | } 22 | 23 | override protected function parseResult(data:Object):Object 24 | { 25 | if (_resultThrowsRTE) { 26 | throw new Error(); 27 | } 28 | return data; 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /src/mesh/core/BatchedListDelegate.as: -------------------------------------------------------------------------------- 1 | package mesh.core 2 | { 3 | /** 4 | * A base delegate class to make implementation simpler. 5 | * 6 | * @see IBatchedListDelegate 7 | * 8 | * @author Dan Schultz 9 | */ 10 | public class BatchedListDelegate implements IBatchedListDelegate 11 | { 12 | /** 13 | * Constructor. 14 | */ 15 | public function BatchedListDelegate() 16 | { 17 | 18 | } 19 | 20 | /** 21 | * @inheritDoc 22 | */ 23 | public function requestLength(list:BatchedList):void 24 | { 25 | 26 | } 27 | 28 | /** 29 | * @inheritDoc 30 | */ 31 | public function requestBatch(list:BatchedList, index:uint, batchSize:uint):void 32 | { 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /src/mesh/core/inflection/camelize.as: -------------------------------------------------------------------------------- 1 | package mesh.core.inflection 2 | { 3 | /** 4 | * Returns a camelized version of a string, where the underscores and whitespaces 5 | * are removed. 6 | * 7 | * @param str The string to camelize. 8 | * @param uppercaseFirstLetter true if the first letter of the result 9 | * is uppercased. 10 | * @return A camelized string. 11 | */ 12 | public function camelize(str:String, uppercaseFirstLetter:Boolean = true):String 13 | { 14 | str = str.replace(/(?:_|\s)+(.)/g, function():String 15 | { 16 | return arguments[1].toUpperCase(); 17 | }); 18 | return (uppercaseFirstLetter ? str.substr(0, 1).toUpperCase() : str.substr(0, 1).toLowerCase()) + str.substr(1); 19 | } 20 | } -------------------------------------------------------------------------------- /tests/mesh/core/number/FractionTests.as: -------------------------------------------------------------------------------- 1 | package mesh.core.number 2 | { 3 | import mesh.core.object.inspect; 4 | 5 | import org.flexunit.assertThat; 6 | import org.hamcrest.object.equalTo; 7 | 8 | public class FractionTests 9 | { 10 | [Test] 11 | public function testGCD():void 12 | { 13 | var tests:Array = [ 14 | {a:1, b:2, expected:1}, 15 | {a:0, b:3, expected:3}, 16 | {a:3, b:0, expected:3}, 17 | {a:108, b:30, expected:6}, 18 | {a:-108, b:30, expected:6}, 19 | {a:108, b:-30, expected:6}, 20 | {a:-108, b:-30, expected:6} 21 | ]; 22 | 23 | for each (var test:Object in tests) { 24 | assertThat("test failed: " + inspect(test), Fraction.gcd(test.a, test.b), equalTo(test.expected)); 25 | } 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /tests/mesh/Person.as: -------------------------------------------------------------------------------- 1 | package mesh 2 | { 3 | import mesh.model.Record; 4 | import mesh.model.validators.NumericValidator; 5 | import mesh.model.validators.PresenceValidator; 6 | 7 | public class Person extends Record 8 | { 9 | public static var validate:Object = 10 | { 11 | name: [{validator:PresenceValidator}], 12 | age: [{validator:NumericValidator, greaterThan:0, integer:true}] 13 | }; 14 | 15 | [Bindable] public var age:Number; 16 | [Bindable] public var firstName:String; 17 | [Bindable] public var lastName:String; 18 | [Bindable] public var name:Name; 19 | 20 | public function Person(properties:Object = null) 21 | { 22 | super(properties); 23 | aggregate("name", Name, ["first:firstName", "last:lastName"]); 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /src/mesh/model/store/ICommitResponder.as: -------------------------------------------------------------------------------- 1 | package mesh.model.store 2 | { 3 | /** 4 | * The ICommitResponder interface is an interface that classes can implement to 5 | * handle callbacks from a commit. 6 | * 7 | * @author Dan Schultz 8 | */ 9 | public interface ICommitResponder 10 | { 11 | /** 12 | * The commit calls this method when it has failed to persist its records. 13 | * 14 | * @param summary The summary of the error. 15 | * @param detail A detailed message of the error. 16 | * @param code An error code. 17 | */ 18 | function failed(summary:String, detail:String = "", code:String = ""):void; 19 | 20 | /** 21 | * The commit call this method when persistence of all its records are successful. 22 | */ 23 | function success():void; 24 | } 25 | } -------------------------------------------------------------------------------- /src/mesh/model/validators/FormatValidator.as: -------------------------------------------------------------------------------- 1 | package mesh.model.validators 2 | { 3 | /** 4 | * A validator that tests a value against a regular expression. 5 | * 6 | * @author Dan Schultz 7 | */ 8 | public class FormatValidator extends EachValidator 9 | { 10 | /** 11 | * @copy Validator#Validator() 12 | */ 13 | public function FormatValidator(options:Object) 14 | { 15 | super(options); 16 | 17 | if (!options.hasOwnProperty("message")) { 18 | options.message = "is invalid"; 19 | } 20 | } 21 | 22 | /** 23 | * @inheritDoc 24 | */ 25 | override protected function validateProperty(obj:Object, property:String, value:Object):void 26 | { 27 | if (!options.format.test(value.toString())) { 28 | obj.errors.add(property, options.message); 29 | } 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /src/mesh/model/source/AssociationCollectionSnapshot.as: -------------------------------------------------------------------------------- 1 | package mesh.model.source 2 | { 3 | import mesh.core.List; 4 | 5 | public class AssociationCollectionSnapshot extends List 6 | { 7 | public function AssociationCollectionSnapshot(source:Array, added:Array, removed:Array) 8 | { 9 | super(source); 10 | _added = added; 11 | _removed = removed; 12 | } 13 | 14 | private var _added:Array; 15 | /** 16 | * The records that have been added to the association. 17 | */ 18 | public function get added():Array 19 | { 20 | return _added.concat(); 21 | } 22 | 23 | private var _removed:Array; 24 | /** 25 | * The records that have been removed from the association. 26 | */ 27 | public function get removed():Array 28 | { 29 | return _removed.concat(); 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /src/mesh/model/ID.as: -------------------------------------------------------------------------------- 1 | package mesh.model 2 | { 3 | /** 4 | * A class for representing object ID's. 5 | * 6 | * @author Dan Schultz 7 | */ 8 | public class ID 9 | { 10 | /** 11 | * Checks if an ID on an object is populated. An ID is populated if the value is not null, and 12 | * one of these cases are met: 13 | * 14 | * 18 | * 19 | * @param obj The object to check. 20 | * @param idField The field to check. 21 | * @return true if the ID is populated. 22 | */ 23 | public static function isPopulated(obj:Object, idField:String = "id"):Boolean 24 | { 25 | return obj != null && obj[idField] != null && (obj[idField] != 0 || obj[idField] != ""); 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /src/mesh/core/range/IntRange.as: -------------------------------------------------------------------------------- 1 | package mesh.core.range 2 | { 3 | /** 4 | * A range for integers. 5 | * 6 | * @see Range#from() 7 | * @author Dan Schultz 8 | */ 9 | public class IntRange extends Range 10 | { 11 | /** 12 | * @copy Range#Range() 13 | */ 14 | public function IntRange(from:*, to:*, exclusive:Boolean = false) 15 | { 16 | super(from, to, exclusive); 17 | } 18 | 19 | /** 20 | * @inheritDoc 21 | */ 22 | override protected function decrease(value:*, size:int):* 23 | { 24 | return value - size; 25 | } 26 | 27 | /** 28 | * @inheritDoc 29 | */ 30 | override protected function increase(value:*, size:int):* 31 | { 32 | return value + size; 33 | } 34 | 35 | /** 36 | * @inheritDoc 37 | */ 38 | override public function get length():int 39 | { 40 | return max - min + (!isExclusive ? 1 : 0); 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /src/mesh/operations/ParallelOperation.as: -------------------------------------------------------------------------------- 1 | package mesh.operations 2 | { 3 | /** 4 | * A type of compound operation that executes all of its operations at the same time. 5 | * 6 | * @author Dan Schultz 7 | */ 8 | public class ParallelOperation extends CompoundOperation 9 | { 10 | /** 11 | * @copy operations.CompoundOperation#CompoundOperation() 12 | */ 13 | public function ParallelOperation(operations:Array = null) 14 | { 15 | super(operations); 16 | } 17 | 18 | /** 19 | * @inheritDoc 20 | */ 21 | override public function during(operation:Operation):Operation 22 | { 23 | add(operation); 24 | return this; 25 | } 26 | 27 | /** 28 | * @inheritDoc 29 | */ 30 | override protected function startExecution():void 31 | { 32 | for each (var operation:Operation in operationSet) { 33 | executeOperation(operation); 34 | } 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /tests/mesh/core/array/FlattenTests.as: -------------------------------------------------------------------------------- 1 | package mesh.core.array 2 | { 3 | import org.flexunit.assertThat; 4 | import org.hamcrest.collection.array; 5 | import org.hamcrest.object.equalTo; 6 | 7 | public class FlattenTests 8 | { 9 | [Test] 10 | public function testFlatten():void 11 | { 12 | var tests:Array = [ 13 | {elements:[1, 2, 3], expected:[1, 2, 3]}, 14 | {elements:[1, 2, 3, [4, 5]], expected:[1, 2, 3, 4, 5]}, 15 | {elements:[1, 2, [3, [4, 5]]], expected:[1, 2, 3, 4, 5]}, 16 | {elements:[1, 2, [3, [4, 5]]], depth:1, expected:[1, 2, 3, [4, 5]]}, 17 | {elements:[[1, 2], [3, 4]], expected:[1, 2, 3, 4]}, 18 | {elements:1, expected:[1]} 19 | ]; 20 | 21 | for each (var test:Object in tests) { 22 | assertThat("test failed with elements: " + test.elements, test.depth != null ? flatten(test.elements, test.depth) : flatten(test.elements), array.apply(null, test.expected)); 23 | } 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /src/mesh/model/validators/ExclusionValidator.as: -------------------------------------------------------------------------------- 1 | package mesh.model.validators 2 | { 3 | import collections.HashSet; 4 | 5 | /** 6 | * A helper that validates that a property's value does not belong to a given set of values. 7 | * This set can be an array or any enumerable object. 8 | * 9 | * @author Dan Schultz 10 | */ 11 | public class ExclusionValidator extends EachValidator 12 | { 13 | /** 14 | * @copy Validator#Validator() 15 | */ 16 | public function ExclusionValidator(options:Object) 17 | { 18 | if (!options.hasOwnProperty("message")) { 19 | options.message = "is reserved"; 20 | } 21 | 22 | super(options); 23 | } 24 | 25 | /** 26 | * @inheritDoc 27 | */ 28 | override protected function validateProperty(obj:Object, property:String, value:Object):void 29 | { 30 | if (new HashSet(options.within).contains(value)) { 31 | obj.errors.add(property, options.message); 32 | } 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /tests/mesh/view/helpers/text/PluralizeByCountTests.as: -------------------------------------------------------------------------------- 1 | package mesh.view.helpers.text 2 | { 3 | import org.flexunit.assertThat; 4 | import org.hamcrest.object.equalTo; 5 | 6 | public class PluralizeByCountTests 7 | { 8 | private var _tests:Array; 9 | 10 | [Before] 11 | public function setup():void 12 | { 13 | _tests = [ 14 | {args:[-1, "person"], expected:"-1 people"}, 15 | {args:[0, "person"], expected:"0 people"}, 16 | {args:[1, "person"], expected:"1 person"}, 17 | {args:[2, "person"], expected:"2 people"}, 18 | {args:[1, "cake", "desserts"], expected:"1 cake"}, 19 | {args:[2, "cake", "desserts"], expected:"2 desserts"} 20 | ]; 21 | } 22 | 23 | [Test] 24 | public function testPluralize():void 25 | { 26 | for each (var test:Object in _tests) { 27 | assertThat("test failed for args '" + test.args + "'", pluralizeByCount.apply(null, test.args), equalTo(test.expected)); 28 | } 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /tests/mesh/model/validators/FormatValidatorTests.as: -------------------------------------------------------------------------------- 1 | package mesh.model.validators 2 | { 3 | 4 | import org.flexunit.assertThat; 5 | import org.hamcrest.object.equalTo; 6 | 7 | import mesh.core.object.inspect; 8 | 9 | public class FormatValidatorTests 10 | { 11 | [Test] 12 | public function testValidate():void 13 | { 14 | var tests:Array = [ 15 | { 16 | object:{str:"Hello123", errors:new Errors(null)}, 17 | options:{property:"str", format:/\A[a-zA-Z]+\z/}, 18 | passes:false 19 | }, 20 | { 21 | object:{str:"Hello", errors:new Errors(null)}, 22 | options:{property:"str", format:/\A[a-zA-Z]+\z/}, 23 | passes:true 24 | } 25 | ]; 26 | 27 | for each (var test:Object in tests) { 28 | new FormatValidator(test.options).validate(test.object); 29 | assertThat("validation failed for test " + inspect(test.options), test.object.errors.length == 0, equalTo(test.passes)); 30 | } 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /src/mesh/model/validators/InclusionValidator.as: -------------------------------------------------------------------------------- 1 | package mesh.model.validators 2 | { 3 | import collections.HashSet; 4 | 5 | /** 6 | * A helper that validates that a property's value belongs to a given set of values. This 7 | * set can be an array or any enumerable object. 8 | * 9 | * @author Dan Schultz 10 | */ 11 | public class InclusionValidator extends EachValidator 12 | { 13 | /** 14 | * @copy Validator#Validator() 15 | */ 16 | public function InclusionValidator(options:Object) 17 | { 18 | if (!options.hasOwnProperty("message")) { 19 | options.message = "is not included in the list"; 20 | } 21 | 22 | super(options); 23 | } 24 | 25 | /** 26 | * @inheritDoc 27 | */ 28 | override protected function validateProperty(obj:Object, property:String, value:Object):void 29 | { 30 | if (!new HashSet(options.within).contains(value)) { 31 | obj.errors.add(property, options.message); 32 | } 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /src/mesh/core/reflection/Metadata.as: -------------------------------------------------------------------------------- 1 | package mesh.core.reflection 2 | { 3 | /** 4 | * A class that represents the metadata that has been defined on a class, variable, or 5 | * method. 6 | * 7 | * @author Dan Schultz 8 | */ 9 | public class Metadata extends Definition 10 | { 11 | /** 12 | * @copy Definition#Definition() 13 | */ 14 | public function Metadata(description:XML, belongsTo:Definition) 15 | { 16 | super(description, belongsTo); 17 | } 18 | 19 | private var _arguments:Object; 20 | /** 21 | * Returns an arguments hash that represents the key-value pairs that are 22 | * defined for this metadata. 23 | */ 24 | public function get arguments():Object 25 | { 26 | if (_arguments == null) { 27 | _arguments = {}; 28 | 29 | for each (var argXML:XML in description..arg) { 30 | _arguments[argXML.@key.toString()] = argXML.@value.toString(); 31 | } 32 | } 33 | return _arguments; 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /tests/mesh/model/AggregateTests.as: -------------------------------------------------------------------------------- 1 | package mesh.model 2 | { 3 | import mesh.Name; 4 | 5 | import org.flexunit.assertThat; 6 | import org.hamcrest.object.equalTo; 7 | 8 | public class AggregateTests 9 | { 10 | private var _record:AggregateTestMockRecord; 11 | 12 | [Before] 13 | public function setup():void 14 | { 15 | _record = new AggregateTestMockRecord(); 16 | } 17 | 18 | [Test] 19 | public function testPropertyChangeUpdatesAggregate():void 20 | { 21 | _record.firstName = "Thom"; 22 | _record.last = "Yorke"; 23 | assertThat(_record.name.first, equalTo(_record.firstName)); 24 | assertThat(_record.name.last, equalTo(_record.last)); 25 | } 26 | 27 | [Test] 28 | public function testAggregateChangeUpdatesProperties():void 29 | { 30 | _record.name = new Name("Thom", "Yorke"); 31 | assertThat(_record.firstName, equalTo(_record.name.first)); 32 | assertThat(_record.last, equalTo(_record.name.last)); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/mesh/model/ILoadable.as: -------------------------------------------------------------------------------- 1 | package mesh.model 2 | { 3 | /** 4 | * The ILoadable interface defines an interface for classes where their 5 | * data is loaded externally. 6 | * 7 | * @author Dan Schultz 8 | */ 9 | public interface ILoadable 10 | { 11 | /** 12 | * Loads the data for this object, if it has not been loaded yet. If the data has 13 | * already been loaded, then it will not be reloaded. Use refresh() 14 | * to reload the data. 15 | * 16 | * @return This instance. 17 | */ 18 | function load():*; 19 | 20 | /** 21 | * Forces a reload of the data for this object. Unlike load(), this 22 | * method will load the data for this object even if it's already been retrieved. 23 | * 24 | * @see #load() 25 | * @return This instance. 26 | */ 27 | function refresh():*; 28 | 29 | /** 30 | * Checks if the data has been loaded. 31 | */ 32 | function get isLoaded():Boolean; 33 | } 34 | } -------------------------------------------------------------------------------- /src/mesh/operations/ProgressOperationEvent.as: -------------------------------------------------------------------------------- 1 | package mesh.operations 2 | { 3 | import flash.events.ProgressEvent; 4 | 5 | /** 6 | * An event that is dispatched by an operation to indicate its progress. 7 | * 8 | * @author Dan Schultz 9 | */ 10 | public class ProgressOperationEvent extends OperationEvent 11 | { 12 | /** 13 | * An event type for when an operation has progressed. 14 | */ 15 | public static const PROGRESS:String = "progress"; 16 | 17 | /** 18 | * @copy OperationEvent#OperationEvent() 19 | */ 20 | public function ProgressOperationEvent(type:String) 21 | { 22 | super(type); 23 | } 24 | 25 | /** 26 | * @copy Operation#unitsComplete 27 | */ 28 | public function get unitsComplete():Number 29 | { 30 | return operation.progress.complete; 31 | } 32 | 33 | /** 34 | * @copy Operation#unitsTotal 35 | */ 36 | public function get unitsTotal():Number 37 | { 38 | return operation.progress.total; 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /src/mesh/core/range/CharRange.as: -------------------------------------------------------------------------------- 1 | package mesh.core.range 2 | { 3 | /** 4 | * A range for single character strings. 5 | * 6 | * @see Range#from() 7 | * @author Dan Schultz 8 | */ 9 | public class CharRange extends Range 10 | { 11 | /** 12 | * @copy Range#Range() 13 | */ 14 | public function CharRange(from:*, to:*, exclusive:Boolean=false) 15 | { 16 | super(from, to, exclusive); 17 | } 18 | 19 | /** 20 | * @inheritDoc 21 | */ 22 | override protected function decrease(value:*, size:int):* 23 | { 24 | return String.fromCharCode(value.charCodeAt(0)-size); 25 | } 26 | 27 | /** 28 | * @inheritDoc 29 | */ 30 | override protected function increase(value:*, size:int):* 31 | { 32 | return String.fromCharCode(value.charCodeAt(0)+size); 33 | } 34 | 35 | /** 36 | * @inheritDoc 37 | */ 38 | override public function get length():int 39 | { 40 | return max.charCodeAt(0) - min.charCodeAt(0) + (!isExclusive ? 1 : 0); 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /src/mesh/model/source/IPersistenceResponder.as: -------------------------------------------------------------------------------- 1 | package mesh.model.source 2 | { 3 | import mesh.model.Record; 4 | 5 | /** 6 | * The IPersistenceResponder interface is an interface that classes can 7 | * implement to handle persistence callbacks from a data source. 8 | * 9 | * @author Dan Schultz 10 | */ 11 | public interface IPersistenceResponder 12 | { 13 | /** 14 | * The data source calls this method when it has successfully persisted a record. 15 | * 16 | * @param snapshot The snapshot that was persisted. 17 | * @param id An ID to set on the record. 18 | */ 19 | function saved(snapshot:Snapshot, id:Object = null):void; 20 | 21 | /** 22 | * The data source calls this method when data retrieval has failed. 23 | * 24 | * @param summary The summary of the error. 25 | * @param detail A detailed message of the error. 26 | * @param code An error code. 27 | */ 28 | function failed(summary:String, detail:String = "", code:String = ""):void; 29 | } 30 | } -------------------------------------------------------------------------------- /src/mesh/view/helpers/number/withDelimiter.as: -------------------------------------------------------------------------------- 1 | package mesh.view.helpers.number 2 | { 3 | import mesh.core.object.merge; 4 | 5 | /** 6 | * Formats a number with a delimiter (123,000) and a separator (123,000.1). 7 | * 8 | *

9 | * Options: 10 | *

15 | *

16 | * 17 | * @param number The number to format. 18 | * @param options The options to use. 19 | * @return A delimited number as a string. 20 | */ 21 | public function withDelimiter(number:Number, options:Object = null):String 22 | { 23 | options = merge({delimiter:",", separator:"."}, options); 24 | 25 | var parts:Array = number.toString().split("."); 26 | parts[0] = parts[0].replace(/(\d)(?=(\d\d\d)+(?!\d))/g, "$&" + options.delimiter); 27 | return parts.join(options.separator); 28 | } 29 | } -------------------------------------------------------------------------------- /src/mesh/core/inflection/ordinalize.as: -------------------------------------------------------------------------------- 1 | package mesh.core.inflection 2 | { 3 | /** 4 | * Turns a number into an ordinal string used to symbolize the position in an 5 | * ordered sequence, such as 1st, 2nd, 3rd. 6 | * 7 | * 8 | * trace( ordinalize(1) ); // 1st 9 | * trace( ordinalize(2) ); // 2nd 10 | * trace( ordinalize(11) ); // 11th 11 | * trace( ordinalize(1002) ); // 1002nd 12 | * trace( ordinalize(1003) ); // 1003rd 13 | * 14 | * 15 | * @param number The number to ordinalize. 16 | * @return The ordinalized string. 17 | */ 18 | public function ordinalize(number:Number):String 19 | { 20 | if ([11, 12, 13].indexOf(int( number ) % 100) != -1) { 21 | return number.toString() + "th"; 22 | } 23 | 24 | switch (int( number ) % 10) { 25 | case 1: 26 | return number.toString() + "st"; 27 | case 2: 28 | return number.toString() + "nd"; 29 | case 3: 30 | return number.toString() + "rd"; 31 | } 32 | 33 | return number.toString() + "th"; 34 | } 35 | } -------------------------------------------------------------------------------- /src/mesh/model/validators/PresenceValidator.as: -------------------------------------------------------------------------------- 1 | package mesh.model.validators 2 | { 3 | import mesh.core.object.isEmpty; 4 | 5 | /** 6 | * The presence validator ensures that a property is populated, that a string is not empty 7 | * or only contains whitespace, and that a number is not NaN. If the property being validated 8 | * contains an isEmpty property or method, that result will be used for evaluation. 9 | * 10 | * @author Dan Schultz 11 | */ 12 | public class PresenceValidator extends EachValidator 13 | { 14 | /** 15 | * @copy Validator#Validator() 16 | */ 17 | public function PresenceValidator(options:Object) 18 | { 19 | if (!options.hasOwnProperty("message")) { 20 | options.message = "can't be empty"; 21 | } 22 | 23 | super(options); 24 | } 25 | 26 | /** 27 | * @inheritDoc 28 | */ 29 | override protected function validateProperty(obj:Object, property:String, value:Object):void 30 | { 31 | if (isEmpty(value)) { 32 | obj.errors.add(property, options.message); 33 | } 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /tests/mesh/core/inflection/HumanizeTests.as: -------------------------------------------------------------------------------- 1 | package mesh.core.inflection 2 | { 3 | import org.flexunit.assertThat; 4 | import org.hamcrest.object.equalTo; 5 | 6 | public class HumanizeTests 7 | { 8 | private var _tests:Array = []; 9 | 10 | [Before] 11 | public function setup():void 12 | { 13 | _tests.push({input:"the quick brown fox", expected:"The quick brown fox"}); 14 | _tests.push({input:"the quIck broWn foX", expected:"The qu ick bro wn fo x"}); 15 | _tests.push({input:"the_quick_brown_fox", expected:"The quick brown fox"}); 16 | _tests.push({input:"the_ quick_ brown_ fox", expected:"The quick brown fox"}); 17 | _tests.push({input:"the _ quick _ brown _ fox", expected:"The quick brown fox"}); 18 | _tests.push({input:"the quick brown fox", expected:"The quick brown fox"}); 19 | } 20 | 21 | [Test] 22 | public function testHumanize():void 23 | { 24 | for each (var test:Object in _tests) { 25 | assertThat("humanize failed for '" + test.input + "'", humanize(test.input), equalTo(test.expected)); 26 | } 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /src/mesh/model/source/IRetrievalResponder.as: -------------------------------------------------------------------------------- 1 | package mesh.model.source 2 | { 3 | import mesh.model.store.Data; 4 | 5 | /** 6 | * The IRetrievalResponder interface is an interface that classes can 7 | * implement to handle retrieval callbacks from a data source. 8 | * 9 | * @author Dan Schultz 10 | */ 11 | public interface IRetrievalResponder 12 | { 13 | /** 14 | * The data source calls this method when a record's data has been loaded. 15 | * 16 | * @param data The loaded data. 17 | */ 18 | function loaded(data:Data):void; 19 | 20 | /** 21 | * The data source calls this method when it has finished retrieving the data for 22 | * the request. 23 | */ 24 | function finished():void; 25 | 26 | /** 27 | * The data source calls this method when data retrieval has failed. 28 | * 29 | * @param summary The summary of the error. 30 | * @param detail A detailed message of the error. 31 | * @param code An error code. 32 | */ 33 | function failed(summary:String, detail:String = "", code:String = ""):void; 34 | } 35 | } -------------------------------------------------------------------------------- /tests/mesh/core/string/CapitalizeTests.as: -------------------------------------------------------------------------------- 1 | package mesh.core.string 2 | { 3 | import org.flexunit.assertThat; 4 | import org.hamcrest.object.equalTo; 5 | 6 | public class CapitalizeTests 7 | { 8 | private var _tests:Array = []; 9 | 10 | [Before] 11 | public function setup():void 12 | { 13 | _tests.push({input:"the quick brown fox", expected:"The Quick Brown Fox"}); 14 | _tests.push({input:"the quIck broWn foX", expected:"The QuIck BroWn FoX"}); 15 | _tests.push({input:"the_quick_brown_fox", expected:"The_quick_brown_fox"}); 16 | _tests.push({input:"the_ quick_ brown_ fox", expected:"The_ Quick_ Brown_ Fox"}); 17 | _tests.push({input:"the _ quick _ brown _ fox", expected:"The _ Quick _ Brown _ Fox"}); 18 | _tests.push({input:"the quick brown fox", expected:"The Quick Brown Fox"}); 19 | } 20 | 21 | [Test] 22 | public function testCapitalize():void 23 | { 24 | for each (var test:Object in _tests) { 25 | assertThat("capitalize failed for '" + test.input + "'", capitalize(test.input), equalTo(test.expected)); 26 | } 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /tests/mesh/model/ValidationTests.as: -------------------------------------------------------------------------------- 1 | package mesh.model 2 | { 3 | import mesh.Name; 4 | import mesh.Person; 5 | 6 | import org.flexunit.assertThat; 7 | import org.hamcrest.collection.arrayWithSize; 8 | import org.hamcrest.collection.emptyArray; 9 | import org.hamcrest.object.equalTo; 10 | 11 | public class ValidationTests 12 | { 13 | [Test] 14 | public function testValidatePasses():void 15 | { 16 | var customer:Person = new Person(); 17 | customer.name = new Name("John", "Doe"); 18 | customer.age = 10; 19 | 20 | assertThat(customer.isValid(), equalTo(true)); 21 | assertThat(customer.isInvalid(), equalTo(false)); 22 | assertThat(customer.errors.toArray(), emptyArray()); 23 | } 24 | 25 | [Test] 26 | public function testValidateFails():void 27 | { 28 | var customer:Person = new Person(); 29 | customer.name = null; 30 | customer.age = 0; 31 | 32 | assertThat(customer.isValid(), equalTo(false)); 33 | assertThat(customer.isInvalid(), equalTo(true)); 34 | assertThat(customer.errors.toArray(), arrayWithSize(2)); 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /tests/mesh/core/inflection/SingularizeTests.as: -------------------------------------------------------------------------------- 1 | package mesh.core.inflection 2 | { 3 | import org.flexunit.assertThat; 4 | import org.hamcrest.object.equalTo; 5 | 6 | public class SingularizeTests 7 | { 8 | private var _tests:Array; 9 | 10 | [Before] 11 | public function setup():void 12 | { 13 | _tests = [ 14 | // regulars 15 | {word:"dresses", expected:"dress"}, 16 | {word:"horns", expected:"horn"}, 17 | {word:"people", expected:"person"}, 18 | {word:"oranges", expected:"orange"}, 19 | 20 | // irregulars 21 | {word:"mice", expected:"mouse"}, 22 | 23 | // uncountables 24 | {word:"moose", expected:"moose"}, 25 | {word:"money", expected:"money"}, 26 | {word:"deer", expected:"deer"}, 27 | 28 | // already singular 29 | {word:"address", expected:"address"} 30 | ]; 31 | } 32 | 33 | [Test] 34 | public function testSingularize():void 35 | { 36 | for each (var test:Object in _tests) { 37 | assertThat("test failed for " + test.word, singularize(test.word), equalTo(test.expected)); 38 | } 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /tests/mesh/view/helpers/number/WithDelimiterTests.as: -------------------------------------------------------------------------------- 1 | package mesh.view.helpers.number 2 | { 3 | import mesh.core.object.inspect; 4 | 5 | import org.flexunit.assertThat; 6 | import org.hamcrest.object.equalTo; 7 | 8 | public class WithDelimiterTests 9 | { 10 | private var _tests:Array; 11 | 12 | [Before] 13 | public function setup():void 14 | { 15 | _tests = [ 16 | {number:12345678, options:null, expected:"12,345,678"}, 17 | {number:12345678.05, options:null, expected:"12,345,678.05"}, 18 | {number:12345678, options:{delimiter:"."}, expected:"12.345.678"}, 19 | {number:12345678, options:{separator:","}, expected:"12,345,678"}, 20 | {number:12345678.05, options:{delimiter:" ", separator:","}, expected:"12 345 678,05"} 21 | ]; 22 | } 23 | 24 | [Test] 25 | public function testNumberWithDelimiter():void 26 | { 27 | for each (var test:Object in _tests) { 28 | assertThat("test failed for number," + test.number + ", with options: " + inspect(test.options), withDelimiter(test.number, test.options), equalTo(test.expected)); 29 | } 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /src/mesh/core/range/DateRange.as: -------------------------------------------------------------------------------- 1 | package mesh.core.range 2 | { 3 | /** 4 | * A range for dates. 5 | * 6 | * @see Range#from() 7 | * @author Dan Schultz 8 | */ 9 | public class DateRange extends Range 10 | { 11 | /** 12 | * @copy Range#Range() 13 | */ 14 | public function DateRange(from:*, to:*, exclusive:Boolean=false) 15 | { 16 | super(from, to, exclusive); 17 | } 18 | 19 | /** 20 | * @inheritDoc 21 | */ 22 | override protected function decrease(value:*, size:int):* 23 | { 24 | var newValue:Date = new Date(value.getTime()); 25 | newValue.setDate(newValue.date - size); 26 | return newValue; 27 | } 28 | 29 | /** 30 | * @inheritDoc 31 | */ 32 | override protected function increase(value:*, size:int):* 33 | { 34 | var newValue:Date = new Date(value.getTime()); 35 | newValue.setDate(newValue.date + size); 36 | return newValue; 37 | } 38 | 39 | /** 40 | * @inheritDoc 41 | */ 42 | override public function get length():int 43 | { 44 | return ((max.time - min.time) / 86400000) + (!isExclusive ? 1 : 0); 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /tests/mesh/core/inflection/PluralizeTests.as: -------------------------------------------------------------------------------- 1 | package mesh.core.inflection 2 | { 3 | import org.flexunit.assertThat; 4 | import org.hamcrest.object.equalTo; 5 | 6 | public class PluralizeTests 7 | { 8 | private var _tests:Array; 9 | 10 | [Before] 11 | public function setup():void 12 | { 13 | _tests = [ 14 | // regulars 15 | {word:"dress", expected:"dresses"}, 16 | {word:"horn", expected:"horns"}, 17 | {word:"person", expected:"people"}, 18 | {word:"orange", expected:"oranges"}, 19 | 20 | // irregulars 21 | {word:"mouse", expected:"mice"}, 22 | 23 | // uncountables 24 | {word:"moose", expected:"moose"}, 25 | {word:"money", expected:"money"}, 26 | {word:"deer", expected:"deer"}, 27 | 28 | // already plural 29 | {word:"cats", expected:"cats"}, 30 | {word:"addresses", expected:"addresses"} 31 | ]; 32 | } 33 | 34 | [Test] 35 | public function testPluralize():void 36 | { 37 | for each (var test:Object in _tests) { 38 | assertThat("test failed for " + test.word, pluralize(test.word), equalTo(test.expected)); 39 | } 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /src/mesh/operations/ResultOperationEvent.as: -------------------------------------------------------------------------------- 1 | package mesh.operations 2 | { 3 | import flash.events.Event; 4 | 5 | /** 6 | * An event dispatched by an operation to indicate that a result was received 7 | * during its execution. 8 | * 9 | * @author Dan Schultz 10 | */ 11 | public class ResultOperationEvent extends OperationEvent 12 | { 13 | /** 14 | * An event type for when an operation has received a result during execution. 15 | */ 16 | public static const RESULT:String = "result"; 17 | 18 | /** 19 | * Constructor. 20 | * 21 | * @param data The parsed result's data. 22 | */ 23 | public function ResultOperationEvent(data:Object) 24 | { 25 | super(RESULT); 26 | _data = data; 27 | } 28 | 29 | /** 30 | * @private 31 | */ 32 | override public function clone():Event 33 | { 34 | return new ResultOperationEvent(data); 35 | } 36 | 37 | private var _data:*; 38 | /** 39 | * The parsed result's data. 40 | */ 41 | public function get data():* 42 | { 43 | return _data; 44 | } 45 | public function set data(value:*):void 46 | { 47 | _data = value; 48 | } 49 | } 50 | } -------------------------------------------------------------------------------- /tests/mesh/model/RecordDirtyTests.as: -------------------------------------------------------------------------------- 1 | package mesh.model 2 | { 3 | import mesh.Name; 4 | import mesh.Person; 5 | import mesh.model.source.FixtureDataSource; 6 | import mesh.model.store.Store; 7 | 8 | import org.flexunit.assertThat; 9 | import org.hamcrest.object.equalTo; 10 | 11 | public class RecordDirtyTests 12 | { 13 | private var _data:Object; 14 | private var _fixtures:FixtureDataSource; 15 | private var _store:Store; 16 | 17 | [Before] 18 | public function setup():void 19 | { 20 | _data = {id:1, firstName:"Jimmy", lastName:"Page"}; 21 | _fixtures = new FixtureDataSource(Person); 22 | _fixtures.add(_data); 23 | _store = new Store(_fixtures); 24 | } 25 | 26 | [Test] 27 | /** 28 | * Make sure that changing a property of a record marks it as dirty. 29 | */ 30 | public function testPropertyChange():void 31 | { 32 | var person:Person = _store.query(Person).find(1).load(); 33 | person.name = new Name("Steve", "Jobs"); 34 | 35 | assertThat(person.changes.hasChanges, equalTo(true)); 36 | assertThat(person.state.isRemote, equalTo(true)); 37 | assertThat(person.state.isSynced, equalTo(false)); 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /src/mesh/core/reflection/Method.as: -------------------------------------------------------------------------------- 1 | package mesh.core.reflection 2 | { 3 | import flash.utils.getDefinitionByName; 4 | 5 | /** 6 | * A class that represents the functions that has been defined on a class, variable, or 7 | * method. 8 | * 9 | * @author Dan Schultz 10 | */ 11 | public class Method extends Definition 12 | { 13 | /** 14 | * @copy Definition#Definition() 15 | */ 16 | public function Method(description:XML, belongsTo:Definition) 17 | { 18 | super(description, belongsTo); 19 | } 20 | 21 | /** 22 | * true if this is a method that has been defined with 23 | * static. 24 | */ 25 | public function get isStatic():Boolean 26 | { 27 | return description.parent().name() == "type"; 28 | } 29 | 30 | /** 31 | * The definition that represents the type that is returned from a function call to 32 | * this method. If no return type is defined, this method returns null. 33 | */ 34 | public function get returnType():Type 35 | { 36 | return description.@returnType.toString() == "void" ? null : Type.reflect(getDefinitionByName(description.@returnType.toString()) as Class); 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /src/mesh/core/functions/closure.as: -------------------------------------------------------------------------------- 1 | package mesh.core.functions 2 | { 3 | /** 4 | * Generates a function that can reference the variables defined within the scope 5 | * of the defined function, or the outer function that created it. For instance: 6 | * 7 | * 8 | * var letters:Array = ["a", "b", "c", "d", "e"]; 9 | * var closures:Array = []; 10 | * for each (var letter:String in letters) { 11 | * closures.push(closure(function():void 12 | * { 13 | * trace(letter); 14 | * })) 15 | * } 16 | * 17 | * for each (var closure:Function in closures) { 18 | * closure(); 19 | * } 20 | * 21 | * // traces: 22 | * // a 23 | * // b 24 | * // c 25 | * // d 26 | * // e 27 | * 28 | * 29 | * @param func The function to close. 30 | * @return A function closure. 31 | */ 32 | public function closure(func:Function):Function 33 | { 34 | var wrapper:Function = function(...args):* 35 | { 36 | var diff:int = func.length - args.length; 37 | if (diff > 0) { 38 | args = args.concat(new Array(diff)); 39 | } 40 | return func.apply(null, args.slice(0, func.length)); 41 | }; 42 | return wrapper; 43 | } 44 | } -------------------------------------------------------------------------------- /src/mesh/operations/QueueProgress.as: -------------------------------------------------------------------------------- 1 | package mesh.operations 2 | { 3 | import mesh.core.number.Fraction; 4 | 5 | /** 6 | * A progress class that's specific for an OperationQueue. 7 | * 8 | * @author Dan Schultz 9 | */ 10 | public class QueueProgress extends Progress 11 | { 12 | private var _queue:OperationQueue; 13 | 14 | /** 15 | * Constructor. 16 | * 17 | * @param queue The queue that owns this progress. 18 | */ 19 | public function QueueProgress(queue:OperationQueue) 20 | { 21 | _queue = queue; 22 | } 23 | 24 | /** 25 | * @inheritDoc 26 | */ 27 | override public function get complete():Number 28 | { 29 | var temp:Number = 0; 30 | for each (var executing:Operation in _queue.executing) { 31 | temp += executing.progress.complete; 32 | } 33 | return confirmed + temp; 34 | } 35 | 36 | private var _confirmed:Number = 0; 37 | /** 38 | * The number of units that have been confirmed to be completed. 39 | */ 40 | public function get confirmed():Number 41 | { 42 | return _confirmed; 43 | } 44 | public function set confirmed(value:Number):void 45 | { 46 | _confirmed = value; 47 | } 48 | 49 | } 50 | } -------------------------------------------------------------------------------- /src/mesh/operations/SequentialOperation.as: -------------------------------------------------------------------------------- 1 | package mesh.operations 2 | { 3 | /** 4 | * A type of compound operation where each operation is executed in a sequence, 5 | * and only one operation is permitted to execute at a time. 6 | * 7 | * @author Dan Schultz 8 | */ 9 | public class SequentialOperation extends CompoundOperation 10 | { 11 | /** 12 | * @copy operations.CompoundOperation#CompoundOperation() 13 | */ 14 | public function SequentialOperation(operations:Array = null) 15 | { 16 | super(operations); 17 | } 18 | 19 | /** 20 | * @inheritDoc 21 | */ 22 | override protected function startExecution():void 23 | { 24 | executeOperation(nextOperation(finishedOperationsCount)); 25 | } 26 | 27 | /** 28 | * @inheritDoc 29 | */ 30 | override protected function nextOperation(finishedOperationsCount:int):Operation 31 | { 32 | return operationSet.toArray()[finishedOperationsCount]; 33 | } 34 | 35 | /** 36 | * Adds the given operation to this sequence. 37 | * 38 | * @inheritDoc 39 | */ 40 | override public function then(operation:Operation):Operation 41 | { 42 | add(operation); 43 | return this; 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /src/mesh/core/object/inspect.as: -------------------------------------------------------------------------------- 1 | package mesh.core.object 2 | { 3 | import mesh.core.functions.closure; 4 | 5 | import mx.utils.ObjectUtil; 6 | import mesh.core.reflection.Property; 7 | import mesh.core.reflection.Type; 8 | import mesh.core.reflection.reflect; 9 | 10 | /** 11 | * Returns a string that contains the name of the given object's class and the values for each 12 | * of its fixed and dynamic properties. 13 | * 14 | * @param obj The object to inspect. 15 | * @return A string. 16 | */ 17 | public function inspect(obj:Object):String 18 | { 19 | if (obj == null) { 20 | return "null"; 21 | } 22 | 23 | if (ObjectUtil.isSimple(obj)) { 24 | return ObjectUtil.toString(obj); 25 | } 26 | 27 | var type:Type = reflect(obj); 28 | var properties:Array = type.properties.map(closure(function(property:Property):String 29 | { 30 | return property.name; 31 | })); 32 | 33 | for (var key:String in obj) { 34 | properties.push(key); 35 | } 36 | 37 | var result:String = "#<" + type.name; 38 | for each (var property:String in properties.sort()) { 39 | result += ", " + property + ": " + inspect(obj[property]); 40 | } 41 | 42 | return result + ">"; 43 | } 44 | } -------------------------------------------------------------------------------- /src/mesh/core/state/TransitionEvent.as: -------------------------------------------------------------------------------- 1 | package mesh.core.state 2 | { 3 | import flash.events.Event; 4 | 5 | /** 6 | * An event that is dispatched from a transition. 7 | * 8 | * @author Dan Schultz 9 | */ 10 | public class TransitionEvent extends Event 11 | { 12 | /** 13 | * Dispatched by a transition when it has been triggered. 14 | */ 15 | public static const TRANSITIONED:String = "transitioned"; 16 | 17 | /** 18 | * Constructor. 19 | * 20 | * @param type The event type. 21 | * @param transition The transition that triggered the event. 22 | */ 23 | public function TransitionEvent(type:String, transition:Transition, bubbles:Boolean=false, cancelable:Boolean=false) 24 | { 25 | super(type, bubbles, cancelable); 26 | _transition = transition; 27 | } 28 | 29 | /** 30 | * @inheritDoc 31 | */ 32 | override public function clone():Event 33 | { 34 | return new TransitionEvent(type, transition, bubbles, cancelable); 35 | } 36 | 37 | private var _transition:Transition; 38 | /** 39 | * The transition that triggered the event. 40 | */ 41 | public function get transition():Transition 42 | { 43 | return _transition; 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /src/mesh/operations/FinishedOperationEvent.as: -------------------------------------------------------------------------------- 1 | package mesh.operations 2 | { 3 | import flash.events.Event; 4 | 5 | /** 6 | * An event that is dispatched by an operation to indicate that its execution 7 | * has finished, either successfully or unsuccessfully. 8 | * 9 | * @author Dan Schultz 10 | */ 11 | public class FinishedOperationEvent extends OperationEvent 12 | { 13 | /** 14 | * An event type for when an operation has finished executing. 15 | */ 16 | public static const FINISHED:String = "finished"; 17 | 18 | /** 19 | * Constructor. 20 | * 21 | * @param successful Indicates whether the operation finished successfully. 22 | */ 23 | public function FinishedOperationEvent(successful:Boolean) 24 | { 25 | super(FINISHED); 26 | _successful = successful; 27 | } 28 | 29 | /** 30 | * @private 31 | */ 32 | override public function clone():Event 33 | { 34 | return new FinishedOperationEvent(successful); 35 | } 36 | 37 | private var _successful:Boolean; 38 | /** 39 | * true if the operation execution finished successfully. 40 | */ 41 | public function get successful():Boolean 42 | { 43 | return _successful; 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /src/mesh/core/array/intersection.as: -------------------------------------------------------------------------------- 1 | package mesh.core.array 2 | { 3 | /** 4 | * Returns the intersection of the elements of each array. The intersection is where 5 | * the result is an array that contains the elements that are common amoung each input 6 | * array. 7 | * 8 | * @param args The arrays to intersect with. 9 | * @return The intersecting elements. 10 | */ 11 | public function intersection(...args):Array 12 | { 13 | if (args.length == 0) { 14 | return []; 15 | } 16 | 17 | if (args.length == 1) { 18 | return args[0]; 19 | } 20 | 21 | var result:Array = []; 22 | var lcd:Array; 23 | if (args.length == 2) { 24 | // Use the array with the least number of elements as the least common denominator. 25 | lcd = args.splice(args[0].length < args[1].length ? 0 : 1, 1).shift(); 26 | var against:Array = args.shift(); 27 | for each (var element:* in lcd) { 28 | if (against.indexOf(element) != -1) { 29 | result.push(element); 30 | } 31 | } 32 | return result; 33 | } 34 | 35 | lcd = args.sortOn("length", Array.NUMERIC).shift(); 36 | while (args.length > 0) { 37 | result = result.concat(intersection(lcd, args.shift())); 38 | } 39 | return result; 40 | } 41 | } -------------------------------------------------------------------------------- /src/mesh/core/number/random.as: -------------------------------------------------------------------------------- 1 | package mesh.core.number 2 | { 3 | /** 4 | * Generates a pseudo-random number. If max and min are zero, the 5 | * result is a number where 0 <= n < 1. If max is non-zero, then the result is 6 | * a number where min <= n <= max. If max is less than 0, it is first absoluted 7 | * then treated as a non-zero number. 8 | * 9 | *

10 | * This method attempts to generate a well-balanced random number. This is done by first adding 11 | * 0.5 to the max, and subtracting 0.5 from the min. The altered max 12 | * and min is then multiplied against Math.random(), and the result rounded to the 13 | * nearest integer. 14 | *

15 | * 16 | * @param max The highest value for the result. 17 | * @param min The lowest value for the result. 18 | * @return A pseudo-random number. 19 | */ 20 | public function random(max:int = 0, min:int = 0):Number 21 | { 22 | if (max != 0 || min != 0) { 23 | var high:Number = Math.max(max, min) + 0.5; 24 | var low:Number = Math.min(min, max) - 0.5; 25 | return Math.round((Math.random() * (high - low)) + low); 26 | } 27 | return Math.random(); 28 | } 29 | } -------------------------------------------------------------------------------- /src/mesh/view/helpers/text/pluralizeByCount.as: -------------------------------------------------------------------------------- 1 | package mesh.view.helpers.text 2 | { 3 | import mesh.core.inflection.pluralize; 4 | 5 | /** 6 | * Pluralizes a singular word based on its count. The word 7 | * is pluralized if and only if count < 1 < count. If plural 8 | * is given, then that word will be used. Otherwise an inflection will be used. 9 | * 10 | *

11 | * Examples: 12 | *

13 | * trace( pluralize(2, "person") ); // 2 people 14 | * trace( pluralize(-2, "person") ); // -2 people 15 | * trace( pluralize(1, "fish") ); // 1 fish 16 | * trace( pluralize(0, "fish") ); // 0 fishes 17 | * trace( pluralize(2, "cake", "desserts") ); // 2 desserts 18 | * trace( pluralize(2, "cake", "desserts") ); // 2 desserts 19 | * 20 | *

21 | * 22 | * @return Either a singular or pluralized word when count is not 1. 23 | * @see mesh.core.inflection.pluralize() 24 | */ 25 | public function pluralizeByCount(count:Number, singular:String, plural:String = null):String 26 | { 27 | return count.toString() + " " + (count == 1 ? singular : (plural != null) ? plural : pluralize(singular)); 28 | } 29 | } -------------------------------------------------------------------------------- /src/mesh/core/object/isEmpty.as: -------------------------------------------------------------------------------- 1 | package mesh.core.object 2 | { 3 | import mx.utils.StringUtil; 4 | 5 | /** 6 | * Checks if the given object is empty. If the object is a string, it checks if the 7 | * string is empty or only contains whitespace. If the object is a number, it checks if 8 | * the number is NaN. If the object contains either a length or 9 | * isEmpty property or method, the result will be evaluated. 10 | * 11 | * @param obj The object to check. 12 | * @return true if the object is empty. 13 | */ 14 | public function isEmpty(obj:Object):Boolean 15 | { 16 | if (obj == null) { 17 | return true; 18 | } 19 | 20 | if (obj is String) { 21 | return StringUtil.trim(obj as String).length == 0; 22 | } 23 | 24 | if (obj is Number) { 25 | return isNaN(obj as Number); 26 | } 27 | 28 | if (obj.hasOwnProperty("isEmpty")) { 29 | if (obj.isEmpty is Function) { 30 | return obj.isEmpty(); 31 | } 32 | return obj.isEmpty; 33 | } 34 | 35 | if (obj.hasOwnProperty("length")) { 36 | if (obj.length is Function) { 37 | return obj.length() == 0; 38 | } 39 | return obj.length == 0; 40 | } 41 | 42 | return false; 43 | } 44 | } -------------------------------------------------------------------------------- /tests/mesh/model/store/DataTests.as: -------------------------------------------------------------------------------- 1 | package mesh.model.store 2 | { 3 | import mesh.Person; 4 | 5 | import org.flexunit.assertThat; 6 | import org.hamcrest.object.equalTo; 7 | import org.hamcrest.object.notNullValue; 8 | 9 | [RunWith("org.flexunit.runners.Parameterized")] 10 | public class DataTests 11 | { 12 | public static var READ_DATA:Array = [ 13 | [{name:"Jimmy Page"}, "name"] 14 | ]; 15 | 16 | public static var SET_DATA:Array = [ 17 | [{}, "name", "Jimmy Page"] 18 | ]; 19 | 20 | [Test] 21 | public function testIdFieldOption():void 22 | { 23 | var ID:int = 1; 24 | var data:Data = new Data(Person, {personId:ID}, {idField:"personId"}); 25 | assertThat(data.id, equalTo(ID)); 26 | } 27 | 28 | [Test(dataProvider="READ_DATA")] 29 | public function testReadProperty(obj:Object, property:String):void 30 | { 31 | var data:Data = new Data(Person, obj); 32 | assertThat(data[property], notNullValue()); 33 | } 34 | 35 | [Test(dataProvider="SET_DATA")] 36 | public function testSetProperty(obj:Object, property:String, value:Object):void 37 | { 38 | var data:Data = new Data(Person, obj); 39 | data[property] = value; 40 | assertThat(data[property], equalTo(value)); 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /tests/mesh/core/inflection/CamelizeTests.as: -------------------------------------------------------------------------------- 1 | package mesh.core.inflection 2 | { 3 | import org.flexunit.assertThat; 4 | import org.hamcrest.object.equalTo; 5 | 6 | public class CamelizeTests 7 | { 8 | private var _tests:Array = []; 9 | 10 | [Before] 11 | public function setup():void 12 | { 13 | _tests.push({input:["the quick brown fox", false], expected:"theQuickBrownFox"}); 14 | _tests.push({input:["the quick brown fox", true], expected:"TheQuickBrownFox"}); 15 | _tests.push({input:["the quIck broWn foX", true], expected:"TheQuIckBroWnFoX"}); 16 | _tests.push({input:["The quIck broWn foX", false], expected:"theQuIckBroWnFoX"}); 17 | _tests.push({input:["the_quick_brown_fox", true], expected:"TheQuickBrownFox"}); 18 | _tests.push({input:["the_ quick_ brown_ fox", true], expected:"TheQuickBrownFox"}); 19 | _tests.push({input:["the _ quick _ brown _ fox", true], expected:"TheQuickBrownFox"}); 20 | _tests.push({input:["the quick brown fox", true], expected:"TheQuickBrownFox"}); 21 | } 22 | 23 | [Test] 24 | public function testCamelize():void 25 | { 26 | for each (var test:Object in _tests) { 27 | assertThat("camelize failed for '" + test.input[0] + "'", camelize.apply(null, test.input), equalTo(test.expected)); 28 | } 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /src/mesh/core/state/StateEvent.as: -------------------------------------------------------------------------------- 1 | package mesh.core.state 2 | { 3 | import flash.events.Event; 4 | 5 | /** 6 | * An event dispatched by a State when the state machine either enters or 7 | * exits a state. 8 | * 9 | * @author Dan Schultz 10 | */ 11 | public class StateEvent extends Event 12 | { 13 | /** 14 | * Dispatched by a state when the machine enters this state. 15 | */ 16 | public static const ENTER:String = "enter"; 17 | 18 | /** 19 | * Dispatched by a state whent he machine exits this state. 20 | */ 21 | public static const EXIT:String = "exit"; 22 | 23 | /** 24 | * Constructor. 25 | * 26 | * @param type The event type. 27 | * @param state The event's state. 28 | */ 29 | public function StateEvent(type:String, state:State, bubbles:Boolean = false, cancelable:Boolean = false) 30 | { 31 | super(type, bubbles, cancelable); 32 | _state = state; 33 | } 34 | 35 | /** 36 | * @inheritDoc 37 | */ 38 | override public function clone():Event 39 | { 40 | return new StateEvent(type, state, bubbles, cancelable); 41 | } 42 | 43 | private var _state:State; 44 | /** 45 | * The state for this event. 46 | */ 47 | public function get state():State 48 | { 49 | return _state; 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /src/mesh/operations/OperationQueueEvent.as: -------------------------------------------------------------------------------- 1 | package mesh.operations 2 | { 3 | import flash.events.Event; 4 | 5 | /** 6 | * An event that is dispatched by the OperationQueue. 7 | * 8 | * @author Dan Schultz 9 | */ 10 | public class OperationQueueEvent extends Event 11 | { 12 | /** 13 | * An event type that is dispatched when the queue has started. 14 | */ 15 | public static const STARTED:String = "started"; 16 | 17 | /** 18 | * An event type that is dispatched when there's any progress within the queue. 19 | */ 20 | public static const PROGRESS:String = "progress"; 21 | 22 | /** 23 | * An event type that is dispatched when the queue has been paused. 24 | */ 25 | public static const PAUSED:String = "paused"; 26 | 27 | /** 28 | * An event type that is dispatched when there are no elements left in the queue. 29 | */ 30 | public static const IDLE:String = "idle"; 31 | 32 | /** 33 | * Constructor. 34 | * 35 | * @param type The event type. 36 | */ 37 | public function OperationQueueEvent(type:String) 38 | { 39 | super(type); 40 | } 41 | 42 | /** 43 | * The queue that dispatched this event. 44 | */ 45 | public function get queue():OperationQueue 46 | { 47 | return OperationQueue( target ); 48 | } 49 | } 50 | } -------------------------------------------------------------------------------- /tests/mesh/core/inflection/UnderscoreTests.as: -------------------------------------------------------------------------------- 1 | package mesh.core.inflection 2 | { 3 | import org.flexunit.assertThat; 4 | import org.hamcrest.object.equalTo; 5 | 6 | public class UnderscoreTests 7 | { 8 | private var _tests:Array = []; 9 | 10 | [Before] 11 | public function setup():void 12 | { 13 | _tests.push({input:"TheQuickBrownFox", expected:"the_quick_brown_fox"}); 14 | _tests.push({input:"THEQuickBrownFox", expected:"the_quick_brown_fox"}); 15 | _tests.push({input:"TheQUICKBrownFox", expected:"the_quick_brown_fox"}); 16 | _tests.push({input:"TheQUICKBrownFOX", expected:"the_quick_brown_fox"}); 17 | _tests.push({input:"The Quick Brown Fox", expected:"the_quick_brown_fox"}); 18 | _tests.push({input:"The quick brown fox", expected:"the_quick_brown_fox"}); 19 | _tests.push({input:"the quick brown fox", expected:"the_quick_brown_fox"}); 20 | _tests.push({input:"the quick brown fox", expected:"the_quick_brown_fox"}); 21 | _tests.push({input:"the _ quick - brown-fox", expected:"the_quick_brown_fox"}); 22 | } 23 | 24 | [Test] 25 | public function testUnderscore():void 26 | { 27 | for each (var test:Object in _tests) { 28 | assertThat("underscore failed for '" + test.input + "'", underscore.call(null, test.input), equalTo(test.expected)); 29 | } 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /tests/mesh/core/number/RoundTests.as: -------------------------------------------------------------------------------- 1 | package mesh.core.number 2 | { 3 | import org.flexunit.assertThat; 4 | import org.hamcrest.object.equalTo; 5 | 6 | public class RoundTests 7 | { 8 | private var _tests:Array; 9 | 10 | [Before] 11 | public function setup():void 12 | { 13 | _tests = [ 14 | {number:123.4567, precision:3, expected:123.457}, 15 | {number:123.4567, precision:2, expected:123.46}, 16 | {number:123.4445, precision:2, expected:123.44}, 17 | {number:123.4567, precision:1, expected:123.5}, 18 | {number:123.5, precision:0, expected:124}, 19 | {number:123.4, precision:0, expected:123}, 20 | {number:-123.4, precision:0, expected:-123}, 21 | {number:-123.5, precision:0, expected:-123}, 22 | {number:-123.6, precision:0, expected:-124}, 23 | {number:-123.4567, precision:1, expected:-123.5}, 24 | {number:-123.4567, precision:3, expected:-123.457}, 25 | {number:-123.4567, precision:2, expected:-123.46}, 26 | {number:-123.4445, precision:2, expected:-123.44}, 27 | ]; 28 | } 29 | 30 | [Test] 31 | public function testRound():void 32 | { 33 | for each (var test:Object in _tests) { 34 | assertThat("test failed for number," + test.number + ", with precision: " + test.precision, round(test.number, test.precision), equalTo(test.expected)); 35 | } 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /tests/mesh/model/InitializeAssociatedRecordsTests.as: -------------------------------------------------------------------------------- 1 | package mesh.model 2 | { 3 | import mesh.Customer; 4 | import mesh.Order; 5 | import mesh.model.source.FixtureDataSource; 6 | import mesh.model.source.MultiDataSource; 7 | import mesh.model.store.Store; 8 | 9 | import org.flexunit.assertThat; 10 | import org.hamcrest.object.notNullValue; 11 | 12 | public class InitializeAssociatedRecordsTests 13 | { 14 | private var _store:Store; 15 | 16 | [Before] 17 | public function setup():void 18 | { 19 | var customers:FixtureDataSource = new FixtureDataSource(Customer); 20 | customers.add({id:1, firstName:"Jimmy", lastName:"Page", accountId:1}); 21 | 22 | var dataSource:MultiDataSource = new MultiDataSource(); 23 | dataSource.map(Customer, customers); 24 | dataSource.map(Order, new FixtureDataSource(Order)); 25 | 26 | _store = new Store(dataSource); 27 | } 28 | 29 | [Test] 30 | public function testInitializeHasOneAssociation():void 31 | { 32 | var customer:Customer = _store.query(Customer).find(1).load(); 33 | assertThat(customer.account, notNullValue()); 34 | } 35 | 36 | [Test] 37 | public function testInitializeHasManyAssociation():void 38 | { 39 | var customer:Customer = _store.query(Customer).find(1).load(); 40 | assertThat(customer.orders, notNullValue()); 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /tests/mesh/model/validators/InclusionValidatorTests.as: -------------------------------------------------------------------------------- 1 | package mesh.model.validators 2 | { 3 | 4 | import mx.collections.ArrayCollection; 5 | 6 | import org.flexunit.assertThat; 7 | import org.hamcrest.object.equalTo; 8 | 9 | import mesh.core.object.inspect; 10 | 11 | public class InclusionValidatorTests 12 | { 13 | [Test] 14 | public function testValidate():void 15 | { 16 | var tests:Array = [ 17 | { 18 | object:{str:"Hello", errors:new Errors(null)}, 19 | options:{property:"str", within:["Hello", "Hi"]}, 20 | passes:true 21 | }, 22 | { 23 | object:{str:"Hello", errors:new Errors(null)}, 24 | options:{property:"str", within:["Hi"]}, 25 | passes:false 26 | }, 27 | { 28 | object:{str:"Hello", errors:new Errors(null)}, 29 | options:{property:"str", within:new ArrayCollection(["Hello", "Hi"])}, 30 | passes:true 31 | }, 32 | { 33 | object:{str:"Hello", errors:new Errors(null)}, 34 | options:{property:"str", within:new ArrayCollection(["a", "b"])}, 35 | passes:false 36 | } 37 | ]; 38 | 39 | for each (var test:Object in tests) { 40 | new InclusionValidator(test.options).validate(test.object); 41 | assertThat("validation failed for test " + inspect(test.options), test.object.errors.length == 0, equalTo(test.passes)); 42 | } 43 | } 44 | } 45 | } -------------------------------------------------------------------------------- /tests/mesh/model/validators/ExclusionValidatorTests.as: -------------------------------------------------------------------------------- 1 | package mesh.model.validators 2 | { 3 | 4 | import mx.collections.ArrayCollection; 5 | 6 | import org.flexunit.assertThat; 7 | import org.hamcrest.object.equalTo; 8 | 9 | import mesh.core.object.inspect; 10 | 11 | public class ExclusionValidatorTests 12 | { 13 | [Test] 14 | public function testValidate():void 15 | { 16 | var tests:Array = [ 17 | { 18 | object:{str:"Hello", errors:new Errors(null)}, 19 | options:{property:"str", within:["Hello", "Hi"]}, 20 | passes:false 21 | }, 22 | { 23 | object:{str:"Hello", errors:new Errors(null)}, 24 | options:{property:"str", within:["Hi"]}, 25 | passes:true 26 | }, 27 | { 28 | object:{str:"Hello", errors:new Errors(null)}, 29 | options:{property:"str", within:new ArrayCollection(["Hello", "Hi"])}, 30 | passes:false 31 | }, 32 | { 33 | object:{str:"Hello", errors:new Errors(null)}, 34 | options:{property:"str", within:new ArrayCollection(["a", "b", "c"])}, 35 | passes:true 36 | } 37 | ]; 38 | 39 | for each (var test:Object in tests) { 40 | new ExclusionValidator(test.options).validate(test.object); 41 | assertThat("validation failed for test " + inspect(test.options), test.object.errors.length == 0, equalTo(test.passes)); 42 | } 43 | } 44 | } 45 | } -------------------------------------------------------------------------------- /src/mesh/core/array/flatten.as: -------------------------------------------------------------------------------- 1 | package mesh.core.array 2 | { 3 | /** 4 | * Returns a new one-dimensional array that is a recursive flattening of elements. 5 | * Meaning, for each element in elements that is an array, insert its elements 6 | * into the new array. 7 | * 8 | *

9 | * Examples: 10 | *

11 | * var a:Array = [1, 2, 3]; 12 | * flatten(a); // [1, 2, 3] 13 | * 14 | * var b:Array = [1, 2, 3, [4, 5]]; 15 | * flatten(b); // [1, 2, 3, 4, 5] 16 | * 17 | * var c:Array = [1, 2, [3, [4, 5]]]; 18 | * flatten(c); // [1, 2, 3, 4, 5] 19 | * flatten(c, 1); // [1, 2, 3, [4, 5]] 20 | * 21 | *

22 | * 23 | * @param elements The array to flatten. 24 | * @depth The maximum depth to flatten. 25 | * @return A new flattened array. 26 | */ 27 | public function flatten(elements:Object, depth:int = -1):Array 28 | { 29 | depth = depth < 0 ? int.MAX_VALUE : depth; 30 | elements = elements is Array ? elements : [elements]; 31 | if (depth > 0) { 32 | var result:Array = []; 33 | for each (var element:Object in elements) { 34 | if (element is Array) { 35 | result = result.concat(flatten(element, depth-1)); 36 | continue; 37 | } 38 | result.push(element); 39 | } 40 | return result; 41 | } 42 | return elements.concat(); 43 | } 44 | } -------------------------------------------------------------------------------- /src/mesh/core/object/merge.as: -------------------------------------------------------------------------------- 1 | package mesh.core.object 2 | { 3 | /** 4 | * Returns a new object that contains the values of obj1 and the 5 | * values from obj2. If the objects have conflicting keys, the resulting 6 | * object will contain the value from obj2. You can overwrite this 7 | * functionality by specifying a block function. This function must 8 | * have the following signature: function(key:String, old:Object, new:Object):Object. 9 | * 10 | * @param obj1 The original object. 11 | * @param obj2 The object to merge with obj1. 12 | * @param block A function to determine how to merge conflicting keys. 13 | * @return The merged object. 14 | */ 15 | public function merge(obj1:Object, obj2:Object, block:Function = null):Object 16 | { 17 | obj1 = (obj1 == null ? {} : obj1); 18 | obj2 = (obj2 == null ? {} : obj2); 19 | 20 | if (block == null) { 21 | block = function(key:String, oldValue:Object, newValue:Object):Object 22 | { 23 | return newValue; 24 | }; 25 | } 26 | 27 | var result:Object = {}; 28 | 29 | for (var key1:String in obj1) { 30 | result[key1] = obj1[key1]; 31 | } 32 | 33 | for (var key2:String in obj2) { 34 | result[key2] = !result.hasOwnProperty(key2) ? obj2[key2] : block(key2, result[key2], obj2[key2]); 35 | } 36 | 37 | return result; 38 | } 39 | } -------------------------------------------------------------------------------- /src/mesh/core/array/ArrayProxy.as: -------------------------------------------------------------------------------- 1 | package mesh.core.array 2 | { 3 | import flash.utils.Proxy; 4 | import flash.utils.flash_proxy; 5 | 6 | /** 7 | * The ArrayProxy is a class that allows an inheriting class to behave like 8 | * an array that supports for each..in loops. 9 | * 10 | * @author Dan Schultz 11 | */ 12 | public class ArrayProxy extends Proxy 13 | { 14 | private var _copy:Function; 15 | 16 | /** 17 | * Constructor. 18 | * 19 | * @param copy A function that copies the elements of the array being proxied. 20 | */ 21 | public function ArrayProxy(copy:Function) 22 | { 23 | super(); 24 | _copy = copy; 25 | } 26 | 27 | // Proxy methods to support for each..in loops. 28 | 29 | /** 30 | * @private 31 | */ 32 | override flash_proxy function nextName(index:int):String 33 | { 34 | return (index-1).toString(); 35 | } 36 | 37 | private var _iteratingItems:Array; 38 | private var _len:int; 39 | /** 40 | * @private 41 | */ 42 | override flash_proxy function nextNameIndex(index:int):int 43 | { 44 | if (index == 0) { 45 | _iteratingItems = _copy(); 46 | _len = _iteratingItems.length; 47 | } 48 | return index < _len ? index+1 : 0; 49 | } 50 | 51 | /** 52 | * @private 53 | */ 54 | override flash_proxy function nextValue(index:int):* 55 | { 56 | return _iteratingItems[index-1]; 57 | } 58 | } 59 | } -------------------------------------------------------------------------------- /src/mesh/operations/Progress.as: -------------------------------------------------------------------------------- 1 | package mesh.operations 2 | { 3 | /** 4 | * The Progress class contains information about the progress of an 5 | * operation. It holds the number of units that have completed, and the total number 6 | * of units needed to complete the operation. 7 | * 8 | * @author Dan Schultz 9 | */ 10 | public class Progress 11 | { 12 | /** 13 | * Constructor. 14 | * 15 | * @param total The total units needed to complete. 16 | */ 17 | public function Progress() 18 | { 19 | 20 | } 21 | 22 | /** 23 | * Returns the percentage as a string. 24 | * 25 | * @return A string. 26 | */ 27 | public function toString():String 28 | { 29 | var percentage:Number = complete/total; 30 | return percentage.toString() + (!isNaN(percentage) ? "%" : ""); 31 | } 32 | 33 | private var _complete:Number = 0; 34 | /** 35 | * The number of units that have completed. 36 | */ 37 | public function get complete():Number 38 | { 39 | return _complete; 40 | } 41 | public function set complete(value:Number):void 42 | { 43 | _complete = value; 44 | } 45 | 46 | private var _total:Number = 0; 47 | /** 48 | * The total number of units to complete the operation. 49 | */ 50 | public function get total():Number 51 | { 52 | return _total; 53 | } 54 | public function set total(value:Number):void 55 | { 56 | _total = value; 57 | } 58 | } 59 | } -------------------------------------------------------------------------------- /src/mesh/core/IBatchedListDelegate.as: -------------------------------------------------------------------------------- 1 | package mesh.core 2 | { 3 | /** 4 | * A delegate that provides data to a BatchedList. The batched list 5 | * looks to its delegate to fetch elements that do not yet exist in the list. A 6 | * base delegate class is provided to make implementing this interface simpler. 7 | * 8 | * @see BatchedList 9 | * @see BatchedListDelegate 10 | * 11 | * @author Dan Schultz 12 | */ 13 | public interface IBatchedListDelegate 14 | { 15 | /** 16 | * Called by the list when it needs to know the total number of elements that 17 | * it contains. After the delegate has fetched the length, it must set the 18 | * length of the list by calling the provideLength() method. 19 | * 20 | * @param list The list requesting its length. 21 | * @se BatchedList#provideLength() 22 | */ 23 | function requestLength(list:BatchedList):void; 24 | 25 | /** 26 | * Called by the list when it needs to fetch a batch of elements. After the 27 | * delegate has fetched the batch, it must call the provideBatch() 28 | * method to insert the batch into the list. 29 | * 30 | * @param list The list requesting the batch. 31 | * @param index The index of the batch. 32 | * @param batchSize The size of the batch to fetch. 33 | * 34 | * @see BatchedList#provideBatch() 35 | */ 36 | function requestBatch(list:BatchedList, index:uint, batchSize:uint):void; 37 | } 38 | } -------------------------------------------------------------------------------- /src/mesh/operations/OperationEvent.as: -------------------------------------------------------------------------------- 1 | package mesh.operations 2 | { 3 | import flash.events.Event; 4 | 5 | /** 6 | * An Operation dispatches this event for simple statuses. 7 | * 8 | * @see mesh.operations.Operation 9 | * 10 | * @author Dan Schultz 11 | */ 12 | public class OperationEvent extends Event 13 | { 14 | /** 15 | * The event type for when an operation is canceled. 16 | */ 17 | public static const CANCELED:String = "canceled"; 18 | 19 | /** 20 | * The event type for when an operation is queued. 21 | */ 22 | public static const QUEUED:String = "queued"; 23 | 24 | /** 25 | * The event type before an operation is executed. 26 | */ 27 | public static const BEFORE_EXECUTE:String = "beforeExecute"; 28 | 29 | /** 30 | * The event type after an operation is executed. 31 | */ 32 | public static const AFTER_EXECUTE:String = "afterExecute"; 33 | 34 | /** 35 | * Constructor. 36 | * 37 | * @param type The event type. 38 | */ 39 | public function OperationEvent(type:String) 40 | { 41 | super(type, bubbles, cancelable); 42 | } 43 | 44 | /** 45 | * @private 46 | */ 47 | override public function clone():Event 48 | { 49 | return new OperationEvent(type); 50 | } 51 | 52 | /** 53 | * The operation that dispatched this event. 54 | */ 55 | public function get operation():Operation 56 | { 57 | return target as Operation; 58 | } 59 | } 60 | } -------------------------------------------------------------------------------- /src/mesh/model/validators/EachValidator.as: -------------------------------------------------------------------------------- 1 | package mesh.model.validators 2 | { 3 | 4 | 5 | /** 6 | * A validator base class that will validate a set of properties on the same 7 | * object. 8 | * 9 | * @author Dan Schultz 10 | */ 11 | public class EachValidator extends Validator 12 | { 13 | /** 14 | * @copy Validator#Validator() 15 | */ 16 | public function EachValidator(options:Object) 17 | { 18 | super(options); 19 | } 20 | 21 | /** 22 | * @inheritDoc 23 | */ 24 | override public function validate(obj:Object):void 25 | { 26 | for each (var property:String in properties) { 27 | validateProperty(obj, property, obj[property]); 28 | } 29 | } 30 | 31 | /** 32 | * Called by validate() to validate a single property on an object. This 33 | * method is intended to be overridden by sub-classes to run the validation. 34 | * 35 | * @param obj The object being validated. 36 | * @param property The property to validate. 37 | * @param value The object's property value. 38 | */ 39 | protected function validateProperty(obj:Object, property:String, value:Object):void 40 | { 41 | 42 | } 43 | 44 | /** 45 | * The properties that the validator is evaluating, as a list of Strings. 46 | */ 47 | protected function get properties():Array 48 | { 49 | if (options.hasOwnProperty("properties")) { 50 | return options.properties; 51 | } 52 | return [options.property]; 53 | } 54 | } 55 | } -------------------------------------------------------------------------------- /tests/mesh/core/string/SentenceizeTests.as: -------------------------------------------------------------------------------- 1 | package mesh.core.string 2 | { 3 | import org.flexunit.assertThat; 4 | import org.hamcrest.object.equalTo; 5 | 6 | public class SentenceizeTests 7 | { 8 | private var _tests:Array = []; 9 | 10 | [Before] 11 | public function setup():void 12 | { 13 | _tests.push({input:"the quick brown fox, jumped over the lazy dog.", expected:"The quick brown fox, jumped over the lazy dog."}); 14 | _tests.push({input:"The Quick Brown Fox, Jumped Over the Lazy Dog.", expected:"The Quick Brown Fox, Jumped Over the Lazy Dog."}); 15 | _tests.push({input:"the quick brown fox, jumped over the lazy dog", expected:"The quick brown fox, jumped over the lazy dog"}); 16 | _tests.push({input:"the quick brown fox; jumped over the lazy dog", expected:"The quick brown fox; jumped over the lazy dog"}); 17 | _tests.push({input:"the quick brown fox. jumped over the lazy dog", expected:"The quick brown fox. Jumped over the lazy dog"}); 18 | _tests.push({input:"the quick brown fox! jumped over the lazy dog", expected:"The quick brown fox! Jumped over the lazy dog"}); 19 | _tests.push({input:"the quick brown fox? jumped over the lazy dog.", expected:"The quick brown fox? Jumped over the lazy dog."}); 20 | } 21 | 22 | [Test] 23 | public function testSentenceize():void 24 | { 25 | for each (var test:Object in _tests) { 26 | assertThat("sentenceize failed for '" + test.input + "'", sentenceize(test.input), equalTo(test.expected)); 27 | } 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /src/mesh/model/store/Query.as: -------------------------------------------------------------------------------- 1 | package mesh.model.store 2 | { 3 | import flash.events.EventDispatcher; 4 | 5 | import mesh.model.source.DataSource; 6 | 7 | /** 8 | * A query contains the information necessary to fetch data from a store or data source. 9 | * 10 | * @author Dan Schultz 11 | */ 12 | public class Query extends EventDispatcher 13 | { 14 | /** 15 | * Constructor. 16 | * 17 | * @param dataSource The data source to load records from. 18 | * @param records The records to query. 19 | * @param recordType The type of records to query. 20 | */ 21 | public function Query(dataSource:DataSource, records:RecordCache, recordType:Class) 22 | { 23 | _dataSource = dataSource; 24 | _records = records; 25 | _recordType = recordType; 26 | } 27 | 28 | /** 29 | * Executes the query. 30 | */ 31 | public function execute():* 32 | { 33 | 34 | } 35 | 36 | private var _dataSource:DataSource; 37 | /** 38 | * The data source to load records from. 39 | */ 40 | protected function get dataSource():DataSource 41 | { 42 | return _dataSource; 43 | } 44 | 45 | private var _recordType:Class; 46 | /** 47 | * The type of records to query. 48 | */ 49 | protected function get recordType():Class 50 | { 51 | return _recordType; 52 | } 53 | 54 | private var _records:RecordCache; 55 | /** 56 | * The records to query. 57 | */ 58 | protected function get records():RecordCache 59 | { 60 | return _records; 61 | } 62 | } 63 | } -------------------------------------------------------------------------------- /src/mesh/operations/Timeout.as: -------------------------------------------------------------------------------- 1 | package mesh.operations 2 | { 3 | /** 4 | * The timeout for a network operation. 5 | * 6 | * @author Dan Schultz 7 | */ 8 | public class Timeout 9 | { 10 | private var _operation:NetworkOperation; 11 | private var _scale:Number; 12 | 13 | public function Timeout(milliseconds:Number, operation:NetworkOperation) 14 | { 15 | _scale = 1; 16 | _value = milliseconds; 17 | _operation = operation; 18 | } 19 | 20 | /** 21 | * Sets the timeout to be interpreted as milliseconds. 22 | * 23 | * @return The network operation. 24 | */ 25 | public function milliseconds():NetworkOperation 26 | { 27 | return _operation; 28 | } 29 | 30 | /** 31 | * Sets the timeout to be interpreted as minutes. 32 | * 33 | * @return The network operation. 34 | */ 35 | public function minutes():NetworkOperation 36 | { 37 | _scale = 60000; 38 | return _operation; 39 | } 40 | 41 | /** 42 | * Sets the timeout to be interpreted as seconds. 43 | * 44 | * @return The network operation. 45 | */ 46 | public function seconds():NetworkOperation 47 | { 48 | _scale = 1000; 49 | return _operation; 50 | } 51 | 52 | public function toString():String 53 | { 54 | return (value/1000).toString() + " seconds"; 55 | } 56 | 57 | public function valueOf():Object 58 | { 59 | return value; 60 | } 61 | 62 | private var _value:Number; 63 | public function get value():Number 64 | { 65 | return _value / _scale; 66 | } 67 | } 68 | } -------------------------------------------------------------------------------- /src/mesh/model/store/CommitResponder.as: -------------------------------------------------------------------------------- 1 | package mesh.model.store 2 | { 3 | /** 4 | * A responder that contains callbacks for failed and successful persistence events. 5 | * 6 | * @author Dan Schultz 7 | */ 8 | public class CommitResponder implements ICommitResponder 9 | { 10 | /** 11 | * Constructor. 12 | * 13 | * @param success The success callback function. 14 | * @param failed The failed callback function. 15 | */ 16 | public function CommitResponder(success:Function = null, failed:Function = null) 17 | { 18 | successHandler = success; 19 | failedHandler = failed; 20 | } 21 | 22 | /** 23 | * @inheritDoc 24 | */ 25 | public function failed(summary:String, detail:String = "", code:String = ""):void 26 | { 27 | if (failedHandler != null) { 28 | failedHandler(summary, detail, code); 29 | } 30 | } 31 | 32 | /** 33 | * @inheritDoc 34 | */ 35 | public function success():void 36 | { 37 | if (successHandler != null) { 38 | successHandler(); 39 | } 40 | } 41 | 42 | /** 43 | * The function that is executed when a commit has failed. This method expects the following 44 | * method signature: function(summary:String, detail:String = "", code:String = ""):void. 45 | */ 46 | public var failedHandler:Function; 47 | 48 | /** 49 | * The function that is executed when a commit has finished successfully. This method expects 50 | * the following method signature: function():void. 51 | */ 52 | public var successHandler:Function; 53 | } 54 | } -------------------------------------------------------------------------------- /src/mesh/operations/Attempt.as: -------------------------------------------------------------------------------- 1 | package mesh.operations 2 | { 3 | /** 4 | * Stores the retries for a network operation. 5 | * 6 | * @author Dan Schultz. 7 | */ 8 | public class Attempt 9 | { 10 | private var _operation:NetworkOperation; 11 | private var _delays:Array = [0]; 12 | 13 | public function Attempt(maxAttempts:int, operation:NetworkOperation) 14 | { 15 | _maxAttempts = Math.max(1, maxAttempts); 16 | _operation = operation; 17 | } 18 | 19 | public function getDelayForAttemptInMilliseconds(attempt:int):Number 20 | { 21 | attempt = Math.min(_delays.length, attempt); 22 | return _delays[attempt-1] * 1000; 23 | } 24 | 25 | /** 26 | * Sets the delay, in seconds, for each retry attempt. The delay for the first 27 | * retry is the first argument, the delay for the second retry is the second 28 | * argument and so on. To set the delay for all attempts, pass in a single 29 | * argument. 30 | * 31 | * @param delays The delays for each retry attempt. 32 | * @return The operation. 33 | */ 34 | public function withDelay(... delays):NetworkOperation 35 | { 36 | _delays = delays; 37 | return _operation; 38 | } 39 | 40 | /** 41 | * Removes the delay between each retry attempt. 42 | * 43 | * @return The operation. 44 | */ 45 | public function withoutDelay():NetworkOperation 46 | { 47 | _delays = [0]; 48 | return _operation; 49 | } 50 | 51 | private var _maxAttempts:int; 52 | public function get maxAttempts():int 53 | { 54 | return _maxAttempts; 55 | } 56 | } 57 | } -------------------------------------------------------------------------------- /src/mesh/core/object/copy.as: -------------------------------------------------------------------------------- 1 | package mesh.core.object 2 | { 3 | /** 4 | * Copies the enumerable values defined on from to to. If 5 | * to is a non-dynamic class, and a key exists on from, but 6 | * not on to, its value is not copied. 7 | * 8 | *

9 | * Additional options can be passed in to configure the copy: 10 | * 11 | *

15 | *

16 | * 17 | * @param from The object who's values to copy from. 18 | * @param to The object to copy to. 19 | * @param options Configurable options. 20 | * @throws ReferenceError If a key exists on from that is a defined function 21 | * on to. 22 | */ 23 | public function copy(from:Object, to:Object, options:Object = null):void 24 | { 25 | options = options == null ? {} : options; 26 | 27 | var includes:Array = (options.includes is Array) ? options.includes : []; 28 | var excludes:Array = (options.excludes is Array) ? options.excludes : []; 29 | 30 | for (var key:String in from) { 31 | if (includes.indexOf(key) == -1) { 32 | includes.push(key); 33 | } 34 | } 35 | 36 | for each (key in includes) { 37 | try { 38 | if (to[key] != from[key]) { 39 | if (excludes.indexOf(key) == -1) { 40 | to[key] = from[key]; 41 | } 42 | } 43 | } catch (e:ReferenceError) { 44 | 45 | } catch (e:TypeError) { 46 | 47 | } 48 | } 49 | } 50 | } -------------------------------------------------------------------------------- /tests/mesh/view/helpers/number/WithPrecisionTests.as: -------------------------------------------------------------------------------- 1 | package mesh.view.helpers.number 2 | { 3 | import mesh.core.object.inspect; 4 | 5 | import org.flexunit.assertThat; 6 | import org.hamcrest.object.equalTo; 7 | 8 | public class WithPrecisionTests 9 | { 10 | private var _tests:Array; 11 | 12 | [Before] 13 | public function setup():void 14 | { 15 | _tests = [ 16 | {number:111.2345, options:null, expected:"111.23"}, 17 | {number:111.2345, options:{precision:3}, expected:"111.235"}, 18 | {number:12, options:{precision:3}, expected:"12.000"}, 19 | {number:234.5, options:{precision:0}, expected:"235"}, 20 | {number:111.234, options:{significant:true}, expected:"110"}, 21 | {number:111.234, options:{precision:1, significant:true}, expected:"100"}, 22 | {number:2, options:{precision:1, significant:true}, expected:"2"}, 23 | {number:15, options:{precision:1, significant:true}, expected:"20"}, 24 | {number:13, options:{precision:5, significant:true}, expected:"13.000"}, 25 | {number:13, options:{precision:5, significant:true, stripInsignificantZeros:true}, expected:"13"}, 26 | {number:389.32314, options:{precision:4, significant:true}, expected:"389.3"}, 27 | {number:1111.2345, options:{precision:2, separator:",", delimiter:"."}, expected:"1.111,23"}, 28 | ]; 29 | } 30 | 31 | [Test] 32 | public function testNumberWithPrecision():void 33 | { 34 | for each (var test:Object in _tests) { 35 | assertThat("test failed for number," + test.number + ", with options: " + inspect(test.options), withPrecision(test.number, test.options), equalTo(test.expected)); 36 | } 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /src/metadata.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 23 | 24 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /src/mesh/core/Set.as: -------------------------------------------------------------------------------- 1 | package mesh.core 2 | { 3 | import flash.utils.Dictionary; 4 | 5 | /** 6 | * The Set class is a list collection that ensures that only one copy of an element 7 | * belongs to the list. This set is implemented as an IList and dispatches collection 8 | * change events when elements are added and removed. The set can also be assigned to data providers 9 | * of list and tree controls in Flex. 10 | * 11 | *

12 | * This set supports ordered iteration. 13 | *

14 | * 15 | * @author Dan Schultz 16 | */ 17 | public class Set extends List 18 | { 19 | private var _elements:Dictionary = new Dictionary(); 20 | 21 | /** 22 | * @copy List#List() 23 | */ 24 | public function Set(source:Array = null) 25 | { 26 | super(source); 27 | } 28 | 29 | /** 30 | * @inheritDoc 31 | */ 32 | override public function addItemAt(item:Object, index:int):void 33 | { 34 | if (!contains(item)) { 35 | _elements[item] = true; 36 | super.addItemAt(item, length); 37 | } 38 | } 39 | 40 | /** 41 | * @inheritDoc 42 | */ 43 | override public function contains(item:Object):Boolean 44 | { 45 | return _elements[item] != null; 46 | } 47 | 48 | /** 49 | * @private 50 | */ 51 | override public function setItemAt(item:Object, index:int):Object 52 | { 53 | // Disabled 54 | return null; 55 | } 56 | 57 | /** 58 | * @inheritDoc 59 | */ 60 | override public function removeItemAt(index:int):Object 61 | { 62 | var item:Object = getItemAt(index); 63 | if (item != null) { 64 | delete _elements[item]; 65 | } 66 | super.removeItemAt(index); 67 | return item; 68 | } 69 | } 70 | } -------------------------------------------------------------------------------- /src/mesh/operations/FaultOperationEvent.as: -------------------------------------------------------------------------------- 1 | package mesh.operations 2 | { 3 | import flash.events.Event; 4 | 5 | /** 6 | * An event dispatched by an operation to indicate that an error or fault 7 | * has occurred during its execution. 8 | * 9 | * @author Dan Schultz 10 | */ 11 | public class FaultOperationEvent extends OperationEvent 12 | { 13 | /** 14 | * An event type for when an operation has errored or faulted during execution. 15 | */ 16 | public static const FAULT:String = "fault"; 17 | 18 | /** 19 | * Constructor. 20 | * 21 | * @param summary A simple description of the fault. 22 | * @param detail A detailed description of the fault. 23 | */ 24 | public function FaultOperationEvent(summary:String, detail:String = "", code:String = "") 25 | { 26 | super(FAULT); 27 | 28 | _summary = summary == null ? "" : summary; 29 | _detail = detail == null ? "" : detail; 30 | _code = code == null ? "" : code; 31 | } 32 | 33 | /** 34 | * @private 35 | */ 36 | override public function clone():Event 37 | { 38 | return new FaultOperationEvent(summary, detail); 39 | } 40 | 41 | private var _summary:String; 42 | /** 43 | * A simple description of the fault. 44 | */ 45 | public function get summary():String 46 | { 47 | return _summary; 48 | } 49 | 50 | private var _detail:String; 51 | /** 52 | * A detailed description of the fault. 53 | */ 54 | public function get detail():String 55 | { 56 | return _detail; 57 | } 58 | 59 | private var _code:String; 60 | /** 61 | * A specific code given to the fault. 62 | */ 63 | public function get code():String 64 | { 65 | return _code; 66 | } 67 | } 68 | } -------------------------------------------------------------------------------- /tests/mesh/model/validators/PresenceValidatorTests.as: -------------------------------------------------------------------------------- 1 | package mesh.model.validators 2 | { 3 | 4 | import org.flexunit.assertThat; 5 | import org.hamcrest.object.equalTo; 6 | 7 | import mesh.core.object.inspect; 8 | 9 | public class PresenceValidatorTests 10 | { 11 | [Test] 12 | public function testValidate():void 13 | { 14 | var tests:Array = [ 15 | { 16 | object:{str:"hello", errors:new Errors(null)}, 17 | options:{property:"str"}, 18 | passes:true 19 | }, 20 | { 21 | object:{str:"", errors:new Errors(null)}, 22 | options:{property:"str"}, 23 | passes:false 24 | }, 25 | { 26 | object:{str:" ", errors:new Errors(null)}, 27 | options:{property:"str"}, 28 | passes:false 29 | }, 30 | { 31 | object:{num:0, errors:new Errors(null)}, 32 | options:{property:"num"}, 33 | passes:true 34 | }, 35 | { 36 | object:{num:NaN, errors:new Errors(null)}, 37 | options:{property:"num"}, 38 | passes:false 39 | }, 40 | { 41 | object:{elements:[1], errors:new Errors(null)}, 42 | options:{property:"elements"}, 43 | passes:true 44 | }, 45 | { 46 | object:{elements:[], errors:new Errors(null)}, 47 | options:{property:"elements"}, 48 | passes:false 49 | }, 50 | { 51 | object:{includes:false, errors:new Errors(null)}, 52 | options:{property:"includes"}, 53 | passes:true 54 | } 55 | ]; 56 | 57 | for each (var test:Object in tests) { 58 | new PresenceValidator(test.options).validate(test.object); 59 | assertThat("validation failed for test " + inspect(test.options), test.object.errors.length == 0, equalTo(test.passes)); 60 | } 61 | } 62 | } 63 | } -------------------------------------------------------------------------------- /src/mesh/operations/MethodOperation.as: -------------------------------------------------------------------------------- 1 | package mesh.operations 2 | { 3 | /** 4 | * An synchronous operation that calls a method on an object. The data returned 5 | * from the method call will be passed to the ResultOperationEvent.data 6 | * property. If an error is thrown during the execution of the method, the operation 7 | * will fault and dispatch the FaultOperationEvent.FAULT event. 8 | * 9 | *

10 | * Example: Using a method operation: 11 | * 12 | *

13 | * var str:String = "Hello World"; 14 | * var operation:MethodOperation = new MethodOperation(str.substr, 0, 5); 15 | * operation.addEventListener(ResultOperationEvent.RESULT, handleOperationResult); 16 | * operation.execute(); 17 | * 18 | * function handleOperationResult(event:ResultOperationEvent):void 19 | * { 20 | * trace(event.data); // Hello 21 | * } 22 | * 23 | *

24 | * 25 | * @author Dan Schultz 26 | */ 27 | public class MethodOperation extends Operation 28 | { 29 | private var _func:Function; 30 | private var _args:Array; 31 | 32 | /** 33 | * Constructor. 34 | * 35 | * @param func The function to execute. 36 | * @param args Arguments to pass to the function when the operation is executed. 37 | */ 38 | public function MethodOperation(func:Function, ... args) 39 | { 40 | super(); 41 | _func = func; 42 | _args = args; 43 | } 44 | 45 | /** 46 | * @inheritDoc 47 | */ 48 | override protected function executeRequest():void 49 | { 50 | try { 51 | result(_func.apply(null, _args)); 52 | } catch (e:Error) { 53 | fault(e.toString(), e.getStackTrace()); 54 | } 55 | } 56 | } 57 | } -------------------------------------------------------------------------------- /tests/mesh/core/reflection/MethodTests.as: -------------------------------------------------------------------------------- 1 | package mesh.core.reflection 2 | { 3 | import flash.events.Event; 4 | import flash.events.ProgressEvent; 5 | import flash.geom.Point; 6 | 7 | import org.flexunit.assertThat; 8 | import org.hamcrest.object.equalTo; 9 | import org.hamcrest.object.notNullValue; 10 | import org.hamcrest.object.nullValue; 11 | 12 | import spark.components.Label; 13 | 14 | public class MethodTests 15 | { 16 | [Test] 17 | public function testMethodsContainsClassMethods():void 18 | { 19 | var method:Method = new Type(Point).method("interpolate"); 20 | assertThat(method, notNullValue()); 21 | assertThat(method.isStatic, equalTo(true)); 22 | } 23 | 24 | [Test] 25 | public function testMethodsContainsInstanceMethods():void 26 | { 27 | var method:Method = new Type(Point).method("add"); 28 | assertThat(method, notNullValue()); 29 | assertThat(method.isStatic, equalTo(false)); 30 | } 31 | 32 | [Test] 33 | public function testMethodsContainsMethodsFromParent():void 34 | { 35 | var method:Method = new Type(ProgressEvent).method("formatToString"); 36 | assertThat(method, notNullValue()); 37 | assertThat(method.isStatic, equalTo(false)); 38 | } 39 | 40 | [Test] 41 | public function testMethodsDoesNotContainPropertiesFromParent():void 42 | { 43 | var method:Method = new Type(Label).method("suspendBackgroundProcessing"); 44 | assertThat(method, nullValue()); 45 | } 46 | 47 | [Test] 48 | public function testMethodReturnType():void 49 | { 50 | assertThat(new Type(ProgressEvent).method("preventDefault").returnType, nullValue()); 51 | assertThat(new Type(ProgressEvent).method("clone").returnType.clazz, equalTo(Event)); 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /src/mesh/model/source/DataSource.as: -------------------------------------------------------------------------------- 1 | package mesh.model.source 2 | { 3 | import flash.errors.IllegalOperationError; 4 | 5 | import mesh.model.Record; 6 | 7 | public class DataSource 8 | { 9 | public function DataSource() 10 | { 11 | 12 | } 13 | 14 | public function create(responder:IPersistenceResponder, snapshot:Snapshot):void 15 | { 16 | 17 | } 18 | 19 | public function createEach(responder:IPersistenceResponder, snapshots:Array):void 20 | { 21 | for each (var snapshot:Snapshot in snapshots) { 22 | create(responder, snapshot); 23 | } 24 | } 25 | 26 | public function belongingTo(responder:IRetrievalResponder, record:Record, type:Class):void 27 | { 28 | 29 | } 30 | 31 | public function destroy(responder:IPersistenceResponder, snapshot:Snapshot):void 32 | { 33 | 34 | } 35 | 36 | public function destroyEach(responder:IPersistenceResponder, snapshots:Array):void 37 | { 38 | for each (var snapshot:Snapshot in snapshots) { 39 | destroy(responder, snapshot); 40 | } 41 | } 42 | 43 | public function retrieve(responder:IRetrievalResponder, record:Record):void 44 | { 45 | 46 | } 47 | 48 | public function retrieveAll(responder:IRetrievalResponder, type:Class):void 49 | { 50 | 51 | } 52 | 53 | public function search(responder:IRetrievalResponder, type:Class, params:Object):void 54 | { 55 | 56 | } 57 | 58 | public function update(responder:IPersistenceResponder, snapshot:Snapshot):void 59 | { 60 | 61 | } 62 | 63 | public function updateEach(responder:IPersistenceResponder, snapshots:Array):void 64 | { 65 | for each (var snapshot:Snapshot in snapshots) { 66 | update(responder, snapshot); 67 | } 68 | } 69 | } 70 | } -------------------------------------------------------------------------------- /src/mesh/model/source/DataSourceRetrievalOperation.as: -------------------------------------------------------------------------------- 1 | package mesh.model.source 2 | { 3 | import mesh.model.RecordState; 4 | import mesh.model.store.Data; 5 | import mesh.model.store.RecordCache; 6 | import mesh.operations.Operation; 7 | 8 | /** 9 | * An operation that is responsible for executing a data source operation and handling 10 | * its result. 11 | * 12 | * @author Dan Schultz 13 | */ 14 | public class DataSourceRetrievalOperation extends Operation implements IRetrievalResponder 15 | { 16 | private var _records:RecordCache; 17 | private var _method:Function; 18 | private var _args:Array; 19 | 20 | /** 21 | * Constructor. 22 | * 23 | * @param records The store's records. 24 | * @param operation The data source method to execute. 25 | * @param args The args that will be passed to the data source. 26 | */ 27 | public function DataSourceRetrievalOperation(records:RecordCache, method:Function, args:Array) 28 | { 29 | super(); 30 | 31 | _records = records; 32 | 33 | _method = method; 34 | _args = [this].concat(args); 35 | } 36 | 37 | /** 38 | * @inheritDoc 39 | */ 40 | public function loaded(data:Data):void 41 | { 42 | _records.materialize(data, RecordState.loaded()); 43 | } 44 | 45 | /** 46 | * @inheritDoc 47 | */ 48 | public function failed(summary:String, detail:String = "", code:String = ""):void 49 | { 50 | fault(summary, detail, code); 51 | } 52 | 53 | /** 54 | * @inheritDoc 55 | */ 56 | public function finished():void 57 | { 58 | finish(true); 59 | } 60 | 61 | /** 62 | * @inheritDoc 63 | */ 64 | override protected function executeRequest():void 65 | { 66 | super.executeRequest(); 67 | _method.apply(null, _args); 68 | } 69 | } 70 | } -------------------------------------------------------------------------------- /src/mesh/operations/ServiceOperation.as: -------------------------------------------------------------------------------- 1 | package mesh.operations 2 | { 3 | import mx.rpc.AbstractService; 4 | import mx.rpc.AsyncResponder; 5 | import mx.rpc.AsyncToken; 6 | import mx.rpc.events.FaultEvent; 7 | import mx.rpc.events.ResultEvent; 8 | 9 | /** 10 | * An asynchronous operation that wraps the execution of a Flex RPC service. This operation 11 | * can be used with either HTTPMultiServices or RemoteObjects. 12 | * 13 | * @author Dan Schultz 14 | */ 15 | public class ServiceOperation extends NetworkOperation 16 | { 17 | private var _service:AbstractService; 18 | private var _name:String; 19 | private var _args:Array; 20 | 21 | /** 22 | * Constructor. 23 | * 24 | * @param service The service to execute the operation. 25 | * @param name The name of the operation to execute. 26 | * @param args A list of arguments to pass to the operation. 27 | */ 28 | public function ServiceOperation(service:AbstractService, name:String, ... args) 29 | { 30 | super(); 31 | _service = service; 32 | _name = name; 33 | _args = args; 34 | } 35 | 36 | /** 37 | * @inheritDoc 38 | */ 39 | override protected function request():void 40 | { 41 | super.request(); 42 | 43 | var token:AsyncToken = _service.getOperation(_name).send.apply(null, _args); 44 | token.addResponder(new AsyncResponder(handleAsyncTokenResult, handleAsyncTokenFault, token)); 45 | } 46 | 47 | private function handleAsyncTokenResult(event:ResultEvent, token:AsyncToken):void 48 | { 49 | result(event.result); 50 | } 51 | 52 | private function handleAsyncTokenFault(event:FaultEvent, token:AsyncToken):void 53 | { 54 | fault(event.fault.faultString, event.fault.faultDetail, event.fault.faultCode); 55 | } 56 | } 57 | } -------------------------------------------------------------------------------- /src/mesh/model/source/Snapshot.as: -------------------------------------------------------------------------------- 1 | package mesh.model.source 2 | { 3 | import mesh.model.Record; 4 | import mesh.model.RecordState; 5 | import mesh.model.associations.Association; 6 | import mesh.model.associations.AssociationCollection; 7 | 8 | import mx.utils.ObjectUtil; 9 | 10 | /** 11 | * The RecordSnapshot class represents a record at a specific time. 12 | * 13 | * @author Dan Schultz 14 | */ 15 | public class Snapshot 16 | { 17 | /** 18 | * Creates a new snapshot from an object. 19 | * 20 | * @param record The record to create the snapshot of. 21 | */ 22 | public function Snapshot(record:Record) 23 | { 24 | _record = record; 25 | snap(); 26 | } 27 | 28 | private function snap():void 29 | { 30 | _data = ObjectUtil.copy(record); 31 | 32 | for each (var association:Association in record.associations) { 33 | if (association is AssociationCollection) { 34 | var collection:AssociationCollection = (association as AssociationCollection); 35 | _data[association.property] = new AssociationCollectionSnapshot(collection.toArray(), collection.added, collection.removed); 36 | } 37 | } 38 | 39 | _state = record.state; 40 | } 41 | 42 | private var _data:Object; 43 | /** 44 | * The copied data. 45 | */ 46 | public function get data():Object 47 | { 48 | return _data; 49 | } 50 | 51 | private var _record:Record; 52 | /** 53 | * The record that this snapshot is based off of. 54 | */ 55 | public function get record():Record 56 | { 57 | return _record; 58 | } 59 | 60 | private var _state:RecordState; 61 | /** 62 | * The state of the record when the snapshot was created. 63 | */ 64 | public function get state():RecordState 65 | { 66 | return _state; 67 | } 68 | } 69 | } -------------------------------------------------------------------------------- /src/mesh/model/store/Cache.as: -------------------------------------------------------------------------------- 1 | package mesh.model.store 2 | { 3 | import flash.utils.Dictionary; 4 | 5 | import mesh.core.reflection.reflect; 6 | 7 | /** 8 | * The Cache is an internal structure used by the Store to store 9 | * its elements. 10 | * 11 | * @author Dan Schultz 12 | */ 13 | public class Cache 14 | { 15 | private var _indexes:Dictionary = new Dictionary(); 16 | 17 | public function Cache() 18 | { 19 | 20 | } 21 | 22 | /** 23 | * Determines the class type of an object. This method can be overridden by sub-classes. 24 | * By default, this method returns the class type of the object. 25 | * 26 | * @param data The object to determine the type for. 27 | * @return A class type. 28 | */ 29 | protected function determineType(data:Object):Class 30 | { 31 | return reflect(data).clazz; 32 | } 33 | 34 | /** 35 | * Returns a list of all the data with the given type. 36 | * 37 | * @param type The types to retrieve. 38 | * @return An indexed list. 39 | */ 40 | public function findIndex(type:Class):Index 41 | { 42 | return index(type); 43 | } 44 | 45 | /** 46 | * Inserts an item into the cache. 47 | * 48 | * @param item The item to insert. 49 | */ 50 | public function insert(item:Object):void 51 | { 52 | findIndex(determineType(item)).add(item); 53 | } 54 | 55 | /** 56 | * Removes an item from the cache. 57 | * 58 | * @param item The item to remove. 59 | */ 60 | public function remove(item:Object):void 61 | { 62 | findIndex(determineType(item)).remove(item); 63 | } 64 | 65 | private function index(type:Class):Index 66 | { 67 | if (_indexes[type] == null) { 68 | _indexes[type] = new Index(); 69 | } 70 | return _indexes[type]; 71 | } 72 | } 73 | } -------------------------------------------------------------------------------- /tests/mesh/model/store/ResultsListTests.as: -------------------------------------------------------------------------------- 1 | package mesh.model.store 2 | { 3 | import mesh.Person; 4 | import mesh.model.source.FixtureDataSource; 5 | import mesh.operations.MethodOperation; 6 | 7 | import mx.collections.ArrayList; 8 | 9 | import org.flexunit.assertThat; 10 | import org.hamcrest.object.equalTo; 11 | 12 | public class ResultsListTests 13 | { 14 | private var _fixtures:FixtureDataSource; 15 | private var _records:RecordCache; 16 | 17 | [Before] 18 | public function setup():void 19 | { 20 | _fixtures = new FixtureDataSource(Person); 21 | _records = new RecordCache(new Store(_fixtures), _fixtures, new DataCache()); 22 | } 23 | 24 | [Test] 25 | public function testLoad():void 26 | { 27 | var called:Boolean; 28 | var results:ResultsList = new ResultsList(new ArrayList(), new MethodOperation(function():void 29 | { 30 | called = true; 31 | })); 32 | results.load(); 33 | 34 | assertThat(called, equalTo(true)); 35 | assertThat(results.isLoaded, equalTo(true)); 36 | } 37 | 38 | [Test] 39 | public function testLoadOnlyOnce():void 40 | { 41 | var called:Boolean; 42 | var results:ResultsList = new ResultsList(new ArrayList(), new MethodOperation(function():void 43 | { 44 | called = true; 45 | })); 46 | results.load(); 47 | 48 | called = false; 49 | results.load(); 50 | 51 | assertThat(called, equalTo(false)); 52 | } 53 | 54 | [Test] 55 | public function testRefresh():void 56 | { 57 | var called:Boolean; 58 | var results:ResultsList = new ResultsList(new ArrayList(), new MethodOperation(function():void 59 | { 60 | called = true; 61 | })); 62 | results.load(); 63 | 64 | called = false; 65 | results.refresh(); 66 | 67 | assertThat(called, equalTo(true)); 68 | } 69 | } 70 | } -------------------------------------------------------------------------------- /tests/mesh/core/proxy/DataProxyTests.as: -------------------------------------------------------------------------------- 1 | package mesh.core.proxy 2 | { 3 | import mx.events.PropertyChangeEvent; 4 | 5 | import org.flexunit.assertThat; 6 | import org.hamcrest.object.equalTo; 7 | import org.hamcrest.object.notNullValue; 8 | 9 | public class DataProxyTests 10 | { 11 | private var _mock:Mock; 12 | private var _proxy:DataProxy; 13 | 14 | [Before] 15 | public function setup():void 16 | { 17 | _mock = new Mock(); 18 | _proxy = new DataProxy(_mock); 19 | } 20 | 21 | [Test] 22 | public function testProxyMethodCall():void 23 | { 24 | var say:String = "hello world"; 25 | assertThat(_proxy.say(say), equalTo(say)); 26 | } 27 | 28 | [Test] 29 | public function testProxyGetProperty():void 30 | { 31 | var name:String = "John Doe"; 32 | _mock.name = name; 33 | assertThat(_proxy.name, equalTo(name)); 34 | } 35 | 36 | [Test] 37 | public function testProxySetProperty():void 38 | { 39 | var name:String = "John Doe"; 40 | _proxy.name = name; 41 | assertThat(_proxy.name, equalTo(name)); 42 | } 43 | 44 | [Test] 45 | public function testDispatchesPropertyChangeEvents():void 46 | { 47 | var e:PropertyChangeEvent; 48 | 49 | _proxy.addEventListener(PropertyChangeEvent.PROPERTY_CHANGE, function(event:PropertyChangeEvent):void 50 | { 51 | e = event; 52 | }); 53 | _proxy.name = "John"; 54 | assertThat(e, notNullValue()); 55 | } 56 | 57 | [Test] 58 | public function testForwardsPropertyChangeEvents():void 59 | { 60 | var e:PropertyChangeEvent; 61 | 62 | _proxy.addEventListener(PropertyChangeEvent.PROPERTY_CHANGE, function(event:PropertyChangeEvent):void 63 | { 64 | e = event; 65 | }); 66 | _mock.name = "John"; 67 | assertThat(e, notNullValue()); 68 | } 69 | } 70 | } 71 | 72 | class Mock 73 | { 74 | [Bindable] public var name:String; 75 | 76 | public function say(str:String):String 77 | { 78 | return str; 79 | } 80 | } -------------------------------------------------------------------------------- /src/mesh/model/validators/Validator.as: -------------------------------------------------------------------------------- 1 | package mesh.model.validators 2 | { 3 | /** 4 | * A class that represents a test to run on an object to ensure a correct state. 5 | * 6 | * @author Dan Schultz 7 | */ 8 | public class Validator 9 | { 10 | /** 11 | * Constructor. 12 | * 13 | * @param options A set of options for this validator, as key-value pairs. 14 | */ 15 | public function Validator(options:Object) 16 | { 17 | _options = options; 18 | } 19 | 20 | /** 21 | * A utility method for sub-classes to parse and populate the fields for a numeric range. 22 | * This method will take a string defined on options[rangeField] in the format 23 | * i..k, and set options[lowerField] = i and options[upperField] = k. 24 | * 25 | * @param rangeField The field defining a range. 26 | * @param lowerField The field defining the lower bounds of the range. 27 | * @param upperField The field defining the upper bounds of the range. 28 | */ 29 | protected function populateRangeInOptions(rangeField:String, lowerField:String, upperField:String):void 30 | { 31 | if (options.hasOwnProperty(rangeField)) { 32 | var parsedRange:Array = options[rangeField].split(".."); 33 | options[lowerField] = parsedRange[0]; 34 | options[upperField] = parsedRange[1]; 35 | } 36 | } 37 | 38 | /** 39 | * Executes the validation on the given object, and returns a list of validation errors 40 | * for validations that have failed. If all validations pass, this method returns an 41 | * empty array. 42 | * 43 | * @param obj The object to validate. 44 | */ 45 | public function validate(obj:Object):void 46 | { 47 | 48 | } 49 | 50 | private var _options:Object; 51 | /** 52 | * The options that were defined for this validator as key-value pairs. 53 | */ 54 | public function get options():Object 55 | { 56 | return _options; 57 | } 58 | } 59 | } -------------------------------------------------------------------------------- /src/mesh/core/state/Action.as: -------------------------------------------------------------------------------- 1 | package mesh.core.state 2 | { 3 | /** 4 | * An Action defines an event that can be triggered from a state machine. 5 | * Actions contain transitions that change the current state of the state machine. These 6 | * transitions are invoked whenever the action is triggered. 7 | * 8 | *

9 | * To trigger an action, invoke the action's trigger() method, or call the 10 | * state machine's triggerAction() method. 11 | *

12 | * 13 | * @author Dan Schultz 14 | */ 15 | public class Action 16 | { 17 | private var _machine:StateMachine; 18 | private var _name:String; 19 | private var _transitions:Array = []; 20 | 21 | /** 22 | * Constructor. 23 | * 24 | * @param machine The machine that the action belongs to. 25 | * @param name The name of this action. 26 | */ 27 | public function Action(machine:StateMachine, name:String) 28 | { 29 | _machine = machine; 30 | _name = name; 31 | } 32 | 33 | /** 34 | * Triggers the action, and any transitions attached to it. 35 | */ 36 | public function trigger():void 37 | { 38 | for each (var transition:Transition in _transitions) { 39 | transition.trigger(); 40 | } 41 | } 42 | 43 | /** 44 | * Attaches a transition to this action. 45 | * 46 | * @param to The transition's end state. 47 | * @param from A list of states or a single start state of the transition. 48 | * @param guard A guard function with the following signature: 49 | * function():Boolean. 50 | * @return This action. 51 | */ 52 | public function transitionTo(to:Object, from:Object, guard:Function = null):Action 53 | { 54 | from = from is Array ? from : [from]; 55 | for each (var fromState:Object in from) { 56 | _transitions.push( new Transition(_machine, _machine.createState(fromState.toString()), _machine.createState(to.toString()) , guard) ); 57 | } 58 | return this; 59 | } 60 | } 61 | } -------------------------------------------------------------------------------- /src/mesh/model/store/RecordCache.as: -------------------------------------------------------------------------------- 1 | package mesh.model.store 2 | { 3 | import flash.errors.IllegalOperationError; 4 | 5 | import mesh.core.reflection.newInstance; 6 | import mesh.mesh_internal; 7 | import mesh.model.Record; 8 | import mesh.model.RecordState; 9 | import mesh.model.source.DataSource; 10 | 11 | use namespace mesh_internal; 12 | 13 | /** 14 | * The index of records belonging to the store. 15 | * 16 | * @author Dan Schultz 17 | */ 18 | public class RecordCache extends Cache 19 | { 20 | private var _store:Store; 21 | private var _cache:DataCache; 22 | private var _dataSource:DataSource; 23 | 24 | /** 25 | * Constructor. 26 | */ 27 | public function RecordCache(store:Store, dataSource:DataSource, cache:DataCache) 28 | { 29 | _store = store; 30 | _dataSource = dataSource; 31 | _cache = cache; 32 | } 33 | 34 | /** 35 | * @inheritDoc 36 | */ 37 | override public function insert(item:Object):void 38 | { 39 | var record:Record = Record( item ); 40 | 41 | if (record.store == null) { 42 | record.store = _store; 43 | } 44 | 45 | if (record.store != _store) { 46 | throw new IllegalOperationError("Cannot insert record into multiple caches."); 47 | } 48 | 49 | super.insert(item); 50 | } 51 | 52 | /** 53 | * Either creates, or returns an existing record from the store with the given data. 54 | * 55 | * @param data The data to assign on the record. 56 | * @param state The state of the record. 57 | * @return A record. 58 | */ 59 | public function materialize(data:Data, state:RecordState = null):* 60 | { 61 | _cache.insert(data); 62 | 63 | var record:Record = findIndex(data.type).byId(data.id); 64 | if (record == null) { 65 | record = newInstance(data.type); 66 | record.id = data.id; 67 | insert(record); 68 | } 69 | 70 | data.transferValues(record); 71 | 72 | if (state != null) { 73 | record.changeState(state); 74 | } 75 | 76 | return record; 77 | } 78 | } 79 | } -------------------------------------------------------------------------------- /tests/mesh/model/validators/LengthValidatorTests.as: -------------------------------------------------------------------------------- 1 | package mesh.model.validators 2 | { 3 | 4 | import org.flexunit.assertThat; 5 | import org.hamcrest.object.equalTo; 6 | 7 | import mesh.core.object.inspect; 8 | 9 | public class LengthValidatorTests 10 | { 11 | [Test] 12 | public function testValidate():void 13 | { 14 | var tests:Array = [ 15 | { 16 | object:{str:"Hello", errors:new Errors(null)}, 17 | options:{property:"str", minimum:5}, 18 | passes:true 19 | }, 20 | { 21 | object:{str:"Hello", errors:new Errors(null)}, 22 | options:{property:"str", minimum:6}, 23 | passes:false 24 | }, 25 | { 26 | object:{str:"Hello", errors:new Errors(null)}, 27 | options:{property:"str", maximum:5}, 28 | passes:true 29 | }, 30 | { 31 | object:{str:"Hello", errors:new Errors(null)}, 32 | options:{property:"str", maximum:4}, 33 | passes:false 34 | }, 35 | { 36 | object:{str:"Hello", errors:new Errors(null)}, 37 | options:{property:"str", between:"0..5"}, 38 | passes:true 39 | }, 40 | { 41 | object:{str:"Hello", errors:new Errors(null)}, 42 | options:{property:"str", between:"1..5"}, 43 | passes:true 44 | }, 45 | { 46 | object:{str:"Hello", errors:new Errors(null)}, 47 | options:{property:"str", between:"0..4"}, 48 | passes:false 49 | }, 50 | { 51 | object:{str:"Hello", errors:new Errors(null)}, 52 | options:{property:"str", between:"0..6"}, 53 | passes:true 54 | }, 55 | { 56 | object:{str:"Hello", errors:new Errors(null)}, 57 | options:{property:"str", length:"5"}, 58 | passes:true 59 | }, 60 | { 61 | object:{str:"Hello", errors:new Errors(null)}, 62 | options:{property:"str", length:"4"}, 63 | passes:false 64 | } 65 | ]; 66 | 67 | for each (var test:Object in tests) { 68 | new LengthValidator(test.options).validate(test.object); 69 | assertThat("validation failed for test " + inspect(test.options), test.object.errors.length == 0, equalTo(test.passes)); 70 | } 71 | } 72 | } 73 | } -------------------------------------------------------------------------------- /src/mesh/model/validators/LengthValidator.as: -------------------------------------------------------------------------------- 1 | package mesh.model.validators 2 | { 3 | /** 4 | * Validates the length of an object, such as the length of string or number of 5 | * elements in an array. 6 | * 7 | * @author Dan Schultz 8 | */ 9 | public class LengthValidator extends EachValidator 10 | { 11 | private static const CHECKS:Object = 12 | { 13 | length:function(propertyValue:Number, validationValue:Number):Boolean 14 | { 15 | return propertyValue == validationValue; 16 | }, 17 | minimum:function(propertyValue:Number, validationValue:Number):Boolean 18 | { 19 | return propertyValue >= validationValue; 20 | }, 21 | maximum:function(propertyValue:Number, validationValue:Number):Boolean 22 | { 23 | return propertyValue <= validationValue; 24 | } 25 | }; 26 | 27 | private static const MESSAGES:Object = 28 | { 29 | length:"wrongLength", 30 | minimum:"tooShort", 31 | maximum:"tooLong" 32 | }; 33 | 34 | /** 35 | * @copy Validator#Validator() 36 | */ 37 | public function LengthValidator(options:Object) 38 | { 39 | if (!options.hasOwnProperty("lengthProperty")) { 40 | options.lengthProperty = "length"; 41 | } 42 | 43 | if (!options.hasOwnProperty("wrongLength")) { 44 | options.wrongLength = "must be a length of {count}"; 45 | } 46 | 47 | if (!options.hasOwnProperty("tooShort")) { 48 | options.tooShort = "is too short (minimum is {count})"; 49 | } 50 | 51 | if (!options.hasOwnProperty("tooLong")) { 52 | options.tooLong = "is too long (maximum is {count})"; 53 | } 54 | 55 | super(options); 56 | populateRangeInOptions("between", "minimum", "maximum"); 57 | } 58 | 59 | /** 60 | * @inheritDoc 61 | */ 62 | override protected function validateProperty(obj:Object, property:String, value:Object):void 63 | { 64 | for (var check:String in CHECKS) { 65 | if (options.hasOwnProperty(check)) { 66 | if (!CHECKS[check](value[options.lengthProperty], options[check])) { 67 | obj.errors.add(property, options[MESSAGES[check]], {count: options[check]}); 68 | } 69 | } 70 | } 71 | } 72 | } 73 | } -------------------------------------------------------------------------------- /src/mesh/core/reflection/Property.as: -------------------------------------------------------------------------------- 1 | package mesh.core.reflection 2 | { 3 | import flash.utils.getDefinitionByName; 4 | 5 | /** 6 | * A class that represents a variable, getter or setter definition on a class. 7 | * 8 | * @author Dan Schultz 9 | */ 10 | public class Property extends Definition 11 | { 12 | /** 13 | * @copy Definition#Definition() 14 | */ 15 | public function Property(description:XML, belongsTo:Definition) 16 | { 17 | super(description, belongsTo); 18 | } 19 | 20 | /** 21 | * Returns the value for this property on an object. If the property does not exist or is 22 | * write only, an error is thrown. 23 | * 24 | * @param object The object to get the value from. 25 | * @return The property's value. 26 | */ 27 | public function value(object:Object):* 28 | { 29 | return isStatic ? reflect(object).clazz[name] : object[name]; 30 | } 31 | 32 | /** 33 | * true if this is a variable that has been defined with 34 | * const. 35 | */ 36 | public function get isConstant():Boolean 37 | { 38 | return description.name() == "constant"; 39 | } 40 | 41 | /** 42 | * true if this is a property that has been defined with 43 | * static. 44 | */ 45 | public function get isStatic():Boolean 46 | { 47 | return description.parent().name() == "type" && description.parent().@isStatic == "true"; 48 | } 49 | 50 | /** 51 | * true if this is a property that can be read from. 52 | */ 53 | public function get isReadable():Boolean 54 | { 55 | return description.name() != "accessor" || description.@access.toString().search("read") != -1; 56 | } 57 | 58 | /** 59 | * true if this is a property that can be read from. 60 | */ 61 | public function get isWritable():Boolean 62 | { 63 | return description.name() == "variable" || (!isConstant && description.@access.toString().search("write") != -1); 64 | } 65 | 66 | /** 67 | * The type that is defined for the property. 68 | */ 69 | public function get type():Type 70 | { 71 | return Type.reflect(getDefinitionByName(description.@type.toString())); 72 | } 73 | } 74 | } -------------------------------------------------------------------------------- /src/mesh/view/helpers/number/toPercentage.as: -------------------------------------------------------------------------------- 1 | package mesh.view.helpers.number 2 | { 3 | /** 4 | * Formats a number to a percentage string (i.e. 95%). 5 | * 6 | *

7 | * Options: 8 | *

18 | *

19 | * 20 | *

21 | * Examples: 22 | *

23 | * numberToPercentage(111.2345); // "111.23%" 24 | * numberToPercentage(111.2345, {precision:3}); // "111.235%" 25 | * numberToPercentage(12, {precision:3}); // "12.000%" 26 | * numberToPercentage(234.5, {precision:0}); // "235%" 27 | * numberToPercentage(111.234, {significant:true}); // "110%" 28 | * numberToPercentage(111.234, {precision:1, significant:true}); // "100%" 29 | * numberToPercentage(2, {precision:1, significant:true}); // "2%" 30 | * numberToPercentage(15, {precision:1, significant:true}); // "20%" 31 | * numberToPercentage(13, {precision:5, significant:true}); // "13.000%" 32 | * numberToPercentage(13, {precision:5, significant:true, stripInsignificantZeros:true}); // "13%" 33 | * numberToPercentage(389.32314, {precision:4, significant:true}); // "389.3%" 34 | * numberToPercentage(1111.2345, {precision:2, separator:",", delimiter:"."}); // "1.111,23%" 35 | * 36 | *

37 | * 38 | * @param number The number to format. 39 | * @param options The options to configure the format. 40 | * @return A formatted string. 41 | */ 42 | public function toPercentage(number:Number, options:Object = null):String 43 | { 44 | return withPrecision(number, options) + "%"; 45 | } 46 | } -------------------------------------------------------------------------------- /src/mesh/operations/FactoryOperation.as: -------------------------------------------------------------------------------- 1 | package mesh.operations 2 | { 3 | import flash.events.Event; 4 | 5 | import mx.rpc.events.ResultEvent; 6 | 7 | /** 8 | * An operation that wraps a factory method that generates an operation that this 9 | * operation will invoke. 10 | * 11 | * @author Dan Schultz 12 | */ 13 | public class FactoryOperation extends Operation 14 | { 15 | private var _factory:Function; 16 | private var _operation:Operation; 17 | private var _args:Array; 18 | 19 | private var _faultEvent:FaultOperationEvent; 20 | private var _resultEvent:ResultOperationEvent; 21 | 22 | /** 23 | * Constructor. 24 | * 25 | * @param factory The factory method that will return an operation to execute. 26 | * @param args The arguments to pass to the factory method. 27 | */ 28 | public function FactoryOperation(factory:Function, ...args) 29 | { 30 | super(); 31 | _factory = factory; 32 | _args = args; 33 | } 34 | 35 | /** 36 | * @inheritDoc 37 | */ 38 | override protected function cancelRequest():void 39 | { 40 | super.cancelRequest(); 41 | _operation.cancel(); 42 | } 43 | 44 | /** 45 | * @inheritDoc 46 | */ 47 | override protected function executeRequest():void 48 | { 49 | super.executeRequest(); 50 | 51 | _faultEvent = null; 52 | _resultEvent = null; 53 | 54 | _operation = _factory.apply(null, _args); 55 | _operation.addEventListener(FaultOperationEvent.FAULT, handleFault); 56 | _operation.addEventListener(ResultOperationEvent.RESULT, handleResult); 57 | _operation.addEventListener(FinishedOperationEvent.FINISHED, handleFinished); 58 | _operation.execute(); 59 | } 60 | 61 | private function handleFault(event:FaultOperationEvent):void 62 | { 63 | _faultEvent = event; 64 | } 65 | 66 | private function handleResult(event:ResultOperationEvent):void 67 | { 68 | _resultEvent = event; 69 | } 70 | 71 | private function handleFinished(event:FinishedOperationEvent):void 72 | { 73 | if (_faultEvent != null) { 74 | fault(_faultEvent.summary, _faultEvent.detail); 75 | } else if (_resultEvent != null) { 76 | result(_resultEvent.data); 77 | } else { 78 | finish(event.successful); 79 | } 80 | } 81 | } 82 | } -------------------------------------------------------------------------------- /tests/mesh/core/object/CopyTests.as: -------------------------------------------------------------------------------- 1 | package mesh.core.object 2 | { 3 | import org.flexunit.assertThat; 4 | import org.hamcrest.object.equalTo; 5 | 6 | public class CopyTests 7 | { 8 | private var _tests:Array; 9 | 10 | [Before] 11 | public function setup():void 12 | { 13 | var func:Function = function():void {}; 14 | 15 | var mockWithFunction:MockClass = new MockClass(); 16 | mockWithFunction.function2 = function():void {}; 17 | 18 | var mock:MockClass = new MockClass(); 19 | mock.value1 = "value1"; 20 | mock.value2 = "value2"; 21 | 22 | _tests = [ 23 | {to:new MockClass(), from:{value1:"value1", value2:"value2"}, expected:{value1:"value1", value2:"value2"}}, 24 | {to:new MockClass(), from:{value1:"value1", value2:"value2", value10:"value10"}, expected:{value1:"value1", value2:"value2"}}, 25 | {to:mockWithFunction, from:{value1:"value1", value2:"value2", function2:func}, expected:{value1:"value1", value2:"value2", function2:func}}, 26 | {to:new MockClass(), from:{value1:"value1", value2:"value2"}, options:{excludes:["value1"]}, expected:{value1:null, value2:"value2"}}, 27 | {to:{}, from:{value1:"value1", value2:"value2"}, expected:{value1:"value1", value2:"value2"}}, 28 | {to:{}, from:mock, options:{includes:["value1", "value2"]}, expected:{value1:"value1", value2:"value2"}}, 29 | {to:{}, from:mock, options:{includes:["value1", "value2"]}, expected:{value1:"value1", value2:"value2"}}, 30 | {to:{}, from:mock, options:{includes:["value1", "value2"], excludes:["value2"]}, expected:{value1:"value1", value2:null}}, 31 | ]; 32 | } 33 | 34 | [Test] 35 | public function testCopy():void 36 | { 37 | for each (var test:Object in _tests) { 38 | copy(test.from, test.to, test.options); 39 | 40 | for (var key:String in test.expected) { 41 | assertThat(test.to[key], equalTo(test.expected[key])); 42 | } 43 | } 44 | } 45 | } 46 | } 47 | 48 | class MockClass 49 | { 50 | public var value1:String; 51 | 52 | private var _value2:String; 53 | public function get value2():String 54 | { 55 | return _value2; 56 | } 57 | public function set value2(value:String):void 58 | { 59 | _value2 = value; 60 | } 61 | 62 | public function function1():String 63 | { 64 | return "hi"; 65 | } 66 | 67 | public var function2:Function; 68 | } -------------------------------------------------------------------------------- /tests/mesh/core/state/StateMachineTests.as: -------------------------------------------------------------------------------- 1 | package mesh.core.state 2 | { 3 | import flash.events.Event; 4 | import flash.events.EventDispatcher; 5 | 6 | import org.flexunit.assertThat; 7 | import org.hamcrest.object.equalTo; 8 | 9 | public class StateMachineTests 10 | { 11 | private var _machine:StateMachine; 12 | private var _state1:State; 13 | private var _state2:State; 14 | private var _state3:State; 15 | private var _changeState:Action; 16 | 17 | [Before] 18 | public function setup():void 19 | { 20 | _machine = new StateMachine(); 21 | _state1 = _machine.createState("state1"); 22 | _state2 = _machine.createState("state2"); 23 | _state3 = _machine.createState("state3"); 24 | _changeState = _machine.createAction("changeState"); 25 | } 26 | 27 | [Test] 28 | public function testOnEnter():void 29 | { 30 | 31 | var entered:Boolean; 32 | _state2.onEnter(function():void 33 | { 34 | entered = true; 35 | }); 36 | 37 | _changeState.transitionTo(_state2, _state1).trigger(); 38 | assertThat(entered, equalTo(true)); 39 | } 40 | 41 | [Test] 42 | public function testOnExit():void 43 | { 44 | var exited:Boolean; 45 | _state1.onExit(function():void 46 | { 47 | exited = true; 48 | }); 49 | 50 | _changeState.transitionTo(_state2, _state1).trigger(); 51 | assertThat(exited, equalTo(true)); 52 | } 53 | 54 | [Test] 55 | public function testGuard():void 56 | { 57 | _changeState.transitionTo(_state2, _state1, function():Boolean 58 | { 59 | return false; 60 | }).trigger(); 61 | assertThat(_machine.current, equalTo(_state1)); 62 | } 63 | 64 | [Test] 65 | public function testTransitionTo():void 66 | { 67 | _changeState.transitionTo(_state2, _state1).trigger(); 68 | assertThat(_machine.current, equalTo(_state2)); 69 | } 70 | 71 | [Test] 72 | public function testTransitionToFromMultiple():void 73 | { 74 | _changeState.transitionTo(_state2, [_state1, _state3]).trigger(); 75 | assertThat(_machine.current, equalTo(_state2)); 76 | } 77 | 78 | [Test] 79 | public function testListen():void 80 | { 81 | var dispatcher:EventDispatcher = new EventDispatcher(); 82 | _machine.listen(dispatcher, Event.ACTIVATE).transitionTo(_state2, _state1); 83 | dispatcher.dispatchEvent(new Event(Event.ACTIVATE)); 84 | assertThat(_machine.current, equalTo(_state2)); 85 | } 86 | } 87 | } -------------------------------------------------------------------------------- /src/mesh/operations/URLLoaderOperation.as: -------------------------------------------------------------------------------- 1 | package mesh.operations 2 | { 3 | import flash.events.Event; 4 | import flash.events.IOErrorEvent; 5 | import flash.events.SecurityErrorEvent; 6 | import flash.net.URLLoader; 7 | import flash.net.URLLoaderDataFormat; 8 | import flash.net.URLRequest; 9 | 10 | /** 11 | * An asynchronous operation that wraps Flash's URLLoader to perform 12 | * a network operation. 13 | * 14 | * @see http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/net/URLLoader.html URLLoader 15 | * @author Dan Schultz 16 | */ 17 | public class URLLoaderOperation extends NetworkOperation 18 | { 19 | private var _request:URLRequest; 20 | private var _loader:URLLoader; 21 | 22 | /** 23 | * Constructor. 24 | * 25 | * @param request The request to execute. 26 | * @param dataFormat The data format of the result. Use one of the constants defined on 27 | * URLLoaderDataFormat. 28 | * @see http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/net/URLLoaderDataFormat.html URLLoaderDataFormat 29 | */ 30 | public function URLLoaderOperation(request:URLRequest, dataFormat:String = URLLoaderDataFormat.TEXT) 31 | { 32 | super(); 33 | 34 | _request = request; 35 | _loader = new URLLoader(); 36 | _loader.dataFormat = dataFormat; 37 | } 38 | 39 | /** 40 | * @inheritDoc 41 | */ 42 | override protected function request():void 43 | { 44 | super.request(); 45 | 46 | _loader.addEventListener(IOErrorEvent.IO_ERROR, handleLoaderIOError); 47 | _loader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, handleLoaderSecurityError); 48 | _loader.addEventListener(Event.COMPLETE, handleLoaderComplete); 49 | _loader.load(_request); 50 | } 51 | 52 | /** 53 | * @inheritDoc 54 | */ 55 | override protected function cancelRequest():void 56 | { 57 | super.cancelRequest(); 58 | 59 | try { 60 | _loader.close(); 61 | } catch (e:Error) { 62 | 63 | } 64 | } 65 | 66 | private function handleLoaderIOError(event:IOErrorEvent):void 67 | { 68 | fault(event.text, event.text); 69 | } 70 | 71 | private function handleLoaderSecurityError(event:SecurityErrorEvent):void 72 | { 73 | fault(event.text, event.text); 74 | } 75 | 76 | private function handleLoaderComplete(event:Event):void 77 | { 78 | result(_loader.data); 79 | } 80 | } 81 | } -------------------------------------------------------------------------------- /tests/mesh/operations/MethodOperationTests.as: -------------------------------------------------------------------------------- 1 | package mesh.operations 2 | { 3 | import org.flexunit.assertThat; 4 | import org.hamcrest.object.equalTo; 5 | import org.hamcrest.object.notNullValue; 6 | import org.hamcrest.object.nullValue; 7 | 8 | public class MethodOperationTests 9 | { 10 | [Test] 11 | public function testExecuteWithoutRTE():void 12 | { 13 | var resultEvent:ResultOperationEvent; 14 | function handleOperationResult(event:ResultOperationEvent):void 15 | { 16 | resultEvent = event; 17 | }; 18 | 19 | var finishedEvent:FinishedOperationEvent; 20 | function handleOperationFinish(event:FinishedOperationEvent):void 21 | { 22 | finishedEvent = event; 23 | }; 24 | 25 | var str:String = "Hello World"; 26 | var operation:MethodOperation = new MethodOperation(str.substr, 0, 5); 27 | operation.addEventListener(ResultOperationEvent.RESULT, handleOperationResult); 28 | operation.addEventListener(FinishedOperationEvent.FINISHED, handleOperationFinish); 29 | operation.execute(); 30 | 31 | assertThat(resultEvent.data, equalTo(str.substr(0, 5))); 32 | assertThat(finishedEvent.successful, equalTo(true)); 33 | assertThat(operation.isExecuting, equalTo(false)); 34 | } 35 | 36 | [Test] 37 | public function testExecuteWithRTE():void 38 | { 39 | var resultEvent:ResultOperationEvent; 40 | function handleOperationResult(event:ResultOperationEvent):void 41 | { 42 | resultEvent = event; 43 | }; 44 | 45 | var faultEvent:FaultOperationEvent; 46 | function handleOperationFault(event:FaultOperationEvent):void 47 | { 48 | faultEvent = event; 49 | }; 50 | 51 | var finishedEvent:FinishedOperationEvent; 52 | function handleOperationFinish(event:FinishedOperationEvent):void 53 | { 54 | finishedEvent = event; 55 | }; 56 | 57 | var str:String = "Hello World"; 58 | var operation:MethodOperation = new MethodOperation(str.substr, 0, 5, 10); 59 | operation.addEventListener(ResultOperationEvent.RESULT, handleOperationResult); 60 | operation.addEventListener(FaultOperationEvent.FAULT, handleOperationFault); 61 | operation.addEventListener(FinishedOperationEvent.FINISHED, handleOperationFinish); 62 | operation.execute(); 63 | 64 | assertThat(resultEvent, nullValue()); 65 | assertThat(faultEvent, notNullValue()); 66 | assertThat(finishedEvent.successful, equalTo(false)); 67 | assertThat(operation.isExecuting, equalTo(false)); 68 | } 69 | } 70 | } -------------------------------------------------------------------------------- /src/mesh/model/store/Store.as: -------------------------------------------------------------------------------- 1 | package mesh.model.store 2 | { 3 | import mesh.core.reflection.newInstance; 4 | import mesh.mesh_internal; 5 | import mesh.model.Record; 6 | import mesh.model.RecordState; 7 | import mesh.model.source.DataSource; 8 | 9 | use namespace mesh_internal; 10 | 11 | /** 12 | * The Store holds all records belonging to your application. 13 | * 14 | * @author Dan Schultz 15 | */ 16 | public class Store 17 | { 18 | /** 19 | * Constructor. 20 | * 21 | * @param dataSource The data source. 22 | */ 23 | public function Store(dataSource:DataSource) 24 | { 25 | _dataSource = dataSource; 26 | _cache = new DataCache(); 27 | _records = new RecordCache(this, _dataSource, _cache); 28 | } 29 | 30 | /** 31 | * Adds a record to the store. 32 | * 33 | * @param record The record to add. 34 | */ 35 | public function add(record:Record):void 36 | { 37 | records.insert(record); 38 | } 39 | 40 | /** 41 | * Creates a new unpersisted record in the store. 42 | * 43 | * @param recordType The type of record to create. 44 | * @return A new record. 45 | */ 46 | public function create(recordType:Class):* 47 | { 48 | var record:Record = newInstance(recordType); 49 | record.changeState(RecordState.created()); 50 | records.insert(record); 51 | return record; 52 | } 53 | 54 | /** 55 | * Returns a query builder for the given record type. 56 | * 57 | * @param recordType The type of record to query. 58 | * @return A query builder. 59 | */ 60 | public function query(recordType:Class):QueryBuilder 61 | { 62 | return new QueryBuilder(_dataSource, _records, recordType); 63 | } 64 | 65 | /** 66 | * @copy Records#materialize() 67 | */ 68 | public function materialize(data:Data, state:RecordState = null):* 69 | { 70 | return _records.materialize(data, state); 71 | } 72 | 73 | private var _cache:DataCache; 74 | /** 75 | * @private 76 | */ 77 | mesh_internal function get cache():DataCache 78 | { 79 | return _cache; 80 | } 81 | 82 | private var _dataSource:DataSource; 83 | /** 84 | * @private 85 | */ 86 | mesh_internal function get dataSource():DataSource 87 | { 88 | return _dataSource; 89 | } 90 | 91 | private var _records:RecordCache; 92 | /** 93 | * @private 94 | */ 95 | mesh_internal function get records():RecordCache 96 | { 97 | return _records; 98 | } 99 | } 100 | } -------------------------------------------------------------------------------- /src/mesh/core/state/Transition.as: -------------------------------------------------------------------------------- 1 | package mesh.core.state 2 | { 3 | import flash.events.EventDispatcher; 4 | 5 | /** 6 | * Dispatched when the transition was triggered. 7 | */ 8 | [Event(name="transitioned", type="mesh.core.state.TransitionEvent")] 9 | 10 | /** 11 | * A Transition defines a state machine's change from one state to another state. 12 | * The transition may be given an optional guard function that is evaluated when the transition 13 | * is triggered. If the guard returns false the transition fails. This guard 14 | * function must have the following signature: function():Boolean. 15 | * 16 | * @author Dan Schultz 17 | */ 18 | public class Transition extends EventDispatcher 19 | { 20 | private var _machine:StateMachine; 21 | private var _guard:Function; 22 | private var _onTransition:Function; 23 | 24 | /** 25 | * Constructor. 26 | * 27 | * @param machine The machine the transition belongs to. 28 | * @param from The transition's start state. 29 | * @param to The transition's end state. 30 | * @param guard The guard function that is invoked upon the triggering of the transition. 31 | */ 32 | public function Transition(machine:StateMachine, from:State, to:State, guard:Function = null) 33 | { 34 | super(); 35 | _machine = machine; 36 | _from = from; 37 | _to = to; 38 | _guard = guard; 39 | } 40 | 41 | /** 42 | * Adds or replaces the callback function that is on a transition. The function should have 43 | * the following signature: function():void. 44 | * 45 | * @param block A function. 46 | * @return This instance. 47 | */ 48 | public function onTransition(block:Function):Transition 49 | { 50 | _onTransition = block; 51 | return this; 52 | } 53 | 54 | /** 55 | * Triggers the transition. If successful, the transition's state machine state will be put 56 | * into the end state. 57 | */ 58 | public function trigger():void 59 | { 60 | if (_from.equals(_machine.current) && (_guard == null || _guard())) { 61 | _machine.transitionTo(_to); 62 | dispatchEvent( new TransitionEvent(TransitionEvent.TRANSITIONED, this) ); 63 | } 64 | } 65 | 66 | private var _from:State; 67 | /** 68 | * The transition's start state. 69 | */ 70 | public function get from():State 71 | { 72 | return _from; 73 | } 74 | 75 | private var _to:State; 76 | /** 77 | * The transition's end state. 78 | */ 79 | public function get to():State 80 | { 81 | return _to; 82 | } 83 | } 84 | } -------------------------------------------------------------------------------- /src/mesh/model/store/ResultsList.as: -------------------------------------------------------------------------------- 1 | package mesh.model.store 2 | { 3 | import mesh.core.List; 4 | import mesh.mesh_internal; 5 | import mesh.model.Record; 6 | import mesh.operations.FinishedOperationEvent; 7 | import mesh.operations.Operation; 8 | 9 | import mx.collections.IList; 10 | import mx.collections.ListCollectionView; 11 | 12 | use namespace mesh_internal; 13 | 14 | public class ResultsList extends List 15 | { 16 | private var _resultsWrapper:ListCollectionView; 17 | 18 | public function ResultsList(results:IList, loadOperation:Operation) 19 | { 20 | super(); 21 | 22 | _loadOperation = loadOperation; 23 | _loadOperation.addEventListener(FinishedOperationEvent.FINISHED, function(event:FinishedOperationEvent):void 24 | { 25 | _isLoaded = event.successful; 26 | }); 27 | 28 | _resultsWrapper = new ListCollectionView(results); 29 | _resultsWrapper.filterFunction = filterRecord; 30 | _resultsWrapper.refresh(); 31 | list = _resultsWrapper; 32 | } 33 | 34 | /** 35 | * @private 36 | */ 37 | override public function addItemAt(item:Object, index:int):void 38 | { 39 | // Disabled so clients can't add records directly to the result. 40 | } 41 | 42 | private function filterRecord(record:Record):Boolean 43 | { 44 | return !(record.state.isDestroyed && record.state.isSynced); 45 | } 46 | 47 | /** 48 | * @copy mesh.model.Record#load() 49 | */ 50 | public function load():* 51 | { 52 | if (!isLoaded) { 53 | refresh(); 54 | } 55 | return this; 56 | } 57 | 58 | /** 59 | * @copy mesh.model.Record#refresh() 60 | */ 61 | public function refresh():* 62 | { 63 | loadOperation.queue(); 64 | loadOperation.execute(); 65 | return this; 66 | } 67 | 68 | /** 69 | * @private 70 | */ 71 | override public function removeItemAt(index:int):Object 72 | { 73 | // Disabled so clients can't remove records directly from the result. 74 | return null; 75 | } 76 | 77 | /** 78 | * @private 79 | */ 80 | override public function setItemAt(item:Object, index:int):Object 81 | { 82 | // Disabled so clients can't replace records directly in the result. 83 | return null; 84 | } 85 | 86 | private var _isLoaded:Boolean; 87 | /** 88 | * @copy mesh.model.Record#isLoaded 89 | */ 90 | public function get isLoaded():Boolean 91 | { 92 | return _isLoaded; 93 | } 94 | 95 | private var _loadOperation:Operation; 96 | /** 97 | * @copy mesh.model.Record#loadOperation 98 | */ 99 | public function get loadOperation():Operation 100 | { 101 | return _loadOperation; 102 | } 103 | } 104 | } -------------------------------------------------------------------------------- /tests/mesh/model/associations/HasOneAssociationTests.as: -------------------------------------------------------------------------------- 1 | package mesh.model.associations 2 | { 3 | import mesh.Account; 4 | import mesh.Customer; 5 | import mesh.Order; 6 | import mesh.mesh_internal; 7 | import mesh.model.source.FixtureDataSource; 8 | import mesh.model.source.MultiDataSource; 9 | import mesh.model.store.Data; 10 | import mesh.model.store.Store; 11 | 12 | import org.flexunit.assertThat; 13 | import org.hamcrest.object.equalTo; 14 | import org.hamcrest.object.notNullValue; 15 | 16 | use namespace mesh_internal; 17 | 18 | public class HasOneAssociationTests 19 | { 20 | private var _store:Store; 21 | private var _accounts:FixtureDataSource; 22 | private var _customers:FixtureDataSource; 23 | 24 | [Before] 25 | public function setup():void 26 | { 27 | _customers = new FixtureDataSource(Customer); 28 | _customers.add({id:1, firstName:"Jimmy", lastName:"Page", accountId:1}); 29 | 30 | _accounts = new FixtureDataSource(Account); 31 | _accounts.add({id:1, customerId:1}); 32 | 33 | var dataSources:MultiDataSource = new MultiDataSource(); 34 | dataSources.map(Customer, _customers); 35 | dataSources.map(Account, _accounts); 36 | dataSources.map(Order, new FixtureDataSource(Order)); 37 | 38 | _store = new Store(dataSources); 39 | } 40 | 41 | [Test] 42 | /** 43 | * Test loading the has-one association. 44 | */ 45 | public function testLoad():void 46 | { 47 | var customer:Customer = _store.query(Customer).find(1).load(); 48 | customer.account.load(); 49 | assertThat(customer.account.customer, equalTo(customer)); 50 | } 51 | 52 | [Test] 53 | /** 54 | * Test that the associated record changes, when the foreign key changes. 55 | */ 56 | public function testResetAssociationOnForeignKeyChange():void 57 | { 58 | var customer:Customer = _store.query(Customer).find(1).load(); 59 | customer.accountId = 2; 60 | assertThat(customer.account.id, equalTo(customer.accountId)); 61 | } 62 | 63 | [Test] 64 | /** 65 | * Test that the foreign key is updated when the association is set. 66 | */ 67 | public function testPopulateForeignKeyWhenAssociationSet():void 68 | { 69 | var customer:Customer = _store.query(Customer).find(1).load(); 70 | var account:Account = _store.materialize( new Data(Account, {id:2}) ); 71 | customer.account = account; 72 | assertThat(customer.accountId, equalTo(customer.account.id)); 73 | } 74 | 75 | [Test] 76 | public function testAssociatedRecordsAreInsertedIntoTheStore():void 77 | { 78 | var customer:Customer = _store.query(Customer).find(1).load(); 79 | var account:Account = new Account(); 80 | customer.account = account; 81 | assertThat(account.store, notNullValue()); 82 | } 83 | } 84 | } -------------------------------------------------------------------------------- /tests/mesh/core/reflection/TypeTests.as: -------------------------------------------------------------------------------- 1 | package mesh.core.reflection 2 | { 3 | import flash.display.DisplayObject; 4 | import flash.events.Event; 5 | import flash.events.ProgressEvent; 6 | 7 | import mx.core.IBorder; 8 | import mx.core.IFlexDisplayObject; 9 | 10 | import org.flexunit.assertThat; 11 | import org.hamcrest.collection.arrayWithSize; 12 | import org.hamcrest.collection.hasItem; 13 | import org.hamcrest.core.allOf; 14 | import org.hamcrest.core.isA; 15 | import org.hamcrest.core.not; 16 | import org.hamcrest.object.equalTo; 17 | import org.hamcrest.object.hasProperty; 18 | 19 | import spark.components.Label; 20 | 21 | public class TypeTests 22 | { 23 | [Test] 24 | public function testClassName():void 25 | { 26 | assertThat(new Type(Event).className, equalTo("Event")); 27 | assertThat(new Type(Number).className, equalTo("Number")); 28 | } 29 | 30 | [Test] 31 | public function testPackageName():void 32 | { 33 | assertThat(new Type(Event).packageName, equalTo("flash.events")); 34 | assertThat(new Type(Number).packageName, equalTo("")); 35 | } 36 | 37 | [Test] 38 | public function testName():void 39 | { 40 | assertThat(new Type(Event).name, equalTo("flash.events::Event")); 41 | } 42 | 43 | [Test] 44 | public function testParent():void 45 | { 46 | assertThat(new Type(ProgressEvent).parent.className, equalTo("Event")); 47 | } 48 | 49 | [Test] 50 | public function testParents():void 51 | { 52 | assertThat(new Type(ProgressEvent).parents, arrayWithSize(2)); 53 | } 54 | 55 | [Test] 56 | public function testImplementing():void 57 | { 58 | assertThat(new Type(Label).implementing, hasItem(allOf(isA(Type), hasProperty("className", equalTo("IUIComponent"))))); 59 | assertThat(new Type(Label).implementing, not(hasItem(hasProperty("className", equalTo("IBorder"))))); 60 | } 61 | 62 | [Test] 63 | public function testIsA():void 64 | { 65 | assertThat(new Type(ProgressEvent).isA(ProgressEvent), equalTo(true)); 66 | assertThat(new Type(ProgressEvent).isA(Event), equalTo(true)); 67 | assertThat(new Type(ProgressEvent).isA(new Type(Event)), equalTo(true)); 68 | assertThat(new Type(ProgressEvent).isA(DisplayObject), equalTo(false)); 69 | assertThat(new Type(Label).isA(IFlexDisplayObject), equalTo(true)); 70 | assertThat(new Type(Label).isA(IBorder), equalTo(false)); 71 | } 72 | 73 | [Test] 74 | public function testReflectInstanceOfClass():void 75 | { 76 | var instance:Class = String; 77 | assertThat(Type.reflect(instance).isA(String), equalTo(true)); 78 | assertThat(Type.reflect(instance).isA(Class), equalTo(false)); 79 | 80 | instance = Class; 81 | assertThat(Type.reflect(instance).isA(Class), equalTo(true)); 82 | } 83 | } 84 | } -------------------------------------------------------------------------------- /src/mesh/core/reflection/Definition.as: -------------------------------------------------------------------------------- 1 | package mesh.core.reflection 2 | { 3 | import flash.errors.IllegalOperationError; 4 | 5 | /** 6 | * A definition is a base class for any property, variable, constant, metadata, or 7 | * method that is specified on a class. 8 | * 9 | * @author Dan Schultz 10 | */ 11 | public class Definition 12 | { 13 | /** 14 | * Constructor. 15 | * 16 | * @param name The name of the definition. 17 | * @param belongsTo The definition that this belongs to. 18 | * @param description The XML that describes this definition. 19 | */ 20 | public function Definition(description:XML, belongsTo:Definition) 21 | { 22 | _belongsTo = belongsTo; 23 | _description = description; 24 | } 25 | 26 | /** 27 | * Checks that two definitions are equal. Two definitions are equal when they belong 28 | * to the same definition, and their names are equal. 29 | * 30 | * @param definition The definition to check. 31 | * @return true if the definitions are equal. 32 | */ 33 | public function equals(definition:Definition):Boolean 34 | { 35 | return this == definition || (definition != null && name == definition.name && belongsTo.equals(definition.belongsTo)); 36 | } 37 | 38 | /** 39 | * Returns the name for this definition. 40 | * 41 | * @return The name. 42 | */ 43 | public function hashCode():Object 44 | { 45 | return name; 46 | } 47 | 48 | /** 49 | * Returns the name of the definition. 50 | * 51 | * @return The definition's name. 52 | */ 53 | public function toString():String 54 | { 55 | return name; 56 | } 57 | 58 | private var _belongsTo:Definition; 59 | /** 60 | * The definition that this definition belongs to. 61 | */ 62 | protected function get belongsTo():Definition 63 | { 64 | return _belongsTo; 65 | } 66 | 67 | private var _description:XML; 68 | /** 69 | * The raw XML description for this definition. 70 | */ 71 | public function get description():XML 72 | { 73 | return _description; 74 | } 75 | 76 | private var _metadata:Array; 77 | /** 78 | * A list of Metadata definitions that are specified on this definition. 79 | */ 80 | public function get metadata():Array 81 | { 82 | if (_metadata == null) { 83 | _metadata = []; 84 | 85 | for each (var metadataXML:XML in description..metadata) { 86 | if (metadataXML.@name.toString() != "__go_to_definition_help") { 87 | _metadata.push(new Metadata(metadataXML, this)); 88 | } 89 | } 90 | } 91 | return _metadata.concat(); 92 | } 93 | 94 | private var _name:String; 95 | /** 96 | * The name of the definition, such as the property's or method's name. 97 | */ 98 | public function get name():String 99 | { 100 | if (_name == null) { 101 | _name = description.@name.toString() 102 | } 103 | return _name; 104 | } 105 | } 106 | } -------------------------------------------------------------------------------- /src/mesh/core/state/State.as: -------------------------------------------------------------------------------- 1 | package mesh.core.state 2 | { 3 | import flash.events.EventDispatcher; 4 | 5 | /** 6 | * Dispatched when the machine enters this state. 7 | */ 8 | [Event(name="enter", type="mesh.core.state.StateEvent")] 9 | 10 | /** 11 | * Dispatched when the machine exits this state. 12 | */ 13 | [Event(name="exit", type="mesh.core.state.StateEvent")] 14 | 15 | /** 16 | * An individual state within the state machine. 17 | * 18 | * @author Dan Schultz 19 | */ 20 | public class State extends EventDispatcher 21 | { 22 | private var _machine:StateMachine; 23 | 24 | private var _onEnter:Function; 25 | private var _onExit:Function; 26 | 27 | /** 28 | * Constructor. 29 | * 30 | * @param machine The state machine the state belongs to. 31 | * @param name The name for this state. 32 | */ 33 | public function State(machine:StateMachine, name:String) 34 | { 35 | super(); 36 | _name = name; 37 | _machine = machine; 38 | 39 | addEventListener(StateEvent.ENTER, function(event:StateEvent):void 40 | { 41 | if (_onEnter != null) { 42 | _onEnter(); 43 | } 44 | }); 45 | addEventListener(StateEvent.EXIT, function(event:StateEvent):void 46 | { 47 | if (_onExit != null) { 48 | _onExit(); 49 | } 50 | }); 51 | } 52 | 53 | public function equals(state:Object):Boolean 54 | { 55 | return state is State && name == state.name; 56 | } 57 | 58 | /** 59 | * Adds or replaces the callback function that is invoked when the state machine 60 | * enters this state. The function should have the following signature: 61 | * function():void. 62 | * 63 | * @param block A function. 64 | * @return This instance. 65 | */ 66 | public function onEnter(block:Function):State 67 | { 68 | _onEnter = block; 69 | return this; 70 | } 71 | 72 | /** 73 | * Called by the state machine when this state becomes the current state. 74 | */ 75 | internal function enter():void 76 | { 77 | dispatchEvent( new StateEvent(StateEvent.ENTER, this) ); 78 | } 79 | 80 | /** 81 | * Adds or replaces the callback function that is invoked when the state machine 82 | * exits this state. The function should have the following signature: 83 | * function():void. 84 | * 85 | * @param block A function. 86 | * @return This instance. 87 | */ 88 | public function onExit(block:Function):State 89 | { 90 | _onExit = block; 91 | return this; 92 | } 93 | 94 | /** 95 | * Called by the state machine when this state is transitioned to a new state. 96 | */ 97 | internal function exit():void 98 | { 99 | dispatchEvent( new StateEvent(StateEvent.EXIT, this) ); 100 | } 101 | 102 | /** 103 | * @private 104 | */ 105 | override public function toString():String 106 | { 107 | return _name; 108 | } 109 | 110 | private var _name:String; 111 | /** 112 | * The state's name. 113 | */ 114 | public function get name():String 115 | { 116 | return _name; 117 | } 118 | } 119 | } -------------------------------------------------------------------------------- /src/mesh/core/BatchedList.as: -------------------------------------------------------------------------------- 1 | package mesh.core 2 | { 3 | /** 4 | * A type of List that does not have all of its elements 5 | * when its first created, but incrementally loads its elements as they're 6 | * requested. 7 | * 8 | * @author Dan Schultz 9 | */ 10 | public class BatchedList extends List 11 | { 12 | private var _batches:Array = []; 13 | private var _delegate:IBatchedListDelegate; 14 | 15 | /** 16 | * Constructor. 17 | * 18 | * @param delegate The object responsible for fetching batches. 19 | * @param batchSize The size of each batch. 20 | */ 21 | public function BatchedList(delegate:IBatchedListDelegate, batchSize:uint) 22 | { 23 | super(); 24 | _delegate = delegate; 25 | _batchSize = batchSize; 26 | } 27 | 28 | private function batchIndexForListIndex(index:int):int 29 | { 30 | return Math.floor(index/batchSize); 31 | } 32 | 33 | /** 34 | * @inheritDoc 35 | */ 36 | override public function getItemAt(index:int, prefetch:int=0):Object 37 | { 38 | // The length hasn't been populated yet, so fetch the list length. 39 | if (isNaN(_providedLength)) { 40 | _delegate.requestLength(this); 41 | return undefined; 42 | } 43 | 44 | // The index is in bounds, so try to return the item if it's loaded, 45 | // or request the batch that would contain the item. 46 | if (index >= 0 && index < length) { 47 | var batchIndex:int = batchIndexForListIndex(index); 48 | if (_batches[batchIndex] != null) { 49 | return super.getItemAt(index); 50 | } else { 51 | _delegate.requestBatch(this, batchIndex, batchSize); 52 | return undefined; 53 | } 54 | } 55 | 56 | throw new RangeError(index.toString() + " is out of range."); 57 | } 58 | 59 | /** 60 | * Called by the delegate to insert the elements from a fetch. 61 | * 62 | * @param arrayOrList The elements of the batch. 63 | * @param index The index to insert the elements at. 64 | */ 65 | public function provideBatch(arrayOrList:Object, index:int):void 66 | { 67 | _batches[index] = arrayOrList; 68 | addAllAt(arrayOrList, index > length ? length : index); 69 | } 70 | 71 | /** 72 | * Delegates call this method to provide the total number of elements 73 | * in the list. 74 | * 75 | * @param length The number of elements in the list. 76 | */ 77 | public function provideLength(length:Number):void 78 | { 79 | _providedLength = length; 80 | } 81 | 82 | /** 83 | * Resets the elements in this list and forces the elements to be reloaded. 84 | */ 85 | public function reset():void 86 | { 87 | removeAll(); 88 | _batches = []; 89 | _providedLength = NaN; 90 | } 91 | 92 | private var _batchSize:uint; 93 | /** 94 | * The size of each batch in a request. 95 | */ 96 | public function get batchSize():uint 97 | { 98 | return _batchSize; 99 | } 100 | 101 | private var _providedLength:Number; 102 | /** 103 | * @inheritDoc 104 | */ 105 | override public function get length():int 106 | { 107 | if (isNaN(_providedLength)) { 108 | _delegate.requestLength(this); 109 | return 0; 110 | } 111 | return _providedLength; 112 | } 113 | } 114 | } -------------------------------------------------------------------------------- /src/mesh/model/validators/NumericValidator.as: -------------------------------------------------------------------------------- 1 | package mesh.model.validators 2 | { 3 | import mesh.core.inflection.humanize; 4 | 5 | /** 6 | * Validates if a property's value is a number and also the bounds of that value. This validator has 7 | * the following options: 8 | * 9 | * 20 | * 21 | * @author Dan Schultz 22 | */ 23 | public class NumericValidator extends EachValidator 24 | { 25 | private static const CHECKS:Object = 26 | { 27 | number:function(propertyValue:Number, ...args):Boolean 28 | { 29 | return !isNaN(propertyValue); 30 | }, 31 | integer:function(propertyValue:Number, ...args):Boolean 32 | { 33 | return propertyValue.toString().search(/\A[+-]?\d+\Z/) == 0; 34 | }, 35 | even:function(propertyValue:Number, ...args):Boolean 36 | { 37 | return (propertyValue % 2) == 0; 38 | }, 39 | odd:function(propertyValue:Number, ...args):Boolean 40 | { 41 | return (propertyValue % 2) != 0; 42 | }, 43 | equalTo:function(propertyValue:Number, validationValue:Number):Boolean 44 | { 45 | return propertyValue == validationValue; 46 | }, 47 | greaterThan:function(propertyValue:Number, validationValue:Number):Boolean 48 | { 49 | return propertyValue > validationValue; 50 | }, 51 | greaterThanOrEqualTo:function(propertyValue:Number, validationValue:Number):Boolean 52 | { 53 | return propertyValue >= validationValue; 54 | }, 55 | lessThan:function(propertyValue:Number, validationValue:Number):Boolean 56 | { 57 | return propertyValue < validationValue; 58 | }, 59 | lessThanOrEqualTo:function(propertyValue:Number, validationValue:Number):Boolean 60 | { 61 | return propertyValue <= validationValue; 62 | } 63 | }; 64 | 65 | private static const MESSAGES:Object = 66 | { 67 | number:"is not a {check}", 68 | integer:"is not an {check}", 69 | even:"must be {check}", 70 | odd:"must be {check}", 71 | greaterThan:"must be {check} {count}", 72 | greaterThanOrEqualTo:"must be {check} {count}", 73 | equalTo:"must be {check} {count}", 74 | lessThan:"must be {check} {count}", 75 | lessThanOrEqualTo:"must be {check} {count}" 76 | }; 77 | 78 | /** 79 | * @copy Validator#Validator() 80 | */ 81 | public function NumericValidator(options:Object) 82 | { 83 | super(options); 84 | options.number = true; 85 | populateRangeInOptions("between", "greaterThanOrEqualTo", "lessThanOrEqualTo"); 86 | } 87 | 88 | /** 89 | * @inheritDoc 90 | */ 91 | override protected function validateProperty(obj:Object, property:String, value:Object):void 92 | { 93 | for (var check:String in CHECKS) { 94 | if (options.hasOwnProperty(check)) { 95 | if (!CHECKS[check](value, options[check])) { 96 | obj.errors.add(property, MESSAGES[check], {check:humanize(check).toLowerCase(), count:options[check]}); 97 | } 98 | } 99 | } 100 | } 101 | } 102 | } -------------------------------------------------------------------------------- /tests/mesh/model/validators/NumericValidatorTests.as: -------------------------------------------------------------------------------- 1 | package mesh.model.validators 2 | { 3 | 4 | import org.flexunit.assertThat; 5 | import org.hamcrest.object.equalTo; 6 | 7 | import mesh.core.object.inspect; 8 | 9 | public class NumericValidatorTests 10 | { 11 | [Test] 12 | public function testValidate():void 13 | { 14 | var tests:Array = [ 15 | { 16 | object:{num:5, errors:new Errors(null)}, 17 | options:{property:"num", odd:true}, 18 | passes:true 19 | }, 20 | { 21 | object:{num:4, errors:new Errors(null)}, 22 | options:{property:"num", odd:true}, 23 | passes:false 24 | }, 25 | { 26 | object:{num:4, errors:new Errors(null)}, 27 | options:{property:"num", even:true}, 28 | passes:true 29 | }, 30 | { 31 | object:{num:5, errors:new Errors(null)}, 32 | options:{property:"num", even:true}, 33 | passes:false 34 | }, 35 | { 36 | object:{num:5, errors:new Errors(null)}, 37 | options:{property:"num", lessThan:6}, 38 | passes:true 39 | }, 40 | { 41 | object:{num:5, errors:new Errors(null)}, 42 | options:{property:"num", lessThan:5}, 43 | passes:false 44 | }, 45 | { 46 | object:{num:5, errors:new Errors(null)}, 47 | options:{property:"num", lessThanOrEqualTo:5}, 48 | passes:true 49 | }, 50 | { 51 | object:{num:5, errors:new Errors(null)}, 52 | options:{property:"num", lessThanOrEqualTo:4}, 53 | passes:false 54 | }, 55 | { 56 | object:{num:5, errors:new Errors(null)}, 57 | options:{property:"num", greaterThan:4}, 58 | passes:true 59 | }, 60 | { 61 | object:{num:5, errors:new Errors(null)}, 62 | options:{property:"num", greaterThan:5}, 63 | passes:false 64 | }, 65 | { 66 | object:{num:5, errors:new Errors(null)}, 67 | options:{property:"num", greaterThanOrEqualTo:5}, 68 | passes:true 69 | }, 70 | { 71 | object:{num:5, errors:new Errors(null)}, 72 | options:{property:"num", greaterThanOrEqualTo:6}, 73 | passes:false 74 | }, 75 | { 76 | object:{num:5, errors:new Errors(null)}, 77 | options:{property:"num", equalTo:5}, 78 | passes:true 79 | }, 80 | { 81 | object:{num:5, errors:new Errors(null)}, 82 | options:{property:"num", equalTo:4}, 83 | passes:false 84 | }, 85 | { 86 | object:{num:5, errors:new Errors(null)}, 87 | options:{property:"num", between:"4..6"}, 88 | passes:true 89 | }, 90 | { 91 | object:{num:5, errors:new Errors(null)}, 92 | options:{property:"num", between:"1..3"}, 93 | passes:false 94 | }, 95 | { 96 | object:{num:5.5, errors:new Errors(null)}, 97 | options:{property:"num", integer:true}, 98 | passes:false 99 | }, 100 | { 101 | object:{num:5, errors:new Errors(null)}, 102 | options:{property:"num", integer:true}, 103 | passes:true 104 | }, 105 | { 106 | object:{num:"abc", errors:new Errors(null)}, 107 | options:{property:"num"}, 108 | passes:false 109 | } 110 | ]; 111 | 112 | for each (var test:Object in tests) { 113 | new NumericValidator(test.options).validate(test.object); 114 | assertThat("validation failed for test " + inspect(test.options), test.object.errors.length == 0, equalTo(test.passes)); 115 | } 116 | } 117 | } 118 | } -------------------------------------------------------------------------------- /tests/mesh/model/RecordLoadTests.as: -------------------------------------------------------------------------------- 1 | package mesh.model 2 | { 3 | import mesh.AsyncTest; 4 | import mesh.Person; 5 | import mesh.model.source.FixtureDataSource; 6 | import mesh.model.store.Store; 7 | import mesh.operations.FinishedOperationEvent; 8 | import mesh.operations.OperationEvent; 9 | 10 | import org.flexunit.assertThat; 11 | import org.hamcrest.object.equalTo; 12 | import org.hamcrest.object.hasProperties; 13 | 14 | public class RecordLoadTests 15 | { 16 | private static const LATENCY:int = 100; 17 | 18 | private var _data:Object; 19 | private var _fixtures:FixtureDataSource; 20 | private var _store:Store; 21 | 22 | [Before] 23 | public function setup():void 24 | { 25 | _data = {id:1, firstName:"Jimmy", lastName:"Page"}; 26 | _fixtures = new FixtureDataSource(Person, {latency:LATENCY}); 27 | _fixtures.add(_data); 28 | _store = new Store(_fixtures); 29 | } 30 | 31 | private function assertRecordLoaded(record:Record, data:Object):void 32 | { 33 | assertThat(record, hasProperties(data)); 34 | assertThat(record.isLoaded, equalTo(true)); 35 | assertThat(record.state.isRemote, equalTo(true)); 36 | } 37 | 38 | [Test(async)] 39 | /** 40 | * Make sure that load() will load the data for the record. 41 | */ 42 | public function testLoad():void 43 | { 44 | var test:AsyncTest = new AsyncTest(this, LATENCY+100, function():void 45 | { 46 | assertRecordLoaded(person, _data); 47 | }); 48 | 49 | var person:Person = _store.query(Person).find(1); 50 | person.loadOperation.addEventListener(FinishedOperationEvent.FINISHED, function(event:FinishedOperationEvent):void 51 | { 52 | if (event.successful) { 53 | test.complete(); 54 | } 55 | }); 56 | person.load(); 57 | 58 | assertThat(person.state.isBusy, equalTo(true)); 59 | assertThat(person.state.isLoading, equalTo(true)); 60 | } 61 | 62 | [Test(async)] 63 | /** 64 | * Make sure that multiple calls to load() will no reload the entity. 65 | */ 66 | public function testOnlyLoadOnce():void 67 | { 68 | var test:AsyncTest = new AsyncTest(this, LATENCY+100, function():void 69 | { 70 | assertThat(loadCount, equalTo(1)); 71 | }); 72 | 73 | var loadCount:int = 0; 74 | var person:Person = _store.query(Person).find(1); 75 | person.loadOperation.addEventListener(OperationEvent.BEFORE_EXECUTE, function(event:OperationEvent):void 76 | { 77 | ++loadCount; 78 | }); 79 | person.loadOperation.addEventListener(FinishedOperationEvent.FINISHED, function(event:FinishedOperationEvent):void 80 | { 81 | if (event.successful) { 82 | person.load(); 83 | test.complete(); 84 | } 85 | }); 86 | person.load(); 87 | } 88 | 89 | [Test(async)] 90 | public function testRefresh():void 91 | { 92 | var test:AsyncTest = new AsyncTest(this, LATENCY+100, function():void 93 | { 94 | assertRecordLoaded(person, data); 95 | }); 96 | 97 | var person:Person = _store.query(Person).find(1); 98 | var data:Object = {id:1, firstName:"Steve", lastName:"Jobs"}; 99 | _fixtures.add(data); 100 | 101 | person.loadOperation.addEventListener(FinishedOperationEvent.FINISHED, function(event:FinishedOperationEvent):void 102 | { 103 | if (event.successful) { 104 | test.complete(); 105 | } 106 | }); 107 | person.refresh(); 108 | 109 | assertThat(person.state.isBusy, equalTo(true)); 110 | assertThat(person.state.isLoading, equalTo(true)); 111 | } 112 | } 113 | } -------------------------------------------------------------------------------- /src/mesh/core/reflection/newInstance.as: -------------------------------------------------------------------------------- 1 | package mesh.core.reflection 2 | { 3 | /** 4 | * Creates a new instance of the given class by using the given arguments. 5 | * 6 | * @param clazz The class to initialize. 7 | * @param args The set of arguments to pass to the class's constructor. 8 | * @return An initialized instance of the class. 9 | */ 10 | public function newInstance(clazz:Class, ... args):* 11 | { 12 | switch (args.length) 13 | { 14 | case 0: 15 | return new clazz(); 16 | break; 17 | case 1: 18 | return new clazz(args[0]); 19 | break; 20 | case 2: 21 | return new clazz(args[0], args[1]); 22 | break; 23 | case 3: 24 | return new clazz(args[0], args[1], args[2]); 25 | break; 26 | case 4: 27 | return new clazz(args[0], args[1], args[2], args[3]); 28 | break; 29 | case 5: 30 | return new clazz(args[0], args[1], args[2], args[3], args[4]); 31 | break; 32 | case 6: 33 | return new clazz(args[0], args[1], args[2], args[3], args[4], args[5]); 34 | break; 35 | case 7: 36 | return new clazz(args[0], args[1], args[2], args[3], args[4], args[5], args[6]); 37 | break; 38 | case 8: 39 | return new clazz(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7]); 40 | break; 41 | case 9: 42 | return new clazz(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8]); 43 | break; 44 | case 10: 45 | return new clazz(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9]); 46 | break; 47 | case 11: 48 | return new clazz(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9], args[10]); 49 | break; 50 | case 12: 51 | return new clazz(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9], args[10], args[11]); 52 | break; 53 | case 13: 54 | return new clazz(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9], args[10], args[11], args[12]); 55 | break; 56 | case 14: 57 | return new clazz(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9], args[10], args[11], args[12], args[13]); 58 | break; 59 | case 15: 60 | return new clazz(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9], args[10], args[11], args[12], args[13], args[14]); 61 | break; 62 | case 16: 63 | return new clazz(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9], args[10], args[11], args[12], args[13], args[14], args[15]); 64 | break; 65 | case 17: 66 | return new clazz(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9], args[10], args[11], args[12], args[13], args[14], args[15], args[16]); 67 | break; 68 | case 18: 69 | return new clazz(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9], args[10], args[11], args[12], args[13], args[14], args[15], args[16], args[17]); 70 | break; 71 | case 19: 72 | return new clazz(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9], args[10], args[11], args[12], args[13], args[14], args[15], args[16], args[17], args[18]); 73 | break; 74 | case 20: 75 | return new clazz(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9], args[10], args[11], args[12], args[13], args[14], args[15], args[16], args[17], args[18], args[19]); 76 | break; 77 | default: 78 | return null; 79 | } 80 | return null; 81 | } 82 | } -------------------------------------------------------------------------------- /tests/mesh/model/RecordCommitTests.as: -------------------------------------------------------------------------------- 1 | package mesh.model 2 | { 3 | import mesh.AsyncTest; 4 | import mesh.Customer; 5 | import mesh.Name; 6 | import mesh.Order; 7 | import mesh.model.source.FixtureDataSource; 8 | import mesh.model.source.MultiDataSource; 9 | import mesh.model.store.CommitResponder; 10 | import mesh.model.store.Store; 11 | import mesh.operations.FinishedOperationEvent; 12 | 13 | import org.flexunit.assertThat; 14 | import org.hamcrest.object.equalTo; 15 | 16 | public class RecordCommitTests 17 | { 18 | private static const LATENCY:int = 100; 19 | 20 | private var _store:Store; 21 | private var _dataSources:MultiDataSource; 22 | 23 | [Before] 24 | public function setup():void 25 | { 26 | var customers:FixtureDataSource = new FixtureDataSource(Customer, {latency:LATENCY}); 27 | customers.add({id:1, firstName:"Jimmy", lastName:"Page", accountId:1}); 28 | 29 | var orders:FixtureDataSource = new FixtureDataSource(Order, {latency:LATENCY}); 30 | orders.add({id:1, customerId:1}); 31 | orders.add({id:2, customerId:1}); 32 | orders.add({id:3, customerId:1}); 33 | 34 | _dataSources = new MultiDataSource(); 35 | _dataSources.map(Customer, customers); 36 | _dataSources.map(Order, orders); 37 | 38 | _store = new Store(_dataSources); 39 | } 40 | 41 | private function assertIsSynced(record:Record, isRemote:Boolean):void 42 | { 43 | assertThat(ID.isPopulated(record), equalTo(true)); 44 | assertThat(record.state.isRemote, equalTo(isRemote)); 45 | assertThat(record.state.isSynced, equalTo(true)); 46 | } 47 | 48 | [Test(async)] 49 | public function testCreate():void 50 | { 51 | var test:AsyncTest = new AsyncTest(this, LATENCY+100, function():void 52 | { 53 | assertIsSynced(customer, true); 54 | }); 55 | 56 | var customer:Customer = _store.create(Customer); 57 | customer.save(new CommitResponder(function():void 58 | { 59 | test.complete(); 60 | })); 61 | 62 | assertThat(customer.state.isBusy, equalTo(true)); 63 | assertThat(customer.state.isSaving, equalTo(true)); 64 | } 65 | 66 | [Test(async)] 67 | public function testDestroy():void 68 | { 69 | var test:AsyncTest = new AsyncTest(this, LATENCY+(200*2), function():void 70 | { 71 | assertIsSynced(customer, false); 72 | }); 73 | 74 | var customer:Customer = _store.query(Customer).find(1); 75 | customer.loadOperation.addEventListener(FinishedOperationEvent.FINISHED, function(event:FinishedOperationEvent):void 76 | { 77 | customer.destroy().save(new CommitResponder(function():void 78 | { 79 | test.complete(); 80 | })); 81 | 82 | assertThat(customer.state.isBusy, equalTo(true)); 83 | assertThat(customer.state.isSaving, equalTo(true)); 84 | }); 85 | customer.load(); 86 | } 87 | 88 | [Test(async)] 89 | public function testUpdate():void 90 | { 91 | var test:AsyncTest = new AsyncTest(this, LATENCY+(200*2), function():void 92 | { 93 | assertIsSynced(customer, true); 94 | }); 95 | 96 | var customer:Customer = _store.query(Customer).find(1); 97 | customer.loadOperation.addEventListener(FinishedOperationEvent.FINISHED, function(event:FinishedOperationEvent):void 98 | { 99 | customer.name = new Name("Steve", "Jobs"); 100 | customer.save(new CommitResponder(function():void 101 | { 102 | test.complete(); 103 | })); 104 | 105 | assertThat(customer.state.isBusy, equalTo(true)); 106 | assertThat(customer.state.isSaving, equalTo(true)); 107 | }); 108 | customer.load(); 109 | } 110 | } 111 | } -------------------------------------------------------------------------------- /src/mesh/model/RecordState.as: -------------------------------------------------------------------------------- 1 | package mesh.model 2 | { 3 | import flash.errors.IllegalOperationError; 4 | import flash.utils.Dictionary; 5 | 6 | public class RecordState 7 | { 8 | public static const SYNCED:int = 0x1; 9 | public static const DIRTY:int = 0x2; 10 | public static const BUSY:int = 0x4; 11 | 12 | public static const ERRORED:int = 0x1000; 13 | 14 | public static const INIT:int = 0x100; 15 | public static const CREATED:int = 0x200; 16 | public static const LOADED:int = 0x400; 17 | public static const DESTROYED:int = 0x800; 18 | 19 | public function RecordState(value:int) 20 | { 21 | _value = value; 22 | } 23 | 24 | private static const CACHE:Dictionary = new Dictionary(); 25 | private static function cache(value:int):RecordState 26 | { 27 | var result:RecordState = CACHE[value]; 28 | if (result == null) { 29 | result = CACHE[value] = new RecordState(value); 30 | } 31 | return result; 32 | } 33 | 34 | public static function init():RecordState 35 | { 36 | return cache(INIT); 37 | } 38 | 39 | public static function created():RecordState 40 | { 41 | return cache(CREATED | DIRTY); 42 | } 43 | 44 | public static function loaded():RecordState 45 | { 46 | return cache(LOADED | SYNCED); 47 | } 48 | 49 | public static function destroy():RecordState 50 | { 51 | return cache(DESTROYED | DIRTY); 52 | } 53 | 54 | public function dirty():RecordState 55 | { 56 | return cache(value | DIRTY); 57 | } 58 | 59 | public function equals(obj:Object):Boolean 60 | { 61 | return obj == this || (obj is RecordState && value == obj.value); 62 | } 63 | 64 | public function busy():RecordState 65 | { 66 | return cache(value | BUSY); 67 | } 68 | 69 | public function synced():RecordState 70 | { 71 | if (isBusy) { 72 | if ((value & CREATED || value & LOADED) && value & BUSY) { 73 | return loaded(); 74 | } else if (value & DESTROYED && value & BUSY) { 75 | return cache(DESTROYED | SYNCED); 76 | } 77 | } 78 | 79 | throw new IllegalOperationError("Record state change not defined."); 80 | } 81 | 82 | public function get isBusy():Boolean 83 | { 84 | return (value & BUSY) != 0; 85 | } 86 | 87 | public function get isDestroyed():Boolean 88 | { 89 | return (value & DESTROYED) != 0; 90 | } 91 | 92 | public function get isInit():Boolean 93 | { 94 | return (value & INIT) != 0; 95 | } 96 | 97 | public function get isLoading():Boolean 98 | { 99 | return isBusy && ((value & INIT) != 0 || !isSaving); 100 | } 101 | 102 | public function get isRemote():Boolean 103 | { 104 | return (value & LOADED) != 0 || (isDestroyed && (isBusy || !isSynced)); 105 | } 106 | 107 | public function get isSaving():Boolean 108 | { 109 | var isCreated:Boolean = (value & CREATED) != 0; 110 | return (isRemote || isCreated) && isBusy; 111 | } 112 | 113 | public function get isSynced():Boolean 114 | { 115 | return (value & DIRTY) == 0; 116 | } 117 | 118 | public function get willBeCreated():Boolean 119 | { 120 | return (value & CREATED) != 0 && !isSynced && !isBusy; 121 | } 122 | 123 | public function get willBeUpdated():Boolean 124 | { 125 | return (value & LOADED) != 0 && !isSynced && !isBusy; 126 | } 127 | 128 | public function get willBeDestroyed():Boolean 129 | { 130 | return (value & DESTROYED) != 0 && !isSynced && !isBusy; 131 | } 132 | 133 | private var _value:int; 134 | public function get value():int 135 | { 136 | return _value; 137 | } 138 | } 139 | } -------------------------------------------------------------------------------- /src/mesh/view/helpers/number/withPrecision.as: -------------------------------------------------------------------------------- 1 | package mesh.view.helpers.number 2 | { 3 | import mesh.core.number.round; 4 | import mesh.core.object.merge; 5 | 6 | /** 7 | * Formats a number with a certain level of precision. For example, 123.45 has 8 | * a precision of 2 when significant is false, and a precision of 9 | * 5 when significant is true. 10 | * 11 | *

12 | * Options: 13 | *

23 | *

24 | * 25 | *

26 | * Examples: 27 | *

28 | * numberWithPrecision(111.2345); // "111.23" 29 | * numberWithPrecision(111.2345, {precision:3}); // "111.235" 30 | * numberWithPrecision(12, {precision:3}); // "12.000" 31 | * numberWithPrecision(234.5, {precision:0}); // "235" 32 | * numberWithPrecision(111.234, {significant:true}); // "110" 33 | * numberWithPrecision(111.234, {precision:1, significant:true}); // "100" 34 | * numberWithPrecision(2, {precision:1, significant:true}); // "2" 35 | * numberWithPrecision(15, {precision:1, significant:true}); // "20" 36 | * numberWithPrecision(13, {precision:5, significant:true}); // "13.000" 37 | * numberWithPrecision(13, {precision:5, significant:true, stripInsignificantZeros:true}); // "13" 38 | * numberWithPrecision(389.32314, {precision:4, significant:true}); // "389.3" 39 | * numberWithPrecision(1111.2345, {precision:2, separator:",", delimiter:"."}); // "1.111,23" 40 | * 41 | *

42 | * 43 | * @param number The number to format. 44 | * @param options The options to configure the format. 45 | * @return A formatted string. 46 | */ 47 | public function withPrecision(number:Number, options:Object = null):String 48 | { 49 | options = merge({precision:2, significant:false, stripInsignificantZeros:false, delimiter:",", separator:"."}, options); 50 | 51 | var digits:Number = 1; 52 | var roundedNumber:Number = 0; 53 | var formattedNumber:String; 54 | var significant:Boolean = options.significant; 55 | var stripInsignificantZeros:Boolean = options.stripInsignificantZeros; 56 | var precision:int = options.precision; 57 | var separator:String = options.separator; 58 | 59 | if (significant && precision > 0) { 60 | if (number != 0) { 61 | digits = Math.ceil(Math.log(number < 0 ? -number: number) * Math.LOG10E); 62 | var magnitude:Number = Math.pow(10, precision - digits); 63 | roundedNumber = Math.round(number*magnitude) / magnitude; 64 | } 65 | 66 | precision = precision - digits; 67 | precision = precision > 0 ? precision : 0; 68 | } else { 69 | roundedNumber = round(number, precision); 70 | } 71 | formattedNumber = roundedNumber.toFixed(precision); 72 | 73 | var parts:Array = withDelimiter(roundedNumber, options).split(separator); 74 | if (formattedNumber.indexOf(separator) != -1) { 75 | parts[1] = formattedNumber.split(separator)[1]; 76 | } 77 | formattedNumber = parts.join(separator); 78 | 79 | if (stripInsignificantZeros) { 80 | formattedNumber = formattedNumber.split(separator).shift(); 81 | } 82 | 83 | return formattedNumber; 84 | } 85 | } -------------------------------------------------------------------------------- /src/mesh/model/store/Data.as: -------------------------------------------------------------------------------- 1 | package mesh.model.store 2 | { 3 | import flash.utils.Proxy; 4 | import flash.utils.flash_proxy; 5 | 6 | import mesh.core.object.merge; 7 | import mesh.model.Record; 8 | 9 | import mx.utils.UIDUtil; 10 | 11 | /** 12 | * The ExternalData class wraps data that is retrieved from a data source and is 13 | * inserted into the store. 14 | * 15 | * @author Dan Schultz 16 | */ 17 | public dynamic class Data extends Proxy 18 | { 19 | private var _data:Object; 20 | private var _options:Object; 21 | 22 | /** 23 | * Constructor. When instantiating a new data object, you can pass an options hash with the 24 | * following options: 25 | * 26 | * 33 | * 34 | * @param type The record type that maps to this data. 35 | * @param data The data from the data source. 36 | * @param options A set of options to configure this data object. 37 | */ 38 | public function Data(type:Class, data:Object, options:Object = null) 39 | { 40 | super(); 41 | _data = data; 42 | _type = type; 43 | _options = merge({idField:"id", deserializer:transfer}, options); 44 | } 45 | 46 | /** 47 | * Checks if two data objects are equal. Data objects are equal if they're keys are the same, 48 | * or if the record types are the same and the objects have the same ID. 49 | * 50 | * @param obj The object to check. 51 | * @return true if the data is equal. 52 | */ 53 | public function equals(obj:Object):Boolean 54 | { 55 | if (obj is Data) { 56 | return (key == (obj as Data).key) || (type == (obj as Data).type && id == (obj as Data).id); 57 | } 58 | return false; 59 | } 60 | 61 | /** 62 | * @inheritDoc 63 | */ 64 | override flash_proxy function getProperty(name:*):* 65 | { 66 | return _data[name]; 67 | } 68 | 69 | /** 70 | * Returns the key that has been given to this data; 71 | * 72 | * @return The data's key. 73 | */ 74 | public function hashCode():Object 75 | { 76 | return key; 77 | } 78 | 79 | /** 80 | * @inheritDoc 81 | */ 82 | override flash_proxy function setProperty(name:*, value:*):void 83 | { 84 | _data[name] = value; 85 | } 86 | 87 | private function transfer(record:Record, data:Object):void 88 | { 89 | for (var key:String in data) { 90 | if (record.hasOwnProperty(key)) { 91 | try { 92 | record[key] = data[key]; 93 | } catch (e:Error) { 94 | 95 | } 96 | } 97 | } 98 | } 99 | 100 | /** 101 | * Transfers the values on this data to the given object. 102 | * 103 | * @param to The object to set the values on. 104 | */ 105 | public function transferValues(to:Object):void 106 | { 107 | _options.deserializer(to, _data); 108 | to.id = id; 109 | } 110 | 111 | /** 112 | * The ID that was assigned to this data by the data source. 113 | */ 114 | public function get id():Object 115 | { 116 | return _data[_options.idField]; 117 | } 118 | 119 | private var _key:Object; 120 | /** 121 | * A global unique key given to this data by the store. 122 | */ 123 | public function get key():Object 124 | { 125 | if (_key == null) { 126 | _key = UIDUtil.createUID(); 127 | } 128 | return _key; 129 | } 130 | 131 | private var _type:Class; 132 | /** 133 | * The record type that maps to this data. 134 | */ 135 | public function get type():Class 136 | { 137 | return _type; 138 | } 139 | } 140 | } -------------------------------------------------------------------------------- /src/mesh/core/proxy/DataProxy.as: -------------------------------------------------------------------------------- 1 | package mesh.core.proxy 2 | { 3 | import flash.events.Event; 4 | import flash.events.EventDispatcher; 5 | import flash.events.IEventDispatcher; 6 | import flash.utils.Proxy; 7 | import flash.utils.flash_proxy; 8 | 9 | import mx.events.PropertyChangeEvent; 10 | 11 | use namespace flash_proxy; 12 | 13 | /** 14 | * The DataProxy class represents a base Proxy implementation. A 15 | * proxy wraps an arbitrary object and forwards function and property calls to it. In 16 | * addition, this proxy will forward any property change events dispatched by its wrapped 17 | * object. This allows for UI controls to bind directly to the proxy class. 18 | * 19 | * @author Dan Schultz 20 | */ 21 | public dynamic class DataProxy extends Proxy implements IEventDispatcher 22 | { 23 | private var _dispatcher:EventDispatcher; 24 | 25 | /** 26 | * Constructor. 27 | */ 28 | public function DataProxy(obj:Object = null) 29 | { 30 | super(); 31 | _dispatcher = new EventDispatcher(this); 32 | object = obj; 33 | } 34 | 35 | /** 36 | * @inheritDoc 37 | */ 38 | override flash_proxy function callProperty(name:*, ...parameters):* 39 | { 40 | if (object != null) { 41 | return object[name].apply(null, parameters); 42 | } 43 | return undefined; 44 | } 45 | 46 | /** 47 | * @inheritDoc 48 | */ 49 | override flash_proxy function getProperty(name:*):* 50 | { 51 | if (object != null) { 52 | return object[name]; 53 | } 54 | return undefined; 55 | } 56 | 57 | private function handlePropertyChange(event:PropertyChangeEvent):void 58 | { 59 | dispatchEvent(event); 60 | } 61 | 62 | /** 63 | * @inheritDoc 64 | */ 65 | override flash_proxy function hasProperty(name:*):Boolean 66 | { 67 | try { 68 | return getProperty(name) !== undefined; 69 | } catch (e:Error) { 70 | 71 | } 72 | return false; 73 | } 74 | 75 | /** 76 | * @inheritDoc 77 | */ 78 | override flash_proxy function setProperty(name:*, value:*):void 79 | { 80 | if (object != null) { 81 | object[name] = value; 82 | } 83 | } 84 | 85 | private var _object:*; 86 | [Bindable] 87 | /** 88 | * The object being proxied. 89 | */ 90 | flash_proxy function get object():* 91 | { 92 | return _object; 93 | } 94 | flash_proxy function set object(value:*):void 95 | { 96 | if (_object is IEventDispatcher) { 97 | (_object as IEventDispatcher).removeEventListener(PropertyChangeEvent.PROPERTY_CHANGE, handlePropertyChange); 98 | } 99 | 100 | _object = value; 101 | 102 | if (_object is IEventDispatcher) { 103 | (_object as IEventDispatcher).addEventListener(PropertyChangeEvent.PROPERTY_CHANGE, handlePropertyChange); 104 | } 105 | } 106 | 107 | /** 108 | * @inheritDoc 109 | */ 110 | public function addEventListener(type:String, listener:Function, useCapture:Boolean = false, priority:int = 0, useWeakReference:Boolean = false):void 111 | { 112 | _dispatcher.addEventListener(type, listener, useCapture, priority, useWeakReference); 113 | } 114 | 115 | /** 116 | * @inheritDoc 117 | */ 118 | public function dispatchEvent(event:Event):Boolean 119 | { 120 | return _dispatcher.dispatchEvent(event); 121 | } 122 | 123 | /** 124 | * @inheritDoc 125 | */ 126 | public function hasEventListener(type:String):Boolean 127 | { 128 | return _dispatcher.hasEventListener(type); 129 | } 130 | 131 | /** 132 | * @inheritDoc 133 | */ 134 | public function removeEventListener(type:String, listener:Function, useCapture:Boolean = false):void 135 | { 136 | _dispatcher.removeEventListener(type, listener, useCapture); 137 | } 138 | 139 | /** 140 | * @inheritDoc 141 | */ 142 | public function willTrigger(type:String):Boolean 143 | { 144 | return _dispatcher.willTrigger(type); 145 | } 146 | } 147 | } -------------------------------------------------------------------------------- /src/mesh/model/associations/HasAssociation.as: -------------------------------------------------------------------------------- 1 | package mesh.model.associations 2 | { 3 | import flash.errors.IllegalOperationError; 4 | 5 | import mesh.model.ID; 6 | import mesh.model.Record; 7 | 8 | import mx.events.PropertyChangeEvent; 9 | 10 | /** 11 | * The base class for any association that links to a single record. 12 | * 13 | * @author Dan Schultz 14 | */ 15 | public class HasAssociation extends Association 16 | { 17 | /** 18 | * @copy AssociationProxy#AssociationProxy() 19 | */ 20 | public function HasAssociation(owner:Record, property:String, options:Object = null) 21 | { 22 | super(owner, property, options); 23 | checkForRequiredFields(); 24 | owner.addEventListener(PropertyChangeEvent.PROPERTY_CHANGE, handleOwnerPropertyChange); 25 | } 26 | 27 | /** 28 | * @inheritDoc 29 | */ 30 | override protected function associate(record:Record):void 31 | { 32 | super.associate(record); 33 | populateForeignKey(); 34 | record.addEventListener(PropertyChangeEvent.PROPERTY_CHANGE, handleAssociatedRecordPropertyChange); 35 | } 36 | 37 | private function checkForRequiredFields():void 38 | { 39 | if (recordType == null) throw new IllegalOperationError("Undefined record type for " + this); 40 | if (options.foreignKey != null && !owner.hasOwnProperty(options.foreignKey)) throw new IllegalOperationError("Undefined foreign key '" + options.foreignKey + " for " + this); 41 | } 42 | 43 | private function handleAssociatedRecordPropertyChange(event:PropertyChangeEvent):void 44 | { 45 | if (event.property == "id") { 46 | populateForeignKey(); 47 | } 48 | } 49 | 50 | private function handleOwnerPropertyChange(event:PropertyChangeEvent):void 51 | { 52 | if (event.property == foreignKey) { 53 | populateRecord(); 54 | } 55 | } 56 | 57 | private function populateRecord():void 58 | { 59 | owner[property] = ID.isPopulated(owner, foreignKey) ? store.query(recordType).find(owner[foreignKey]) : null; 60 | } 61 | 62 | private function populateForeignKey():void 63 | { 64 | // If the foreign key is undefined, try to automagically set it. 65 | var key:String = foreignKey; 66 | 67 | if (owner.hasOwnProperty(key)) { 68 | owner[key] = _record.id; 69 | } 70 | } 71 | 72 | private function unassociateForeignKey():void 73 | { 74 | // If the foreign key is undefined, try to automagically set it. 75 | var key:String = foreignKey; 76 | 77 | if (owner.hasOwnProperty(key)) { 78 | owner[key] = null; 79 | } 80 | } 81 | 82 | /** 83 | * @inheritDoc 84 | */ 85 | override protected function unassociate(record:Record):void 86 | { 87 | super.unassociate(record); 88 | record.removeEventListener(PropertyChangeEvent.PROPERTY_CHANGE, handleAssociatedRecordPropertyChange); 89 | unassociateForeignKey(); 90 | } 91 | 92 | /** 93 | * The property on the owner that defines the foreign key to load this association. 94 | */ 95 | public function get foreignKey():String 96 | { 97 | return options.foreignKey != null ? options.foreignKey : property + "Id"; 98 | } 99 | 100 | /** 101 | * The associated type of record. If the type is not defined as an option, then the association 102 | * will look up the type defined on the record through reflection. 103 | */ 104 | protected function get recordType():Class 105 | { 106 | try { 107 | return options.recordType != null ? options.recordType : owner.reflect.property(property).type.clazz; 108 | } catch (e:Error) { 109 | 110 | } 111 | return null; 112 | } 113 | 114 | private var _record:Record; 115 | /** 116 | * @inheritDoc 117 | */ 118 | override public function set object(value:*):void 119 | { 120 | if (value != _record) { 121 | if (_record != null) unassociate(_record); 122 | _record = value; 123 | super.object = _record; 124 | if (_record != null) associate(_record); 125 | } 126 | } 127 | } 128 | } -------------------------------------------------------------------------------- /src/mesh/core/number/Fraction.as: -------------------------------------------------------------------------------- 1 | package mesh.core.number 2 | { 3 | /** 4 | * The Fraction class represents a number that is part of a larger whole. 5 | * This class stores fractions in the form of a numerator and denominator. 6 | * 7 | * @author Dan Schultz 8 | */ 9 | public class Fraction 10 | { 11 | /** 12 | * Constructor. 13 | * 14 | * @param numerator The numerator of the fraction. 15 | * @param denominator The denominator of the fraction. 16 | */ 17 | public function Fraction(numerator:int, denominator:int) 18 | { 19 | _numerator = numerator; 20 | _denominator = denominator; 21 | } 22 | 23 | /** 24 | * Calculates the greatest common divisor of two integers using Stein's algorithm. 25 | * 26 | * @param a The first integer. 27 | * @param b The second integer. 28 | * @return The greatest common divisor of a and b. 29 | * @see http://en.wikipedia.org/wiki/Binary_GCD_algorithm Stein's algorithm 30 | */ 31 | public static function gcd(a:int, b:int):int 32 | { 33 | a = Math.abs(a); 34 | b = Math.abs(b); 35 | 36 | if (a == b) { 37 | return b; 38 | } 39 | if (a == 0) { 40 | return b; 41 | } 42 | if (b == 0) { 43 | return a; 44 | } 45 | 46 | if (a%2 == 0) { // if u is even 47 | if (b%2 == 0) { // if u and v are even 48 | return (2*gcd(a/2, b/2)); 49 | } else { // u is even and v is odd 50 | return gcd(a/2, b); 51 | } 52 | } else if (b%2 == 0) { // if u is odd and v is even 53 | return gcd(a, b/2); 54 | } 55 | 56 | if (a >= b) { 57 | return gcd((a-b)/2, b); 58 | } 59 | return gcd((b-a)/2, a); 60 | } 61 | 62 | /** 63 | * Calculates the lowest common multiple of two integers using Euclid's algorithm. 64 | * 65 | * @param a The first integer. 66 | * @param b The second integer. 67 | * @return The lowest common multiple of a and b. 68 | * @see http://en.wikipedia.org/wiki/Greatest_common_divisor Euclid's algorithm 69 | */ 70 | public static function lcm(a:int, b:int):int 71 | { 72 | return gcd(b, a%b); 73 | } 74 | 75 | /** 76 | * Checks if two fractions or numbers are equal. 77 | * 78 | * @param obj The object to check. 79 | * @return true if the fraction or number are equal. 80 | */ 81 | public function equals(obj:Object):Boolean 82 | { 83 | return obj != null && obj.valueOf() === value; 84 | } 85 | 86 | /** 87 | * Returns a string representation of this fraction in the form: numerator + "/" + denominator. 88 | * 89 | * @return A string. 90 | */ 91 | public function toString():String 92 | { 93 | return numerator + "/" + denominator; 94 | } 95 | 96 | /** 97 | * Returns a decimal representation of this fraction. 98 | * 99 | * @return A decimal. 100 | */ 101 | public function toNumber():Number 102 | { 103 | return value; 104 | } 105 | 106 | /** 107 | * Returns a representation of this fraction as a percentage where 100 represents 1/1. 108 | * 109 | * @return A percentage. 110 | */ 111 | public function toPercentage():Number 112 | { 113 | return value*100; 114 | } 115 | 116 | /** 117 | * The primitive value of this fraction, which is a decimal. 118 | * 119 | * @return A decimal. 120 | */ 121 | public function valueOf():Object 122 | { 123 | return value; 124 | } 125 | 126 | private var _denominator:int; 127 | /** 128 | * The fractions denominator. 129 | */ 130 | public function get denominator():int 131 | { 132 | return _denominator; 133 | } 134 | 135 | private var _numerator:int; 136 | /** 137 | * The fractions numerator. 138 | */ 139 | public function get numerator():int 140 | { 141 | return _numerator; 142 | } 143 | 144 | private function get value():Number 145 | { 146 | return numerator/denominator; 147 | } 148 | } 149 | } -------------------------------------------------------------------------------- /src/mesh/model/store/QueryBuilder.as: -------------------------------------------------------------------------------- 1 | package mesh.model.store 2 | { 3 | import mesh.model.source.DataSource; 4 | 5 | public class QueryBuilder 6 | { 7 | private var _dataSource:DataSource; 8 | private var _records:RecordCache; 9 | private var _recordType:Class; 10 | 11 | public function QueryBuilder(dataSource:DataSource, records:RecordCache, recordType:Class) 12 | { 13 | _dataSource = dataSource; 14 | _records = records; 15 | _recordType = recordType; 16 | } 17 | 18 | /** 19 | * Generates a new query that fetches a single record by its ID. 20 | * 21 | * @param id The ID of the record to fetch. 22 | * @return A query. 23 | */ 24 | public function find(id:Object):* 25 | { 26 | return new FindQuery(_dataSource, _records, _recordType, id).execute(); 27 | } 28 | 29 | /** 30 | * Generates a new query that fetches all records of a single type. 31 | * 32 | * @return A result list. 33 | */ 34 | public function findAll():* 35 | { 36 | return new FindAllQuery(_dataSource, _records, _recordType).execute(); 37 | } 38 | 39 | /** 40 | * Returns a new result list that contains the records that meet the given conditions. 41 | * 42 | * @param conditions The conditions for the record. 43 | * @return A result list. 44 | */ 45 | public function where(conditions:Object):* 46 | { 47 | return new WhereQuery(_dataSource, _records, _recordType, conditions).execute(); 48 | } 49 | } 50 | } 51 | 52 | import mesh.core.reflection.newInstance; 53 | import mesh.mesh_internal; 54 | import mesh.model.Record; 55 | import mesh.model.source.DataSource; 56 | import mesh.model.source.DataSourceRetrievalOperation; 57 | import mesh.model.store.Query; 58 | import mesh.model.store.RecordCache; 59 | import mesh.model.store.ResultsList; 60 | 61 | import mx.collections.ListCollectionView; 62 | 63 | use namespace mesh_internal; 64 | 65 | class FindQuery extends Query 66 | { 67 | private var _id:Object; 68 | 69 | public function FindQuery(dataSource:DataSource, records:RecordCache, recordType:Class, id:Object) 70 | { 71 | super(dataSource, records, recordType); 72 | _id = id; 73 | } 74 | 75 | override public function execute():* 76 | { 77 | var record:Record = records.findIndex(recordType).byId(_id); 78 | 79 | // The record doesn't belong to the store. We need to retrieve it from the data source. 80 | if (record == null) { 81 | record = newInstance(recordType); 82 | record.id = _id; 83 | records.insert(record); 84 | } 85 | 86 | return record; 87 | } 88 | } 89 | 90 | class FindAllQuery extends Query 91 | { 92 | private var _results:ResultsList; 93 | 94 | public function FindAllQuery(dataSource:DataSource, records:RecordCache, recordType:Class) 95 | { 96 | super(dataSource, records, recordType); 97 | } 98 | 99 | override public function execute():* 100 | { 101 | if (_results == null) { 102 | _results = new ResultsList(records.findIndex(recordType), new DataSourceRetrievalOperation(records, dataSource.retrieveAll, [recordType])); 103 | } 104 | return _results; 105 | } 106 | } 107 | 108 | class WhereQuery extends Query 109 | { 110 | private var _results:ResultsList; 111 | private var _conditions:Object; 112 | 113 | public function WhereQuery(dataSource:DataSource, records:RecordCache, recordType:Class, conditions:Object) 114 | { 115 | super(dataSource, records, recordType); 116 | _conditions = conditions; 117 | } 118 | 119 | override public function execute():* 120 | { 121 | if (_results == null) { 122 | var collection:ListCollectionView = new ListCollectionView(records.findIndex(recordType)); 123 | collection.filterFunction = function(record:Record):Boolean 124 | { 125 | for (var property:String in _conditions) { 126 | if (record[property] != _conditions[property]) { 127 | return false; 128 | } 129 | } 130 | return true; 131 | }; 132 | collection.refresh(); 133 | _results = new ResultsList(collection, new DataSourceRetrievalOperation(records, dataSource.search, [recordType, _conditions])); 134 | } 135 | return _results; 136 | } 137 | } -------------------------------------------------------------------------------- /src/mesh/operations/FileReferenceUploadOperation.as: -------------------------------------------------------------------------------- 1 | package mesh.operations 2 | { 3 | import flash.events.DataEvent; 4 | import flash.events.Event; 5 | import flash.events.IOErrorEvent; 6 | import flash.events.ProgressEvent; 7 | import flash.events.SecurityErrorEvent; 8 | import flash.net.FileReference; 9 | import flash.net.URLRequest; 10 | 11 | import mesh.core.object.merge; 12 | 13 | /** 14 | * An operation executes an upload on a FileReference. This operation defines 15 | * a set of options that are passed to FileReference.upload(). These include: 16 | * 17 | * 29 | * 30 | * @see http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/net/FileReference.html#upload() FileReference.upload() 31 | * @author Dan Schultz 32 | */ 33 | public class FileReferenceUploadOperation extends NetworkOperation 34 | { 35 | private var _file:FileReference; 36 | private var _request:URLRequest; 37 | private var _options:Object; 38 | 39 | /** 40 | * Constructor. 41 | * 42 | * @param file The file to upload. 43 | * @param request The request that contains the URL to upload to. 44 | * @param options Options to pass to FileReference.upload(). 45 | */ 46 | public function FileReferenceUploadOperation(file:FileReference, request:URLRequest, options:Object = null) 47 | { 48 | super(); 49 | _file = file; 50 | _file.addEventListener(ProgressEvent.PROGRESS, handleFileProgress); 51 | _file.addEventListener(IOErrorEvent.IO_ERROR, handleFileIOError); 52 | _file.addEventListener(SecurityErrorEvent.SECURITY_ERROR, handleFileSecurityError); 53 | _file.addEventListener(DataEvent.UPLOAD_COMPLETE_DATA, handleFileCompleteWithData); 54 | _file.addEventListener(Event.COMPLETE, handleFileComplete); 55 | 56 | _request = request; 57 | _options = merge({uploadDataFieldName:"Filedata", testUpload:false, finishOn:Event.COMPLETE}, options); 58 | } 59 | 60 | /** 61 | * @inheritDoc 62 | */ 63 | override protected function cancelRequest():void 64 | { 65 | super.cancelRequest(); 66 | _file.cancel(); 67 | } 68 | 69 | private function canFinishWithEvent(event:Event):Boolean 70 | { 71 | return event.type == _options.finishOn; 72 | } 73 | 74 | /** 75 | * @inheritDoc 76 | */ 77 | override protected function request():void 78 | { 79 | super.request(); 80 | 81 | _file.cancel(); 82 | _file.upload(_request, _options.uploadDataFieldName, _options.testUpload); 83 | } 84 | 85 | private function handleFileProgress(event:ProgressEvent):void 86 | { 87 | progressed(event.bytesLoaded); 88 | } 89 | 90 | private function handleFileIOError(event:IOErrorEvent):void 91 | { 92 | fault(event.text, event.text); 93 | } 94 | 95 | private function handleFileSecurityError(event:SecurityErrorEvent):void 96 | { 97 | fault(event.text, event.text); 98 | } 99 | 100 | private function handleFileCompleteWithData(event:DataEvent):void 101 | { 102 | result(event.data); 103 | } 104 | 105 | private function handleFileComplete(event:Event):void 106 | { 107 | if (canFinishWithEvent(event)) { 108 | finish(true); 109 | } 110 | } 111 | 112 | /** 113 | * @inheritDoc 114 | */ 115 | override protected function get unitsTotal():Number 116 | { 117 | return _file.size; 118 | } 119 | } 120 | } --------------------------------------------------------------------------------