@cute-dw/core
v0.5.0
Published
This TypeScript library is the main part of a more powerfull package designed for the fast WEB software development. The cornerstone of the library is the **DataStore** class, which might be useful when you need a full control of the data, but do not need
Downloads
2
Maintainers
Readme
@cute-dw/core
This TypeScript library is the main part of a more powerfull package designed for the fast WEB software development. The cornerstone of the library is the DataStore class, which might be useful when you need a full control of the data, but do not need the visual representation of it. The second important element of the library is the abstract DataWindow class, which implements the same interface as the DataStore class, but is designed for developing visual components using Angular/TypeScript. Another author's project (@cute-dw/ui) is an example of the implementation of this functionality based on the @angular/material component library.
Installation
npm install @cute-dw/core
Quick start
Without further ado, let's assume that somewhere in the cloud there is a data source containing the product table with the following structure, and we want to perform some CRUD operations on it:
CREATE TABLE product_table (
ID BIGSERIAL NOT NULL,
CODE VARCHAR(30) NOT NULL,
NAME VARCHAR(80),
NOTE TEXT,
QTY INTEGER NOT NULL DEFAULT 0,
PRICE NUMERIC(15,2) NOT NULL DEFAULT 0,
ISACTIVE NUMERIC(1) NOT NULL DEFAULT 1,
OPEN_DATE DATETIME DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (ID)
)
The following code snippet shows how you can describe the target data source (model) and read the data from it using the DataStore object:
let options: DataStoreOptions = {};
/* General DataStore options */
options.datawindow = {
name: "Product list",
paginator: {pageSize: 20, pageSizeOptions: [10, 20, 50, 100] /*, showFirstLastPage: true*/}
};
/* Source table columns with which we want to work (display) */
options.table = {
columns: [
{ name: "id", type: "long", key: true, identity: true },
{ name: "code", type: "char(30)", values: [{label:"Code 001", value:"001", icon:"home"}, {label:"Code 002", value:"002", icon:"wifi"}] },
{ name: "name", type: "char(80)" },
{ name: "notactive", type: "number", values: [{label:"Yes",value:1}, {label:"No ", value:0}], initial: 0 },
{ name: "open_date", type: "datetime", initial: (new Date())}
],
parameters: TableParam.map({ name: "isactive", type: "number"/*, defValue:0*/ }),
//groups: ["Upper(left(name, 2))", "Upper(substr(name, 2, 2))"],
startFilter: "code != null && id > 10000",
selectMethod: productService.readProducts,
updateMethod: productService.updateProduct,
deleteMethod: productService.deleteProduct,
updateWhere: UpdateWhereClause.KeyAndModifiedCols,
updateTable: "product_table"
};
/* DataWindow controls (optional for DataStore) */
options.controls = {
"rownum": { type: "compute", header: "#", width: 40, fwidth: 100, alignment: "center", expression: "GetRow()+1", sortable: false, microhelp: "Row number", sticky:"left" },
"icon": { type: "compute", width: 50, expression: "((notactive==1)?'home':'face')", $class: "=((notactive==1)?'md-36':'md-24')", palette: "primary", $style: "={color: iif(GetRow()%2==0,'#ffbb00','')}", displayas: "picture", alignment:"center", badge: "", badgecolor: "=iif(GetRow()%2==0,'primary','warn')", resizeable: true },
"id": { type: "column", header: "ID", label:"Identity", width: 60, fwidth: 100, alignment: "center", editstyle: {name:"edit", readonly: true}, microhelp: "Row identity"},
"code": { type: "column", label:"Product Code", width: 60, fwidth: 200, visible: true, required: true, alignment: "left", microhelp: "Product Code/Article", $style: "={color:(GetRow()%2==0)?'red':'blue'}", summable: true,
editstyle: {name: "ddplb", placeholder: "Enter Product Code"}},
"name": { type: "column", label: "Product Name", width: 500, visible: true, alignment: "justify", resizeable: false, microhelp: "Product full name", editstyle:{"name":"edit", placeholder:"Enter Product name"}},
"notactive": { type: "column", width: 60, fwidth: 150, editstyle: { name: "radiobuttons"}, alignment: "center", summable:true, microhelp: "Is Product active?" },
"open_date": {type: "column", label:"Created", width: 100, fwidth: 220, alignment: "center"},
"cb_edit": { type: "compute", label: "Button Edit", width: 92, expression: "'Edit...'", icon: "more_vert", displayas: "button", alignment:"center", palette: "primary", microhelp: "Edit Product" },
};
/* Initialize object */
this.ds = new DataStore<any>(options, this.productService);
/* Set start filter value */
this.ds.filterValue = "code != null && id > 20000";
/* Set start sort condition */
this.ds.sortValue = "Upper(left(name, 4)) asc \t";
/* Subscribe on Database errors */
this.ds.onDbError.subscribe((err)=>alert(err));
/* Gets data from the data source with optional parameter values */
this.ds.retrieve(1); // Gets active products only
As we can see, the DataStore object initialization needs two arguments of the following types: DataStoreOptions and HttpService. The first argument is a plain JavaScript object with the predefined properties. The second one is an instance of the some injectable service object. DataStoreOptions contains several key namespaces:
datawindow - defines general DataStore/DataWindow options.
table - this namespace is required and describes columns of the retrieved data model, query parameters, methods for select/update data and other attributes. More about it see in the next sections.
controls - optional namespace which defines a map object containing DataStore/DataWindow control objects. For table columns, control must have the type attribute value equals to
column
and key/name must be identical to one of the names in the table.columns array. Another group of controls may belong to thecompute
type. In this case the expression attribute must be fulfilled. Expression is a text string written on embedded domain-specific programming language DwScript and evaluated dynamically at run time. Evaluation context of the expression is the current row of the DataStore/DataWindow. Thus, any definedcolumn
/compute
name can be used to evaluate its current value but NOT to change it. DwScript is a extensible language though it understands many specific and regular JavaScript functions and operators from a box. More about DwScript see in the next sections.
The following code snippet is an example of the futher data processing:
/* Get count of rows in the Primary buffer of the DataStore */
let rows: number = this.ds.rowCount();
let res: any;
for (let i:number = 0, i < rows; i++) {
// we can read the column's data in the row...
res = this.ds.getItem(i, "code"); // string
res = this.ds.getItem(i, "id"); // number
res = this.ds.getItem(i, "open_date"); // Date
res = this.ds.getItem(i, 3); // 4-th column's data
// or calculate some expressions on it
res = this.ds.evaluate("Round(qty*price,2)", i);
}
// or in the 'for-of' style
for (let row of this.ds.rowsCursor("Primary!")) {
res = row.code; // row["code"]
res = row.id;
res = row.open_date;
res = row.rownum; // computed field
}
// or more concise
for (let row of this.ds) {
...
...
}
Buffers
Data in the DataStore may reside in one of the following data buffers: Primary!
, Filter!
, Delete!
. The default data storage location is the Primary!
buffer. Thus, most methods of DataStore without special notes work with Primary!
data. To get the number of records currently existing in the corresponding data buffer, you can use such methods:
let primaryCount = this.dataStore.rowCount();
let filteredCount = this.dataStore.filteredCount();
let deletedCount = this.dataStore.deletedCount();
Data retrieving
To retrieve rows from the remote data source you need to call retrieve
method of the DataStore. If arguments are included, the argument values are set as http parameters for client service call:
/**
* Example of Angular http service class
*/
@Injectable({
providedIn: 'root'
})
export class ProductService extends HttpService{
private baseUrl = "http://www.mycool.site:9090";
constructor(http:HttpClient) {
super(http);
}
getProducts(where?: HttpParams): Observable<Product[]> {
return this.get(this.baseUrl+"/ws_products", {params: where});
}
...
}
And in the some method of your class call something like this:
this.ds.retrieve(1).then(rows => console.log(rows) );
You can get a direct readonly reference to the Primary!
data buffer of your DataStore using its data
property. But a more rubust option is to get a subscription to any primary data changes in your DataStore object in the future using the dataStream
property:
/* Class properties */
private _dataSource: any[] = [];
...
this.ds.dataStream.subscribe(data => this._dataSource = data);
Filtering
To move data to the Filter!
buffer of the DataStore
you have to set filter condition and then run filtering process something like this:
/* Example. Need to move all rows which `code` value ends with 'xyz' to Filter! buffer. */
/* Step 1. Set condition */
this.ds.filterValue = "like(code, '%xyz')";
/* Step 2. Run filtering process */
this.ds.applyFilter();
/* Handle result */
let rows = this.ds.filteredRows();
There are special methods for moving data between any DataStore buffers such as
rowsCopy
,rowsMove
. More about it see DataStore's API documentation.
Sorting
To sort rows in the Primary!
buffer of the DataStore/DataWindow we need to set sort condition first. Then, we can run data sort processing:
this.ds.sortValue = "code asc, qty desc";
this.ds.applySort();
Row/item status
DataStore/DataWindow supports the following states for each row and row's item (column) in each data buffer:
| Status | Applies to |Meaning |
| :--- | --- |:--- |
|NotModified!
| Rows & Columns | The information in the row or column is unchanged from what was retrieved. |
|DataModified!
| Rows & Columns | The information in the column or one of the columns in the row has changed since it was retrieved. |
|New!
| Rows | The row is new but no values have been specified for its columns. (Applies to rows only, not to individual columns.) |
|NewModified!
| Rows | The row is new, and values have been assigned to its columns. In addition to changes caused by user entry or the setItem
method, a new row gets the status NewModified!
when one of its columns has a default value. (Apples to rows only, not to individual columns.) |
To get the row/item current status we can call a special method:
let rowNumber = 2; // zero based
let status: ItemStatus | undefined;
/* Get the third row status in the Primary! buffer */
status = this.ds.getItemStatus(rowNumber, null);
/* Get the third row status in the Filter! buffer (if exists) */
status = this.ds.getItemStatus(rowNumber, null, "Filter!");
/* Get the column status of the third row */
status = this.ds.getItemStatus(rowNumber, "code", "Primary!");
Deleting rows
To delete row in the DataStore's Primary!
buffer we need to call the corresponding deleteRow
method. If it returns the successfull result, the specified row was moved from Primary!
to Delete!
buffer. Note that you can simply discard a range of specified rows from the DataStore buffer by calling the rowsDiscard
method.
let rowToDelete = 5
console.log(this.ds.rowCount()); // 10
console.log(this.ds.deletedCount()); // 0
this.ds.deleteRow(rowToDelete);
console.log(this.ds.rowCount()); // 9
console.log(this.ds.deletedCount()); // 1
// Discard rows
this.ds.rowsDiscard(0, 1);
console.log(this.ds.rowCount()); // 7
console.log(this.ds.deletedCount()); // 1 !!!
// Delete multiple rows
this.ds.rowsMove(0, this.ds.RowCount(), "Primary!", this.ds, 0, "Delete!");
console.log(this.ds.rowCount()); // 0
console.log(this.ds.deletedCount()); // 8
Inserting rows
There are several methods to insert row(s) to the Primary!
buffer of the DataStore. First of all there is an insertRow
method:
/* Append row to the end of the data list */
let row: number = this.ds.insertRow();
this.ds.setItem(row, "code", "XY-1000");
this.ds.setItem(row, "name", "Super product, L-1000");
/* Insert row before second row */
let row1:number = this.ds.insertRow(1);
this.ds.setItem(row1, "code", "A-600");
this.ds.setItem(row1, "name", "Another cool product, 600kg");
If an initial value has been defined for a column in the DataStore's configuration object, this value is applied to the newly inserted row. The inserted row (with a status flag of New!
) is not included in the modified count until data is entered in the row (its status flag becomes NewModified!
).
Another option to append one or more rows to the DataStore is to use such methods as importString
, rowsCopy
, rowsMove
.
Share data
Often we need to share our data repository between several components particularly if they display the identical data in different styles, for example grids, forms, graphs/charts, crosstabs, etc. To help solve such a problem DataStore has a special method shareData
. This method shares data retrieved by one DataStore (or DataWindow control), which is referred to as the primary DataStore, with another DataStore (or DataWindow control), referred to as the secondary DataStore/DataWindow. The controls do not share formatting, only the data is shared, including data in buffers and sorting order. The order and type of the data columns of the primary and secondary DataStore/DataWindow must be identical.
this.mainDS.shareData(dwGrid);
this.mainDS.shareData(dwGraph);
...
this.mainDS.filterValue = "InList(code, 'ABC', 'DEF')";
this.mainDS.applyFilter();
// Now all our views display data for rows in which the "code" column has only two specified values
...
this.mainDS.filterValue = "";
this.mainDS.applyFilter();
// Now all filters are canceled
...
// Turns off the sharing of data buffers for a DataWindow control or DataStore.
this.mainDS.shareDataOff();
Save changes. Part 1.
Before sending any changes to a remote endpoint DataStore carefully analyzes the states of all its rows and columns. There are a few simple rules that DataStore uses to determine the type of data update operation:
- Row will be inserted if the row resides in the
Primary!
orFilter!
buffer and its status isNewModified!
- Row will be modified if the row resides in the
Primary!
orFilter!
buffer and its status isDataModified!
- Row will be deleted if the row resides in the
Delete!
buffer and its status is NOTNew!
orNewModified!
DataStore is NOT updateable if its
datawindow.readonly
configuration property is set totrue
or the value oftable.updateTable
property is empty. Table columns with the truthykey
and/orupdatable
properties must also exist.
There is another important aspect that needs to be taken into account when sending data for updating and/or deletion in the SQL database on the backend. The value of table.updateWhere
property. This value indicates which columns will be included in the WHERE clause of the SQL Update/Delete statement. updateWhere
can impact performance or cause lost data when more than one user accesses the same tables at the same time. Values are:
- 0 - Key columns only (risk of overwriting another user's changes, but fast).
- 1 - Key columns and all updatable columns (risk of preventing valid updates; slow because SELECT statement is longer).
- 2 - Key and modified columns (allows more valid updates than 1 and is faster, but not as fast as 0).
The default value of updateWhere
is 0, as this is a more appropriate value for the most web applications.
Save changes. Part 2
DataStore saves its changes to the backend by sending batch requests via methods that assigned to the following properties: table.insertMethod
, table.updateMethod
, table.deleteMethod
, table.batchMethod
. If the batchMethod
is defined then the others are ignored. All client methods are required to implement the RpcMethodSignature
interface, which defines only one required parameter. The value that will be assigned to this parameter by DataStore is an array of JSON objects that follow to JSON-RPC 2.0 specification. Thus table methods should send a POST request via http because others (PUT, DELETE, PATCH, etc.) don't make much sense in this case.
// Product service (excerpt)
...
// Example of the batch update method
batchUpdate(body: any): Observable<any> {
const options = this.updateOptions;
return this.post(this.baseUrl+"/rpc/db/update", JSON.stringify(body), options);
}
...
So, as mentioned above, each array's element is the client request object (rpc-method) and we can describe its type like this:
type ColumnName = string;
type ValuesMap = {[key: string]: any};
type InsertedRow = {values: ValuesMap, table?: string, returning?: ColumnName[]|"*"};
type UpdatedRow = {set: ValuesMap, where: ValuesMap, table?: string, returning?: ColumnName[]|"*"};
type DeletedRow = {where: ValuesMap, table?: string};
type JsonRpcRequest = {jsonrpc: "2.0", method: string, id: number|string|null};
/* RPC-request types */
type JsonRpcInsert = JsonRpcRequest & {params: InsertedRow};
type JsonRpcUpdate = JsonRpcRequest & {params: UpdatedRow};
type JsonRpcDelete = JsonRpcRequest & {params: DeletedRow};
By default, the value that is assigned to the method
parameter has the following format: <entity>.<action>
. The table.updateTable
property value replaces the entity
. The action
can be one of the following options: insert
, update
, delete
. Evantually the request method for, for example, inserting rows into product_table
table looks like this: product_table.insert
. Inserting and updating requests may have the returning
property if the DataStore is interested in the new values of some table columns.
To change the
method
's name generation procedure assign a code value totable.rpcMethodFormat
property.
The URL of the endpoint for RPC requests is not interseting for DataStore and may have any value which is defined in the appropriate http service object.
The backend MUST reply to the client's request using the same JSON-RPC 2.0 specification. The Server should respond with an Array containing the corresponding Response objects, after all of the batch Request objects have been processed. The response object can be one of the two types depending on the result of the operation:
/* `returning` property is a map of values for homonymous property in the client's request object */
type RpcResponseResult = {jsonrpc: "2.0", result: {sqlnrows?: number, returning?: {[key:string]:any}}, id: number|string|null};
type RpcResponseError = {jsonrpc: "2.0", error: {code: number, message: string, data?: {sqlcode?: number, sqlstate?: string, sqlerrtext?: string}}, id: number|string|null};
DataStore sends a batch of update requests in the hope that the server will execute them in the
transactional
scope. If this is the case and some error occurs, the server application SHOULD save information about it, and execute the ROLLBACK statement. Then, the server SHOULD send to the client ONLY ONE error response object with anid
value equal to null anderror
property populated.
The successful response SHOULD be an array of the Result objects where
id
property value MUST be identical to the request'sid
that was received from a client.
Save changes. Part 3
DataStore supports updatesPending
readonly property to check if DataStore (or DataWindow control) has any pending updates. The value of this property can help you design your user interface more thoroughly.
Finally, we can run updating process like this:
this.ds.update().then(rc => console.log(rc)) // 1 - successfull
Method update
returns a Promise object that represents the eventual completion (or failure) of our asynchronous update operation. When the updating process will end successfully Promise
returns 1, else -1. If there are no pending updates Promise
returns 0.
DataStore emits three events during its updating process: onUpdateStart
, onUpdateEnd
and onDbError
. The first one is the cancelable event i.e. DataStore reacts on event's defaultPrevented
property value. For example:
const usBeg = this.ds.onUpdateStart.subscribe(event => {
if (someCondition == true) {
// stop updating process
event.preventDefault();
}
});
const usEnd = this.ds.onUpdateEnd.subscribe(event => {
console.log(event.rowsInserted);
console.log(event.rowsUpdated);
console.log(event.rowsDeleted);
});
const usErr = this.ds.onDbError.subscribe(event => {
console.log(event)
});
...
...
usBeg.unsubscribe();
usEnd.unsubscribe();
usErr.unsubscribe();
More about supporting events in the DataStore see in the next sections.
Update JOINs
Let's imagine that we retrieve rows from more than one SQL table or an updatable VIEW
. For example, consider this simple database:
CREATE TABLE employee (
id BIGSERIAL NOT NULL,
initials TEXT NOT NULL,
gender CHAR(1) NOT NULL,
birthday DATE,
salary NUMERIC(15,2) NOT NULL DEFAULT 0,
dep_id INTEGER NOT NULL,
PRIMARY KEY(id),
FOREIGN KEY (dep_id) REFERENCES department (id)
);
CREATE TABLE department (
id BIGSERIAL NOT NULL,
code VARCHAR(30) NOT NULL,
name VARCHAR(100),
note TEXT,
PRIMARY KEY(id)
);
And our SELECT
statement could be like this:
SELECT emp.id, emp.initials, emp.gender, emp.birthday, emp.salary, emp.dep_id, dep.code, dep.name
FROM employee emp
JOIN department dep ON (emp.dep_id = dep.id);
This is on the server. On the client side we would like to have a DataStore that would help us update employee
table, for example. Examine the next excerpt of configuration object:
...
options.table = {
columns: [
{ name: "emp_id", type: "long", key: true, identity: true, updatable: true, dbname: "emp.id"},
{ name: "emp_initials", type: "text", updatable: true, dbname: "emp.initials"},
{ name: "emp_gender", type: "char(1)", updatable: true, dbname: "emp.gender", initial: "M"},
{ name: "emp_birthday", type: "date", updatable: true, dbname: "birthday" },
{ name: "salary", type: "number", updatable: true, initial: 0},
{ name: "emp_dep_id", type: "long", updatable: true, dbname: "emp.dep_id"},
{ name: "dep_code", type: "char(30)", updatable: false, dbname: "dep.code"},
{ name: "dep_name", type: "char(100)", updatable: false, dbname: "dep.name"}
],
batchMethod: httpService.rpcUpdate,
updateTable: "my_employee_table_name",
updateAlias: "emp"
};
...
In the snippet above the value of updateTable
property is selected arbitrary and doesn't equal to the database table name. The main thing is that it should be recognized on the server side. We set updatable
property to true for all columns that correspondent to the employee
table and false for the others. According to the dbname
property, DataStore recognizes what columns belong to the updatable table. The format of the dbname
property is the following: [table_alias.]column_name. If the table alias is not specified in the dbname
property its value equals to the value of updateAlias
(see emp_birthday
column, for example). In turn, updateAlias
property is also optional. If it is not specified, its value will be equal to the updateTable
value that is required for the updatable DataStore (or the DataWindow control). Eventually, if the dbname
property is absent in the column definition its value equals to the name
property (for example, salary
column).
When DataStore creates JSON-RPC requests it uses dbname
value without the alias part and sets it as the key of the nested properties of the request's params
value.
At least one updateable column with a truthy value of the
key
attribute must exists in the column list. Otherwise, the DataStore will not be available for updating.
Events
DataStore issues some events during its lifecycle, all of which are Observable
objects. So, you can subscribe and unsubscribe from it at any time while the DataStore is available. Most of the events receive one object as its argument - the object of the class that is derived from DwEvent
. Each dw-event class contains its own set of properties that reflect the context that was being executed at the time of their occurrence. The names and descriptions of the events are presented in the table below:
|Event | Parameter type | Description |
|:--- | :--- | :--- |
|onDbCancel
| void | Cancels the retrieval in process in a DataStore. Raised by dbCancel
method call |
|onDbError
| DwDbmsErrorEvent | Triggered when an error response is received from the backend. |
|onDwError
| any | Raises when an internal error occurs in a data or property expression of the DataStore |
|onHttpError
| HttpErrorResponse | Triggered from a non-successful HTTP status, an error while executing the request, or some other failure which occurred during the parsing of the response |
|onItemChanged
| DwItemChangedEvent | Raised after successfull setItem
method call |
|onRetrieveEnd
| DwRetrieveEndEvent | Occurs when the retrieval for the DataStore is complete |
|onRetrieveStart
| DwRetrieveStartEvent| Occurs when the retrieval for the DataWindow or DataStore is about to begin. |
|onUpdateEnd
| DwUpdateEndEvent | Occurs when all the updates to the database from the DataStore
are complete |
|onUpdateStart
| DwUpdateStartEvent | Occurs after a script calls the update
method and just before changes in the DataStore are sent to the database. |
Row selection
DataStore can both highlight and remove highlightings from its rows. You can select all rows or a single row, perform a search or filter rows with the status selected and so on. Examine the following code snippet:
this.ds.selectRow(0, true); // highlights first row
if this.ds.isSelected(0) {
console.log(this.ds.selectedCount()); // 1
}
this.ds.selectRow(0, false); // unselect the row
this.ds.selectRow(Infinity, true); // select all rows
this.ds.selectRow(Infinity, false); // remove all selections
DwScript language has a built-in IsSelected()
function that you can use in your dynamic expressions like this:
let row = this.ds.find("price > 100 && IsSelected()", 0, this.ds.rowCount());
if (row >= 0) {
...
}
this.ds.filterValue = "IsSelected()";
this.ds.applyFilter();
// Now we have selected rows only in the primary buffer
Grouping rows
You can group related rows together and, optionally, calculate statistics for each group separately. For example, you might want to group employee information by department and get total salaries for each department.
Each group is defined by one or more DataStore/DataWindow object columns or expressions. Each time the value in a grouping column changes, a break occurs and a new section begins.
To describe groups in your DataStore you should add group levels to the groups
property of the DataStore configuration object. The group level definition contains a column name or some expression that will be evaluated over the each row of the table records. The following code snippet shows a table definition with two grouping levels, department code and employee status:
...
options.table = {
columns: [...],
groups: ["dep_code", "emp_status"],
startSort: "dep_code asc, emp_status asc, emp_initials",
}
...
To correctly display grouping levels, the data should be sorted accordingly. You should call
groupCalc
method after change sort order or filter value of the DataStore/DataWindow data.
The DataStore's API has several methods that are designed to work with groups. Among them is findGroupChange
which searches for the next break for the specified group. For example:
// This code finds the number of the row at which a break occurs in group 1.
// It then checks whether the department number is 121. The search begins at row 0:
let found: boolean = false;
let breakRow: number = 0;
while (!found) {
breakRow = this.ds.findGroupChange(breakRow, 1);
// If no breaks are found, exit.
if (breakRow < 0) break;
// Have we found the section for Dept 121?
if (this.ds.getItemNumber(breakRow, "dept_id") == 121) {
found = true;
} else {
// Increment starting row to find next break
breakRow = breakRow + 1;
}
}
The DwScript language has many aggregate functions that you can use in the computed fields:
let res:number;
res = this.ds.evaluate("Sum(Round(qty*price,2), {range:'group_0'})", 0);
res = this.ds.evaluate("Count(dep_code, {range:'group_1'})", 10);
// for all rows
res = this.ds.evaluate("Max(salary, {range:'all'})", 0);
// or more concise
res = this.ds.evaluate("Max(salary)", 0);
You should not use these functions in validation rules or filter expressions
DwScript language
DwScript is a domain-specific programming language embedded into DataStore/DataWindow functionality. The program code (expression) is parsed and evaluated dynamically at run time within some context scope. This context is always a row object that resides inside the DataStore/DataWindow's data array. Thus, any column
/compute
name of the DataStore`s row model is a valid identifier for the DwScript. The result of the evaluation process, if successful, is returned to the calling procedure. If a syntax error is detected during the parsing stage, a runtime error is generated.
DwScript language doesn't understand the assignment operator (
=
) for now, so you CANNOT change a value of any identifiers, either internals or externals.
DwScript is a JavaScript
based language. It understands almost all set of the JavaScript language operators out of the box, but the names of the built-in functions, unlike the latter, do not depend on the case of characters, so for DwScript getRow
, GetRow
, GETROW
are identifiers of the same function object. Use the syntax that suits you best.
Supported operators
|Type | Values| | --- | --- | |Arithmetic | +, -, *, /, **, % | |Relational | ==, ===, !=, !==, >, >=, <, <=, &&, ||, in, ?? | |Bitwise | &, |, ^, >>>, >>, << | |Unary | !, -, +, ~, typeof, void | |Conditional| (...) ? _ : _ | |Identifers| Model properties, functions | |Literals | string, numeric, // (RegExp), true, false, null, undefined | |Structures| Arrays, Objects |
Pre-defined functions
The following table contains the names and descriptions of DataStore/DataWindow pre-defined expression functions, that is divided by categories. The default value of the function parameter (if any) is designated as: parameter_name=...
General functions
|Function | Returns |Description |
| --- | --- | --- |
|Between(v, b, e)| boolean | Tests whether a value v
is between b
and e
|
|Fork(v, m, d=null) | any | Returns a key value of the map object m
if it contains a key v
, otherwise d
|
|IfNull(v, d) | any | Returns d
value if v
is null, else v
|
|IIF(b, t, f) | any | Returns t
if the b
is truthy, else f
|
|InList(v, ...a[]) | boolean | Returns true if the v
is in the list a
, else false |
|IsNull(v) | boolean | Returns true if the v
is null, else false |
|IsNumber(v) | boolean | Returns true if the v
is a number, else false |
|NullIf(v, c) | any | Returns null if the v
equals to c
, else v
|
|String(v) | string | Converts v
to string. |
String functions
|Function | Returns |Description |
| --- | --- | --- |
|Asc(s) | number | Converts the first character of a string s
to its Unicode
code point |
|Char(n) | char | Converts an integer to a Unicode character|
|CharAt(s, n)| char | Returns character of the string s
at position n
|
|Dec(s) | Decimal | Converts the value of a string to a Decimal |
|Decode(s, f=Base64) | string | Returns the result of decoding a string s
in the format f
|
|Encode(s, f=Base64) | string | Returns the result of encoding a string s
in the format f
|
|EndsWith(s, e) | boolean | Checks if a string s
is ended on e
|
|Fill(s, n) | string | Builds a string of the specified length n
by repeating the specified characters until the result string is long enough |
|Format(s, ...a[]) | string | Replaces the format item in a specified template s
with the text equivalent of the value in arguments array a
|
|Hex(n)| string | Converts number n
to hexadecimal string|
|Integer(s) | number | Converts string s
to integer |
|IsDate(s) | boolean| Tests whether a string value is a valid date|
|IsNumber(s)| boolean| Reports whether the value of a string is a number|
|LastToken(s, d)| string | Gets the last token of string s
delimited by d
|
|Len(s) | number | Returns length of the string s
|
|Like(s, p) | boolean | Tests the string s
against the pattern p
, similar to the SQL LIKE function does |
|Left(s, n)| string | Obtains n
number of characters from the beginning of a string s
|
|LeftTrim(s, c=spaces)| string | Removes spaces from the beginning of a string |
|Long(s) | Long | Converts the value of a string s
to a Long data type |
|Lower(s)| string | Converts all the characters in a string to lowercase |
|Match(s, p)| boolean | Determines whether a string's value contains a particular RegExp pattern of characters|
|Mid(s, f, n=all) | string | Obtains a specified number n
of characters from a specified position f
in a string |
|Number(s)| number | Converts a string s
to a number |
|PadLeft(s, n, p=space)| string | Pads the left side of the string s
by characer p
up the length n
|
|PadRight(s, n, p=space)| string | Pads the right side of the string s
by characer p
up the length n
|
|Pos(s1, s2, n=0)| number | Finds one string within another string |
|PosR(s1, s2, n=last)| number | Finds one string within another string starting from the end |
|Real(s) | Real | Converts a string value to a Real datatype |
|Repeat(s, n)| string | Repeats the string n
times |
|Replace(s, t, r)| string | Replaces a portion of one string with another |
|Right(s, n)| string | Obtains n
number of characters from the end of a string s
|
|RightTrim(s, c=spaces)| string | Removes spaces from the end of a string |
|Space(n=1) | string | Builds a string of the specified length whose value consists of spaces |
|StartsWith(s, w)| boolean | Checks if a string s
is started with w
string |
|Substr(s, f, e)| string | Gets the substring of s
starting from f
position and ending on e
excluding|
|Token(s, d) | string | Gets the first part of the string s
delimited by d
|
|Trim(s) | string | Removes leading and trailing spaces from a string |
|TrimEnd(s) | string | Removes spaces from the end of a string |
|TrimStart(s) | string | Removes spaces from the beginning of a string|
|Upper(s) | string | Converts all characters in a string to uppercase letters |
|WordCap(s) | string | Sets the first letter of each word in a string to a capital letter and all other letters to lowercase |
Date/Time functions
| Function | Returns | Description |
| --- | --- | --- |
|Date(v=now) | Date | Converts v
to Date data type |
|DateTime(v=now) | DateTime| Converts v
to DateTime data type|
|Day(d=now) | number | Obtains the day of the month in a date value|
|DayName(d=now) | string | Gets the day of the week in a date value and returns the weekday's name |
|DayNumber(d=now)| number | Gets the day of the week of a date value and returns the number of the weekday|
|DaysAfter(d1,d2)| number |Gets the number of days one date occurs after another|
|Hour(d=now) | number |Obtains the hour in a time value. The hour is based on a 24-hour clock.|
|Hours(d=now) | number |Obtains the hour in a time value. The hour is based on a 24-hour clock. (The same as Hour
)|
|MilliSeconds(d=now) | number |Obtains the number of milliseconds in the date value|
|Minutes(d=now) | number| Obtains the number of minutes in the date value|
|Month(d=now) | number | Gets the month of a date value |
|Now() | Date | Obtains the current time based on the system time of the client machine |
|RelativeDate(d, n) | Date | Obtains the date that occurs a specified number of days n
after or before another date |
|RelativeTime(d, s) | Date | Obtains a time that occurs a specified number of seconds s
after or before another time within a 24-hour period |
|Seconds(d=now)| number | Gets the seconds of a Date object d
, using local time |
|SecondsAfter(d1,d2)| number | Gets the number of seconds one time occurs after another |
|Time(s) | Time | Converts a string to a Time datatype |
|Today() | Date | Obtains the system date and time |
|TzOffset() | number | Gets the difference in minutes between the time on the local computer and Universal Coordinated Time (UTC) |
|WeekDay(d=now)| number | Gets the day of the week, using local time |
|YMD(y,m=0,d=1)| Date | Gets the Date object from numbers of y
ear, m
onth and d
ay|
|Year(d=now) | number | Gets the year of a date value |
Numeric functions
| Function | Returns | Description | | --- | --- | --- | |Abs(n)| number | Calculates the absolute value of a number | |Atan(n)| number | Calculates the arc tangent of an angle | |Ceil(n)| number | Retrieves the smallest whole number that is greater than or equal to a specified limit | |Cos(n)| number | Calculates the cosine of an angle | |Exp(n)| number | Raises e to the specified power| |Floor(n)| number | Returns the greatest integer less than or equal to its numeric argument.| |Greater(...n[])| number | Returns the larger of a set of supplied numeric expressions| |Int(n)| number | Gets the largest whole number less than or equal to a number | |Lesser(...n[])| number | Returns the smaller of a set of supplied numeric expressions| |Log(n)| number | Gets the natural logarithm of a number| |PI(n=1)| number | This is the ratio of the circumference of a circle to its diameter| |Rand(n=1)| number| Returns a pseudorandom number between 0 and 1| |Round(n, d=0)| number | Rounds a number to the specified number of decimal places| |Sign(n)| number | Reports whether the number is negative, zero, or positive by checking its sign| |Sin(n)| number |Calculates the sine of an angle| |Sqrt(n)| number | Calculates the square root of a number| |Tan(n)| number | Calculates the tangent of an angle| |Truncate(n, d=0)| number | Truncates a number to the specified number of decimal places|
DataWindow specific functions
| Function | Returns | Description |
| --- | --- | --- |
|Avg(x, r={range:"all"}) | number | Calculates the average of the values of the column or expression x
in the specified range r
of data |
|Count(x, r={range:"all"}) | number | Calculates the total number of rows in the specified column or expression x
in the specified range r
of data|
|CumulativePercent(x, r={range:"all"}) | number | Calculates the total value of the rows up to and including the current row in the specified column as a percentage of the total value of the column/expression x
(a running percentage) in the specified range r
of data|
|CumulativeSum(x, r={range:"all"}) | number | Calculates the total value of the rows up to and including the current row in the specified column/expression x
(a running total) in the specified range r
of data|
|CurrentRow()| number | Reports the number of the current row (the row with focus)|
|Describe(...s[])| string[] | Reports the values of properties of a DataWindow object and controls within the object|
|First(x, r={range:"all"}) | any | Reports the value in the first row in the specified column/expression x
in the specified range r
of data|
|GetRow()| number | Reports the number of a row associated with a band in a DataWindow object|
|IsRowModified()| boolean | Reports whether the row has been modified|
|IsRowNew()| boolean| Reports whether the row has been newly inserted|
|IsSelected()| boolean | Determines whether the row is selected|
|Last(x, r={range:"all"}) | any | Gets the value in the last row in the specified column/expression x
in the specified range r
of data|
|LookupDisplay(c,v)| string| Obtains the display value in the code table associated with the data value in the specified column|
|Max(x, r={range:"all"}) | number | Gets the maximum value in the specified column/expression x
in the specified range r
of data |
|Min(x, r={range:"all"}) | number | Gets the minimum value in the specified column/expression x
in the specified range r
of data |
|Percent(x, r={range:"all"}) | number | Gets the percentage that the current value represents of the total of the values in the column/expression x
in the specified range r
of data|
|RowCount()| number | Obtains the number of rows that are currently available in the primary buffer |
|StDev(x, r={range:"all"})| number | Calculates an estimate of the standard deviation for the specified column/expression x
in the specified range r
of data |
|StDevP(x, r={range:"all"})| number | Calculates the standard deviation for the specified column/expression x
in the specified range r
of data |
|Sum(x, r={range:"all"}) | number | Calculates the sum of the values in the specified column/expression x
in the specified range r
of data|
|VarE(x, r={range:"all"})| number | Calculates an estimate of the variance for the specified column/expression x
in the specified range r
of data |
|VarP(x, r={range:"all"})| number | Calculates the variance for the specified column/expression x
in the specified range r
of data |
All aggregate functions of the DataStore/DataWindow ignore null values while processing the result of an expression
DataStore's API
| Method | Returns | Description |
| --- | --- | --- |
| applyFilter | Promise | Moves rows that do not meet the current filter criteria to the filter buffer |
| applySort | Promise | Sorts the rows of the DataStore based on its current sort criteria |
| create | ResultCode | Creates a DataWindow internal structure using the specified options |
| dbCancel | ResultCode | Cancels a database retrieval in progress |
| deletedCount | number |Returns the number of rows that have been deleted from the DataStore but have not yet been updated in the associated database table.|
| deleteRow | ResultCode | Deletes the specified row from the DataStore |
| describe | any[] | Returns requested information about the structure of the DataStore |
| distinctValues | Set | Returns a sorted set of unique values calculated by the specified expression over each row of the DataStore |
| exportToString | Promise | Saves the contents of a DataWindow or DataStore in the format you specify |
| evaluate | any | Evaluates a DwScript
expression for a particular data row |
| filteredCount | number | Returns the number of rows that do not meet the current filter criteria |
| find | number | Returns the number of the first row that meets the search criteria within a specified search range in the detail area of a DataStore |
| findGroupChange | number | Searches starting at a specified row for the first break for the specified group in the DataStore |
| findRequired | number | Identifies the required columns that the user has not filled |
| getColumn | number | Returns the number of the current column in the DataStore |
| getColumnName | string | Returns the name of the current column in the DataStore |
| getItem | any | Returns the data in the specified row and column of the DataStore |
| getItemDate | Date | Returns the date data in the specified row and column of the DataStore |
| getItemDateTime | DateTime | Returns the datetime data in the specified row and column of the DataStore |
| getItemDecimal | Decimal | Returns the decimal data in the specified row and column of the DataStore |
| getItemNumber | number | Returns the numeric data in the specified row and column of the DataStore |
| getItemStatus | ItemStatus | Returns the status of the item at the specified row and column location in the specified buffer |
| getItemString | string | Returns the string data in the specified row and column of the DataStore |
| getItemTime | Time | Returns the time data in the specified row and column of the DataStore |
| getNextModified | number | Returns the number of the first row that was modified in the specified buffer in the specified DataStore after the specified row |
| getRow | number | Returns an integer containing the number of the current row in the DataStore |
| getRowFromRowId | number | Gets the row number of a row in a DataStore from the unique row identifier associated with that row |
| getRowIdFromRow | number | Gets the unique row identifier of a row in a DataStore from the row number associated with that row |
| getSelectedRow | number | Returns the number of the first selected row after the specified row number in the DataStore |
| getValidate | string | Returns the validation rule used in the specified column of the DataStore |
| getValue | CodeTableItem | Returns the specified item in the value list for the specified column |
| groupCalc | ResultCode | Recalculates the breaks in the groups in the DataStore |
| groupCount | number | Returns the number of groups that have been defined in the DataStore's configuration |
| importString | Promise | Copies formatted data (CSV,TXT,JSON,XML) from a string to the DataStore |
| insertRow | number | Inserts a new initialized row before the specified row in the DataStore |
| isSelected | boolean | Returns true if the specified row in the DataStore is selected; returns false if the row is not selected or is greater than the number of rows in the DataStore |
| modifiedCount | number | Returns the number of rows that have been modified in the DataStore but have not yet been updated in the associated database table |
| modify | string | Uses the specification contained in a string to modify the DataStore |
| reselectRow | Promise | Accesses the database to reselect all columns that can be updated and refreshes all timestamp columns in a row in the DataStore |
| reselectRows | Promise | Accesses the database to retrieve rows with values of parameters, filter and sort criterias that is currently used in the DataStore |
| reset | ResultCode | Clears all the data from a DataStore |
| resetUpdate | ResultCode | Resets the update flags for the DataStore |
| retrieve | Promise | Causes the DataStore to retrieve rows from the database |
| rowCount | number | Returns the number of rows currently available in the DataStore (all the rows retrieved minus any deleted rows plus any inserted rows minus any rows that have been filtered out) |
| rowsCopy | ResultCode | Copies a range of rows from one DataStore to another DataStore (or DataWindow control) or from one buffer to another within a single DataStore |
| rowsDiscard | ResultCode | Discards a range of rows. The rows cannot be restored unless retrieved from the database |
| rowsMove | ResultCode | Clears a range of rows from a DataStore and inserts the rows in another DataStore (or DataWindow control) or another buffer of the same DataStore |
| selectRow | ResultCode | Selects or deselects the specified row of the DataStore |
| selectedCount | number | Reports if the DataStore object has selected rows and how many |
| setColumn | ResultCode | Makes the specified column the current column in the DataStore |
| setItem | ResultCode | Sets the value of the specified row and column of the specified DataStore |
| setItemStatus | ResultCode | Sets the status of a row in a specified column of the DataStore in the specified buffer |
| setHttpService | ResultCode | Causes a DataWindow control or DataStore to use a programmer-specified http-service object |
| setRow | ResultCode | Makes the specified row the current row in the DataStore |
| setValidate | ResultCode | Changes the validation rule used for the specified column of the DataStore |
| setValue | ResultCode | Sets the value of the specified item in the value list or the code table of the specified column of the DataStore |
| shareData | ResultCode | Shares data between a primary DataStore (or DataWindow control) and a secondary DataStore (or DataWindow control) |
| shareDataOff | ResultCode | Turns off sharing for the DataStore. If the DataStore is primary, all secondary DataStores (or DataWindow controls) are disconnected and their DataWindow objects no longer contain data |
| update | Promise | Sends to the database all inserts, deletes, and updates of the DataStore |
More information about the methods and events of the DataStore/DataWindow class and its collaborators you can find in the
*.d.ts
files, which are located in thelib
folder of the installed package.
CHANGELOG.md
file contains the history of changes to this library.
License
MIT
Copyright
(c) 2022 ALEXANDER STRELKOV, [email protected]