End To End Sample - Java and PHP backend - Html DataGrid with Lazy Load

clock December 30, 2015 03:06 by author htmltreegrid

In the previous section we saw the power of the datagrid with lazy loaded flat data. That example demonstrated lazily loading flat data. In terms of hierarchical data, this means it demonstrated lazy loading data at the "top level". As you may have guessed, we support this same functionality at the inner (children) levels. Before we get too far ahead, you may be better served by understanding the concept of 'level"


Below is the documentation of the class FlexDataGridColumnLevel:


A class that contains information about a nest level of grid. This includes the columns at this level, information about whether or not to enable paging, footers, filters, the row sizes of each, the property of the dataprovider to be used as the key for selection, the property of the data provider to be used as the children field, the renderers for each of the cells, etc.The Grid always contains at least one level. This is the top level, and is accessible via the columnLevel property of the grid.


One of the most important concepts behind the Architecture of Flexicious Ultimate arose from the fundamental requirement that the product was created for - that is display of Heterogeneous Hierarchical Data.


The notion of nested levels is baked in to the grid via the "columnLevel" property. This is a property of type "FlexDataGridColumnLevel". The grid always has at least one column level. This is also referred to as the top level, or the root level. In flat grids (non hierarchical), this is the only level. But in nested grids, you could have any number of nested levels. This can be demonstrated in the following image.





The columns collection actually belongs to the columnLevel, and since there is one root level, the columns collection of the grid basically points to the columns collection of this root level. The FlexDataGridColumnLevel class has a "nextLevel" property, which is a pointer to another instance of the same class, or a "nextLevelRenderer" property, which is a reference to a ClassFactory the next level. Please note, currently, if you specify nextLevelRenderer, the nextLevel is ignored. This means, at the same level, you cannot have both a nested subgrid as well as a level renderer. Bottom line - use nextLevelRenderer only at the innermost level. Our examples demonstrate this.


Another thing to note is that there are two modes in which inner levels work with columns. Either same set of columns shared across each level, or each level has its own set of columns (We call them nested grids vs grouped grids). More information here :http://htmltreegrid.com/newdocs/html/Flexicious%20HTMLTreeGrid.html?AdvancedConfigurationOptionsHier.html


In markup, these would be defined as such:

='<grid id="grid"  ...>'+
'                        <level ...>'+
'                                <columns>'+
'                                        <column type="checkbox"  />'+
...

'                                        <column enableCellClickRowSelect="false" width="2000" excludeFromSettings="true" excludeFromExport="true" excludeFromPrint="true" />'+
'                                </columns>'+
'                                <nextLevel>'+
'                                        <level  ...>'+
'                                                <columns>'+
'                                                        <column type="checkbox" />'+
'                                                        ...
'                                                        <column itemEditor="flexiciousNmsp.DatePicker" editable="true" editorDataField="selectedDate"  dataField="dealDate" headerText="Deal Date" labelFunction="flexiciousNmsp.UIUtils.dataGridFormatDateLabelFunction"/>'+
'                                                </columns>'+
'                                                <nextLevel>'+
'                                                        <level  ...>'+
'                                                                <columns>'+
'                                                                        <column type="checkbox" />'+
'                                                                        ...
'                                                                        <column editable="true" dataField="lineItemAmount" headerText="Line Item Amount" textAlign="right" footerLabelFunction2="myCompanyNameSpace.fullyLazyLoaded_getFooterLabel" footerAlign="right" labelFunction="flexiciousNmsp.UIUtils.dataGridFormatCurrencyLabelFunction"/>'+
'                                                                                </columns>'+
'                                                                        </level>'+
'                                                                </nextLevel>'+
'                                                        </level>'+
'                                                </nextLevel>'+
'                                        </level>'+
'                                </nextLevel>'+
'                        </level>'+
'        </grid>';

 

