The following code example shows a number of client-side CRUD and submit operations that can be used with JSDO:
const progress = require("@progress/jsdo-core").progress;

const options = {    
    catalogURI: "https://oemobiledemo.progress.com/OEMobileDemoServices/static/CustomerService.json",
    serviceURI: "https://oemobiledemo.progress.com/OEMobileDemoServices/",
    authenticationModel: "anonymous"
};

let session,
    jsdo;

// A JSDOSession is how you connect to a service. JSDOs use the connections made by
// JSDOSessions to get data. 
progress.data.getSession(options).then((object) => {
    // getSession() returns an object that has three properties: jsdosession, result, and info
    console.log("The result of getSession() is: " + object.result);
    console.log("The JSDOSession is connected to the service at: " + object.jsdosession.serviceURI);
    
    session = object.jsdosession;
    
    // A JSDO is a representation of a table. fill() reads records from the service 
    jsdo = new progress.data.JSDO({name: "Customer"});
    return jsdo.fill();
})
.then((object) => {
    let rec;

    // fill() returns an object that has three properties: jsdo, success, and request
    console.log("The result of fill() is: " + object.success);
    console.log("The resulting HTTP status code returned is: " + object.request.xhr.status);

    // After a fill(), you can read, modify, and delete records and then send the changes
    // back to the service via saveChanges().

    // You can iterate through the records via a foreach on the JSDO's temp-table, which
    // will return a record object with the appropriate fields in the data property
    jsdo.ttCustomer.foreach((customer) => {
        if (customer.data.SalesRep === "BBB") {
            console.log("The SalesRep BBB handles the customer: " + customer.data.Name);
            rec = customer;
        }
    });

    // You can update a record just by modifying any of its fields
    rec.data.Balance = rec.data.Balance + 1500;

    // You can also use assign() to overwrite a record
    jsdo.ttCustomer.foreach((customer) => {
        if (customer.data.Name === "All Sports") {
            // If you do not specify a value for a field, it will be overwritten to be null
            customer.assign({
                Name: 'All Sports',
                SalesRep: 'R2D2',
                Balance: '1337',
                State: 'MN'
            });
        }
    });

    // add() creates a record  
    jsdo.ttCustomer.add({
        Name: 'Robert Good',
        SalesRep: 'NS',
        Balance: '1337',
        State: 'MA',
    });

    // remove() deletes a record
    jsdo.ttCustomer.foreach((customer) => {
        if (customer.data.Name === "Bobby Bad") {
            customer.remove();
            console.log("This customer was deleted: " + customer.data.Name);
        }
    });

    // All of the changes so far are just local until you send them back to the server
    // via a call to saveChanges(). saveChanges() also updates the local records in the jsdo.
    return jsdo.saveChanges();
})
.then((object) => {
    // saveChanges() returns an object that has three properties: jsdo, success, and request
    console.log("The result of saveChanges() is: " + object.success);

    // The local records are already updated but you can pass a filter to fill() to only get
    // a subset of the records
    return jsdo.fill('Balance = 1337');
})
.then((object) => {
    jsdo.ttCustomer.foreach((customer) => {
        console.log("We edited this customer: " + customer.data.Name);
    });

    // Always clean up your session afterwards!
    return session.invalidate();
})
.then((object) => {
    // invalidate() returns an object that has three properties: jsdo, result, and request
    console.log("The result of invalidate() is: " + object.result);
});

Event example

The following code example shows event handlers and demonstrates the proper sequence for firing them:
// This is a sample of how to use event handlers.
const progress = require("@progress/jsdo-core").progress;

const options = {    
    catalogURI: "https://oemobiledemo.progress.com/OEMobileDemoServices/static/CustomerService.json",
    serviceURI: "https://oemobiledemo.progress.com/OEMobileDemoServices/",
    authenticationModel: "anonymous"
};

let session,
    jsdo;

// Here is where we define all of our event handlers.
// Please note that the JSDO is passed to every event handler.
function beforeFill(jsdo) {
    console.log('In beforeFill()');
    console.log('The name of the table of the JSDO is: ' + jsdo.name);
}

function afterFill(jsdo) {
    console.log('In afterFill()');
}

function beforeUpdate(jsdo) {
    console.log('In beforeUpdate()');
}

function afterUpdate(jsdo) {
    console.log('In afterUpdate()');
}

function beforeSaveChanges(jsdo) {
    console.log('In beforeSaveChanges()');
}

function afterSaveChanges(jsdo) {
    console.log('In afterSaveChanges()');
}

