Announcement

Collapse
No announcement yet.
X
  • Filter
  • Time
Clear All
new posts

    Freeze visible rows in ListGrid on update

    SmartClient_v120p_2020-01-08_PowerEdition

    I have a ListGrid (autoFetchData: true) with a DataSource (clientOnly: true) that is getting real-time updates (adds, updates, and deletes) via a WebSocket. If I am getting a high volume of added rows, the grid starts auto-scrolling my data (depending on my sort). Is there a way to freeze my current visible rows when updates are flowing in so that the data I am viewing doesn't scroll out of view?

    #2
    Data updates should not, on their own, trigger scrolling, although putting focus in a specific row (via eg startEditing()) would do so.

    Can you clarify the specific scenario where scrolling in occurring, that is:

    1. what is the specific type of update (add, update, multi-column, updating a sorted field?)

    2. what direction does scrolling occur in?

    3. where was the viewport prior to scrolling (eg, top, bottom, middle, slightly offset from either)?

    Finally, scrollToRow() can be used to place scrolling a particular desired position. See also resultSet.updatePartialCache for how updates are performed when we have a partial cache, so it's not possible to place rows at a definite position w.r.rt. server sort order.

    Comment


      #3
      Clarifications:

      1. The specific type of update is an add. When the message comes in, I do a dataSource.addData(message).

      2. The grid scrolls up.

      3. The same behavior occurs regardless of where the viewport is. I mean, if it's at the top already, it stays there obviously, but anywhere else it starts scrolling up.

      Having said all of this, I should add that each message (i.e. record) has an ID field. The grid is sorted by ID descending. Each new message that comes in will have a higher ID than the previous one, so the data automatically gets put at the top of the grid to obey the sort order.

      What I had attempted (with no success) was:
      - Before the addData() call, grid.getVisibleRows() and then retrieve the top record in the viewport using the firstRowNum returned from getVisibleRows()
      - Call addData()
      - Find the new row number of the top record (before the add)
      - Use scrollToRow() to scroll to that row number

      It appears that the grid doesn't know the new row number of the record right away (although the datasource does).

      I will read up on updatePartialCache to see what I'm missing. Thank you.

      Comment


        #4
        addData() is asynchronous even with a client-only DataSource, so if you tried to immediately find out the row number from the ListGrid after calling addData(), that would be expected to fail. Use the callback to addData() to know when the data has been added.

        You may also need to redraw() the ListGrid, depending on how your code is structured, since it may not immediately redraw to show the new row if there were multiple updates.

        Comment


          #5
          I am revisiting this as I never got it to work. I read up on updatePartialCache, which is part of the ResultSet class, and that seems like it would work. However, I'm not explicitly using a ResultSet, even though I know it's working under the covers somewhere. I put together a very simplified code sample to exhibit what I am seeing.

          If you run the code in the Feature Explorer, click on "Initialize Data" first. Then, once the initial data is loaded, you can select a row (ID=15), then click the "Update Data" button. You'll see that the row you selected scrolls out of view. Note, that it doesn't matter if you select the row or not; the same behavior occurs.

          Perhaps I need to address this functionality in a different manner to get the desired results, but I'm still fairly new at this and not sure if there is a better solution.

          Code:
          let gridDS = isc.DataSource.create({
              ID: "gridDS",
              fields: [   
                  {name: 'id', type: "integer", primaryKey: true},            
                  {name: 'name', type: "text", canFilter: false},    
                  {name: 'fieldX', type: "float", canFilter: false},
                  {name: 'fieldY', type: "float", canFilter: false},           
                  {name: 'fieldZ', type: "integer", canFilter: false}
              ],
              dataFormat: "json",
              clientOnly: true
          });
          
          let myGrid = isc.ListGrid.create({
              ID: "myGrid",
              width: "100%", height: 250,
              alternateRecordStyles: true,
              dataSource: "gridDS",
              autoFetchData: true,
              canFreezeFields: true,
              selectionType: "multiple",
              cellHeight: 30,
              showResizeBar: true,
              resizeBarTarget: "next",
              resizeBarSize: 12,
              wrapCells: true,
              autoFitHeaderHeights: true,
              leaveHeaderMenuButtonSpace: true,
              reselectOnUpdate: true,
              stopOnErrors: true,                                                    
              fields: [
                  {name: 'id', title: "ID", width: 70, align: "center", frozen: true, canHide: false},  
                  {name: 'name', title: "Name", width: 180, align: "center"},        
                  {name: 'fieldX', title: "X", width: 100, align: "center"},
                  {name: 'fieldY', title: "Y", width: 100, align: "center"},
                  {name: 'fieldZ', title: "Z", width: 100, align: "center"}        
              ],
              sortField: "id",
              sortDirection: "descending"
          });
          
          isc.IButton.create({
              title: "Initialize Data",
              left: 0, top: 270,
              click: function() {
                  sendInitialData();
              }
          });
          
          isc.IButton.create({
              title: "Update Data",
              left: 150, top: 270,
              click: function() {
                  sendUpdateData();
              }
          });
          
          isc.IButton.create({
              title: "Clear Data",
              left: 300, top: 270,
              click: function() {
                  gridDS.setCacheData([]);
                  myGrid.invalidateCache();
              }
          });
          
          function updateGrid(record) {
              if( gridDS !== undefined ) {
          
                  let data = gridDS.getCacheData(); 
                  if( data !== undefined ) {
          
                      // See if the record exists
                      let result = data.find("id", record.id);
                      if( result === null ) {
                          gridDS.addData(record);
                      } else {
                          gridDS.updateData(record);
                      }
                  }
              }
          }
          
          function sleep(ms){
              return new Promise(resolve => setTimeout(resolve, ms));
          }
          
          function sendInitialData() {
              for( let i = 0; i < 20; i++ ) {
                  let record = {id: i, name: "Name " + i, fieldX: i + 1.2, fieldY: i + 3.4, fieldZ: i + 5};
                  updateGrid(record);
              }
          }
          
          async function sendUpdateData() {
              for( let i = 15; i < 45; i++ ) {
                  let record = {id: i, name: "Name update " + i, fieldX: i + 1.2, fieldY: i + 3.4, fieldZ: i + 5};
                  updateGrid(record);
                  await sleep(500);
              }
          }

          Comment


            #6
            Looking at this implementation, it does use a ResultSet (due to the autoFetchData), so that's fine. That results in the expected behavior of ResultSet, but updatePartialCache doesn't apply here, because the cache is complete. So the record is simply inserted in sorted order, which is at the top.

            And then, running your code, we are not seeing auto-scrolling to the top, even testing in the 12.0 Showcase. The scrollbar just stays in the range where you leave it. Can you clarify what browser/OS platform is showing this?

            Finally, it's not really clear what you want here: because of your sort order, new updates appear at the top, so presumably, you'd want to stay there. Also, if that was a mistake in the test case, and you really meant for new records to be at the bottom, note that you would have to explicitly tell the grid to scroll to the bottom; that's not an automatic consequence of adding a record at the bottom. The scrollbar just stays put instead.



            Comment


              #7
              I have several environments and they are as follows:
              CentOS Linux 7.8 / Firefox v68
              Windows 10 / Firefox v75
              Windows 10 / Chrome v86

              But, as I read your response, I believe that I did not clearly state what I'm trying to accomplish, so I apologize for that. I'll try to clarify.

              The user can change the sort on the grid, so this test case (ordered by ID descending) is the "worst case scenario" that exhibits the issue.

              Let's say that the user is viewing the data on row 10 (i.e. ID = 10) to perform some data analysis. As updates come in, that row of data that the user is looking at scrolls out of view. So, the user would have to either filter by that ID, change the sort, or request to pause updates to the grid so that the data that they are looking at remains in view. Basically what I need is for the data to remain in view while the grid/scrollbar adjusts accordingly to the updates. Perhaps this is just not the nature of the grid and it is adjusting as it's designed to, but I need to figure out how to keep what they are looking at in view when updates come in. Hopefully this makes a little more sense.

              Comment


                #8
                The behavior of the grid is that it keeps the same numerical row range visible as the data changes. That's a reasonable default, as trying to keep the same data rows in view is not really a well-defined behavior, when what is changing may be a filter (so totally different records are present), or removal of some or all of the records currently in view, or even a sort change in a partly loaded dataset, where extra operations would be required to even figure out the new index of the previously shown records, so that that range can be loaded into cache.

                This is not even covering the question of what records are intended to be in view: the top one from the former viewport? The middle?

                So, if your dataset is changing in this particular uniform way and you want to have the scroll position lock to a particular record, then just implement that: when the data is updated, call scrollToRecord() on whatever record you think the user will expect to keep in view.

                Comment


                  #9
                  Thank you for the clarification of the grid behavior. That does indeed make sense.

                  The records currently in view could really be any of them, depending on what the user chooses to view and where that lies in the grid.

                  I'll revisit my attempts to use the scrollToRow() option in the callback to addData(). Thanks for your help.

                  Comment

                  Working...
                  X