(Full markup here : http://www.htmltreegrid.com/demo/examples/js/samples/Nested.js)


OR


'<grid id="grid" ...>'+
'                '+
'                        <level  ... >'+
'                                <columns>'+
'                                        <column type="checkbox"   />'

....

'                                        <column itemEditor="flexiciousNmsp.DatePicker"  '+
'                                                                                                                   dataField="dueDate" headerText="Due Date" filterControl="DateComboBox"'+
'                                                                                                                   labelFunction="flexiciousNmsp.UIUtils.dataGridFormatDateLabelFunction"/>'+
'                                </columns>'+
'                                <nextLevel>'+
'                                        <level ... reusePreviousLevelColumns="true" >'+
'                                                '+
'                                                <nextLevel>'+
'                                                        <level ... reusePreviousLevelColumns="true">'+
'                                                                '+
'                                                        </level>'+
'                                                </nextLevel>'+
'                                        </level>'+
'                                </nextLevel>'+
'                        </level>'+
'                '+
'        </grid>';


(Full markup here : http://www.htmltreegrid.com/demo/examples/js/samples/GroupedData.js)



Now, lets look at what a simple configuration with hierarchical data looks like:


Hierarchical data works very similar to flat data, with the exception that the JavaScript object at the top level have a property usually named children that is a pointer to an array of nested objects which represent the children of the top-level objects.  These nested objects in turn can have children of their own which in turn can have children of their own up to any number of nested levels.  The one thing to keep in mind here is that the name of the property that points to the next level children is configurable via the childrenField property.  This defaults to the string “children”  but can be anything as long as it's configured appropriately


Another thing to notice about this example is the use of the enableDynamicLevels flag:

For those of you who are not familiar with what a dynamic tree grid is, (Actually the term is dynamicLevels) - This means that the grid will introspect the data provider to automatically figure out how deep the tree will nest. This is in contrast to other configurations where you explicitly define how "deep" the tree will be, what columns will be at each level, etc. But in case the hierarchy is unknown at design time, the grid is capable of introspecting the data provider and automatically generating the levels at run time. You do this by setting enableDynamicLevels="true" on the grid. However, since the levels are not defined at design time, to be able to manipulate their properties at runtime, we have an event, DYNAMIC_LEVEL_CREATED and DYNAMIC_ALL_LEVELS_CREATED. Both these events are defined on the FlexDataGridEvent class.




<script type="text/javascript">
  
   $(document).ready(function () {
       var grid new flexiciousNmsp.FlexDataGrid(document.getElementById("gridContainer"),
               {

                   configuration:'<grid id="grid" enableDynamicLevels="true" variableRowHeight="true" horizontalScrollPolicy="on" recalculateSeedOnEachScroll="true" enableExport="true" forcePagerRow="true" pageSize="50" enableFilters="true" enableFooters="true" >' +
                           '                        <level  enableFooters="true" ' +
                           '   childrenField="children">' +
                           '                                <columns>' +
                           '                                        <column enableHierarchicalNestIndent="true" dataField="id" headerText="ID" width="100"/>' +
                           '                                        <column dataField="type" headerText="Type" width="100" wordWrap="true"/>' +
                           '                                        <column dataField="type" headerText="Type" />' +
                           '                                </columns>' +
                           '                        </level>' +
                           '                +
                           '        </grid>',
                   dataProvider:[
                       { "id":"5001""type":"None None None None None None None None None None None None None "  children:[
                           { "id":"5001""type":"None 1 None 1 None 1 None 1 None 1 None 1 None 1 None 1 None 1"  },
                           { "id":"5002""type":"Glazed 1 Glazed 1 Glazed 1 Glazed 1 Glazed 1 Glazed 1 Glazed 1 Glazed 1 Glazed 1 Glazed 1" },
                           { "id":"5005""type":"Sugar 1 Sugar 1 Sugar 1 Sugar 1 Sugar 1 Sugar 1 Sugar 1 Sugar 1" },
                           { "id":"5007""type":"Powdered Sugar 1 Powdered Sugar 1 Powdered Sugar 1 Powdered Sugar 1 Powdered Sugar 1" },
                           { "id":"5006""type":"Chocolate with Sprinkles 1 Chocolate with Sprinkles 1 Chocolate with Sprinkles 1 Chocolate with Sprinkles 1" },
                           { "id":"5003""type":"Chocolate 1 Chocolate 1  Chocolate 1  Chocolate 1  Chocolate 1  Chocolate 1  Chocolate 1  Chocolate 1  Chocolate 1 " },
                           { "id":"5004""type":"Maple 1 Maple 1  Maple 1  Maple 1  Maple 1  Maple 1  Maple 1  Maple 1  Maple 1  Maple 1 " }
                       ]

                       },
                       { "id":"5002""type":"Glazed" },
                       { "id":"5005""type":"Sugar" },
                       { "id":"5007""type":"Powdered Sugar" },
                       { "id":"5006""type":"Chocolate with Sprinkles" },
                       { "id":"5003""type":"Chocolate" },
                       { "id":"5004""type":"Maple" }
                   ]
               });

           grid.expandAll();
           grid.validateNow();
   });

</script>


In this example we can see that the top level object with the ID of 5001 has a list of children.


 


Hierarchical Data - Lazy Load - Java and PHP SampleParent Previous

 

In the previous section we looked at the structure of a hierarchical treegrid - we talked about the concept of levels, nested vs grouped and dynamic levels. Now, lets talk about another complex topic, lazy load. In this example in addition to the top level lazy load we talked about in the previous example (using filterPageSortMode), we introduce a concept of itemLoadMode.


There are also two different modes of loading hierarchical data.

itemLoadMode=client (default) - This assumes the parent objects and child objects are all loaded in client memory upfront.

itemLoadMode=server - This assumes only the top level items are loaded, and the grid will trigger an event that you will then listen for, and load children in a lazy load mechanism (or load on demand). This is more appropriate when there are very large datasets.


When itemLoadMode is server, you may want to set childrenCountField.

A property on the object that identifies if the object has children. Only needed in itemLoadMode=server In lazy loaded hierarchical grids levels, the child level items are loaded when the user actually expands this level for the first time. In scenarios where it is known initially that there are children, set this property to the value of the object that identifies whether there are children. If this property is set, the expand collapse icon will be drawn only when the value of this property on the object returns an integer greater than zero. Otherwise, the level will always draw the expand collapse icon.


To summarize, In client mode, the grid will assume that the children of items at this level are pre-fetched. In server mode, the grid will dispatch a ITEM_LOAD event (itemLoad) that should be used to construct an appropriate query to be sent to the back-end, to retrieve the child objects at this level. Once the results are retrieved, please call the "setChildData" method on the grid to set the results at this level. Please note, the "childrenField" is still required at this level, because that is where the setChildData method persists the loaded children. Future itemOpen events do not result in itemLoads because the data for this particular entity has already been loaded and persisted.


So, lets take a quick look at what this example looks like:


1) Java Version : http://flexicious.com:8400/HtmlTreeGridSpring/

2) PHP Version : http://flexicious.com:8081/php-sql-demo-app


