actionscript3 collection query api proposal

Post on 10-May-2015

142 Views

Category:

Education

3 Downloads

Preview:

Click to see full reader

DESCRIPTION

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

TRANSCRIPT

OO STYLE - L INQ TO OBJECTS IMPLEMENTATION PROPOSAL

ActionScript3 collection query

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

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(newConditions().contains("title", “Voislav Seselj", false)).first() as NewsFeed;

//last item published in 2013 or 2014var lastItem:NewsFeed = query.where(newConditions().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;

Usage examples – where, select, limit

//get users for given conditions ordered by company and then by years propertyvar 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 resultsvar 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;

Usage examples – nested complex objects

//select streets from complex member “address” for each user in users collectionvar streets:IList = usersQuery.select("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("address.city", "Kragujevac").OR.lt("address.number", 400).eq("address.city", "Belgrade"))

.orderBy("address.city")

.orderBy("address.number", false, true)

.execute() as IList;

Usage examples – groupBy

//group users named Marija or Slavisa by city, ordere by name and then by lastnamevar 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("address.city") //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}

API

IConditionIQueryIQueryGroupResultIQueryItemResultCollectionQueryConditionsPredicateQueryGroupResultQueryResultItem

IQuery

/** * Query interface * @author Slavisa */public interface IQuery{

function select(value:String = null):IQuery; //select columnsfunction from(coll:IList):IQuery; //sets collection to be queriedfunction where(conditions:ICondition):IQuery; //sets condition chainfunction groupBy(property:String):IQuery; //sets group keysfunction orderBy(property:String, ascending:Boolean = true, numeric:Boolean = false):IQuery; //order by given property

function limit(count:int = 100):IQuery; //limits result’s item countfunction first():Object; //returns first or null function last():Object; //returns last or nullfunction itemAt(index:int):Object; //returns item by the given index, or nullfunction execute():Object; //actual execution, returns IList or IQueryGroupResult objectfunction any():Boolean; //determines if at least one result exists

}

ICondition

/** * Condition interface * @author Slavisa */public interface ICondition{

function eq(prop:String, value:Object):ICondition; //equalfunction diff(prop:String, value:Object):ICondition; //not equalfunction contains(prop:String, value:String, caseSensitive:Boolean = true):ICondition;

//containsfunction lt(prop:String, value:Object):ICondition; //lower thanfunction lte(prop:String, value:Object):ICondition; //lower than or equalfunction gt(prop:String, value:Object):ICondition; //greater thanfunction gte(prop:String, value:Object):ICondition; //greater than or equal

function get OR():ICondition; //OR – new condition groupfunction get AND():ICondition; //AND – new condition group

function get root():ICondition;//get root condition}

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; //gets keys collectionfunction get length():int; //gets number of groups

}

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;}

}

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 comparisonpublic var value:Object; //value to compare withpublic var type:String; //predicate typepublic 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

Predicate

public function Predicate(cond:ICondition, prop:String, val:Object, type:String, attributes:Object = null){

this._root = cond.root; //set the root condition this.property = prop;this.value = val;this.type = type;this.conditions = cond as Conditions;this.conditions.addPredicate(this); //authomaticly add me to the predicate listthis.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 valuesprivate function createNextPredicate(prop:String, value:Object, type:String, attributes:Object = null):ICondition{ var pred:Predicate = new Predicate(conditions, prop, value, type, attributes); return pred;}

Predicate

// Creates new condition with OR relation public function get OR():ICondition{

var condition:Conditions = new Conditions();condition.type = OPERATOR_OR;condition.root = root;this.conditions.next = 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;this.conditions.next = condition;return condition;

}

//gets root condition public function get root():ICondition{ return _root;}}

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 collectionprivate var _next:ICondition; //next condition in a rowprivate var _root:Conditions; //starting conditionpublic 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);}

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;}

}

CollectionQuery

/** * Actual collection query implementation * @author Slavisa */public class CollectionQuery implements IQuery{

private var _coll:IList; //collectionprivate var _conditions:ICondition; //conditionsprivate var _comparatorGroups:ArrayCollection; //comparator groups built before collection iteration private const _comparatorMap:Object = createComparatorMap(); //comparator delegatesprivate var _selectColumns:String = null; //select columns. If null, row items are selectedprivate var _limitCount:int = 0; //limited item countprivate var _groupBy:String = null; //group by this propertyprivate var _orderBy:ArrayCollection = new ArrayCollection(); //order by these properties

private var _hasGroupBy:Boolean = false;private var _hasOrderBy:Boolean = false;

//ctorpublic function CollectionQuery(coll:IList = null, selectColumns:String = null){

_coll = coll;this._selectColumns = selectColumns;

}

CollectionQuery

//creates comparator delegate mapprivate 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 comparatorprivate 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 comparatorprivate function eq(a:Object, b:Object, attributes:Object = null):Boolean{ return a == b;}

//…other comparators

CollectionQuery

//sets select propertiespublic function select(value:String = null):IQuery{

_selectColumns = value;return this;

}//sets target collectionpublic function from(coll:IList):IQuery{

_coll = coll;return this;

}//sets conditions rootpublic function where(conditions:ICondition):IQuery{

this._conditions = conditions.root;return this;

}

/*

Similar for :groupByorderBylimit

*/

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];

}

CollectionQuery

public function execute():Object //interface impl{ return runExecution(_limitCount);}//actual query executionprivate function runExecution(limit:int = 0, reverse:Boolean = false):Object{ implementation details excluded for insufficient space, if anyone cares send me an email to slavisapokimica@yahoo.com, 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();

}

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; }

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 propertiesreturn extractPropertyValue(item[property.substring(0, property.indexOf("."))], property.substring(property.indexOf(".") + 1));

}

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;}

What’s next

OptimizationValidationInterface expansion

“In” and “Between” conditions Joins Comparison between two properties Query reset Nested conditions And much more…

Also kewl to have - code snippets I

<template autoinsert="true"context="com.adobe.flexide.as.core.codetemplates.action_script"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(newConditions().${operation:values(eq, lt, lte, gt, gte, diff,contains)}("${prop}", ${value})).first() as ${type};

</template>

<template autoinsert="true"context="com.adobe.flexide.as.core.codetemplates.action_script"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>

Code snippets II

<template autoinsert="true"context="com.adobe.flexide.as.core.codetemplates.action_script"deleted="false" description="Grouping query results"enabled="true" name="query_group">var query:IQuery = newCollectionQuery(${_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>

top related