Child pages
  • Foundset property type

Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

When updates are received from the server for this foundset property, any listeners registered via .addChangeListener() - see above - will get notified.

This was added in order to improve performance by removing the need for angular watches. You no longer need to add lots of angular watches, deep or collection watches in order to be aware of incomming incoming server changes to the foundset property. Each such watch would slow down the page - as watches are triggered a lot for all kinds of user actions or socket traffic. Also the listener can give more detailed information in order to do more granular updates to the UI easier.

So to To add a an incoming server change listener to this property type just call:

...

Code Block
languagejs
titlewhat "changes" parameter can contain:
{
    // if value was non-null and had a listener attached before, and a full value update was
    // received from server, this key is set; if newValue is non-null, it will automatically get the old
    // value's listeners registered to itself
    // NOTE: it might be easier to rely just on a shallow $watch of the foundset value (to catch even the
    // null to non-null scenario that you still need) and not use NOTIFY_FULL_VALUE_CHANGED at all
    $foundsetTypeConstants.NOTIFY_FULL_VALUE_CHANGED: { oldValue : ..., newValue : ... },

    // the following keys appear if each of these got updated from server; the names of those
    // constants suggest what it was that changed; oldValue and newValue are the values for what changed
    // (e.g. new server size and old server size) so not the whole foundset property new/old value
    $foundsetTypeConstants.NOTIFY_SERVER_SIZE_CHANGED: { oldValue : ..., newValue : ... },
    $foundsetTypeConstants.NOTIFY_HAS_MORE_ROWS_CHANGED:  { oldValue : ..., newValue : ... },
    $foundsetTypeConstants.NOTIFY_MULTI_SELECT_CHANGED:  { oldValue : ..., newValue : ... },
    $foundsetTypeConstants.NOTIFY_COLUMN_FORMATS_CHANGED:  { oldValue : ..., newValue : ... },
    $foundsetTypeConstants.NOTIFY_SORT_COLUMNS_CHANGED:  { oldValue : ..., newValue : ... },
    $foundsetTypeConstants.NOTIFY_SELECTED_ROW_INDEXES_CHANGED:  { oldValue : ..., newValue : ... },
    $foundsetTypeConstants.NOTIFY_VIEW_PORT_START_INDEX_CHANGED:  { oldValue : ..., newValue : ... },
    $foundsetTypeConstants.NOTIFY_VIEW_PORT_SIZE_CHANGED:  { oldValue : ..., newValue : ... },
    $foundsetTypeConstants.NOTIFY_VIEW_PORT_ROWS_COMPLETELY_CHANGED:  { oldValue : ..., newValue : ... },

    // if we received add/remove/change operations on a set of rows from the viewport, this key
    // will be set; as seen below, it contains "updates" which is an array that holds a sequence of
    // granular update operations to the viewport; the array will hold one or more granular add or, remove
    // or update operations;
    //
    // BEFORE Servoy 8.4: all the "startIndex" and "endIndex" values below are relative to the viewport's
state after all  // state after //all previous updates in the array were already processed (so they are NOT relative to
    // the initial or final state); of the viewport  // indexes are 0 based
    $foundsetTypeConstants.NOTIFY_VIEW_PORT_ROW_UPDATES_RECEIVED: {data!). Updates can come in a random order so there is
    // NO guarantee related to each change/insert/delete startingindexes with 8.3.2; sometimes knowing the old
   pointing to the correct new data in the
    // final current viewport sizestate
helps calculate incomming granular updates//
easier    // STARTING WITH  Servoy $foundsetTypeConstants.NOTIFY_VIEW_PORT_ROW_UPDATES_OLD_VIEWPORTSIZE: ...,

        // starting with 8.3.2 you can use instead of 'updates' below the new constant;
        // $foundsetTypeConstants.NOTIFY_VIEW_PORT_ROW_UPDATES
   8.4: all the "startIndex" and "endIndex" values below are relative to the
    // viewport's state after all previous updates in the array were already processed. But due to some
    // before 8.3.2 just use 'updates';
        updates : [
            {
    pre-processing that happens server-side (it merges and sorts these ops), the indexes of update
    // operations THAT POINT TO DATA (so ROWS_INSERTED and ROWS_CHANGED operations) are relative also to
    // the viewport's final/current state, so after ALL updates in the typearray : $foundsetTypeConstants.ROWS_CHANGED,
were already processed
    // (so these indexes are correct both related to the intermediate state of the viewport startIndexdata :
...,    // and to the final state of viewport data).
    // This means that endIndexit :is ...now easier to apply UI changes to the component as these granular updates
},    // GUARANTEE that if you apply them in sequence {(one by one) to the component's UI (delete, insert and
    // change included) you can safely use //the whenindexes anin INSERTthere happenedto butget viewportnew sizedata remainedfrom the same, it is
  present state
    // of the viewport.
    //
    // indexes are 0 based
   // possible that some of the rows that were previously at the end of the viewport
            $foundsetTypeConstants.NOTIFY_VIEW_PORT_ROW_UPDATES_RECEIVED: {

        // DEPRECATED in Servoy 8.4: granular updates are much easier to apply now; see comment above
        // slidedAdded out of it; "removedFromVPEnd" givesin 8.3.2; sometimes knowing the number ofold
such rows that were removed    // viewport size helps calculate incomming granular updates easier
        // from the end of the viewport due to the insert operation;
           $foundsetTypeConstants.NOTIFY_VIEW_PORT_ROW_UPDATES_OLD_VIEWPORTSIZE: ...,

        // starting with 8.3.2 you can use instead of 'updates' below the new constant;
        // NOTE: insert signifies an insert into the client viewport, not necessarily
  $foundsetTypeConstants.NOTIFY_VIEW_PORT_ROW_UPDATES
        // before 8.3.2 just use 'updates';
        updates : [
      // an insert in the foundset itself;{
for example calling "loadExtraRecordsAsync"                 type : $foundsetTypeConstants.ROWS_CHANGED,
 // can result in an insert notification + bigger viewport size notification,        startIndex : ...,
          // with removedFromVPEnd = 0      endIndex : ...
            type : $foundsetTypeConstants.ROWS_INSERTED},
            {
       startIndex : ...,           // NOTE: insert signifies an insert into the client viewport, endIndexnot :necessarily
...,                    // removedFromVPEndan :insert ...in the foundset itself; for example calling "loadExtraRecordsAsync"
     },             {  // can result in an insert notification + bigger viewport size notification,
       // when a DELETE happened inside the viewport but there were more rows available// inwith theremovedFromVPEnd = 0
                  // foundset aftertype current viewport: $foundsetTypeConstants.ROWS_INSERTED,
it is possible that some of those rows             startIndex : ...,
     // slided into the viewport; "appendedToVPEnd " gives the number of such rows   endIndex : ...,

              //    that were appended// toDEPRECATED thestarting endwith of the viewport due to the DELETE operationServoy 8.4; it would always be 0 here
                    // NOTE: delete signifies aas server-side code will add a separate delete fromoperation theinstead client- viewport,if notnecessary
necessarily                     // a delete in the foundset itself; for example calling "loadLessRecordsAsync" canBEFORE 8.4: when an INSERT happened but viewport size remained the same, it was
                    // resultpossible infor asome deleteof notificationthe +rows smallerthat viewportwere sizepreviously notification,at the end of the viewport
                    // with appendedToVPEnd = 0 to slide out of it; "removedFromVPEnd" gives the number of such rows that were
                    // removed from the end of the viewport due to this insert operation;
                    typeremovedFromVPEnd : $foundsetTypeConstants.ROWS_DELETED,...
            },
          startIndex : ...,{
                    endIndex// NOTE: ...,delete signifies a delete from the client viewport, not necessarily
                     appendedToVPEnd : ...
     // a delete in the foundset itself; for example calling "loadLessRecordsAsync" can
      }         ]     }// 
}