The source code for this example can be downloaded from :

1) Javahttp://www.htmltreegrid.com/demo/javasample.zip

2) PHP: http://www.htmltreegrid.com/demo/phpsample.zip


Below is the code for this example (client side only) - the server side code is included in the above zip file for you to inspect:


/**

* This Example demonstrates the next level of lazy load capabilities of the Flexicious DataGrid, that is each level of detail, as well as paging at each level in a lazy loaded configuration.

* There are two properties to pay attention to here, both of which are defined at the column level:

* FilterPageSortMode : The Filter/Page/Sort Mode. Can be either "server" or "client". In client mode, the grid will take care of paging, sorting and filtering once the dataprovider is set. Inserver mode, the grid will fire a filterPageSortChange event that should be used to construct an appropriate query to be sent to the backend.

* ItemLoadMode : The Item Load Mode. Can be either "server" or "client". In client mode, the grid will assume that the children of items at this level are prefreched. In server mode, the grid willdispatch a ITEM_LOAD event (itemLoad) that should be used to construct an appropriate query to be sent to the backend, to retrieve the child objects at this level. Once the results are retrieved,please call the "setChildData" method on the grid to set the results at this level. Please note, the "childrenField" is still required at this level, because that is where the setChildData methodpersists the loaded children.

* Future itemOpen events do not result in itemLoads because the data for this particular entity has already been loaded and persisted.

*

* In this example, we see how to wire up a partially lazy loaded Hierarchical DataGrid. That is, we load up the top level with no children records, and then lazy load them in as the user clicks onexpand.

* Please note, it is not advisable to set enableDrillDown on lazy loaded grids, because this will result in too many server calls being issued.

*/

