JavaScript OData library
The JavaScript npm/bower mvcct-odata library is a JavaScript OData client. It enables the developer to build queries as trees of objects, and then to transform these trees either into OData urls or into JavaScript functions that may execute the queries against JavaScript arrays.
All JavaScript classes that work as building blocks for this tree have a constructor
that accepts a JavaScript object that mimics all properties of the class. This way the whole
tree may be created by a Json represntation of it passed to the root object.
Since the Json generated by a mvcct-odata query tree is the same as the Json generated
by serializing a .Net query tree in the format of the MvcControlsToolkit.Core.OData Nuget package,
clients and servers using these libraries may easily exchange queries.
The mvcct-odata library distributions contains 3 versions of the library: a version that follows the es6
module convention, a version that follows the UMD convention (compatible with both AMD, and Node.js),
and a version that exposes all classes through the mvcct.odata
global namespace.
The QueryDescription class
The QueryDescription
class is the root of the query tree, and contains all methods and properties to interact
with the whole query tree:
interface IQueryDescription { skip: number | null; take: number; page: number; search: IQuerySearch; filter: IQueryFilterBooleanOperator; grouping: IQueryGrouping; sorting: Array<IQuerySortingCondition>; attachedTo: IEndpoint; } class QueryDescription implements IQueryDescription { customUrlEncode(func: (x: string) => string): void; skip: number | null; take: number; page: number; search: QuerySearch; filter: QueryFilterBooleanOperator; grouping: QueryGrouping; sorting: Array<QuerySortingCondition>; attachedTo: Endpoint; static fromJson(x: string): QueryDescription; constructor(origin: IQueryDescription); addFilterCondition(filter: QueryFilterClause | null, useOr?: boolean): void; getGroupDetailQuery(o: any): QueryDescription | null; queryString(): string | null; addToUrl(url: string | null): string | null; toString(): string | null; toQuery(): (o: Array<any>) => Array<any>; }
interface IEndpoint extends Endpoint { } class Endpoint implements IEndpoint { static Get: string; static Post: string; static Put: string; static Delete: string; static Patch: string; baseUrl: string; verb: string; accpetsJson: boolean; returnsJson: boolean; bearerToken: string | null; ajaxId: string | null; constructor(x: IEndpoint); constructor(baseUrl: string, verb: string, accpetsJson?: boolean, returnsJson?: boolean, bearerToken?: string | null, ajaxId?: string | null); }
- skip, take, page
- paging components of the query. page is computed from skip and take.
- search, filter, grouping, sorting
- free search, filter grouping/aggregation and sorting components of the query
- attachedTo
- where and how to send the query to the server. Namely, baseUrl is the base url where to send the query, verb, the http verb to use, if the server endpoint requires json or url encoded data, if the server endpoint returns Json or html, and an optional bearer token to add to the request headers. If ajaxId is not null, then the request is issued with ajax also if it is not a json request, and the html returned is attached to the html node whose id is ajaxId.
- static fromJson(x: string): QueryDescription;
- Creates a whole query tree from its Json representation.
- toString(): string | null;
- returns the complete OData url.
- addToUrl(url: string | null): string | null;
- returns the complete OData url, but instead of using the baseUrl contained in the Endpoint object uses the url passed as argument.
- queryString(): string | null;
- returns just the query string part of the query url.
-
toQuery(): (o: Array
) => Array ; - compiles the query tree into a JavaScript function that may apply the query to a JavaScript array.
- addFilterCondition(filter: QueryFilterClause | null, useOr?: boolean): void;
- Add the filter condition passed as argument to the filter already contained in the object. If useOr is true the new condition is combined with the preexisting conditions with a logical or, otherwise with a logical and.
- getGroupDetailQuery(o: any): QueryDescription | null;
- If the QueryDescription contains a grouping component it gets the query that yields the grouping details of the aggregated item passed as argument(ie the query that explodes all aggregated items).
Building Filter, and Search trees
Filter Trees are made of subclasses of the QueryFilterClause
class. The leafs of
a filter tree are instances of the QueryFilterCondition
class that represents a simple
condition involving a property and a JavaScript value. QueryFilterCondition
are combined
into complex conditions with instances of the QueryFilterBooleanOperator
class
that represents boolean combinations of their children conditions. Children condtitions may be either other
QueryFilterBooleanOperator
instances or leaf QueryFilterCondition
instances.
QueryFilterCondition
inherits from the QueryValue
class that represents
a constant value. QueryValue
instances are the leaf nodes of free search queries that
combine single values with free search and, or and not. Also free search booleans
are represented with QueryFilterBooleanOperator
instances whose operators are free search
boolean operators. Below the definition of QueryValue
:
interface IQueryValue { value: any; dateTimeType: number; } class QueryValue extends QueryFilterClause implements IQueryValue { static IsNotDateTime: number; static IsDate: number; static IsTime: number; static IsDateTime: number; static IsDuration: number; value: any; dateTimeType: number; constructor(origin?: IQueryValue); private formatInt(x, len); private normalizeTime(x, days, maxTree); isGuid(): boolean; setDate(x: Date | null): void; setTime(x: Date | null): void; setDuration(days: number, hours: number, minutes?: number, seconds?: number, milliseconds?: number): void; setDateTimeLocal(x: Date | null): void; setDateTimeInvariant(x: Date | null): void; setBoolean(x: boolean | null): void; setNumber(x: number | null): void; setString(x: string | null): void; setNotDateTime(x: any): void; getValue(): any; toString(): string | null; toQuery(): ((o: any) => boolean) | null; }
Each instance contains a field for the JavaScript value and the dateTimeType
field that further specifies the type when the value is a JavaScript date. The possible
values of dateTimeType
are defined as static fields of the QueryValue
class. One may create an empty instance and then set its value with the various setType
methods; this way the dateTimeType
field is automatically set properly.
Once a free search tree has been built it must be assigned to an instance of
the QuerySearch
class:
interface IQuerySearch { value: IQueryFilterBooleanOperator; } class QuerySearch extends QueryNode implements IQuerySearch { value: QueryFilterBooleanOperator; constructor(origin: IQuerySearch | IQueryFilterBooleanOperator | IQueryFilterCondition); toString(): string | null; toQuery(): ((o: any) => boolean) | null; }
filter trees, instead, have the QueryFilterCondition
subclass of QueryValue
as leaf nodes:
interface IQueryFilterCondition extends IQueryValue { operator: string | null; property: string | null; inv: boolean; } class QueryFilterCondition extends QueryValue implements IQueryFilterCondition { static readonly eq: string; static readonly ne: string; static readonly gt: string; static readonly lt: string; static readonly ge: string; static readonly le: string; static readonly startswith: string; static readonly endswith: string; static readonly contains: string; static fromModelAndName(dateTimeType: number, property: string, o: any, op?: string, inv?: boolean): QueryFilterCondition | null; operator: string | null; property: string | null; inv: boolean; constructor(origin?: IQueryFilterCondition); toQuery(): ((o: any) => boolean) | null; toString(): string | null; }
The QueryFilterCondition
subclass adds the operator
and
property
fields. All possible values for the operator
field
are listed as static members of the class. The property
field contains the name
of the property involved in the condition; nested properties are separated by dots.
One may create an instance of QueryFilterCondition
with a fake or undefined value
and then set the value by calling one of the setType methods inherited by QueryValue
.
Below the definition of QueryFilterBooleanOperator
:
interface IQueryFilterBooleanOperator { operator?: number; argument1?: IQueryValue; argument2?: IQueryValue; child1?: IQueryFilterBooleanOperator; child2?: IQueryFilterBooleanOperator; } class QueryFilterBooleanOperator extends QueryFilterClause implements IQueryFilterBooleanOperator { static readonly and: number; static readonly or: number; static readonly not: number; static readonly AND: number; static readonly OR: number; static readonly NOT: number; operator: number; argument1: QueryValue; argument2: QueryValue; child1: QueryFilterBooleanOperator; child2: QueryFilterBooleanOperator; constructor(origin: IQueryFilterBooleanOperator); constructor(operator: number, a1: QueryFilterClause, a2?: QueryFilterClause); toString(): string | null; toQuery(): ((o: any) => boolean) | null; }
The class has two different properties to store its children depending if they are
QueryFilterBooleanOperator
or leaf conditions. All possible values for
the operator
field are listed as static fields of the class; The upper case
operators are the free search boolean operators.
An instance may be created either by passing an instance of the
IQueryFilterBooleanOperator
interface or by passing the operator with
the children nodes. In case the operator is unary (not for instance) the unique
child node may be passed either as first or as second child, the other child being null.
Buildin sorting clauses
Sorting is an array of QuerySortingCondition
instances. Below the definition
of the QuerySortingCondition
class:
interface IQuerySortingCondition { property: string; down: boolean; } class QuerySortingCondition extends QueryNode implements IQuerySortingCondition { property: string; down: boolean; constructor(x: IQuerySortingCondition); constructor(property: string, down?: boolean); toString(): string | null; toCompare(): ((o1: any, o2: any) => number) | null; }
The class contains just the name of the property involved in the sorting (nested properties are separated by dots) and a boolean that specifies if the sorting is inverse.
Building groupings and aggregations
Groupings+aggregations are encoded by instances of the QueryGrouping
class:
interface IQueryGrouping { keys: Array<string>; aggregations: Array<IQueryAggregation>; dateTimeTypes: Array<number>; } class QueryGrouping extends QueryNode implements IQueryGrouping { keys: Array<string>; aggregations: Array<QueryAggregation>; dateTimeTypes: Array<number>; constructor(origin?: IQueryGrouping); toString(): string | null; toQuery(): (input: any[]) => any[]; }
The class contains an array with the names of all grouped properties (nested properties are separated by dots),
a parallel array containing all dateTimeTypes of the grouped properties,
and an array of QueryAggregation
instances, each specifying a property aggregation.
class QueryAggregation extends QueryNode implements IQueryAggregation { static readonly count: string; static readonly sum: string; static readonly average: string; static readonly min: string; static readonly max: string; operator: string; property: string; isCount: boolean; alias: string; constructor(x: IQueryAggregation); constructor(operator: string, property: string, alias: string); toString(): string | null; toQuery(): IAggregation; }
Each instance contains the name of the property to aggregate (nested properties are separated by dots),
the aggregation operator and the name of the aggregated property (alias
). The name
of the aggregated property can't contain nested properties. All possible values for the
aggregation operator are listed as static properties of the class.