ActionScript3 collection query API proposal

Uploaded on

ActionScript3 collection query API proposal. …

ActionScript3 collection query API proposal.
Collection Querying API similar to LINQ 2 Objects (OO style).

  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Be the first to comment
    Be the first to like this
No Downloads


Total Views
On Slideshare
From Embeds
Number of Embeds



Embeds 0

No embeds

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

    No notes for slide


  • 1. ActionScript3 collection query OO STYLE - LINQ TO OBJECTS IMPLEMENTATION PROPOSAL
  • 2. Introduction  Draft implementation, just an idea, would be great if we have something like this in ActionScript (but more robust, complex, optimized of course)  Purpose – collection sorting, filtering, grouping wrapper  OO-Style LINQ to Objects lib for flex
  • 3. Usage examples – first, last, any, itemAt //get first with title containing case insensitive string “Voislav Seselj” var query:IQuery = new CollectionQuery(_news); var firstItem:NewsFeed = query.where(new Conditions().contains("title", “Voislav Seselj", false)).first() as NewsFeed; //last item published in 2013 or 2014 var lastItem:NewsFeed = query.where(new Conditions().eq("year", 2014).OR.eq("year", 2013)).last() as NewsFeed; var usersQuery:IQuery = new CollectionQuery(); if(usersQuery.from(users).where(new Conditions().eq("name", "Peter Pan")).any()) { //excellent, there is a user (or users) with name "Peter Pan“ } //get third adault from the end of the collection ordered by name ascending and lastname //descending. var user:User = new CollectionQuery(users) .where(new Conditions().gte("years", 18)) .orderBy("name").orderBy("lastname", false) .itemAt(2) as User;
  • 4. Usage examples – where, select, limit //get users for given conditions ordered by company and then by years property var users:IList = usersQuery.where(new Conditions().lt("years", 18).gt("years", 5).OR .eq("company", "Company 1")) .orderBy("company“).orderBy("years") .execute() as IList; //select collection containing anonymous objects with properties given in select clause //extracted from user objects, for given conditions, ordered by salary with ascending order //and compare as numerics. //Limit to maximum 10 results var users:IList = new CollectionQuery() .select("name, lastname, company, salary") .from(users) .where(new Conditions().gte("salary", 40000).lte("salary", 45000)) //AND by default .orderBy("salary", true, true) .limit(10).execute() as IList;
  • 5. Usage examples – nested complex objects //select streets from complex member “address” for each user in users collection var streets:IList ="address.street").from(users).execute() as IList; //select anonymous objects containing user’s name, lastname, address->number, address//>street, address->uniqueId->value, for users having address number greater then 400 in //Kragujevac city, or having address number lower then 400 and live in Belgrade city. //Order list by city ascending and by address number descending comparing address.number //property as numbers. var users:IList = usersQuery .select("name, lastname, address.number, address.street, address.uniqueId.value") .from(users) .where(new Conditions() .gt("address.number", 400).eq("", "Kragujevac").OR. lt("address.number", 400).eq("", "Belgrade")) .orderBy("") .orderBy("address.number", false, true) .execute() as IList;
  • 6. Usage examples – groupBy //group users named Marija or Slavisa by city, ordere by name and then by lastname var query:IQuery = new CollectionQuery(); var groupResults:IQueryGroupResult = query .select("id, name, lastname, address.street") //get anonimous objects .from(users) .where(new Conditions() .eq("name", "Slaviša").OR .eq("name“,"Marija").AND.contains("lastname", "ić")) .orderBy("name").orderBy("lastname", false) //order by name , lastname descending .groupBy("") //group by city .execute() as IQueryGroupResult; //get grouped results for each(var city:Object in groupResults.keys) //iterate through key collection { printUsersFromCity(key, groupResults[key] as IList); //and print collection for each key } if(groupResults.keys.contains("Kragujevac")) //test if there is a key { var kragujevcani:IList = groupResults["Kragujevac"] as IList; //print the collection }
  • 7. API  ICondition  IQuery  IQueryGroupResult  IQueryItemResult  CollectionQuery  Conditions  Predicate  QueryGroupResult  QueryResultItem
  • 8. IQuery /** * Query interface * @author Slavisa */ public interface IQuery { function select(value:String = null):IQuery; //select columns function from(coll:IList):IQuery; //sets collection to be queried function where(conditions:ICondition):IQuery; //sets condition chain function groupBy(property:String):IQuery; //sets group keys function orderBy(property:String, ascending:Boolean = true, numeric:Boolean = false):IQuery; //order by given property function limit(count:int = 100):IQuery; //limits result’s item count function first():Object; //returns first or null function last():Object; //returns last or null function itemAt(index:int):Object; //returns item by the given index, or null function execute():Object; //actual execution, returns IList or IQueryGroupResult object function any():Boolean; //determines if at least one result exists }
  • 9. ICondition /** * Condition interface * @author Slavisa */ public interface ICondition { function eq(prop:String, value:Object):ICondition; //equal function diff(prop:String, value:Object):ICondition; //not equal function contains(prop:String, value:String, caseSensitive:Boolean = true):ICondition; //contains function lt(prop:String, value:Object):ICondition; //lower than function lte(prop:String, value:Object):ICondition; //lower than or equal function gt(prop:String, value:Object):ICondition; //greater than function gte(prop:String, value:Object):ICondition; //greater than or equal function get OR():ICondition; function get AND():ICondition; function get root():ICondition; } //OR – new condition group //AND – new condition group //get root condition
  • 10. IQueryItemResult, QueryResultItem, IQueryGroupResult /** * Query Item result interface * @author Slavisa */ public interface IQueryItemResult { } /** * Query Item result dynamic class * Stores custom selection (anonimous object) * @author Slavisa */ public dynamic class QueryResultItem implements IQueryItemResult { } /** * Query Grouped results * @author pokimsla */ public interface IQueryGroupResult { function get keys():ArrayCollection; function get length():int; } //gets keys collection //gets number of groups
  • 11. QueryGroupResult public dynamic class QueryGroupResult extends Dictionary implements IQueryGroupResult { //Gets array collection of group keys public function get keys():ArrayCollection { var keys:ArrayCollection = new ArrayCollection(); for(var key:Object in this) { keys.addItem(key); } return keys; } //number of keys public function get length():int { return keys.length; } }
  • 12. Predicate /** * Predicate class * @author Slavisa * Used for combining multiple conditions joined with AND operator */ [Bindable] public class Predicate implements ICondition { /* Predicate types */ public static const EQ:String = "equal"; public static const DIFF:String = "different"; public static const CONTAINS:String = "contains"; public static const LT:String = "less"; public static const GT:String = "greater"; public static const GTE:String = "greater or equal"; public static const LTE:String = "lower or equal"; /* operators */ public static const OPERATOR_OR:String = "or"; public static const OPERATOR_AND:String = "and"; public var property:String; //property for comparison public var value:Object; //value to compare with public var type:String; //predicate type public var conditions:Conditions; //joining conditions (group of predicates to which this predicate belongs) public var attributes:Object; //additional attributes private var _root:ICondition; //root condition
  • 13. Predicate public function Predicate(cond:ICondition, prop:String, val:Object, type:String, attributes:Object = null) { this._root = cond.root; //set the root condition = prop; this.value = val; this.type = type; this.conditions = cond as Conditions; this.conditions.addPredicate(this); //authomaticly add me to the predicate list this.attributes = attributes; } //creates next predicate (not equal) public function diff(prop:String, value:Object):ICondition { return createNextPredicate(prop, value, DIFF); } //creates next predicate (equal) public function eq(prop:String, value:Object):ICondition { return createNextPredicate(prop, value, EQ); } … //create next predicate for given values private function createNextPredicate(prop:String, value:Object, type:String, attributes:Object = null):ICondition { var pred:Predicate = new Predicate(conditions, prop, value, type, attributes); return pred; }
  • 14. Predicate // Creates new condition with OR relation public function get OR():ICondition { var condition:Conditions = new Conditions(); condition.type = OPERATOR_OR; condition.root = root; = condition; return condition; } //creates new condition with AND relation public function get AND():ICondition { var condition:Conditions = new Conditions(); condition.type = OPERATOR_AND; condition.root = root; = condition; return condition; } //gets root condition public function get root():ICondition { return _root; } }
  • 15. Conditions /** * Condition contains group of predicates, used for joining multiple condition groups (predicates) * @author Slavisa * <br/> * Condition chain boolean result is calculated from last to root condition group */ public class Conditions implements ICondition { private var _predicates:ArrayCollection=new ArrayCollection(); //predicate collection private var _next:ICondition; //next condition in a row private var _root:Conditions; //starting condition public var type:String; //starting condition shouldn't have a type property populated. EQ, OR, AND, LT... //Condition contains group of predicates, used for joining multiple condition groups (predicates) public function Conditions() { _root = this; } /** * add predicates to a collection * @param predicate * predicates are validated in groups with ANR logical operator */ public function addPredicate(predicate:ICondition):void { _predicates.addItem(predicate); }
  • 16. Conditions //creates and appends diff predicate public function diff(prop:String, value:Object):ICondition { return new Predicate(this, prop, value, Predicate.DIFF); } //creates and appends eq predicate public function eq(prop:String, value:Object):ICondition { return new Predicate(this, prop, value, Predicate.EQ); } //… other predicates, getters and setters //OR condition does nothing on Conditions instance public function get OR():ICondition { return this; } //AND condition does nothing on Conditions instance public function get AND():ICondition { return this; } }
  • 17. CollectionQuery /** * Actual collection query implementation * @author Slavisa */ public class CollectionQuery implements IQuery { private var _coll:IList; //collection private var _conditions:ICondition; //conditions private var _comparatorGroups:ArrayCollection; //comparator groups built before collection iteration private const _comparatorMap:Object = createComparatorMap(); //comparator delegates private var _selectColumns:String = null; //select columns. If null, row items are selected private var _limitCount:int = 0; //limited item count private var _groupBy:String = null; //group by this property private var _orderBy:ArrayCollection = new ArrayCollection(); //order by these properties private var _hasGroupBy:Boolean = false; private var _hasOrderBy:Boolean = false; //ctor public function CollectionQuery(coll:IList = null, selectColumns:String = null) { _coll = coll; this._selectColumns = selectColumns; }
  • 18. CollectionQuery //creates comparator delegate map private function createComparatorMap():Object { var map:Object = new Object(); map[Predicate.DIFF] = diff; map[Predicate.EQ] = eq; map[Predicate.GT] = gt; map[Predicate.GTE] = gte; map[Predicate.LT] = lt; map[Predicate.LTE] = lte; map[Predicate.CONTAINS] = contains; return map; } //contains comparator private function contains(a:Object, b:Object, attributes:Object = null):Boolean { var strA:String = attributes && attributes.caseSensitive ? String(a) : String(a).toLowerCase(); var strB:String = attributes && attributes.caseSensitive ? String(b) : String(b).toLowerCase(); return strA.indexOf(strB) > -1; } //equal comparator private function eq(a:Object, b:Object, attributes:Object = null):Boolean { return a == b; } //…other comparators
  • 19. CollectionQuery //sets select properties public function select(value:String = null):IQuery { _selectColumns = value; return this; } //sets target collection public function from(coll:IList):IQuery { _coll = coll; return this; } //sets conditions root public function where(conditions:ICondition):IQuery { this._conditions = conditions.root; return this; } /* Similar for : groupBy orderBy limit */
  • 20. CollectionQuery public function first():Object { if(_hasGroupBy) throw new Error("First() is allowed only for non-grouped results"); var result:IList = runExecution(1) as IList; return result.length > 0 ? result[0] : null; } public function last():Object { if(_hasGroupBy) throw new Error("Last() is allowed only for non-grouped results"); var result:IList = runExecution(1, true) as IList; return result.length > 0 ? result[0] : null; } public function itemAt(index:int):Object { if(_hasGroupBy) throw new Error("ItemAt() is allowed only for non-grouped results"); if(index < 0) throw new ArgumentError("Invalid argument for itemAt method"); var result:IList = runExecution(index + 1) as IList; return result.length > (index + 1) ? null : result[index]; }
  • 21. CollectionQuery public function execute():Object //interface impl { return runExecution(_limitCount); } //actual query execution private function runExecution(limit:int = 0, reverse:Boolean = false):Object { implementation details excluded for insufficient space, if anyone cares send me an email to, I’ll be happy to provide all you need } private function orderCollection(collection:ArrayCollection):void { if(_orderBy.length == 0) return; var sort:Sort = new Sort(); sort.fields = new Array(); for each(var fieldDef:Object in _orderBy) { var sortField:SortField = new SortField(fieldDef.field, false, !fieldDef.ascending, fieldDef.numeric); sort.fields.push(sortField); } collection.sort = sort; collection.refresh(); }
  • 22. CollectionQuery /** * Creates result item * @param item * @return Row Item or anonymous object with properties generated from selectColumns variable */ private function createResultItem(item:Object):Object { if(_selectColumns == null) //return row item return item; var columns:Array = _selectColumns.split(","); //split columns if(columns.length == 1) return extractPropertyValue(item, columns[0]); //if only one property, than return it var res:QueryResultItem = new QueryResultItem(); //anonymous dynamic object for each(var column:String in columns) //populate anonymous object { var col:String = StringUtil.trim(column); var newPropname:String = col.replace(/[.]/g, '_'); res[newPropname] = extractPropertyValue(item, col); //set property value } return res; }
  • 23. CollectionQuery /** * extracts property from given item */ private function extractPropertyValue(item:Object, property:String):Object { var single:String = StringUtil.trim(property); if(single.indexOf(".") == -1) { if( !item.hasOwnProperty(single) ) throw new ArgumentError("Property with name "" + single + "" does not exist!"); return item[single]; } //recursive call when extracting complex member’s properties return extractPropertyValue(item[property.substring(0, property.indexOf("."))], property.substring(property.indexOf(".") + 1)); }
  • 24. CollectionQuery /** * Actual filtering logic * @param item collection item currently being validated * @param validationGroups condition groups to be validated with * @return Boolean * <ul> * <li>validation groups empty - return true</li> * <li>single item in validation group - return predicate validation result</li> * <li>iterate through all condition groups and its predicates and validate them all</li> * </ul> */ private function filterFunction(item:Object, validationGroups:ArrayCollection):Boolean { if(validationGroups.length == 0) return true; if(validationGroups.length == 1) return validatePredicates(item, _comparatorGroups[0].predicates); var result:Boolean = validatePredicates(item, _comparatorGroups[0].predicates); for (var i:int = 0; i < _comparatorGroups.length - 1; i++) { result = _comparatorGroups[i].operation == Predicate.OPERATOR_AND ? result && validatePredicates(item, _comparatorGroups[i + 1].predicates) : result || validatePredicates(item, _comparatorGroups[i + 1].predicates); } return result; }
  • 25. What’s next  Optimization  Validation  Interface expansion  “In” and “Between” conditions  Joins  Comparison between two properties  Query reset  Nested conditions  And much more…
  • 26. Also kewl to have - code snippets I <template autoinsert="true" context="" deleted="false" description="Query first item by the given conditions" enabled="true" name="query_first"> var query:IQuery = new CollectionQuery(${_collection}); var firstItem:${type} = query.where(new Conditions().${operation:values(eq, lt, lte, gt, gte, diff, contains)}("${prop}", ${value})).first() as ${type}; </template> <template autoinsert="true" context="" deleted="false" description="Order queried collection" enabled="true" name="query_order"> var query:IQuery = new CollectionQuery(${_collection}); var orderedResults:IList = query.where(new Conditions() .${operation:values(eq, lt, lte, gt, gte, diff, contains)}("${prop}", ${value})) .orderBy("${orderProp}", ${ascending:values(true, false)}, ${numeric:values(false, true)}) .execute() as IList; </template>
  • 27. Code snippets II <template autoinsert="true" context="" deleted="false" description="Grouping query results" enabled="true" name="query_group"> var query:IQuery = new CollectionQuery(${_collection}); var groups:IQueryGroupResult = query .where(new Conditions() .${operation:values(eq, lt, lte, gt, gte, diff, contains)}("${prop}", ${value})) .groupBy("${groupProperty}") .execute() as IQueryGroupResult; for each(var key:Object in groups.keys) { var groupedItems:ArrayCollection = groups[key] as ArrayCollection; } </template>
  • 28. Contact details  Email>