var GridConFig = window.GridConFig = {

   //ApiCallBaseUrl : "http://localhost:63343/php-sql-demo-app/api/sever_records/", // - php call

   ApiCallBaseUrl : window.location+"api/server_records/"//- java call

   XmlConfig : {

       sampleGrid: '<grid ' +

                       'height="100%" ' +

                       'width="100%" ' +

                       'enablePrint="true" ' +

                       'enableExport="true" ' +

                       'forcePagerRow="true" ' +

                       'enableFilters="true" ' +

                       'pagerRowHeight = "35" ' +

                                               'enablePreferencePersistence="true" '+

                       'rowHeight = "30" ' +

                       'pageSize = "15" '+

                       'pageIndex = "1" '+

                       'horizontalScrollPolicy="auto" ' +

                       'selectionColor="transparent" ' +

                       'showSpinnerOnFilterPageSort="true" ' +

                       'enableDrillDown = "true" ' +

                       'nestIndent="36" ' +

                       'filterPageSortMode="server" ' +

                       'enableDefaultDisclosureIcon="false" '+

                       'enablePaging="true" ' +

                       'pagerRenderer="flexiciousNmsp.CustomPagerRenderer" ' +

                       'multiSortRenderer="flexiciousNmsp.CustomMultiColumnSortPopupRenderer" '+

                       'filterPageSortChange="filerPageSortHandle" '+

                       'enableMultiColumnSort="true" ' +

                       'enableColumnHeaderOperation="true" ' +

                       'selectionMode="multipleRows"> ' +

                           '<level name="Top Level" headerHeight="30" childrenField="children" itemLoad="itemLoadHandler"  itemLoadMode="server" childrenCountField="childCounts">' +

                               '<columns>' +

                                   '<column dataField="record_type" width="200" headerText="Record Type" ' +

                                           'enableHierarchicalNestIndent="true" paddingLeft="25" enableExpandCollapseIcon="true"  filterControl="MultiSelectComboBox" filterOperation="Contains" filterTriggerEvent="enterKeyUpOrFocusOut" filterComboBoxDataProvider= "eval__getFilterComboBoxDP_RecordType()" />' +

                                   '<column filterControl="TextInput" filterOperation="Contains" filterTriggerEvent="enterKeyUpOrFocusOut" dataField="record" width="350" headerText="Record"/>' +

                                   '<column filterControl="TextInput" filterOperation="Contains" filterTriggerEvent="enterKeyUpOrFocusOut" dataField="site" width="100" headerText="Site"/>' +

                                   '<column filterControl="TextInput" filterOperation="Contains" filterTriggerEvent="enterKeyUpOrFocusOut" dataField="system" width="100" headerText="System"/>' +

                                   '<column filterControl="DateComboBox"  dataField="start_time"  width="350" headerText="Start Time" labelFunction="startTimeLabelFunction"/>' +

                                   '<column filterControl="TextInput" filterOperation="Contains" filterTriggerEvent="enterKeyUpOrFocusOut" dataField="run_time"  width="100" headerText="Run Time" labelFunction="runTimeLabelFunction"/>' +

                                   '<column filterControl="TextInput" filterOperation="Contains" filterTriggerEvent="enterKeyUpOrFocusOut" dataField="status" headerRenderer="CustomHeaderRender"  width="100" headerText="status" labelFunction="statusLabelFunction"/>' +

                                   '<column filterControl="TextInput" filterOperation="Contains" filterTriggerEvent="enterKeyUpOrFocusOut" dataField="jenkins" width="100" headerText="jenkins" />' +

                                   '<column filterControl="TextInput" filterOperation="Contains" filterTriggerEvent="enterKeyUpOrFocusOut" dataField="result"  width="100" headerText="result"/>' +

                                                                       '<column filterControl="DateComboBox"  dataField="start_time"  width="350" headerText="Start Time" labelFunction="startTimeLabelFunction"/>' +

                                   '<column filterControl="TextInput" filterOperation="Contains" filterTriggerEvent="enterKeyUpOrFocusOut" dataField="run_time"  width="100" headerText="Run Time" labelFunction="runTimeLabelFunction"/>' +

                                   '<column filterControl="TextInput" filterOperation="Contains" filterTriggerEvent="enterKeyUpOrFocusOut" dataField="status" headerRenderer="CustomHeaderRender"  width="100" headerText="status" labelFunction="statusLabelFunction"/>' +

                                   '<column filterControl="TextInput" filterOperation="Contains" filterTriggerEvent="enterKeyUpOrFocusOut" dataField="jenkins" width="100" headerText="jenkins" />' +

                                   '<column filterControl="TextInput" filterOperation="Contains" filterTriggerEvent="enterKeyUpOrFocusOut" dataField="result"  width="100" headerText="result"/>' +

                               '</columns>' +

                               '<nextLevel>' +

                                   '<level name="Second Level" nestIndent="36" headerHeight="35" reusePreviousLevelColumns="true" rowHeight="35" childrenField="children" filterVisible="false" />' +

                               '</nextLevel>' +

                           '</level>' +

                       '</grid>'

   }

};