progress.data.getSession(options).then((object) => {
    session = object.jsdosession;
    jsdo = new progress.data.JSDO({name: "Customer"});
    
    // Here is where we subscribe all of our event handlers
    jsdo.subscribe('beforeFill', beforeFill);
    jsdo.subscribe('afterFill', afterFill);

    jsdo.subscribe('beforeUpdate', beforeUpdate);
    jsdo.subscribe('afterUpdate', afterUpdate);

    jsdo.subscribe('beforeSaveChanges', beforeSaveChanges);
    jsdo.subscribe('afterSaveChanges', afterSaveChanges);

    console.log("Initial call to fill()");
    return jsdo.fill();
    
})
.then((object) => {
    let rec;

    console.log('In the success handler of fill()');

    console.log("Initial update of a record")
    jsdo.ttCustomer.foreach((customer) => {
        if (customer.data.Name === "All Sports") {
            customer.assign({
                Name: 'All Sports',
                SalesRep: 'R2D2',
                Balance: '1337',
                State: 'MN'
            });
        }
    });

    console.log("Initial call to saveChanges()");
    return jsdo.saveChanges();
})
.then((object) => {
    console.log('In the success handler of saveChanges()');

    // Always clean up your session afterwards!
    return session.invalidate();
});

// This is the output of this program when run: 
// Initial call to fill()
// In beforeFill()
// The name of the table of the JSDO is: Customer
// In afterFill()
// In the success handler of fill()
// Initial update of a record
// Initial call to saveChanges()
// In beforeSaveChanges()
// In beforeUpdate()
// In afterUpdate()
// In afterSaveChanges()
// In the success handler of saveChanges()

Create operation

Call the add() method on a JSDO table reference to create a new record in JSDO memory. The fields of the new record contain the values specified and passed to the method. For any fields whose values you do not provide, the method provides default values taken from the Data Object schema in the Data Service Catalog.

When each invocation of the add() method completes, the new record becomes the working record for the associated table and the JSDO marks this record as a new record for pending creation on the server. (Note that if the table has child tables, a working record is not set for these child tables.)

To synchronize the server database with new records you have created in JSDO memory without using a Submit operation, you call saveChanges() by passing either an empty parameter list to the method or a single parameter value of false. This executes the OpenEdge ABL server routine that implements the Data Object Create operation once for each newly created record in JSDO memory. In addition, for any other changed records in JSDO memory, this call also executes the server routine that implements the associated Delete or Update operation once for each associated record change.

When you call the saveChanges() method, if jQuery Promises are supported in your environment, it returns a Promise object on which you can register callbacks to handle completion of all record-change operations that it has invoked. Otherwise, you can subscribe event callbacks to handle the results of these operations.

Operation results are returned as follows:

  1. For each Create operation that completes, the JSDO fires an afterCreate event to execute any callbacks you have subscribed to handle that event. The JSDO also fires any afterDelete and afterUpdate events to execute any callbacks you have subscribed to handle these events.
  2. After all record-change operations complete, the JSDO fires an afterSaveChanges event to execute any callbacks you have subscribed to handle that event. In addition, any returned Promise object executes the Promise callbacks that you have registered, depending on the combined operation results. (Note that the signatures of all Promise callbacks are identical to the signature of the afterSaveChanges event callback.)

After the saveChanges() method and all resource operations that it has invoked successfully complete, no working records are set for the tables in the JSDO.

Note: If a successful Create or Update operation makes changes to the record on the server that was sent from the client, JSDO memory is automatically synchronized with these server changes when the request object with these results is returned to the client.
Note: If an error occurs on the server, and the autoApplyChanges property has the default value of true, any newly created record is automatically deleted from JSDO memory on the client.

Read operation

To load data into JSDO memory on the client, you call the fill() method on the JSDO, passing an optional parameter to specify selection criteria as either an object or a string. This executes the ABL server routine that implements the Data Object Read operation. Each time fill() is called, all records currently in JSDO memory are cleared and replaced by the records returned by the method.

When you access a Progress Data Service from the Telerik Platform and the JSDO dialect of the Kendo UI DataSource calls fill() on its JSDO, the DataSource passes an object parameter to the method. This object contains properties specified according to DataSource requirements to select, order, and organize the return of the records from the server. The Data Object resource on the server then receives the DataSource selection criteria specified by this object in the form of a Progress-defined JSON Filter Pattern (JFP). Note that the resource Read operation must be programmed to handle this JFP according to the requirements of the specific Progress Data Object Service. For more information, see the description of the fill( ) method.

