How to Handle Data Properly in the TreeGrid’s Data Arrived Event

One of the strengths of the SmartClient framework is how it automatically pages data that is displayed in its grid widget. It pulls, by default, 75 rows of data at a time and performs this paging automatically. However, in the course of converting our project to SmartClient, we were already performing some complicated validations in a stored procedure which stores the errors as XML to a table. So even though the SmartClient framework provides robust data validation, we chose to retain our tried and true method in this particular case.

segue-How-to-Handle-Data-Properly-TreeGrid-Data-Arrived-Event

 

So how does one apply validations errors to existing data in a SmartClient TreeGrid? The appropriate place to perform such an operation is the “dataArrived” event, a method that is called immediately after SmartClient performs the database call to retrieve another page of data. The method described is here: TreeGrid.dataArrived. As you can see, the method has a single parameter, “parentNode”, which is a ResultTree object.

Like most developers, my first thought was to simply output the “parentNode’s” structure to console and see how the data was stored within. The first test case used had only two records in the TreeGrid, so no paging was involved. The following adds an iscLogWarn to display the parentNode object in the TreeGrid’s dataArrive method:

//  TreeGrid's dataArrived
dataArrived: function(parentNode) {
    isc.logWarn(isc.JSON.encode(parentNode));  // Output the parentNode contents
    this.Super("dataArrived", arguments);
}

And this produced the following in the console:

// The output from isc.Console (much of it removed for brevity)
{

    "children":[
        {
            "isOpen":true,
            "treeName":"Tree1",
            "children":[
                {
                    "PARENT_ID":17049500,
                    "CHILD_ID":8435725,
                    "NAME":"XYZ",
                    "START_DATE":"07/01/2001",
                    "VALIDATIONS":"{\"NAME\":\"The name must me 4 or more characters.\",\" START_DATE \":\"The create date must be today or later.\"}"
                },
                {
                    " PARENT_ID ":17049500,
                    " CHILD_ID":8435837,
€¦

The contents of the parentNode appears to have an array of data in the “children” property nested within another “children” array.  That might lead you to believe you can simply loop over the two nested arrays and start applying the VALIDATION to each row.

dataArrived: function(parentNode) {
    this.Super("dataArrived", arguments);
    for (var i = 0; parentNode.children.length && i < parentNode.children.length; i++) {
        var parentRecord = parentNode.children[i];
        for ( var x = 0; parentRecord.children && x < parentRecord.children.length; x++ ){
            var record = parentRecord.children[x];
            if ( record ) {
                // As long as there is a record, clear any errors.
                this.clearRowErrors( this.getRecordIndex( record ) );
                // if we have validations, load the errors.
                if (record.VALIDATIONS ) {
                    var propertiesValidations =JSON.parse( record.VALIDATIONS );
                    this.setRowErrors( this.getRecordIndex( record ), propertiesValidations );
                }
            }
        } // for x
    } // for i
} // dataArrived

This solution, however, has unexpected results.  If a larger dataset is returned and paging is in play, the structure changes.

Outputting the contents of the parentNode when accessing a larger, paged resultset returns the following:

// The output form isc.Console (much of it removed for brevity)

{

“children”:{

},

“childCount”:50,

“isFolder”:true

}

There is no data listed in the TreeResult object’s “children” property. Now my nested loops fail to set the errors as expected. The data is there, you just have to know the correct ResultSet methods to access it: “get” and “rowIsLoaded“.

The check with isRowLoaded is critical because the limit for the outer for loop, parent.children.getLength(), is the number of all the records that could be returned for the query, not just the 75 rows that were returned in the paged resultset. In the case that thousands of records meet your criteria, forgetting the isRowLoaded property will cause SmartClient to iterate over every page of data available. In our particular case, leaving out isRowLoaded caused the grid to load in over 30 seconds for some tests. That’s not the kind of user experience you want to provide. Below is the final piece of code with the critical changes highlighted:

dataArrived: function(parentNode) {
    this.Super("dataArrived", arguments);
    for (var i = 0;i < parentNode.children.getLength(); i++) {
        // Check that the record has been loaded before performing a "get"
       if ( isc.isA.ResultSet( parentNode.children ) && !parentNode.children.isRowLoaded() ) {
            continue;
        }
        var parentRecord = parentNode.children[i];
        for ( var x = 0; parentRecord.children && x < parentRecord.children.length; x++ ){
            var record = parentRecord.children.get(i);
            if ( record ) {
                // As long as there is a record, clear any errors.
                this.clearRowErrors( this.getRecordIndex( record ) );
                // if we have validations, load the errors.
                if (record.VALIDATIONS ) {
                    var propertiesValidations =JSON.parse( record.VALIDATIONS );
                    this.setRowErrors( this.getRecordIndex( record ), propertiesValidations );
                }
            }
        } // for x
    } // for i
} // dataArrived

In summary, knowing the right methods to call on a SmartClient ResultSet object will provide you the performance and stability you expect from your application.