/**

// grid callbacks

//These have lookup based filters. Since at any time, we only load the top level filter, we need to query the database for all possible values for this pickers.

//This is not a problem with filterPageSortMode=client, because we load up the entire dataset and run a distinct on it to figure out the values for the picker.

//However with server based filterPageSortMode, we need to query the server to get the entire list of possible values to pick from. In this case we are just hardcoding the list

//look at the Dot.Net example to show how to load it from server and wire up in the return call.

*/

function getFilterComboBoxDP_RecordType(){

        return [    

       {label:'Batch',data:'batch'},

               {label:'Profile',data:'profile'},        

       {label:'Testcase',data:'testcase'}    

   ];

  }


/**

* The item load handler receives a filterPageSortChangeEvent, which contains a parentObject property that represents the item being opened.

* We basically issue a server request for the children of that time, and in the result event, call the setChildData method passing in the parent item, the children,

* the level at which the parent item exists, and the total number of records that we have, if it is different than the length of the children collection (i.e.) if we are just pushing down a singlepage of data.  

* This handler basically calls out to the services layer, gets the data, and calls the setChildData method on the grid on result.

**/

var itemLoadHandler = function(event){

   var parentData = event.filter.parentObject;

   $.ajax({

       url : GridConFig.ApiCallBaseUrl,

       data : {name : "child_data", parent_id : parentData.id},

       type : "GET",

       success : function(res){

           var response = JSON.parse(res);

           if(response.success){

               var grid = event.target;

               grid.setChildData(parentData, response.data, event.filter.level.getParentLevel());

           } else {

               alert(response.message);

           }

       }

   });

};

/**

The filterPageSortChange Event: You have to wire up the "filterPageSortChange" event. This is the event that get dispatched when the user user clicks on the sort header  on any of the columns,   orrequest a change using either the page navigation buttons or the page navigation drop down in the toolbar, or runs a filter with in any of the columns. This event has 2 properties that are ofinterest:

event.filter: This object contains all the information that you would potentially need to construct a SQL statement on the backend. Full documentation on this object can be found here:  

http://www.flexicious.com/resources/docs29/com/flexicious/grids/filters/Filter.html

event.cause - This can be one of the three values:

public static const FILTER_CHANGE:String = filterChange

public static const PAGE_CHANGE:String = pageChange

public static const SORT_CHANGE:String = sortChange

**/

var filerPageSortHandle = function(event){

  setTimeout(function () {

      var filterPageSort = {};

      var grid = event.target;

      if(event.cause == "pageChange"){

          filterPageSort.pageIndex = event.triggerEvent.currentTarget._pageIndex;

      } else{

          filterPageSort.pageIndex = grid.getColumnLevel()._pageIndex;

      }

      filterPageSort.pageSize = grid.getPageSize();

      var sorts = event.target.getCurrentSorts();

      if(sorts && sorts.length){

          filterPageSort.sorts = [];

          for(var i = 0; i <  sorts.length; i++){

              filterPageSort.sorts.push({

                  sortColumn : sorts[i].sortColumn,

                  isAscending : sorts[i].isAscending,

                  sortNumeric : sorts[i].sortNumeric

              });

          }

      }

      var filter = event.target.getRootFilter();

      if(filter.filterExpressions && filter.filterExpressions.length){

          filterPageSort.filters = [];

          for(var i = 0; i <  filter.filterExpressions.length; i++){

              filterPageSort.filters.push({

                  columnName : filter.filterExpressions[i].columnName,

                  expression : filter.filterExpressions[i].expression,

                  filterOperation : filter.filterExpressions[i].filterOperation,

                  filterComparisonType : filter.filterExpressions[i].filterComparisionType

              });

          }

      }


      $.ajax({

          url : GridConFig.ApiCallBaseUrl,

          data : {name : "top_data", filterPageSort : JSON.stringify(filterPageSort)},

          type : "GET",

          success : function(res){

              if(res.trimLeft().indexOf("<") == 0){

                  grid.hideSpinner();

                  alert("Error occur while loading the data.");

                  return;

              }

              var response = JSON.parse(res);

              if(response.success){

                  grid.setDataProvider(response.data);

                  if(event.cause == "filterChange")

                      grid.setTotalRecords(response.details.totalRecords);

              }else{

                  alert(data);

              }

          }

      });

  },1);

};