If you are calling this method yourself, you can use a string to specify the selection criteria, again, according to the requirements of the Data Object resource whose data you are reading. If you do not pass a parameter to fill(), the records returned to the client depend entirely on the implementation of the resource Read operation. For more information, see the description of the fill( ) method.

When you call the fill() method, if jQuery Promises are supported in your environment, it returns a Promise object on which you can register callbacks to handle completion of the Read operation that it executes. Otherwise, you can subscribe event callbacks to handle the results of the Read operation:

Operation results are returned as follows:

  1. The JSDO fires an afterFill event for any callbacks you have subscribed to handle the event.
  2. Any returned Promise object executes the Promise callbacks that you have registered, depending on the operation results.

Note that before any callbacks execute, if the Read operation completes successfully, the working record for each JSDO table reference is set to its first record returned, depending on any active parent-child relationships. Thus for each child table reference, the first record is determined by its relationship to the related working record in its parent.

Update operation

To modify an existing record in JSDO memory on the client, you can call the assign() method to assign values to one or more fields of either a working record or a specific record in JSDO memory, or you can assign a value directly to the field of a working record. Using the assign() method, you set the values of fields to be updated in an object that you pass as a parameter to the method.

You can call the assign() method on:

  • A table reference on the JSDO that has a working record, as in the example below
  • A specific progress.data.JSRecord object reference

When the assign() method completes, any working record settings that existed prior to calling the method remain unchanged.

You can also assign a value directly to a single field of a JSDO working record as follows:

Syntax

jsdo-ref.table-ref.field-ref = value;
jsdo-ref.field-ref = value;

Where:

jsdo-ref
The reference to a JSDO, and if the JSDO contains only a single table, an implied reference to any working record that is set for that table.
table-ref
A table reference with the name of a table in jsdo-ref memory that has a working record.
field-ref
A field reference on a table-ref with the name and value of a field in the working record of the referenced table.
value
The value to set the field referenced by field-ref.

After either a successful call to the assign() method or a successful assignment to a field-ref of a working record, the JSDO marks the affected record for pending update on the server.

Note: Do not write directly to a field-ref that you reference on the data property of a table-ref; use field-ref on the data property only to read the referenced field value. Writing field values using the data property does not mark the record for pending update on the server, nor does it initiate a re-sort the record in JSDO memory according to any order you have established using the autoSort property. For more information, see the description of the data property. To mark a record for pending update and automatically re-sort the record according to the autoSort property, you must assign a record field value using one of the mechanisms described above.

To synchronize the server database with existing records you have updated in JSDO memory without using a Submit operation, you call saveChanges() by passing either an empty parameter list to the method or a single parameter value of false. This executes the OpenEdge ABL server routine that implements the Data Object Update operation once for each updated record in JSDO memory. In addition, for any other changed records in JSDO memory, this call also executes the server routine that implements the associated Create or Delete operation once for each associated record change.

Note: When multiple pending record changes exist in JSDO memory, the saveChanges() method invoked without Submit groups invocation of the associated resource operations in the order of 1) all Delete operations, 2) all Create operations, and 3) all Update operations, and invokes each such operation one record at a time over the network.

When you call the saveChanges() method, if jQuery Promises are supported in your environment, it returns a Promise object on which you can register callbacks to handle completion of all record-change operations that it has invoked. Otherwise, you can subscribe event callbacks to handle the results of these operations.

Operation results are returned as follows:

  1. For each Update operation that completes, the JSDO fires an afterUpdate event to execute any callbacks you have subscribed to handle that event. The JSDO also fires any afterCreate and afterDelete events to execute any callbacks you have subscribed to handle these events.
  2. After all record-change operations complete, the JSDO fires an afterSaveChanges event to execute any callbacks you have subscribed to handle that event. In addition, any returned Promise object executes the Promise callbacks that you have registered, depending on the combined operation results. (Note that the signatures of all Promise callbacks are identical to the signature of the afterSaveChanges event callback.)

After the saveChanges() method and all resource operations that it has invoked successfully complete, no working records are set for the tables in the JSDO.

Note: If successful execution of a resource Create or Update operation results in changes to the record on the server that was sent from the client (for example, an update to a sequence value), JSDO memory is automatically synchronized with these server changes when the request object with these results is returned to the client.
Note: If an error occurs on the server, and the autoApplyChanges property has the default value of true, any updated record has its changes automatically backed out from JSDO memory on the client.

Delete operation

To delete an existing record from JSDO memory on the client, you call the remove() method on a JSDO table reference.