To make the "updates" part above clearer:

Then you got these "updates" from the listener:

updates: [ // "newRow1" inserted { type: $foundsetTypeConstants.ROWS_INSERTED,
result in a delete notification + smaller viewport size notification,
           
start:
 
2,
 
end:
 
2,
 
removedFromVPEnd:
 
1
 
},
   
// 
update
with 
row
appendedToVPEnd 
to
= 
"newRow2"
0 
contents
   
{
 
type:
 
$foundsetTypeConstants.ROWS_CHANGED,
     
start:
 
4,
 
end:
 
4
 
}
                  
                    type : $foundsetTypeConstants.ROWS_DELETED,
                    startIndex : ...,
                    endIndex : ...,

                    // DEPRECATED starting with Servoy 8.4; it would always be 0 here
                    // as server-side code will add a separate insert operation instead - if necessary
                    // BEFORE 8.4: when a DELETE happened inside the viewport but there were more rows
                    // available in the foundset after current viewport, it was possible for some of those
                    // rows to slide into the viewport; "appendedToVPEnd " gives the number of such rows
                    // that were appended to the end of the viewport due to this delete operation
                    appendedToVPEnd : ...
            }
        ]
    }

}
Section
bordertrue
Column
width15%

Let's say you had in your viewPort (before the incomming changes got applied to it):