var runTimeLabelFunction  = function(data, col){

   var totalSec = Math.round(data["run_time"]/1000);

   var hours = parseInt( totalSec / 3600 ) % 24;

   var minutes = parseInt( totalSec / 60 ) % 60;

   var seconds = totalSec % 60;


   return (hours < 10 ? "0" + hours : hours) + ":" + (minutes < 10 ? "0" + minutes : minutes) + ":" + (seconds  < 10 ? "0" + seconds : seconds);

};


var startTimeLabelFunction = function(data, col){

   var start_date = data["start_time"];

   return new Date(Date.parse(start_date)).toString();

};


var statusLabelFunction=function(data, col){

   var status = data["status"];

   if(status.toLowerCase() == "stopped")

       return "<span style='color: #ff4545; font-weight: bold'>Stopped</span>";

   else if(status.toLowerCase() == "running")

       return "<span style='color: #3434FF; font-weight: bold'>Running</span>";

   else if(status.toLowerCase() == "passed")

       return "<span style='color: #458F00; font-weight: bold'>Passed</span>";

   else if(status.toLowerCase() == "failed")

       return "<span style='color: #FF0000; font-weight: bold'>Failed</span>";

   return "";

};



On the server side, the key class to note is the FilterBuilder class


This class is responsible for taking the Filter object and build the filter from it.

 

 



End To End Sample - ASP.NET backend - Html DataGrid with Lazy Load

clock December 29, 2015 14:17 by author htmltreegrid

Parent Previous Next

 

Among the most powerful features of the grid is the building support for lazy loading of large datasets. For flat data this is done via the use of the filterPageSortMode property. This is a key property to understand. There are two values for this property, “server” and “client”. Let us assume you are working on a Human Resource Management Application. You have a DataGrid that shows a list of Employees. You have a few hundred Employees to show. This can be easily accomplished by getting the list of employees from the server, and setting the data provider property of the grid to an Array that contains this list of Employees. As you need, you enable filters, footers, paging, export, print, etc, and all is well and good. You will be up and running in no time with filterPageSortMode=client (The default setting). The grid will take care of paging, filtering and cross page sorting for you. However, now consider a scenario where you have to display a time sheet search page. Each employee creates hundreds of time-sheets each year, and multiply that by the number of employees, you are looking at thousands, even hundreds of thousands of records of data. Although it may be possible to load thousands of records in any DataGrid (including ours) with no noticeable drag, this is not recommended, and often unnecessary. What we suggest in this scenario is you use the filterPageSortMode =”server”. What the product does, in this setup, is it assumes that the current Array is a part of a much larger record-set, and you are showing just the current page of data. So if there are 100,000 records in the database, and the pageSize is 50, the grid will show the 50 records, and provide paging UI for the user to navigate to any record in the result-set. At any point in time, no more than 50 records will be loaded on client memory. This setup, will require a little more effort in terms of configuration, but it will be considerably easier to do this with our grid as opposed to building it yourself, because the product is architected to cater to a scenario like this.


filterPageSortMode=client

When this property is set  to client mode,  there's very little work that you have to do.  Just give us an array of objects and the gird takes care of paging sorting, filtering, export and everything.. We are able to do this because we have all the data on the client in memory.  so when your filter we can run through the entire data set and get you the records that you're filtering on. The same applies to the sort. Since all the pages of data are re loaded in memory all we have to do is to navigate the grid to the page that your request when you click on the paging buttons.  similarly when you click on the print or the export buttons we have the entire dataset loaded in memory so were able to export or print all the data without any interaction from the server.  


filterPageSortMode=server