You can call the remove() method on:

  • A table reference on the JSDO that has a working record
  • A specific progress.data.JSRecord object reference, as in the example below

When the remove() method completes, no working record is set for the associated table or any of its child tables.

After either a successful call to the remove() method, the JSDO marks the affected record for pending deletion on the server.

To synchronize the server database with existing records you have deleted in JSDO memory without using a Submit operation, you call saveChanges() by passing either an empty parameter list to the method or a single parameter value of false. This executes the OpenEdge ABL server routine that implements the Data Object Update operation once for each deleted record in JSDO memory. In addition, for any other changed records in JSDO memory, this call also executes the server routine that implements the associated Create or Update operation once for each associated record change.

Note: When multiple pending record changes exist in JSDO memory, the saveChanges() method invoked without Submit groups invocation of the associated resource operations in the order of 1) all Delete operations, 2) all Create operations, and 3) all Update operations, and invokes each such operation one record at a time over the network.

When you call the saveChanges() method, if jQuery Promises are supported in your environment, it returns a Promise object on which you can register callbacks to handle completion of all record-change operations that it has invoked. Otherwise, you can subscribe event callbacks to handle the results of these operations.

Operation results are thus returned as follows:

  1. For each Delete operation that completes, the JSDO fires an afterDelete event to execute any callbacks you have subscribed to handle that event. The JSDO also fires any afterCreate and afterUpdate events to execute callbacks you have subscribed to handle these events.
  2. After all record-change operations complete, the JSDO fires an afterSaveChanges event to execute any callbacks you have subscribed to handle that event. In addition, any returned Promise object executes the Promise callbacks that you have registered, depending on the combined operation results. (Note that the signatures of all Promise callbacks are identical to the signature of the afterSaveChanges event callback.)

After the saveChanges() method and all resource operations that it has invoked successfully complete, no working records are set for the tables in the JSDO.

Note: If an error occurs on the server, and the autoApplyChanges property has the default value of true, any deleted record is restored to JSDO memory on the client.

Submit operation

To send multiple record changes over the network in a single request, you:

  1. Use the same mechanisms to create, update, and delete records in JSDO memory as described in this topic for the Create, Update, and Delete operations.
  2. Call the saveChanges() method using the Submit operation by passing it a parameter value of true.

When you call saveChanges(true) to synchronize pending changes with the server database, this executes the OpenEdge ABL server routine that implements the Data Object Submit operation. This operation processes all pending record changes sent from the client in a single network request and returns the results of all these changes from the server in a single network response.

Note that this Submit operation is supported only for OpenEdge Data Object Services, where the Data Object is implemented for a ProDataSet resource with one or more temp-tables that supports before-imaging. This method call relies on client before-image data to identify all pending record changes for the network request since the last invocation of the fill() or saveChanges() method on the JSDO. The server then relies on the before-image data to identify and process each record change according to its type. If a ProDataSet resource and the JSDO that accesses it are not defined to support before-image data, making this call to saveChanges() raises an exception.

When you call the saveChanges() method to invoke a Submit operation on a supported resource, if jQuery Promises are supported in your environment, it returns a Promise object on which you can register callbacks to handle the results for all record changes in the request. Otherwise, you can subscribe event callbacks to handle the results of the request.

Operation results are thus returned as follows:

  1. For each record change that the Submit operation completed, the JSDO fires an afterDelete, afterCreate, and afterUpdate event for each record change of the corresponding type in order to execute any callbacks you have subscribed to handle these events.
  2. The JSDO fires an afterSaveChanges event to execute any callbacks you have subscribed to handle that event. In addition, any returned Promise object executes the Promise callbacks that you have registered, depending on the overall Submit operation results. (Note that the signatures of all Promise callbacks are identical to the signature of the afterSaveChanges event callback.)

After the saveChanges() method completes a Submit operation successfully, no working records are set for the tables in the JSDO.

Note: If a Submit operation results in successful record create or update that includes changes to the record on the server that was sent from the client (for example, an update to a sequence value), JSDO memory is automatically synchronized with these server changes when the request object with the operation results is returned to the client.
Note: If an error occurs on the server for a Submit operation, and the autoApplyChanges property has the default value of true, each record change is automatically accepted or rejected in JSDO memory based on the presence of an error string that can be returned by calling the JSDO getErrorString() method on the record. If you want to accept and reject record changes to synchronize with any server transaction that handles for the Submit operation, you must set autoApplyChanges to false and call the appropriate accept*Changes() or reject*Changes() method to handle the results.