No Format
row1
row2
row3
row4
row5
Column
width55%
Code Block
languagejs

To make the "updates" part above clearer:

Section
bordertrue
Column
width15%

Let's say you had in your viewPort (before the incomming changes got applied to it):

No Format
row1
row2
row3
row4
row5
Column
width55%

Then you got these "updates" from the listener (before Servoy 8.4):

Code Block
languagejs
updates: [
  // "newRow1" inserted
  { type: $foundsetTypeConstants.ROWS_INSERTED,
    start: 2, end: 2, removedFromVPEnd: 1 },

  // update row to "newRow2" contents
  { type: $foundsetTypeConstants.ROWS_CHANGED,
    start: 4, end: 4 }
]

that would be equivalent to the following (starting with Servoy 8.4):

Code Block
languagejs
updates: [
  // "newRow1" inserted
  { type: $foundsetTypeConstants.ROWS_INSERTED,
    start: 2, end: 2 },

  // update row to "newRow2" contents
  { type: $foundsetTypeConstants.ROWS_CHANGED,
    start: 4, end: 4 }

  // "row5" slides out of viewport due to
  // the initial insert
  { type: $foundsetTypeConstants.ROWS_DELETED,
    start: 5, end: 5 },
]
Column
width15%

that means that the viewport has changed like this after the first update got applied:

No Format
row1
row2
newRow1
row3
row4
[row5] // ver. >= 8.4
Column
width15%

and like this after the second (and third for >= 8.4) update got applied:

No Format
row1
row2
newRow1
row3
newRow2

Please note the when your listener is called the actual contents of the viewPort are already updated. So, at that time your viewport already looks like the last version above. You might find (for Servoy < 8.4) what $foundsetTypeUtils below provides useful depending on how you plan on using this listeneryou plan on using this listener. For 8.4 and higher you no longer need to compute indexes like that client-side.

Defining/using a foundset property with a random set of dataproviders

...