However in server mode,  things are a lot different.  At any point in time we only have the current page of data in memory. This means when you click on the paging buttons we have to go back to the server to get the requested page of data.  When you click on the sort headers we have to run the sort on the server and  get only the current page of data according to the updated sort criteria.  Along the same lines when you run a filter, we have to go back to the server and run the filter on the server potentially converting it into a SQL statement and returning the results of that SQL  statement on basis of the current page filter and sort criteria. Finally when you click on the  train or the export buttons  and choose to export the entire data set as opposed to the current page we have to go back to the server and get the entire data set for the print or the export. There are a few things you have to do to make this happen. In this document, we are only going to focus on the client side of things (the server side is technology dependent, and we provide samples of this independent of this document.)


  1. filterPageSortMode : Needless to say, this needs to be set to "server".
  2. The filterPageSortChange Event: You have to wire up the "filterPageSortChange" event. This is the event that get dispatched when the user user clicks on the sort header  on any of the columns,   or request a change using either the page navigation buttons or the page navigation drop down in the toolbar, or runs a filter with in any of the columns. This event has 2 properties that are of interest:
  •  
    • event.filter: This object contains all the information that you would potentially need to construct a SQL statement on the backend. Full documentation on this object can be found here:  

http://www.flexicious.com/resources/docs29/com/flexicious/grids/filters/Filter.html

  •  
    • event.cause - This can be one of the three values:

public static const FILTER_CHANGE:String = filterChange

public static const PAGE_CHANGE:String = pageChange

public static const SORT_CHANGE:String = sortChange

  1. The printExportDataRequest Event: If you want to support exporting of the entire dataset (all pages), then you also have to wire up the printExportDataRequest event. This is the same as fiterPageSortChange event, in that it has a event.filter object, that in addition to the filter, also contains printExportOptions object.
  2. The selectedKeyField: Also, another key aspect - is the selectedKeyField. You have to wire this up if you want to maintain selection across pages. The selectedKeyField is a property on the object that identifies the object uniquely. Similar to a surrogate key, or a business key, as long as it uniquely identifies the object. When this property is set, the selected keys returns the ID values of the objects that were selected. When a row is selected in the grid, we store the selectedKeyField property of the selected object in this array collection. This allows for multiple pages of data that comes down from the server and not maintained in memory to still keep track of the ids that were selected on other pages. If you use Flexicious in filterPageSortMode=client, this really does not apply to you, but in server mode, each page of data potentially represents a brand new dataprovider. Let's assume you have a Database table of 100,000 records, with the pageSize property set on Flexicious to 50. You load page 1, select a few items, and move on to page 2. The grid exposes a property called selectedItems, which will be lost when the new page of data is loaded. This is why we have the selectedObjects and selectedKeys property, that is, to keep the selection that was loaded in memory on prior pages of data. Now, in most LOB applications, each record can be identified by a surrogate key (or some unique identifier). This surrogate key is then used to uniquely identify different instances of at the same Object. For example, when the page 1 is loaded for the first time, there is a Employee OBJECT with EmployeeId=1. When the user selects this record, navigates to a different page, and switches back to page 1, the Employee with ID 1 is still there, and need to stay selected, but it could be an altogether different INSTANCE of the same record in the database. This is why we have the selectedKeyField property, which would in this case, be "EmployeeID" so we can uniquely identify the selection made by the user across multiple pages.
  3. filterComboBoxDataProvider where filterControl="MultiSelectComboBox" or "ComboBox":  These have lookup based filters. Since at any time, we only load the top level filter, we need to query the database for all possible values for this pickers. This is not a problem with filterPageSortMode=client, because we load up the entire dataset and run a distinct on it to figure out the values for the picker. However with server based filterPageSortMode, we need to query the server to get the entire list of possible values to pick from.

 

In the following example we are going to look at the intricacies involved in this use case.


You can find the example running below here : http://www.sqledt.com/DotNetSample


The source code for this example can be downloaded from : http://www.htmltreegrid.com/demo/dotnetsample.zip


