Row virtualization shows last page after data source change after previous data set did not have enough rows to fill the grid

Hello,


Given row virtualization is enabled and a data source has not enough records to fill the grid (in my case: 1)
If the user scrolled down (making that row partially hide under the header) before a dataSource change (and setting grid.pageSettings.currentPage = 1 in Javascipt before the dataSource change), the last rows/page for the new dataSource are retrieved and displayed (if the user did not scroll, it correctly starts at the first row of the new dataSource)

I also tried grid.contentModule.virtualEle.adjustTable(0, 0); but that did not seem to help in this case.

Kind regards,
Remco Beurskens

66 Replies

SK Sujith Kumar Rajkumar Syncfusion Team March 10, 2020 11:53 AM UTC

Hi Lon, 

Greetings from Syncfusion support. 

Before proceeding with the query please let us know the following information, 

  • How many records is bound to the Grid?
  • By your query do you mean you want to scroll the grid content back to the first page on reaching the last page? If not please explain it further.
  • If possible share a video demonstration of the problem to better understand it.

Let us know if you have any concerns. 

Regards, 
Sujith R 



LH Lon Hofman March 11, 2020 08:40 AM UTC

  • How many records is bound to the Grid?
              Initially: 1. After switching to another (server side) table (by custom AJAX parameter in UrlAdaptor) : total count:  500000, retrieved in batches of 26 at a time.
  • By your query do you mean you want to scroll the grid content back to the first page on reaching the last page? If not please explain it further.
               I intend to scroll back to the first page when changing the data set bound to it, as it can have a different amount of total records and different fields
  • If possible share a video demonstration of the problem to better understand it.
               Right now I can't make a video recording, but I hope the provided images provide enough clarification:
  1. Image 'grid1' displays the initial situation where the grid is bound to a table with one record.
  2. Image 'grid2' displays how it looks like when using the mouse wheel to scroll down even though there are no more records to fetch.
  3. Image 'grid3' displays changing the value of the SELECT that has a change event handler attached that changes the bound table by refreshing the grid with the additional parameter containing the id of the selected table. ( the Query object has been added a parameter with {'key':'tableId, 'value': () => $('#mySelect).value} using a function so it only needs to be set once on page load)
  4. Image 'grid4' displays the records displayed after the table has been changed. Notice the last records in the set are displayed even though I set current page to 1 (or 0) in javascipt in grid.pagesettings before refreshing it.
  5. Image 'grid5' displays the expected situation. This is is also the result if I did not scroll down in the initial 1 row table before the change.
These are some lines of code from the SELECT change event handler that should reset the grid to the selected table: (settings culumns to [] triggers grid.refresh() so I don't need to call it manually)
                        grid.clearSelection();
                        grid.clearFiltering();
                        grid.clearGrouping();
                        grid.clearSorting();
                        grid.setProperties({
                            pageSettings: { currentPage: 1 },
                            columns: [],
                            columnModel: []
                        });

Attachment: Pictures_cb7fdec9.zip


SK Sujith Kumar Rajkumar Syncfusion Team March 12, 2020 01:55 PM UTC

Hi Lon, 

Thanks for your update. 

We checked your reported problem with the provided code snippet but unfortunately were unable to reproduce it from our end. On dynamically updating the grid data from one record to around 50000 records the first page was properly displayed. Please check below sample for your reference, 


You can compare the above sample with your application to check if you done something wrong. 

It would be helpful to identify your problem better if you could share the following information, 

  • Syncfusion package version used.
  • Entire Grid code file.
  • If possible provide us a simple sample to replicate the problem or try reproducing it in the above provided sample.

Note: If you are using old Syncfusion package version we suggest you to update to the latest version and check if this resolves the problem. 

Let us know if you have any concerns. 

Regards, 
Sujith R 



LH Lon Hofman March 16, 2020 02:28 PM UTC

Strange that in your sandbox code the one row does not partially scroll behind the header (as in the 'grid2.png' picture I shared before), which seems to trigger the behavior.
Also, note that when a grouping is active when data switch is clicked, something strange happens. (see also my question in https://www.syncfusion.com/forums/152280 )
We use a minified version of a custom download of :
 * filename: ej2_grid.js
 * version : 17.4.46
I don't think the server side binary package version is relevant in this behavior, but it is indeed an older version:
 Syncfusion.EJ2.dll, v 16.4500.0.45 (ej2-asp-core)

This is the script generated to initialize the grid on the page: ( I will provide a sample project when I've finished isolating it from the solution )
<script>var Gridyn21jorb5x0=new ejs.grids.Grid({
  "allowFiltering": true,
  "allowGrouping": true,
  "allowMultiSorting": true,
  "allowSorting": true,
  "dataSource": new ejs.data.DataManager({
  "url": "",
  "adaptor": null,
  "insertUrl": null,
  "removeUrl": null,
  "updateUrl": null,
  "crudUrl": null,
  "batchUrl": null,
  "json": null,
  "headers": null,
  "accept": false,
  "data": [],
  "timeTillExpiration": 0,
  "cachingPageSize": 0,
  "enableCaching": false,
  "requestType": null,
  "key": null,
  "crossDomain": false,
  "jsonp": null,
  "dataType": null,
  "offline": false,
  "requiresFormat": false,
  "Id": null
}),
  "editSettings": {},
  "enableColumnVirtualization": true,
  "enableVirtualization": true,
  "frozenColumns": 0.0,
  "frozenRows": 0.0,
  "height": "500",
  "rowHeight": 38.0,
  "selectedRowIndex": -1.0,
  "width": "1500",
  "dataBound": dataBound,
  "load": load
});
Gridyn21jorb5x0.appendTo("#DataViewerGrid"); 
</script>


SK Sujith Kumar Rajkumar Syncfusion Team March 17, 2020 12:22 PM UTC

Hi Lon, 
 
Thanks for your update. 
 
Query – 1: Strange that in your sandbox code the one row does not partially scroll behind the header (as in the 'grid2.png' picture I shared before), which seems to trigger the behavior. 
 
Based on the provided code snippet, we could reproduce the problem from our end on enabling columnVirtualization in the Grid. For the virtual content table the grid content’s height will be set as its minimum height. So when column virtualization is enabled horizontal scroller will be set in the Grid which increases the content height more than the minimum height value causing the scroller to be enabled. Due to this the content is made scrollable even though there is only one record in the Grid. This problem occurs only when small number of records are present in the Grid where virtualization is not required. So you can resolve this by decreasing the virtual content table’s minimum height from 18px(considering the scroller height) if the grid has less number of records. This is demonstrated in the below code snippet, 
 
function onDataBound() { 
        if (this.dataSource.length < 20) { 
            var virtualTable = this.element.querySelector('.e-content .e-virtualtable'); 
            virtualTable.style.minHeight = (Number(virtualTable.style.minHeight.replace("px", "")) - 18).toString() + "px"; 
        } 
} 
 
Modified sample for your reference, 
 
 
Query – 2: Also, note that when a grouping is active when data switch is clicked, something strange happens. 
 
Since the column and data source is getting updated while un-grouping operation is being performed this was causing the column to not update properly. So we suggest you to remove the column object and update the data source in a time out function(so that the grouping operation gets completed) to resolve the problem as demonstrated in below code snippet, 
 
setTimeout(function () { 
            grid.columns = []; 
            grid.columnModel = []; 
            grid.dataSource = data(500000); 
}, 300); 
 
Let us know if you have any concerns. 
 
Regards, 
Sujith R 



LH Lon Hofman March 17, 2020 03:11 PM UTC

For "Query - 2", I refer to https://www.syncfusion.com/forums/152280/

Concerning "Query - 1" (the original topic):

I tried the code suggested, but I am using data through a DataManager (+ UrlAdaptor) instance, which has no length property to check the amound of objects returned from the server. I don't know how to access the local array from it in the databound handler.
Should I count the data records by overriding UrlAdptor's processResponse function, store it in a global variable and read it back in the databound handler or is there a simpler / more elegant way ?

Kind regards, 
Remco Beurskens


SK Sujith Kumar Rajkumar Syncfusion Team March 18, 2020 11:03 AM UTC

Hi Lon, 

For remote data the total data count can be checked from the beforeDataBound event where the total count will be returned as argument value as displayed in the below image, 

 

So you can use this event to check the data count based on which you can modify the virtual table min height in the dataBound event. 

Let us know if you have any concerns. 

Regards, 
Sujith R 



LH Lon Hofman March 19, 2020 11:10 AM UTC

Thanks.

Can this also be tested like this (so I do not need to know/guess the amount of rows that would fit in the grid): ?
function onDataBound() {
        const virtualTable = this.element.querySelector('.e-content .e-virtualtable'); 
        if (virtualTable.offsetHeight <= virtualTable.offsetParent.offsetHeight) {
            const scrollbarHeight = virtualTable.offsetHeight - virtualTable.offsetParent.clientHeight;
            virtualTable.style.minHeight = (Number(virtualTable.style.minHeight.replace("px""")) - scrollbarHeight).toString() + "px";
        } 
} 

Kind regards,
Remco Beurskens


SK Sujith Kumar Rajkumar Syncfusion Team March 20, 2020 04:40 AM UTC

Hi Lon, 

Yes you can also check the virtual table height and its parent height as you have mentioned instead of checking the data source length to modify the table height. 

Regards, 
Sujith R 



LH Lon Hofman March 20, 2020 04:49 PM UTC

Nice. This seems to resolve this issue. 
Now, if I have another one, in the same project , should I make a new thread or add a reply to this one? 

Kind regards,
Remco Beurskens


SK Sujith Kumar Rajkumar Syncfusion Team March 23, 2020 06:43 AM UTC

Hi Lon, 
 
We are glad to hear that your issue has been resolved. 

For your other question, you can ask the query in this forum itself. 

Regards, 
Sujith R 



LH Lon Hofman March 23, 2020 10:43 AM UTC

I tested a bit more and experienced an issue when I sorted the data after scrolling down the data rows a bit.
It resulted in the following:
(see attached screenshot)
Sometimes, usually after scrolling further down before sorting, there are no records visible at all.

Kind regards,
Remco Beurskens

Attachment: grid_sortAfterScroll_cb7d6e31.zip


SK Sujith Kumar Rajkumar Syncfusion Team March 26, 2020 01:02 PM UTC

Hi Lon, 

Sorry for the delay in the response. 

Since the data and columns are dynamically updated the grid is not refreshed properly causing the reported problem. So we suggest you to call the freezeRefresh method of the grid after updating the column and data source to resolve the problem. 

Sample for your reference, 


Let us know if you have any concerns. 

Regards, 
Sujith R 



LH Lon Hofman March 30, 2020 09:10 AM UTC

Hi, I tried what you suggested, but your sample code only deals with one single data change.
My situation involves a select box where you can select a data source.
I adjusted your code to make the button 'toggle' data sources, and now it seems to have the issues I am experiencing too.

Kind regards,
Remco Beurskens


SK Sujith Kumar Rajkumar Syncfusion Team March 31, 2020 09:48 AM UTC

Hi Lon, 

In the sample you provided the changes mentioned(toggle data source on button click) are not updated. We checked in your mentioned scenario by changing different data source on button click but were not able to reproduce the problem from our end. Please check below updated sample for your reference, 


Can you please share us the modified sample you mentioned so that we can validate further on your reported case. 

Let us know if you have any concerns. 

Regards, 
Sujith R 



LH Lon Hofman March 31, 2020 11:16 AM UTC

Excuses.
I forgot to press 'save'.. I've done it now.

Kind regards,
Remco Beurskens.


SK Sujith Kumar Rajkumar Syncfusion Team April 1, 2020 09:57 AM UTC

Hi Lon, 

Since the virtual scroll does not work when there are less number of records it would be better to disable the virtualization in the Grid in this scenario. The scrolling related problems while dynamically updating grid data with less number of records was occurring since the code we provided for modifying the virtual table’s min height was executed in the dataBound event only on initial render and not executed after dynamic update due to the flag variable. So instead of modifying the virtual table’s min height we suggest you to enable/disable the virtualization feature based on the grid records as demonstrated in the below code snippet, 

// Grid’s dataBound event function 
function onDataBound() { 
   const virtualTable = this.element.querySelector('.e-content .e-virtualtable'); 
   const normaltable = this.element.querySelector('.e-content .e-table'); 
   // Virtualization is disabled based on the virtual table’s height 
   if (virtualTable && virtualTable.offsetHeight <= virtualTable.offsetParent.offsetHeight) { 
          this.enableVirtualization = false; 
          this.enableColumnVirtualization = false; 
   } 
   // Virtualization is enabled based on the normal table’s height  
   else if (normaltable && !virtualTable && normaltable.offsetHeight > normaltable.offsetParent.offsetHeight) { 
          this.enableVirtualization = true; 
          this.enableColumnVirtualization = true; 
   } 
} 
                                                                                                         
We have modified the sample provided based on this which you can find below, 


Let us know if you have any concerns. 

Regards, 
Sujith R 



LH Lon Hofman April 6, 2020 04:15 PM UTC

Unfortunately, this does not work for me as I use a remote data source.
Once a data set has not enough records to fill the grid, virtualization will be turned off and another request for the same data is sent to the server, but without the 'take' argument. Now an empty set is returned.
From now on I keep getting empty results, so virtualization is never turned on again no matter how much records there are on the server. (My server returns 0 rows if 'take' is not specified, but depending on server implementation it it could as well return ALL data instead.)


SK Sujith Kumar Rajkumar Syncfusion Team April 7, 2020 10:47 AM UTC

Hi Lon, 

If enabling and disabling virtualization based on the grid record count does not work for your case then you can resolve the problem by removing the – “initialRender” flag variable which is checked in the dataBound event. As mentioned in our last update and based on the modified sample which you provided we could see that the problem you are referring to is the one you reported initially when less number of records are bound to the Grid. This problem was occurring since the code we provided for modifying the virtual table’s min height was executed in the dataBound event only on initial render due to the flag variable. So please remove this flag variable checked in the dataBound to resolve this. 

We have modified the sample provided based on this which you can find below, 


If we misunderstood your problem or if you require any further assistance, please get back to us. 

Regards, 
Sujith R 



LH Lon Hofman April 8, 2020 06:05 PM UTC

It could be back then, it was only of the initial data set had only one row, but now it looks like it occurs regardless if I had the 'unwanted horizontal scrollbar' issue or not.

However, I have modified my project in a way that when selecting a dataset (id) from the SELECT, the grid's column definitions are pre-fetched from the server (without data) to set the correct model so the grid does not have to 'guess' it from the first record. And to allow the user to use the column picker to pre-select fields that will be visible.

This step is executed in a function that looks like this:


  function getFields(dataSetId) {
        const grouped = grid.groupSettings.columns.length;
        
        $.getJSON("@JavaScript.EncodeString(Url.Action("GetFields"))",
            {
                dataSetId: dataSetId
            },
            function (data) {
                console.log("start setFields");
                const walkFields = function (f) {
                    if (f.columns)
                        f.columns = f.columns.map(walkFields);
                    else {
                        f.autoFit = true;
                        //f.visible = false;
                        f.disableHtmlEncode = true;
                    }
                    return f;
                }
                console.log(data);

                primaryKey = data.primaryKey;
                
                grid.dataSource = [];
                grid.clearSelection();
                grid.clearFiltering();
                grid.clearGrouping();
                grid.clearSorting();
                grid.columnModel = [];
                grid.columns = data.fields.map(walkFields);
                //grid.columnChooserModule.clearActions();
                //grid.freezeRefresh();

                if (data.errorMessage)
                    toastr.error($("<div/>").text(data.errorMessage).html(), TextResources.Specification + " '" + dataSetId + "'");
                console.log("finished setFields");
            }


This should trigger a refresh, ending up in the dataBound handler, where the grid's dataSource being an array or not is used to verify if the fields have changed. If it is an array, all columns are hidden (Apparently I need to render them all first before I can modify their properties/visibility - so I turned column virtualization off to force them all to render.) and after that, handling is done in the onActionComplete handler:

    function actionComplete(args) {
        console.log("action complete: " + args.requestType);
        if (args.requestType === "columnstate" && Array.isArray(grid.dataSource)) {
            grid.dataSource = dataSource; // this is defined in a higher scope and contains a reference to the DataManager
            //grid.freezeRefresh(); // this should solve the rendering issue, but un-commenting this causes a Javascipt exception when selecting columns in the columnchooser.
        }
        if (args.requestType === "refresh" && Array.isArray(grid.dataSource)) {
            if (grid.getVisibleColumns().length) {
                grid.hideColumns(grid.getColumns().map(m => m.field), "field");
            }
        }
    }

I tried reproducing similar functionality here https://stackblitz.com/edit/vzjnaz-aizm9w?file=index.js but could not get it working there either  (for different reasons - here I get strange behavior while locally, I get a JavaScript error when the columnchooser closes :

NOTE:  After some trying out it seems the error below only happens when calling freezeRefresh() after changing the dataSource after ColumnChooser closes. (column definitions are already set at that point). I want to do it in this order to avoid requesting data from the server before any columns are selected by the user. - It seems freezeRefresh() causes the HTML node saved in the dlgDiv field of ColumnInstance to refer to a (old?) detached DIV element. Calling refresh() after freezeRefresh() does not resolve it.


ej2_custom2.min.js:formatted:59980 Uncaught TypeError: Cannot read property 'classList' of null
    at e.addcancelIcon (ej2_custom2.min.js:formatted:59980)
    at e.clearActions (ej2_custom2.min.js:formatted:59782)
    at e.confirmDlgBtnClick (ej2_custom2.min.js:formatted:59762)

        ColumnChooser.prototype.addcancelIcon = function() {
            this.dlgDiv.querySelector(".e-cc.e-ccsearch-icon").classList.add("e-cc-cancel")   // querySelector returns null
        }

this.dlgDiv seems to have no parent element nor children. outerHTML: <div class="e-ccdlg" id="DataViewerGrid_ccdlg" aria-label="Column chooser dialog"></div>

)

A lot of trial and error :p.

Kind regards,
Remco Beurskens


SK Sujith Kumar Rajkumar Syncfusion Team April 9, 2020 12:26 PM UTC

Hi Lon, 

On checking with the provided information and sample we could reproduce the problem in the column chooser method but it is not the same issue as mentioned in your update. We suspect that might be due to old packages in your application so we suggest you to update the Syncfusion packages to the latest version(v18.0.43) and use the below code in the dataBound event to set width to the dynamically updated columns to resolve the problem, 

function onDataBound() { 
        if (newV && !grid.dataSource.length) { 
            grid.getColumns().map(m => m.width = 'auto'); 
            const columns = grid.getColumns().map(m => m.field); 
            console.log(columns); 
            grid.hideColumns(columns, 'field'); 
        } 
} 

Also for the problem of not being able to open the column chooser properly using openColumnChooser method. Since multiple operations(Updating column model, data, modifying column visibility) are getting executed at the same time the operations refresh and update the DOM structure and since the column chooser is called before the operations are fully completed it was automatically getting closed. So you need to open the column chooser in a time out function in the dataBound event(Invoked after the actionComplete event for the refresh method is called) to ensure it gets executed only after all the operations all completed. 

function onDataBound() { 
        console.log("databound"); 
        console.log("newV: " + newV); 
        if (columnChooser) { 
            columnChooser = false; 
            setTimeout(function () { 
                grid.openColumnChooser(); 
            }, 300); 
 
        } 
} 
 
function actionComplete(args) { 
        if (args.requestType === "refresh" && newV) { 
            grid.dataSource = data(10000); 
            columnChooser = true; 
            newV = false; 
        } 
} 

We have prepared a local sample based on this for your reference which you can find below, 


Let us know if you have any concerns. 
  
Regards, 
Sujith R 



LH Lon Hofman April 16, 2020 12:58 PM UTC

I updated the scripts and binaries to 18.1.42.

However, the issue with not all rows being shown after sorting when virtualization is enabled still persists.
Also, even without sorting, during scrolling the table jumps to unpredictable positions when scrolling over a virtual page boundary. (also when data is loaded from cache instead of from the server)
(Where I expect a scrolldown/up to jump 1-3 rows every time, it sometimes jumps to what seems to be a to the start or end of a virtual page)

Kind regards,
Remco Beurskens

EDIT: 
Example for unexpected scrolling behavior: 
About 13 rows fit in my grid, virtualization loads data in blocks of 26 rows from the server.
When rows 13 - 25 were being displayed and I scrolled down, the scrolling jumps to range 29 - 41 after new data ( row 26-51 ) has been loaded. I expect it to scroll to range ~15 - ~27  as it would if virtualization were disabled.


SK Sujith Kumar Rajkumar Syncfusion Team April 17, 2020 01:31 PM UTC

Hi Lon 
 
From your query we could see that you have set the Grid pageSize property value as ‘26’. The page size provided in the Grid when virtualization is enabled must be two times larger than the number of visible rows in the Grid(i.e. the viewport size). If the pageSize is provided a value less that this it might cause some problems like the scrolling one you reported. This is the Virtual Scrolling’s default behavior and it has been documented in its limitations section. 
 
 
So we suggest you to try setting the pageSize value to ‘50’(You can change this based on your browser’s view port size) to resolve this problem. 
 
Let us know if you have any concerns. 
 
Regards, 
Sujith R 



LH Lon Hofman April 20, 2020 07:13 AM UTC

I am aware. But I do not set the PageSize property, I looked at how much records where requested from the AJAX request to see its calculated value. I have set Height and RowHeight, so PageSize would be calculated.
Should I set it explicitly?

Kind Regards,
Remco Beurskens


SK Sujith Kumar Rajkumar Syncfusion Team April 20, 2020 09:37 AM UTC

Hi Lon, 

When virtualization is enabled in the Grid, it will try to show the records based on the scroll position and page size. For some scroll-top values and page size combination whitespaces appear due to low page size values and if Grid finds these whitespaces then it will try to load next set of records to avoid these spaces. So to avoid this problem we suggest you to set the page size explicitly based on the number of the row height and visible rows depending on the viewport size. 

Let us know if you have any concerns. 

Regards, 
 Sujith R


LH Lon Hofman April 20, 2020 12:24 PM UTC

Thanks.
This seems to resolve the expected scrolling issue (even when not sorting). In my example, Height was set to 500 and rowHeight to 38 on initialization (which was calculated to exactly 13 rows). I noticed that 500 end up in the initial style for the '.e-content' div. (I guess you mean this element's height by 'viewport', excluding the grid's headers/borders etc)
Increasing page size a little bit to (500/38+1)*2 solved this scrolling issue. EDIT: It works now when easily scolling clicking the arrow one by one, but using the scollwheel the view still 'jumps ahead' frequently.

However, since I use a remote API to get my data (and I scroll faster than it can be delivered and processed), scrolling seems to jump to the position of the data arriving data, even when no longer in view. Should I store all pending XHR objects and abort them whenever a new is about to be sent? How does the DataAdaptor/DataManager handle aborted or failed requests and how does it effect the cached data? Will data for those rows be requested again when those rows come in view or will this data just remain missing on the client?
This is especially noticeable with server side queries combined with virtualization.

After changing sorting while not on the top rows, scrolling down now seem to work. However, when scrolling UP after sorting, unexpected rows are shown and when the scrollbar appears to be on the top, it's not always the top data that is shown. I can imagine the grid needs to calculate the amount of rows above the current, which may not always match the actual amount of data. Maybe it would be better to set the grid to first page when change sorting, grouping or filtering if it does not work as expected. If so, what would be the best way to do so? Or drop virtualization if it dean't work well with possible delays and use normal paging instead.

(I think some issues I have got are related to client-server delay - which could be why it is hard to reproduce with local data) 

Kind regards,
Remco Beurskens



SK Sujith Kumar Rajkumar Syncfusion Team April 21, 2020 12:52 PM UTC

Hi Lon, 

Sorry for not being clear. By viewport size we meant the browser’s total height where the Grid is rendered for the full height and set the page size to twice the records that would be visible in that case. That is why we had suggested you to explicitly set the pageSize to ‘50’. When the minimum pageSize is around 50 the white spaces problem will not occur and hence the scroll jump of records can be avoided. 

Since you have mentioned there are possible delays with the client-server requests and if you think Virtualization does not properly suit for these cases then you can use the normal paging itself. With paging you wouldn’t have to deal with any scroll delay/skip problems for your scenario and you can perform all the operations that you have done previously without any issues. 

Regards, 
Sujith R  



LH Lon Hofman April 21, 2020 04:46 PM UTC

Sorry, but it's still not clear to me:

I need to pick window.innerHeight or document.documentElement.clientHeight as property? 
The grid is only a part of the document and is fixed size where the window can be resized by the user at any time.
Does it mean I have to re-calculate pageSize every time the window is resized by the user? ( window.innerHeight/grid.rowHeight*2 )  ?
How does the window height impact the size of the grid if it is is set to a fixed height?
How did you get to page size fixed value 50? Browser height would be 50/2*38 = 950px in that case. 

Wow .. that's a lot of questions. I'm just trying to understand how it works. ;)

And for Virtualization (or the new infinite scrolling): I'd prefer it if it works if data loading is transparent to the user and no up and down jumping occurs. But no disaster if I'd have to fall back to 'classic' paging if client-server performance is an issue.


By the way, I noticed if the server returns an empty response with (error) info stored in the response headers, (ASP.NET + OWIN return an empty response with an X-Responded-JSON header containing the real status code while the response status code is set to 200 (OK) ), Ajax sets 'data' to undefined where later in the Datamanager's onSuccess handler, it accesses a property on it, resulting in a javascript exception: ( I don't know why Microsoft chooses to set the status to 200 )

    Ajax.prototype.stateChange = function (resolve, reject) {
        var data = this.httpRequest.responseText;
        if (this.dataType && this.dataType.toLowerCase() === 'json') {
            if (data === '') {
                data = undefined;
            }
            else {
                try {
                    data = JSON.parse(data);
                }
                catch (error) {
                    // no exception handle
                }
            }
        }
        if (this.httpRequest.readyState === 4) {
            //success range should be 200 to 299
            if ((this.httpRequest.status >= 200 && this.httpRequest.status <= 299) || this.httpRequest.status === 304) {
                resolve(this.successHandler(data)); 
            }
            else {
                if (this.emitError) {
                    reject(new Error(this.failureHandler(this.httpRequest.statusText)));
                }
                else {
                    resolve();
                }
            }
        }
    };

    DataManager.prototype.makeRequest = function (url, deffered, args, query) {
        var _this = this;
        var isSelector = !!query.subQuerySelector;
        var fnFail = function (e) {
            args.error = e;
            deffered.reject(args);
        };
        var process = function (data, count, xhr, request, actual, aggregates, virtualSelectRecords) {
            args.xhr = xhr;
            args.count = count ? parseInt(count.toString(), 10) : 0;
            args.result = data;
            args.request = request;
            args.aggregates = aggregates;
            args.actual = actual;
            args.virtualSelectRecords = virtualSelectRecords;
            deffered.resolve(args);
        };
        var fnQueryChild = function (data, selector) {
            var subDeffer = new Deferred();
            var childArgs = { parent: args };
            query.subQuery.isChild = true;
            var subUrl = _this.adaptor.processQuery(_this, query.subQuery, data ? _this.adaptor.processResponse(data) : selector);
            var childReq = _this.makeRequest(subUrl, subDeffer, childArgs, query.subQuery);
            if (!isSelector) {
                subDeffer.then(function (subData) {
                    if (data) {
                        _util__WEBPACK_IMPORTED_MODULE_1__["DataUtil"].buildHierarchy(query.subQuery.fKey, query.subQuery.fromTable, data, subData, query.subQuery.key);
                        process(data, subData.count, subData.xhr);
                    }
                }, fnFail);
            }
            return childReq;
        };
        var fnSuccess = function (data, request) {
            if (request.httpRequest.getResponseHeader('Content-Type').indexOf('xml') === -1 && _this.dateParse) {
                data = _util__WEBPACK_IMPORTED_MODULE_1__["DataUtil"].parse.parseJson(data);
            }
            var result = _this.adaptor.processResponse(data, _this, query, request.httpRequest, request);
            var count = 0;
            var aggregates = null;
            var virtualSelectRecords = 'virtualSelectRecords';
            var virtualRecords = data[virtualSelectRecords];   // <== Exception: data is undefined
            if (query.isCountRequired) {
                count = result.count;
                aggregates = result.aggregates;
                result = result.result;
            }
            if (!query.subQuery) {
                process(result, count, request.httpRequest, request.type, data, aggregates, virtualRecords);
                return;
            }
            if (!isSelector) {
                fnQueryChild(result, request);
            }
        };
        var req = this.extendRequest(url, fnSuccess, fnFail);
        var ajax = new _ej2_base__WEBPACK_IMPORTED_MODULE_0__["Ajax"](req);
        ajax.beforeSend = function () {
            _this.beforeSend(ajax.httpRequest, ajax);
        };
        req = ajax.send();
        req.catch(function (e) { return true; }); // to handle failure remote requests.        
        this.requests.push(ajax);
        if (isSelector) {
            var promise = void 0;
            var res = query.subQuerySelector.call(this, { query: query.subQuery, parent: query });
            if (res && res.length) {
                promise = Promise.all([req, fnQueryChild(null, res)]);
                promise.then(function () {
                    var args = [];
                    for (var _i = 0; _i < arguments.length; _i++) {
                        args[_i] = arguments[_i];
                    }
                    var result = args[0];
                    var pResult = _this.adaptor.processResponse(result[0], _this, query, _this.requests[0].httpRequest, _this.requests[0]);
                    var count = 0;
                    if (query.isCountRequired) {
                        count = pResult.count;
                        pResult = pResult.result;
                    }
                    var cResult = _this.adaptor.processResponse(result[1], _this, query.subQuery, _this.requests[1].httpRequest, _this.requests[1]);
                    count = 0;
                    if (query.subQuery.isCountRequired) {
                        count = cResult.count;
                        cResult = cResult.result;
                    }
                    _util__WEBPACK_IMPORTED_MODULE_1__["DataUtil"].buildHierarchy(query.subQuery.fKey, query.subQuery.fromTable, pResult, cResult, query.subQuery.key);
                    isSelector = false;
                    process(pResult, count, _this.requests[0].httpRequest);
                });
            }
            else {
                isSelector = false;
            }
        }
        return req;
    };


LH Lon Hofman April 22, 2020 09:39 AM UTC

function onDataBound() { 
        if (newV && !grid.dataSource.length) { 
            grid.getColumns().map(m => m.width = 'auto'); 
            const columns = grid.getColumns().map(m => m.field); 
            console.log(columns); 
            grid.hideColumns(columns, 'field'); 
        } 
} 

Just a side note:
I think this should better be grid.getColumns().forEach(m => m.width = 'auto');  to avoid confusion (using .map() like this is kind of an anti-pattern, I think) ? (Array.map() creates a new Array filled with 'auto' string literals here which is discarded right away. Just as column, which is of a reference type, is modified in the callback, those changes persist as a side effect)


SK Sujith Kumar Rajkumar Syncfusion Team April 22, 2020 01:08 PM UTC

Hi Lon, 
 
Query – 1: “Virtualization query” 
 
The Virtual/infinite scrolling features work like when the limit for particular set of records in the current page is reached it will request for additional data. Now for your scenario while you are scrolling fast - for some scroll-top values and page size combination whitespaces might appear. This occurs if low page size values are set as these feature’s behavior works considering large page size values as that is where these functionalities come in use. So when these low page size values are set these white spaces might occur as explained previously and hence to avoid it the Grid loads next set of records. So considering your use-case only we had informed you to set the pageSize to a static value of around ‘50’ without calculating it based on the height. This ‘50’ is like a minimum value for the Virtual scroll that needs to be set to avoid the scrolling related problems occurring in your scenario. But as mentioned previously if you find this inconvenient then you can use the Paging functionality itself. 
 
Query – 2: By the way, I noticed if the server returns an empty response with (error) info stored in the response headers, Ajax sets 'data' to undefined where later in the Datamanager's onSuccess handler, it accesses a property on it, resulting in a javascript exception 
 
Before proceeding further could you share the following information regarding the query to better understand it, 
 
  • Share the exception details thrown.
  • How have returned the error and response from your server?
  • Network tab response for this request.
 
Query – 3: “I think this should better be grid.getColumns().forEach(m => m.width = 'auto');  to avoid confusion” 
 
Sorry we missed from checking that case. As we needed to map the width values to all the columns at the current instance this method was used. You can use the forEach method itself for performing this action. 
  
Regards, 
Sujith R 



LH Lon Hofman April 28, 2020 04:59 PM UTC

Query1 : Virtualization/paging still not working properly (also if both enabled, scroll position is often not set to the position where the elements are rendered in the virtual table - showing a complete or partial blank content area), I will isolate it in a small project and send it later for better diagnosis.
Query2 : ( just for your information, I changed default server behavior to return the proper response code, so right know it no no blocker for me, but maybe for others?)

Ajax requests sent by the UrlAdaptor look like this: (example)

Header:
POST /MyApplication/Controller/GetData HTTP/1.1 Host: localhost Connection: keep-alive Content-Length: 16988 Pragma: no-cache Cache-Control: no-cache User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.122 Safari/537.36 X-Requested-With: XMLHttpRequest <<== I had to add this header manually in DataManager.headers, where I expected it to be sent with every XHR (OWin checks (only) for this header to see if it is an ajax request or not. If it is missing, it will just return the full HTML login page as response to the AJAX request if the login expired or is invalid) Content-Type: application/json; charset=UTF-8 Accept: */* Origin: http://localhost Sec-Fetch-Site: same-origin Sec-Fetch-Mode: cors Sec-Fetch-Dest: empty Referer: http://localhost/MyApplication/Controller Accept-Encoding: gzip, deflate, br Accept-Language: en-US,en;q=0.9 Cookie: ASP.NET_SessionId=i32wbaenbqh20scalklztszh; __RequestVerificationToken_L0JsYWlzZQ2=OxAS5J05xV2qF403PET3z3Xpt2V7N8N2L-4KLnk2KY0l24JFhqdriSEnwpk9i3dLfsJW-fhkDTwXXuOvrm3AWM0AiAP-cBDAZnvDqpftxiU1; .AspNet.ApplicationCookie=wiDReiBh0XgLkbVgJBLBKOj2uE1EirmW4w58PnvgmFlD-W4_nl1Ni0opNw09t_hyKmykOcsKdA1KrQoprAqIHFEemYH5AYPOFIASFItIW64YRr4fFYCi8FaguqXnFe69N2prQ8F5TpoiiFTQcqxqrp4hJlMKqBBsg2S_KOGIlZQVFBzn53yNmYINtW2sb1QawP9j50sQLZt3a__TJ1_v8AHNvayEcFLUYWh50RbFfWiYp40O6MzeKq-SNlRRHo4n1vXk0lok-g3PPhwFQXzVBuhhAd8roSvOi1gj2pSvTFVXyevMD9nviyYHqfwanWI1un3vMu41a-kpQCXrMi38EQ
Content: (parsed)
  1. instrumentId"99886e8c-b3e3-4dc8-91c0-8c4f15a77359"
  2. instrumentName"LargeData"
  3. requiresCountstrue
  4. skip50
  5. take50
An example for a normal response header on a data request from my server looks like this:
HTTP/1.1 200 OK
Cache-Control: public, no-store, max-age=0, s-maxage=0 Content-Type: application/json; charset=utf-8 Expires: Tue, 28 Apr 2020 13:04:42 GMT Last-Modified: Tue, 28 Apr 2020 13:04:42 GMT Vary: * Server: Microsoft-IIS/10.0 X-AspNetMvc-Version: 5.2 X-AspNet-Version: 4.0.30319 X-Powered-By: ASP.NET Date: Tue, 28 Apr 2020 13:04:43 GMT Content-Length: 188793

 Now, on a session/cookie timeout, a 200 OK is returned: (the server is running ASP.NET using MS OWin (3.0.1) for authentication , as documented here , using this configuration: (by now , I changed it to return the status code from the json as the actual HTTP response code too)

            app.UseCookieAuthentication(new CookieAuthenticationOptions //// ( This is from Startup.Auth.cs in void ConfigureAuth(IAppBuilder) )

            {

                AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,

                LoginPath = new PathString("/Account/Login"),

                Provider = new CookieAuthenticationProvider(),  /// < = this is responsible for returning 200 instead of 401 for AJAX requests (like below) by default

                ExpireTimeSpan = TimeSpan.FromMinutes(20),

                SlidingExpiration = true,

                CookieHttpOnly = true,

            });

)

HTTP/1.1 200 OK Cache-Control: private Server: Microsoft-IIS/10.0 X-AspNetMvc-Version: 5.2 X-AspNet-Version: 4.0.30319 X-Responded-JSON: {"status":401,"headers":{"location":"http:\/\/localhost\/MyApplication\/Account\/Login?ReturnUrl=%2FMyApplication%2FController%2FGetData"}} X-Powered-By: ASP.NET Date: Tue, 28 Apr 2020 15:09:42 GMT Content-Length: 0

For me, I worked around it by adjusting the server behavior, but client code should not rely on Content-type to be defined:
"Any HTTP/1.1 message containing an entity-body SHOULD include a Content-Type header field defining the media type of that body. If and only if the media type is not given by a Content-Type field, the recipient MAY attempt to guess the media type via inspection of its content and/or the name extension(s) of the URI used to identify the resource. If the media type remains unknown, the recipient SHOULD treat it as type "application/octet-stream"."

Before, when i described this issue, the Content-Type header was not missing from the response, so the exception occurred on a different line (as from my earlier post).
Now it is thrown just before it reaches that point: I think it's not just the 'safe programming' in the success handler, but also maybe providing a way to have the AJAX component a way to override the default code by custom code to decide if the response is success or failure.
        var fnSuccess = function (data, request) {
            if (request.httpRequest.getResponseHeader('Content-Type').indexOf('xml') === -1 && _this.dateParse) { // <== TypeError: Cannot read property indexOf of null (if Content-Type is not provided)
                data = _util__WEBPACK_IMPORTED_MODULE_1__["DataUtil"].parse.parseJson(data);
            }
            var result = _this.adaptor.processResponse(data, _this, query, request.httpRequest, request);
            var count = 0;
            var aggregates = null;
            var virtualSelectRecords = 'virtualSelectRecords';
            var virtualRecords = data[virtualSelectRecords];   // <== Uncaught ReferenceError: data is undefined (if Content-type is present, but content is empty)

Attachment: jsExceptionMissingHeader_bc49e5e1.zip


SK Sujith Kumar Rajkumar Syncfusion Team April 29, 2020 11:49 AM UTC

Hi Lon, 

Query – 1: “Virtualization and paging” 

Both the virtual scroll and paging are functionalities that have separate approaches to fetch the data count. The basic records for a particular page in virtual scroll differs from that of the paging functionality and the default virtual scroll behavior does not consider the pager while rendering. As both them have different approaches these features cannot be combinedly integrated in a single Grid. So we suggest you to either use the Virtual scroll or paging functionality in the Grid. 

Query – 2: “Request and response related problem” 

From your query we suspect the requirement you are trying to achieve is to send a custom exception from the server to the Grid. Please let us know if this is what you are going for and based on that we will provide the further details. 

Regards, 
Sujith R  



LH Lon Hofman April 29, 2020 12:59 PM UTC

Query 1: It seemed they could be combined as when I enabled them both, the pager was updated while using virtual scrolling behavior. If they are intended to be mutably exclusive, maybe make it impossible to combine them or add this to documentation.

Query2:
Of course it is recommended to handle application errors at controller level in ASP MVC.
If an exception is not handled there, it ends up in the Application_Error in global.asax.cs,

However, in this case it is a login/session timeout. Not an exception. I consider it good practice to avoid using exceptions for control flow where possible. I modified the server to respond as below when that happens to make it clear to the client it should be handled as a failure:


Server code sample: (C#)

            

            //// ( This is from Startup.Auth.cs in void ConfigureAuth(IAppBuilder) )

            var cookieAuthenticationProvider = new CookieAuthenticationProvider();

            var defaultRedirect = cookieAuthenticationProvider.OnApplyRedirect;

            cookieAuthenticationProvider.OnApplyRedirect = context =>

                {

                    int originalResponseCode = context.Response.StatusCode;

                    defaultRedirect(context);

                    // Override default behavior which sets response code for AJAX requests to 200, which caused client code to interpret it as success

                    if (context.Response.StatusCode == (int) HttpStatusCode.OK)

                        context.Response.StatusCode = originalResponseCode;

                };

            app.UseCookieAuthentication(new CookieAuthenticationOptions

            {

                AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,

                LoginPath = new PathString("/Account/Login"),

                Provider = cookieAuthenticationProvider,

                ExpireTimeSpan = TimeSpan.FromMinutes(20),

                SlidingExpiration = true,

                CookieHttpOnly = true,

            });

Response: (this will make the AJAX component call the failure handler and I will get control in the Grid.actionFailure(args) handler with access to the XHR in args.error.error to handle the error with custom code )

HTTP/1.1 401 Unauthorized
Cache-Control: private Server: Microsoft-IIS/10.0 X-AspNetMvc-Version: 5.2 X-AspNet-Version: 4.0.30319 X-Responded-JSON: {"status":401,"headers":{"location":"http:\/\/localhost\/MyApplication\/Account\/Login?ReturnUrl=%2FMyApplication%2FController%2FGetData"}} X-Powered-By: ASP.NET Date: Tue, 28 Apr 2020 15:09:42 GMT Content-Length: 0



SK Sujith Kumar Rajkumar Syncfusion Team April 30, 2020 11:27 AM UTC

Hi Lon, 
 
Query – 1: Virtualization and paging 
 
The combining of both features is not restricted in the source but what we are saying is the virtualization feature was not implemented considering the paging(Based on the virtual functionality’s default architecture) and hence it is not recommended to combine both these features. We will improve the documentation for this from our end. 
 
Query – 2: Request and response related problem 
 
From the response you provided we could understand that you were able to resolve the problem and receive the custom error message in the actionFailure event. We are glad to hear that you were able to resolve it. 
 
Let us know if you have any concerns. 
 
Regards, 
Sujith R 



LH Lon Hofman April 30, 2020 01:35 PM UTC

Ah ok. That makes sense. Thanks for explaining.

I disabled virtualization just as I could not get stable/reliable behavior with it enabled (also the order in which asynchronous data can arrive makes it seem to jump to unexpected positions in my situation. - especially when multiple requests are pending to return or combined with sorting/grouping/etc)
Now since our datasets can have a LOT of columns, it would be useful to have column virtualization still for performance reasons (from my testing data arrives quite quickly from the server, but parsing/binding it takes a lot of time even if just a few columns are visible.
Question: Can column virtualization be used without row virtualization? (if not, I will just try to put a restriction for the amount of columns that can be selected in the column chooser)

Something else, I found a small error in documentation:
"Defaults to { columnChooserOperator: ‘startsWith’ }"
However, the Javascipt code in ColumnChooser.prototype.beforeOpenColumnChooserEvent uses this.parent.columnChooserSettings.operator

Not an error, but it is not clearly documented what operators are supported (I figured out it's the ones in DataUtil.fnOperators ) and that you can add custom operators by assigning them to a lowercase property of that object using function signature function (actual[, expected[, ignoreCase[, ignoreAccent]]])

Kind regards,
Remco Beurskens


SK Sujith Kumar Rajkumar Syncfusion Team May 4, 2020 12:46 PM UTC

Hi Lon, 
 
Not a problem. As for your other queries, 
 
Query – 1: Can column virtualization be used without row virtualization? 
 
Yes, the column virtualization can be used without enabling the row virtualization but both these virtualizations does not support paging and hence you cannot enable paging with the column virtualization. 
 
 
If you have large amount of data then it would cause performance problems without paging and row virtualization so for this case you can use the approach of restricting the columns that are selected as you mentioned. 
 
Query – 2: “Column chooser documentation issue” 
 
Thanks for reporting this issue in our documentation site. We have logged a task internally to correct this and we will update and refresh it online as soon as possible. You are also right regarding the supported and custom operators. 
 
Let us know if you have any concerns. 
 
Regards, 
Sujith R 



LH Lon Hofman May 6, 2020 07:07 AM UTC

Is restricting the number of selected columns in the column chooser a supported feature or do I need to override/overwrite one or more of the functions of it in the Grid load handler?
(Actually, since we often use hierarchical fields, maybe it can be combined to have it display as a treeview)


SK Sujith Kumar Rajkumar Syncfusion Team May 7, 2020 10:40 AM UTC

Hi Lon, 
 
You can hide the columns from being displayed in the column chooser by setting the columns showInColumnChooser property as false. The required columns can be dynamically shown/hidden by changing this property value. 
 
 
By this query – Since we often use hierarchical fields, maybe it can be combined to have it display as a treeview do you wish to display TreeView inside the column chooser. Since the column chooser is a fixed dialog, the TreeView cannot be rendered inside it. Even with custom toolbar item it would require a lot of customization to render a TreeView control there, assign all the column fields to it as data source and then change its state on selection. Otherwise you can render the TreeView outside the Grid and do all these operations. If this is not your requirement then can you please elaborate on it. 
 
Let us know if you have any concerns. 
 
Regards, 
Sujith R 



LH Lon Hofman May 7, 2020 02:59 PM UTC

1:  I need to limit the number of visible columns, no matter which ones are selected. The idea is to have all columns show up in the chooser, but limit the number that can be selected to view.

2: (maybe this is also a way to achieve the above as it may offer some callback to en-/disable an 'ok/apply' button based on the number of checkboxes checked) I do not require the built-in ColumnChooser of the Grid to achieve the functionality of a column chooser. It would be fine to render it outside the Grid (permanently/collapsible or as a separate dialog) and have checkboxes for each column show/hide the corresponding column in the grid.

Kind regards, 
Remco Beurskens


SK Sujith Kumar Rajkumar Syncfusion Team May 8, 2020 11:05 AM UTC

Hi Remco, 
 
The columns can only be prevented from being displayed in the column chooser and not prevent selection of the displayed columns. If rendering a separate control outside the Grid to perform the functionality of the column chooser is ok for you then you can utilize this method itself. We have demonstrated achieving the column chooser’s functionality using a list control rendered outside for your reference in the below code snippet, 
 
index.html 
<div><span>Column chooser</span> <button id='btnapply' style='margin-left: 10%'>Apply</button></div> 
<input id="multi-select-listbox" style='width: 200px' /> 
 
index.js 
// Initialize the ListBox component. 
var listObj = new ej.dropdowns.ListBox({ 
        // Grid columns are set as the list data source 
        dataSource: grid.getColumnFieldNames(), 
        // Set the selection settings with showCheckbox as enabled. 
        selectionSettings: { showCheckbox: true }, 
        created: function () { 
            // All the items are selected by default 
            this.selectAll(); 
        } 
}); 
listObj.appendTo('#multi-select-listbox'); 
 
var button = new ej.buttons.Button({ isPrimary: true }); 
button.appendTo('#btnapply'); 
 
document.getElementById('btnapply').addEventListener('click', function (args) { 
        // Entire list items 
        var listItems = listObj.getItems(); 
        // Selected list items 
        var selectedItems = listObj.getSelectedItems(); 
        var i = 0; 
        while (i < listItems.length) { 
            var count = 0; 
            // Item name(column name) is retrieved from the list element 
            var itemName = listItems[i].getAttribute("data-value"); 
            // List items are compared with selected item 
            selectedItems.forEach(item => count = item.getAttribute("data-value") === listItems[i].getAttribute("data-value") ? count + 1 : count) 
            // Grid column model is retrieved using the item name(column name) 
            var gridColumn = grid.getColumnByField(itemName); 
            // If list item is not selected the column visibility is set as false and vice-versa 
            (count === 0) ? gridColumn.visible = false : gridColumn.visible = true; 
            i++; 
        } 
        grid.refreshColumns(); 
}) 
 
Sample for your reference, 
 
 
 
Here instead of the List Box, you can use the control which best suits your requirement. 
 
 
Let us know if you have any concerns. 
 
Regards, 
Sujith R 



LH Lon Hofman May 27, 2020 02:45 PM UTC

Thanks. That looks promising.

In terms of functionality, it looks like the MultiSelect Dropdown (with checkboxes and grouping, mirroring column hierarchy) which has a built-in maxSelection feature serves my needs if I can bind it to the grid's columns property.
However, I'd like to have it shown as a (collapsible) sidebar/treeview instead of as a dropdown so the user has the option to have it also serve as an overview of the columnStructure (which in our case can have many fields and deep hierarchies) while browsing the grid.
Which control or combination of controls would you recommend?

Kind regards,
Remco Beurskens


SK Sujith Kumar Rajkumar Syncfusion Team May 28, 2020 08:29 AM UTC

Hi Remco, 

Thanks for the update. 

For your requirement the following controls can be used – Dropdown Tree, Multiselect Dropdown and TreeView. More details on these controls can be checked from the below links, 




From this you can choose the required control which best suits your requirement. 

Note: The instance of the particular child grid needs to be used for changing column visibility for that corresponding grid. Since you have mentioned your case involves deep level hierarchies, this would mean that in the control rendered outside for each Child Grid you need to define a set of fields or based on a single set of fields for all the child grids you need to determine on which child grid this needs to be applied. This will not be feasible when there are many child grid’s present. However, this would not be a problem if the column chooser is present in the corresponding Grid/Child Grid itself – either the default column chooser menu or a custom control rendered using custom toolbar item. 

Let us know if you have any concerns. 

Regards, 
Sujith R 



LH Lon Hofman May 28, 2020 09:50 AM UTC

Thanks. I'll give them a try.

By the way, I think there's a misunderstanding of what I meant by "hierarchical structure", as we don't use child grids. Just the column structure can be "hierarchical" (maybe "nested" is a better word) as in stacked columns. (example pictures attached). There is only one single grid on the page and our datasets ("instruments") do not have any foreign keys/master-detail relations.

Is it possible to bind the mentioned controls directly to the grid's columns? (maybe a custom JsonAdaptor ?)
( as these define nested structure by using the '.' character in the field property as an object separator and are set in the form:
grid.columns: [ColumnDef | GroupDef][] where
ColumnDef:{ field : string, headerText : string }
GroupDef:{ headerText : string, columns: [ColumnDef | GroupDef][] }
)


Kind regards,
Remco Beurskens

Attachment: columnstructuresamples_c5086035.zip


SK Sujith Kumar Rajkumar Syncfusion Team May 29, 2020 12:07 PM UTC

Hi Remco, 
 
Thanks for the information. We can understand your scenario now, for stacked columns it would not be a problem binding column fields to the custom control based on their structure. As for your query – Is it possible to bind the mentioned controls directly to the grid's columns, yes you can render other controls inside the Grid columns using column template property. This requirement is already documented for rendering a dropdown list component inside the Grid columns in the below help link, 
 
 
Using the same approach you can bind the required controls in the Grid columns. 
 
Let us know if you have any concerns. 
 
Regards, 
Sujith R 



LH Lon Hofman June 4, 2020 03:23 PM UTC

I'm sorry, but I think I wasn't clear enough.

I'm not looking to render other controls inside column headers as that already displays as intended.
What I intend to do is have a separate control outside the grid displaying the columns of the grid as a tree with checkboxes, so it can replace the functionality of the column chooser which can only display a plain listbox to control column visibility.
By 'bound to columns' I meant the columns property of the grid can be used as a data source for the separate control.
I aim to have the nested structure of the columns as an overview in a sidebar outside the grid with checkboxes to control each column visibility. (preferably also with the search option the column chooser and multiselect dropdown have - just not as a dropdown, but as a (collapsible) sidebar )
If the grid's columnchooser would be customizable to use a multiselect dropdown without nested grouping that would also be a nice solution. (or use both)


EDIT: I've started to play around with a TreeView to display the columns outside to my grid.
I inserted this in the dataBound of the grid, but the TreeView shows blank text for each node:

if (columnsChanged && treeView) {
     treeView.fields = { get dataSource() { return grid.columns; }, id: 'uid', child: 'columns', text: 'headerText', expanded: 'visible', isChecked: 'visible', hasChildren: 'columns'};
}

Why doesn't it get the text from the headerText property?
It seems internally, the dataSource is cloned using JSON.stringify, which ignores this property.
My workaround for now:
function drawNode(args) {
    if (typeof args.text === 'undefined') {
        const text = grid.getColumnByUid(args.nodeData[treeView.fields.id])[treeView.fields.text];
        args.text = text;
        $(".e-list-text", args.node).text(text);
    }
    console.log(args);
}

Kind regards,
Remco Beurskens


SK Sujith Kumar Rajkumar Syncfusion Team June 5, 2020 12:20 PM UTC

Hi Remco, 
 
When the Grid column object is stringified, it will lose header text, template and value accessor property instances since these values can be mapped as id or method that is defined in the application level or even modified and hence cannot be maintained as string value. And as you have mentioned, the data source will be stringified before assigning to the TreeView which is why the headerText is not set as the tree node text since it will not be present in the tree data source. You can resolve this by assigning the header text property to another custom property and bind that to the TreeView fields as demonstrated in the below code snippet, 
 
// Grid’s dataBound event function 
function onDataBound() { 
        if (flag) { 
            var treeData = []; 
            Object.assign(treeData, grid.columns); 
            treeData.forEach(x => x.gridHeader = x.headerText); 
            treeObj.fields = { dataSource: treeData, id: 'uid', child: 'columns', text: 'gridHeader', expanded: 'visible', isChecked: 'visible', hasChildren: 'columns' }; 
 
            flag = false; 
        } 
} 
 
Sample for your reference, 
 
 
Let us know if you have any concerns. 
 
Regards, 
Sujith R 



LH Lon Hofman June 18, 2020 03:32 PM UTC

Thanks.

With your help, I now have a basic checkbox-Treeview inside a custom (modal) dialog to replace the ColumnChooser. In addition to be able to view the nested column structure, I can now add custom features to it like min/max number of visible columns, etc. Some built-in features from ColumnChooser I needed to add manually: searching columns, (de)select all.
As a data source for TreeView, I use an array of  proxy objects for the grid columns (with getter/setter functions that forward to the method calls on the grid using the field name to map on). If they would not just be copied, I could almost accomplish two-way binding this way ;)

Q:
Just noticed that in your previous sample with the ListBox, every time a column visibility is changed - both how and hide - the data source is queried because of the refreshColumns() call, which in my case means a request to the server, even if the required data for the columns are already present in the grid ( returned by grid.getRowDataObject() ) so why does it have to be queried again just to refresh the UI?  Enabling caching on the data manager does not seem to help.
This seems to occur only if data was loaded -before- the column was made visible for the first time (when column.visible == false initially). The same behavior applies if the default ColumnChooser is used in these conditions.
Is there a way to just (re-)bind the existing local row data in the grid and update the UI without querying the server for the same data ? (it looks like refreshColumns() just calls .refresh() )?

(My grid is for viewing data only - rows can't be added or edited)

Kind regards,
Remco Beurskens




SK Sujith Kumar Rajkumar Syncfusion Team June 19, 2020 12:28 PM UTC

Hi Remco, 
 
We are glad to hear that. 
 
As for your other query – Every time a column visibility is changed - both how and hide - the data source is queried because of the refreshColumns() call, which in my case means a request to the server, even if the required data for the columns are already present in the grid. Instead of changing the column visibility using the columns visible property you can use the Grid’s hideColumns and showColumns method to hide and show the columns respectively. We have modified the previously provided sample(With Grid and TreeView as its column chooser) based on this for which you can find the modified code snippet and sample below, 
 
document.getElementById('btnapply').addEventListener('click', function(args){ 
      // TreeView’s entire data source 
      var treeItems = treeObj.fields.dataSource; 
      // TreeView’s checked nodes 
      var checkedItems = treeObj.checkedNodes; 
      var treeFields = []; 
      // Field data for the checked items 
      checkedItems.forEach(id => treeFields.push(treeObj.getNodeObject(id))); 
      var i = 0; 
      while (i < treeItems.length) { 
        var count = 0; 
        var name = treeItems[i].gridHeader; 
        // Checked field data is compared with each Tree node 
        treeFields.forEach( item => count = item.gridHeader === name ? count+1 : count ) 
        var gridColumn = grid.getColumnByField(treeItems[i].field); 
        if (count === 0) { 
          grid.hideColumns(gridColumn.headerText); 
        } else if(count !== 0 && !gridColumn.visible) { 
          grid.showColumns(gridColumn.headerText); 
        } 
        i++; 
      } 
}) 
 
 
 
Let us know if you have any concerns. 
 
Regards, 
Sujith R 



LH Lon Hofman June 22, 2020 04:47 PM UTC

Thanks.

Just one case where it does not work for me: (further details / test case further down)

1. On initialization (or when the user selects another dataset), I load the field definitions from the server. Grid data source is set to an empty array then.
2. Then the column chooser ( which I will replace by the treeview dialog) will show with no columns selected ( as they are all initially visible == false )
3. When a selection is made by the user, grid.dataSource will be set to my dataManager, which will cause the grid to load and show (only) the data for the selected columns.
4. This works good, except when column data is returned by the server for columns that are not visible and are made visible afterwards.

When using columnChooser, I relied on the "columnstate" in the actionComplete to detect changes to visibility of the columns, but grid.Show/HideColumns do not fire this event (only the actionStart - but without information about what columns are shown/hidden)

// My grid.actionComplete handler
function actionComplete(args) {
    console.log("action complete: " + args.requestType);
    if (args.requestType === "columnstate") {
        console.log(args);
        if (args.dialogInstance) { // if triggered from column chooser, update treeView's checkboxes
            treeView.refresh();
        }
        if (!dataLoaded && Array.isArray(grid.dataSource)) { // no data is loaded yet => set the data source (query sends grid.getVisibleColumns().map(c=>c.field) to the server, so only data needed will be returned) 
            console.log("setting remote dataSource...");
            grid.dataSource = dataSource; // global variable dataSource holds my instance of DataManager
        } else {
            // this should load column data not present on the client yet from the server if and only if we need it (when rows that are in memory have no data for the columns about to be shown )
            // however, this seems not to work if the data for a column was returned by the server before it was made visible for the first time (then the column is just not made visible - not even in the header, but it shows as visible in the columnchooser) It seems data binding for this column doesn't happen in that case.
            const newColumns = args.columns.filter(c => c.visible);
            if (!newColumns.length)
                return;
            // only if any data property for a column made visible in memory is undefined, we need to refresh the grid to load this data from the server
            const fieldNameParts = newColumns.map(c => c.field.split(".")); // split nested property names into arrays
            if (grid.getRowsObject()
                .find(row => row.data && fieldNameParts.find(parts => typeof parts.reduce((prev, curr) => prev && prev[curr], row.data) === "undefined"))) {
                console.log("Found rows in memory that don't have data for new columns. => refreshing grid... ");
                grid.refresh();
            }
        }
    }

This is the property setter function on the proxy objects (entries in treeview.fields.datasource) to change column visibility: 
                function setColVisibility(isVisible) {
                    // 'this' refers to an instance of a column info object we create elsewhere (similar structure as the columnDefinition)
                    const col = grid.getColumnByField(this.field);
                    if (isVisible !== col.visible) {
                        if (isVisible)
                            grid.showColumns(this.field, 'field');
                        else
                            grid.hideColumns(this.field, 'field');
                        // function calls above are now replacing these lines
                        //grid.trigger("actionBegin", { requestType: 'columnstate' });
                        //col.visible = isVisible;
                        // still manually trigger columnstate actionComplete as I need to know mutations in that handler (Columnchooser triggers it, but show/hideColumns do not)
                        const params = {
                            requestType: 'columnstate',
                            element: grid.element,
                            columns: [col],
                            dialogInstance: null
                        };
                        grid.trigger("actionComplete", params);
                       // also removed this line as I assume grid show/hide functions already cover this.
                        //grid.getShowHideService.setVisible([col]);
                    }
                }

Workflow:
- reset to initial state if not already: grid.dataSource is set to [] as well as fields. Filters/groups/sorts/etc are cleared.
- value for grid.fields is loaded using a manual AJAX call, all with visible == false.  (fields are set, data is not loaded yet)
- ColumnChooser / Treeview show up with no columns selected. The user can pick the columns they want to see.
- Rows are loaded, only data for columns the user selected are returned from the server. But data for primary key, group key, sort key fields are always returned as they are required.

Illustration to reproduce my problem:
Given a model for grid.columns : [ {field: "Id"} , {field: "person.firstname", headerText: "First name"},{field: "person.lastname", headerText: "Last name"}] 
Primary key: "Id", it is always returned from the server even if column is hidden.

Sample case:
- User selects only "person.firstname" initially
- grid.dataSource is set to preconfigured dataManager instance in the script (which will send fields to return along with the query to the server)
- Server returns rows like { Id : 1, person: { firstname: "John"}}  (Primary key "Id" whose column is currently not visible in the grid, is returned in addition as configured in dataManager)
- Grid shows the column requested by the user (just "person.firstname") as it should when selection is confirmed.
- User now opens columnchooser/treeview again and selects "Id" to be visible in the grid as well.
- Q: Now nothing happens. The column is NOT shown in the grid, but its .visible property is set to true (and shows as selected in columnchooser if opened again).  I don't know why.
- If anything happens that triggers a data request (even though the data is already on the client), the column shows up. The problem does not occur if "Id" was among the columns initially selected by the user.
Q: I also notice the total width does not change width when a column is hidden. (remaining columns grow larger in width)
What am I missing?


Kind regards,
Remco Beurskens


SK Sujith Kumar Rajkumar Syncfusion Team June 23, 2020 09:11 AM UTC

Hi Remco, 

From the information you provided we could understand the problem is occurring for you when following the below steps, 

  • Initially empty array is bound to the Grid and all column visibility is set as false.
  • Then columns to be visible are selected in the TreeView column chooser where the columns are made visible and data is updated to the Grid.
  • Now when another column is set as visible it is not getting displayed in the Grid.

We checked this case from our end but this was working fine. Please let us know if the steps we mentioned above is not your problem reproducing scenario. 

Also please modify the below line while hiding columns so that this operation is not performed unnecessarily for already hidden columns, 

document.getElementById('btnapply').addEventListener('click'function(args){  
              . 
              . 
        var gridColumn = grid.getColumnByField(treeItems[i].field);  
        if (count === 0 && gridColumn.visible) {  
          grid.hideColumns(gridColumn.headerText); 
        } else if(count !== 0 && !gridColumn.visible) {  
          grid.showColumns(gridColumn.headerText);  
        }  
        i++;  
      }  
})  


While hiding/showing columns the actionBegin and actionComplete events will be triggered with requestType as ‘columnstate’ and the respective column and its new state. 

 

Please check below updated sample for your reference, 


Let us know if you have any concerns. 

Regards, 
Sujith R 



LH Lon Hofman June 23, 2020 07:04 PM UTC

Thanks for informing me the actionComplete for columnstate triggeres when using the show/hideColumns methods so I dont have to trigger them myself.

I'm still trying to isolate my problem. Your summary was correct, just with one addition:
Only when the server returns data for a column on the very first response while it was not made visible by a user action, the column will remain not show until something else trigger a new data request to the server.

Also differences from the same you provided and my code:
- I use a custom UrlAdaptor for remote data binding. The sample uses local queries that resolve to an array being set to grid.dataSource.
- The data from the server only returns properties in the JSON objects for the columns that are visible (or a primary/group/sort key)
- The JSON data I return from the server can be in nested form. (in my test scenario the primary key was a nested field such as "Person.Id" )

Maybe I should just include the whole JavaScript file for my page in progress to get better insight.


SK Sujith Kumar Rajkumar Syncfusion Team June 24, 2020 11:01 AM UTC

Hi Lon, 

The data updating technique that you are mentioning is still not clear with us. So it would be better if you share us your JavaScript file so that we can identify your exact scenario and validate further based on that. 

We have also prepared a sample with the same scenario by binding URL Adaptor data to the Grid dynamically. You can download it from the following link, 


So can you please check if you are able to reproduce your problem scenario in the above sample and let us know. It would be helpful to narrow down the problem scenario and provide a proper solution based on that. 

Let us know if you have any concerns. 

Regards,
Sujith R 



LH Lon Hofman June 25, 2020 07:28 AM UTC

Good idea.

I was able to modify the sample and reproduce the problem I am facing.
( I hope it is a bit readable as it is still draft code. I need to be able to load fields dynamically to be able to switch to different data sets in my project. The sample has just one, but no more was needed to reproduce the problem.)

Steps to reproduce: (it is not only when selecting using a treeview. Using the built-in columns chooser shows the same behavior)
- Load column definitions by clicking the button
- Select any column(s) but not the one for the primary key ( "OrderId" ) and click "apply".
- Select "OrderId" and click "apply"

Also note that auto resize does not work properly unless a server data request is being made by calling .refresh() causing the table width to remain the same and columns taking more space than they should. (I should not need to query the server for data every time visibility is toggled unless the data is unavailable)

Reducing properties returned by the server to the ones for columns visible in the grid is not even needed to reproduce. 
Just comment out / delete the line 
     DataSource = operation.PerformSelect(DataSource, fields);
 in HomeController. UrlDatasource() to make the issue apply for all fields that are not initially selected (not just the primary key "OrderId")

Kind regards,
Remco Beurskens

Attachment: Urladaptor787640266_modified_797624eb.zip


SK Sujith Kumar Rajkumar Syncfusion Team June 25, 2020 12:07 PM UTC

Hi Remco, 

Thanks for providing the modified sample. 

The problem was occurring because of setting the width as “auto” for all the dynamically generated columns as for this case the width will be automatically set so you need not define it. So you can resolve this by removing that line in the column generation code. You also can auto fit the columns if needed by calling the autoFitColumns method after showing/hiding the columns. We have modified the sample provided based on this. You can download it from the following link, 


Let us know if you have any concerns. 

Regards, 
Sujith R 



LH Lon Hofman July 2, 2020 07:37 AM UTC

Thank you.

It looks like the problem with the columns not showing up was solved just by adding a call to grid.autoFitColumns() ( I guess it might be created previously, but not properly rendered?)

The reason why I am clearing the grouping/filter/sort arrays instead of calling the clearGrouping/Filter/Sorting methods is that if I use the methods, another data request is sent to the server even though grid.dataSource is no longer set to the dataManager instance, which causes an error because in my case it would ask fields of the old data set with from the new dataset.

I also noticed that in my project, an empty header was still visible even if no columns were set to visible. This seems to be caused by a leftover setting from when I tried to get virtualization to work: rowHeight was set to a value in initialization. I removed it and the header hides nicely now. (previously I had to do this manually by altering/restoring the grid.element.querySelector(".e-gridheader") element's style in dataBound)

Also, the code in the sample can only deal with a flat structure in the treeview, but that is easy to fix.


SK Sujith Kumar Rajkumar Syncfusion Team July 3, 2020 07:31 AM UTC

Hi Lon, 

You’re welcome. The width set as “auto” to the dynamically created columns was causing the columns to not be generated properly and the autoFitColumns() adjusted each column based on its content. We can also understand why you are clearing the group/sort/filter arrays instead of calling the method. So you can use which ever best suits your requirement.  

By the rest of the provided information we suspect you have mentioned that you resolved the remaining queries. If so we are glad to hear that and if not can you please elaborate on that to validate further. 

Regards, 
Sujith R 



LH Lon Hofman July 7, 2020 08:42 AM UTC

Hi again,

Now I think the most important issues have been resolved (thanks for guiding me thought it), I started fine tuning my solution and encountered an error when adding 'format' strings to the filed definitions for date/time/number columns.

When loading the definition for a columns defined with an additional format property  (ex. { ..., type: 'date', format:'yMd' } ) like in the sample, in my project this resulted in a javascript error when the fields are loaded from the server:
ej2_custom2.min.js:formatted:2460 Uncaught TypeError: Cannot create property 'currency' on string 'yMd'
    at e.getNumberFormat (ej2_custom2.min.js:formatted:2460)
    at e.getFormatFunction (ej2_custom2.min.js:formatted:22001)
    at new e (ej2_custom2.min.js:formatted:22060)
    at dp (ej2_custom2.min.js:formatted:33111)
    at dp (ej2_custom2.min.js:formatted:33110)
    at t.render (ej2_custom2.min.js:formatted:30809)
    at t.refresh (ej2_custom2.min.js:formatted:5346)
    at t.freezeRefresh (ej2_custom2.min.js:formatted:32093)
    at Object. (dataviewer.js:622)
    at c (jquery?v=lyZ81pppW0PFRnq5TvyU-b4SWvCcEzfLrPRTRnn-vIo1:1)

( I have seen several users getting the same error in the forums, but none seem to match my scenario )

EDIT: actually, from the browser debugger, it looks like some other JavaScript code is (also) setting/overriding String.prototype.format. I just have to find out what other library is causing this conflict to further isolate the problem and then try and find a solution.
UPDATE: found the 'evil' function definition of String.prototype.format in global Javascript for our site and converted it to a standalone function. Problem solved.

Kind regards,
Remco Beurskens


SK Sujith Kumar Rajkumar Syncfusion Team July 7, 2020 11:47 AM UTC

Hi Remco, 

You are welcome. We are glad that we were able to assist you and also to hear that the problem reported in your last update has been resolved. 

Regards, 
Sujith R 



LH Lon Hofman July 10, 2020 11:22 AM UTC

Hello again.

I found a JavaScript error is being thrown when no columns are visible and the user clicks within the grid body. (no vertical space is allocated for column headers and only "No records to display" is visible in the body) :
ej2_custom2.min.js:formatted:26576 Uncaught TypeError: Cannot read property 'length' of undefined
    at e.select (ej2_custom2.min.js:formatted:26576)
    at e.onFocus (ej2_custom2.min.js:formatted:26122)

        Matrix.prototype.select = function(e, t) {
            e = Math.max(0, Math.min(e, this.rows)),
            t = Math.max(0, Math.min(t, this.matrix[e].length - 1)),
            this.current = [e, t]
        }

        FocusStrategy.prototype.onFocus = function() {
            if (!(this.parent.isDestroyed || Di.isDevice || this.parent.enableVirtualization)) {
                this.setActive(!this.parent.enableHeaderFocus && 0 === this.parent.frozenRows, 0 !== this.parent.frozenColumns),
                this.parent.enableHeaderFocus || this.parent.getCurrentViewRecords().length || this.getContent().matrix.generate(this.rowModelGen.generateRows({
                    rows: [new Th({
                        isDataRow: !0
                    })]
                }), this.getContent().selector, !1);
                var e = this.getContent().matrix.get(0, -1, [0, 1], null, this.getContent().validator());
                this.getContent().matrix.select(e[0], e[1]),
                this.skipFocus && (this.focus(),
                this.skipFocus = !1)
            }
        }

It does not stop the page from functioning and probably the user won't even notice it, but I aim for not leaking unhandled exceptions.

Kind regards,
Remco Beurskens


SK Sujith Kumar Rajkumar Syncfusion Team July 13, 2020 07:16 AM UTC

Hi Remco, 

We validated your reported problem in the source-level and would like to let you know that by default at-least one column needs to be visible in the Grid. Based on the Grid’s architecture all the columns cannot be hidden. This is its default behavior and so we suggest you to set at-least one column as ‘visible’ in the Grid. 

Regards, 
Sujith R 



LH Lon Hofman July 14, 2020 10:09 AM UTC

The problem is, that I like to have the grid displayed even when the user has no dataset selected. I can enforce a minimum in the treeview fieldselector and make it a modal dialog, but it will prevent the user to switch dataset as the selector is on the same layer of the page as the grid.

As a workaround, I could block the mouse event by adding
 $(grid.element).mousedown(function(e) { if (!grid.getVisibleColumns().length) { e.stopImmediatePropagation(); e.preventDefault();}});
and set grid.element.tabIndex == -1 as long as no columns are visible.

Now there's another question:

My ASP.NET MVC5 project (we are not on core yet) does not have the data itself, but gets it from a WCF service.
Currently, I have to get the full dataset loaded in memory in the web controller and use DataOperations.Execute to be able to serve the web client the data it asked for.
However, this is not very efficient, especially since our data service also has a custom API for paging/ filtering/ sorting /grouping. It uses a custom query request structure  that resembles that of an SQL query more than a IQueryable expression tree.
I think writing our own IQueryable provider would be overkill for the simple data requests we use from the grid.
What would you advise to translate the query from the Datamanager into the request for our data service ourselves instead of write a IQueryable provider or parse the DataManagerRequest ourselves? (Like DataOperations is doing it, but it is kind of a black box - only .Skip() and .Take() seem quite straightforward)
(We are only reading data, not writing)

Kind regards,
Remco Beurskens


SK Sujith Kumar Rajkumar Syncfusion Team July 14, 2020 01:25 PM UTC

Hi Remco, 
 
Since it is the default behavior that at-least one column needs to be set as visible in the Grid(As actions are performed based on that), preventing the mouse click for this case would be the best way.  
 
For your other query we could understand that your requirement is to modify the data queries sent in the server request. You can achieve this by using custom adaptor to extend the built-in adaptor that you have bound in the Grid and override the built-in request processing method – ProcessQuery to modify the request query as per your need. This is demonstrated in the below code snippet with a simple JavaScript example,  
  
// Web api adaptor is overridden 
class CustomAdaptor extends ej.data.WebApiAdaptor { 
        processQuery() { 
            // calling base class processQuery function 
            var original = super.processQuery.apply(this, arguments); 
            // Here you can modify the queries in the request URL as per your requirement and return it 
            return original; 
        } 
} 
 
var data = new ej.data.DataManager({ 
        url: hostUrl + 'api/Orders', 
        adaptor: new CustomAdaptor() 
}); 
 
var grid = new ej.grids.Grid({ 
        dataSource: data. 
              . 
              . 
}); 
grid.appendTo('#Grid'); 
 
Sample for your reference, 
 
 
 
Let us know if you have any concerns. 
 
Regards, 
Sujith R 



LH Lon Hofman July 20, 2020 03:05 PM UTC

Hello,

Composing the query client side was not really the issue ( I already have a custom adaptor in use, based on UrlAdaptor, that adds the equivalent of OData $select to the query, based on the visible columns )

It is how to translate the DataManagerRequest in the MVC web controller action method to a custom query object that will be sent to a WCF data service.

In addition, if I want group sizes to be returned ( which is by default achieved by retrieving full groups from the server in order to determine their sizes), is it possible to have the server returning the group sizes instead of returning complete groups just to count them client side?

Kind Regards, 
Remco Beurskens


SK Sujith Kumar Rajkumar Syncfusion Team July 21, 2020 01:13 PM UTC

Hi Remco, 

Since you are using WCF data service to perform your operations the URL adaptor will not be best suited for it. For this we suggest you to bind data to the Grid using OData or OData V4 adaptor(which sends odata queries to the server) so you would not need to translate it separately. 


Also we are not able to clearly understand your requirement for the grouping query so can you please elaborate on it based on which we will validate further and update the details. 

Let us know if you have any concerns. 

Regards, 
Sujith R 



LH Lon Hofman July 21, 2020 04:07 PM UTC

Thanks for your response,

The web browser is not directly communicating with the WCF service (which is by the way an 'old style' service/data contract based WCF service, not a OData capable WCF data service). The web server which is service the HTML pages also offers the JSON responses requested. The browser has no direct access to the backend WCF services the webserver itself is using.

I hope this clarifies the situation a bit:

Browser      |    ASP.NET MVC  webserver   |     backend  'old style'  WCF services                      |     backend database / datastore
request    ==>      ==>  translate query to query for generic interface    ==>      execute query in specific implementation of the data interface
                                                                                                                                                                          || 
dataManager  <==                    <==                            <==      return result 
                                                                                                                                                           (I would like to filter/page/sort/group/etc here instead of on the web server)

Currently, there is problem is, the WCF service does have an interface for grouping/sorting/paging/filtering, but is currently not used as the complete resultset has to be retrieved by the webserver for DataOperations to convert it to IQueryable and execute the full query (sorting/paging/grouping/filtering/etc) in memory (I have played around with OData already, but it looks it relies on IQueryable as well if I want to use it in ASP.NET and it seems to require static data models where ours are built dynamically at runtime). I would prefer passing the query clauses/parameters to the data store to execute there to return only the requested data and reduce network/memory load.
Our project has a long history and we require full backwards compatibility for all past releases across all application layers. The data service is no exception and would need a big refactor/rewrite for this.

For now, I have to find out which (former?) colleague(s) know more about our own data interface and what it supports in a query and how. When I have more information about this, I will get back to you if I need further assistance.

(by the way, can I have the ODataAdaptor submit the OData $select= clause as well, so only visible columns will be returned? (I already covered the case where a column would be made visible and no data is loaded/bound for it yet - OData would be a more elegant interface than the custom Ajax action method we use now where we pass visible columns as a custom parameter)

Kind regards,
Remco Beurskens






SK Sujith Kumar Rajkumar Syncfusion Team July 22, 2020 09:45 AM UTC

Hi Remco, 

We have created a new incident under your Direct trac account to follow up with this query. We suggest you to follow up with the incident for further updates. Please log in using the below link.   


Regards, 
Sujith R 


Loader.
Up arrow icon