Code Block
languagejs
titleClient-side .js
function getChildFoundSet(rowObjFromFoundsetsViewport, parentLevelGroupColumnIndex,
                                                  newLevelGroupColumnIndex) {
    // 'rowObjFromFoundsetsViewport' comes from the foundset (that contributed the expanded row)
    //     property type's viewport so equivalent to something like
    //     $scope.model.foundsetProps[i].viewPort.rows[expandedRowIndex]
    // 'parentLevelGroupColumnIndex' and 'newLevelGroupColumnIndex' are indexes in
    //     an array property that holds the grouping newLevelGroupColumnIndex)column {dataproviders
    // if 'rowObjFromFoundsetsViewportnewLevelGroupColumnIndex' comes from the foundset (that contributed the expanded rowis undefined, then we are requesting tree leafs (not groups)
    //     and then property type's viewport so equivalent to something likethe server-side query is a bit different

    // foundset query needed  $scope.model.foundsetProps[i].viewPort.rows[expandedRowIndex]
    // 'parentLevelGroupColumnIndex' and 'newLevelGroupColumnIndex' are indexes infor leaf level: Select pk from orders where Country = ? and City = ? and ...
    // foundset query needed for intermediate grouped level; anie. arraywhen propertyyou thatwant holdsto theexpand groupinga columncountry dataprovidersgroup
    // if 'newLevelGroupColumnIndex' is undefined, thento wenext arelevel requestingthat treeis leafsgrouped (not groups)
    //     and then the server-side query is a bit different

    // foundset query needed for leaf level: Select pk from orders where Country = ? and City = ? and ...
    // foundset query needed for intermediate grouped level; ie. when you want to expand a country groupby city: SELECT DISTINCT MIN(pk) FROM Customers Where Country = 
    //     "Mexico" GROUP BY City;
                    
    // as you can see below I try to send abstract things to the server (the client shouldn't really know
    // real datasource names, real column/dataprovider tonames nextand levelso thaton is(those groupedcan bybe city:determined SELECT DISTINCT MIN(pkon server)
FROM Customers Where Country =// so both client and server //code should be done so "Mexico"that GROUPthese BYkinds City;of information never reach the
    // client in the first place - only as abstract ids or indexes/names of component properties)
 // as you can see below I try to send abstract things to the server (the client shouldn't really know
    // realso datasourceserver names,needs realto column/dataproviderbe namesgiven andthe soexpanded onrow (those can be determined on server)
it can get the foundset of the Record from that) and
    // so both client and server code should be done so that these kinds of information never reach thethe groupColumn (index of the column) of the child level if that one is an grouped intermediate level
     // clientotherwise, inif theit firstis placegoing -to onlyrequest asleafs abstractit idsshould orset indexes/namesundefined offor component"newLevelGroupColumnIndex"
properties)
    // NOTE: if we could get it nicely from the parent foundset's query there would be no use sending the
 // so server needs// toexpanded benode's givengroup thecolumn expandedbecause rowthat (it can get the foundset ofis already available on the Recordserver from that) andfoundset's
    // thequery groupColumn (indexgroup ofby theclause)