Lets review the sample code  that demonstrates this:

  <div class="row">

       <div ng-app="app">

           <div ng-controller="myCtrl">

               <div id="gridContainer" fd-grid="" ng-model="gridOptions" style="height300px;width100%;"

                    xicreation-complete="onGridCreationComplete"

                    xienable-export="true"

                    xienable-copy="true"

                    xiforce-pagerrow="true"

                    xipage-size="20"

                    xienable-multi-column-sort="true"

                    xienable-filters="true"

                    xino-data-message=""

                    xihorizontal-scroll-policy="auto"

                    xienable-footers="false">


                   <level xienable-paging="true" xipage-size="25" xienable-filters="true" xiselected-key-field="employeeId"

                          xienable-footers="false"

                          xireuse-previous-level-columns="true" xichildren-field="PositionDataChildFXItem" xifilter-page-sort-change="filterPageSortChangeHandler" xifilter-page-sort-mode="server">

                       <columns>

                           <column xitype="checkbox"></column>

                           @*<column xidata-field="employeeId" xiheader-text="id" xiheader-align="middle" xitext-align="left" xifilter-control="TextInput" xifilter-operation="Contains" xisort-case-insensitive="true"></column>*@


                       <column xidata-field="firstName" xiheader-text="First Name" xiheader-align="middle" xifilter-control="TextInput" xifilter-operation="Contains"></column>

                       <column xidata-field="lastName" xiheader-text="Last Name" xiheader-align="middle" xifilter-control="TextInput" xifilter-operation="Contains"></column>

                       <column xidata-field="department.departmentId" xiheader-text="Department" xiheader-align="middle" xifilter-control="MultiSelectComboBox" xifilter-combobox-build-from-grid="false"

                               xifilter-combobox-width="150" xilabel-function="getDepartment"></column>

                       @*<column xidata-field="departmentId" xiheader-text="department id" xiheader-align="middle" xifilter-control="TextInput" xifilter-operation="Contains"></column>*@

                       <column xidata-field="phoneNumber" xiheader-text="Phone" xiheader-align="middle" xifilter-control="TextInput" xifilter-operation="Contains"></column>

                       <column xidata-field="stateCode" xiheader-text="State" xiheader-align="middle" xifilter-control="ComboBox" xifilter-combobox-build-from-grid="true" xifilter-combobox-width="150"></column>

                       <column xidata-field="hireDate" xiheader-text="Hired" xifilter-control="DateComboBox"

                               itemeditor="flexiciousNmsp.DatePicker" xicolumn-width-mode="fitToContent"></column>


                       <column xiheader-align="middle" xisort-numeric="true" xidata-field="annualSalary" xiheader-text="Annual Salary"

                               xifilter-control="NumericRangeBox" xilabel-function="flexiciousNmsp.UIUtils.dataGridFormatCurrencyLabelFunction"></column>

                       <column xidata-field="isActive.toString()" xiheader-text="Active" xifilter-control="TriStateCheckBox" xifilter-operation="Equals" xilabel-function="getActive"></column>

                   </columns>

               </level>

           </div>

       </div>

   </div>

</div>

@section css{

   <!--css imports-->

   <link rel="stylesheet" href="http://htmltreegrid.com/demo/flexicious/css/flexicious.css" type="text/css" />

   <link rel="stylesheet"

         href="http://htmltreegrid.com/demo/external/css/adapter/jquery/jquery-ui-1.9.1.custom.min.css"

         type="text/css" />

   <!--End-->

}

@section scripts{

   <script src="//www.parsecdn.com/js/parse-1.3.0.min.js"></script>


   <script type="text/javascript"

           src="http://htmltreegrid.com/demo/external/js/adapters/jquery/jquery-1.8.2.js"></script>

   <script type="text/javascript"

           src="http://htmltreegrid.com/demo/external/js/adapters/jquery/jquery-ui-1.9.1.custom.min.js"></script>

   <script type="text/javascript"

           src="http://htmltreegrid.com/demo/external/js/adapters/jquery/jquery.maskedinput-1.3.js"></script>

   <script type="text/javascript"

           src="http://htmltreegrid.com/demo/external/js/adapters/jquery/jquery.watermarkinput.js"></script>

   <script type="text/javascript"

           src="http://htmltreegrid.com/demo/external/js/adapters/jquery/jquery.ui.menu.js"></script>

   <script type="text/javascript"

           src="http://htmltreegrid.com/demo/external/js/adapters/jquery/jquery.toaster.js"></script>

   <!--End-->

   <!--These are specific to htmltreegrid-->

   <script type="text/javascript" src="http://htmltreegrid.com/demo/minified-compiled-jquery.js"></script>

   <script type="text/javascript" src="http://htmltreegrid.com/demo/examples/js/Configuration.js"></script>

   <script type="text/javascript" src="http://htmltreegrid.com/demo/themes.js"></script>




   <!--AngularJs -->

   <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.16/angular.min.js"></script>

   <script type="text/javascript" src="http://htmltreegrid.com/demo/flexicious/js/htmltreegrid-angular-directive.js"></script>



   <!--End-->



   <script>

       /*

        We are using the angular integration in this example, so setup a module.

       */

       angular.module('app', ['fdGrid'])

           .factory('localStorage'function () {

               return {};

           })