column)
of the child level ifvar thatchildFoundsetPromise;
one
is an grouped intermediate levelif (newLevelGroupColumnIndex) {
  // otherwise, if it is going tochildFoundsetPromise request leafs it should set undefined for "newLevelGroupColumnIndex"= $scope.svyServoyapi.callServerSideApi("getGroupedChildFoundsetId",
           // NOTE: if we could get it nicely from the parent foundset's query there would be no use sending the
 [rowObjFromFoundsetsViewport, parentLevelGroupColumnIndex, newLevelGroupColumnIndex]);
    } else {
   // expanded node's group column becausechildFoundsetPromise that is already available on the server from that foundset's= $scope.svyServoyapi.callServerSideApi("getLeafChildFoundsetId",
            // query (group by clause)      var childFoundsetPromise[rowObjFromFoundsetsViewport, parentLevelGroupColumnIndex]);
	}

   if (newLevelGroupColumnIndexchildFoundsetPromise.then(function(childFoundsetId) {
        var childFoundsetPromisechildFoundset = $scope.svyServoyapi.callServerSideApi("getGroupedChildFoundsetId",
 getFoundSetByFoundsetId(childFoundsetId);
        mergeData(..., childFoundset);
    }, function() {
        // some error happened
  [rowObjFromFoundsetsViewport, parentLevelGroupColumnIndex, newLevelGroupColumnIndex]);      (...)
    } else);
}
(...)
function getFoundSetByFoundsetId(foundsetId) {
    if ($scope.model.childFoundsets)
  childFoundsetPromise = $scope.svyServoyapi.callServerSideApi("getLeafChildFoundsetId",
              for (var i = 0; i < $scope.model.childFoundsets.length; i++) {
            if [rowObjFromFoundsetsViewport, parentLevelGroupColumnIndex]);
	}
($scope.model.childFoundsets[i].foundsetId == foundsetId)
    childFoundsetPromise.then(function(childFoundsetId) {         var childFoundset = getFoundSetByFoundsetId(childFoundsetId);
return $scope.model.childFoundsets[i];
		 
    return  mergeData(..., childFoundset);
    }, function() {null;
}
Code Block
languagejs
titleServer-side .js
$scope.getGroupedChildFoundsetId = function(parentRecord, parentLevelGroupColumnIndex,
        // some error happened         (...)     });
} (...) function getFoundSetByFoundsetId(foundsetId) {     if ($scope.model.childFoundsets)         fornewLevelGroupColumnIndex) (var{
i
= 0; i < $scope.model.childFoundsets.length; i++) {  var parentFoundset = parentRecord.foundset;
        if ($scope.model.childFoundsets[i].foundsetId == foundsetId)
var childQuery = parentFoundset.getQuery();
		
        if (parentLevelGroupColumnIndex == undefined) {
  return $scope.model.childFoundsets[i]; 		      return null;
}
Code Block
languagejs
titleServer-side .js
$scope.getGroupedChildFoundsetId = function(parentRecord, parentLevelGroupColumnIndex,
            // this is the first grouping operation; alter initial query to get all first level groups
            (...)
            return;
      newLevelGroupColumnIndex) { } else {
 			// this is an intemediate  var parentFoundset = parentRecord.foundsetgroup expand; alter query of parent level for the child varlevel
childQuery = parentFoundset.getQuery			childQuery.groupBy.clear();
		         if (parentLevelGroupColumnIndex == undefined) { childQuery.groupBy.add(childQuery
             // this is the first grouping operation; alter initial query to get all first level groups .columns[$scope.model.columns[newLevelGroupColumnIndex].dataprovider]);
            var parentGroupColumnName =    ($scope.model..)columns[parentLevelGroupColumnIndex].dataprovider;
            return;
   childQuery.where.add(childQuery.columns[parentGroupColumnName]
    } else { 			// this is an intemediate group expand; alter query of parent level for the child level 			childQuery.groupBy.clear().eq(parentRecord[parentGroupColumnName]));
        }
		
  childQuery.groupBy.add(childQuery      var childFoundset = parentFoundset.duplicateFoundSet();
		childFoundset.loadRecords(childQuery);
		
		var dps = {};
		for (var idx = 0; idx < $scope.model.columns.length; idx++) {
			dps["dp" + idx] = $scope.model.columns[newLevelGroupColumnIndexidx].dataprovider]);
            var parentGroupColumnName = $scope.model.columns[parentLevelGroupColumnIndex].dataprovider;
            childQuery.where.add(childQuery.columns[parentGroupColumnName]
   ;
		}
		
		$scope.model.childFoundsets.push({
			foundset: childFoundset,
			dataproviders: dps,
			sendSelectionViewportInitially: false,
			initialPreferredViewPortSize: 15
		}); // send it to client as a foundset property in the array of foundsets
		
		return childFoundset; // return the foudnsetId that points to this foundset (return type
                  .eq(parentRecord[parentGroupColumnName]));         } 		  // 'foundsetRed' will make a foundsetID from varthe childFoundset = parentFoundset.duplicateFoundSet();
		childFoundset.loadRecords(childQuery);
		
		var dps = {};
		for (var idx = 0; idx < $scope.model.columns.length; idx++) {
			dps["dp" + idx] = $scope.model.columns[idx].dataprovider;
		}
		
		$scope.model.childFoundsets.push({
			foundset: childFoundset,
			dataproviders: dps,
			sendSelectionViewportInitially: false,
			initialPreferredViewPortSize: 15
		}); // send it to client as a foundset property in the array of foundsets
		
		return childFoundset; // return the foudnsetId that points to this foundset (return type
   )
	};
Code Block
languagejs
title.spec file
	"serverscript": "mycomppck/mycompname/mycomp_server.js",
(...)
	"model": 
	{
        "columns": { "type": "columnDef[]", "droppable": true },
        "childFoundsets": { "type": "foundset[]", "default": [] }
(...)
	"types": 
	{
        "columnDef": {
            "dataprovider": { "type": "dataprovider", "forFoundset": "myFoundset" }
            (...)
        }
	},
	"internalApi" :  // 'foundsetRed' will make a foundsetID from the childFoundset)
	};
Code Block
languagejs
title.spec file
	"serverscript": "mycomppck/mycompname/mycomp_server.js",
(...)
	"model": 
	{{
		"getGroupedChildFoundsetId" : {
			"returns" : "foundsetRef",
			"parameters" : 
			[ { "name" : "parentRecord", "type" : "record" },
             "columns": { "typename": "columnDef[]parentLevelGroupColumnIndex", "droppabletype": true"int" },
        "childFoundsets":      { "typename": "foundset[]newLevelGroupColumnIndex", "defaulttype": ["int" } ]
		},
(...)
	"types": 
	{
        "columnDef": {
            "dataprovider": { "type": "dataprovider", "forFoundset": "myFoundset" }
            (...)
        }
	},
	"internalApi" : {
		"getGroupedChildFoundsetId" : {
			"returns" : "foundsetRef",
			"parameters" : 
			[ { "name" : "parentRecord", "type" : "record" },
              { "name": "parentLevelGroupColumnIndex", "type": "int" },
              { "name": "newLevelGroupColumnIndex", "type": "int" } ]
		},
(...)

...

Starting with Servoy 8.3.2 $foundsetTypeUtils service was added. It's purpose is to help make the foundset property type easier to use.

Code Block
languagejs
title$foundsetTypeUtils
/**
 * The purpose of this method is to aggregate after-the-fact granular updates with indexes
 * that are relevant only when applying updates 1-by-1 into indexes that are
 * related to the new/final state of the viewport. It only calculates new indexes
 * for updates of type $foundsetTypeConstants.ROWS_CHANGED. (taking into account
 * any insert/delete along the way)
 * 
 * @param viewportRowUpdates what a foundset/component property type (viewport) change listener
 * would receive in changeEvent[$foundsetTypeConstants.NOTIFY_VIEW_PORT_ROW_UPDATES_RECEIVED]
 * [$foundsetTypeConstants.NOTIFY_VIEW_PORT_ROW_UPDATES]
 * 
 * @param oldViewportSize what a foundset/component property type (viewport) change listener
 * would receive in changeEvent[$foundsetTypeConstants.NOTIFY_VIEW_PORT_ROW_UPDATES_RECEIVED]
 * [$foundsetTypeConstants.NOTIFY_VIEW_PORT_ROW_UPDATES_OLD_VIEWPORTSIZE]...)

$foundsetTypeUtils helper methods
Anchor
foundsetTypeUtils
foundsetTypeUtils

Starting with Servoy 8.3.2 $foundsetTypeUtils service was added. It's purpose is to help make the foundset property type easier to use.

Code Block
languagejs
title$foundsetTypeUtils
/**
 * NOTE: Starting with Servoy 8.4 you no longer need to use this method; see @deprecated
 * comment.
 * 
 * The purpose of this method is to aggregate after-the-fact granular updates with indexes
 * that are relevant only when applying updates 1-by-1 into indexes that are
 * related to the new/final state of the viewport. It only calculates new indexes
 * for updates of type $foundsetTypeConstants.ROWS_CHANGED. (taking into account
 * any insert/delete along the way)
 * 
 * @param viewportRowUpdates what a foundset/component property type (viewport) change listener
 * would receive in changeEvent[$foundsetTypeConstants.NOTIFY_VIEW_PORT_ROW_UPDATES_RECEIVED]
 * [$foundsetTypeConstants.NOTIFY_VIEW_PORT_ROW_UPDATES]
 * 
 * @param oldViewportSize what a foundset/component property type (viewport) change listener
 * would receive in changeEvent[$foundsetTypeConstants.NOTIFY_VIEW_PORT_ROW_UPDATES_RECEIVED]
 * [$foundsetTypeConstants.NOTIFY_VIEW_PORT_ROW_UPDATES_OLD_VIEWPORTSIZE]
 * 
 * @deprecated starting with 8.4 this is no longer needed as foundset/component/foundsetlinked
 * property change listeners guarantee that the rows in inserts and updates have their indexes
 * relative to the already changed viewport (data in the viewport at those indexes at the
 * moment these listeners trigger does match correctly). So basically calling this method would
 * not alter any update operations - they would remain the same.
 * 
 * @returns an array of $foundsetTypeConstants.ROWS_CHANGED updates with their indexes corrected
 * to reflect the indexes in the final state of the viewport (after all updates were applied).
 */
coalesceGranularRowChanges: function(viewportRowUpdates, oldViewportSize);

 

 operations THAT