Announcement

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

    listgrid with custom widget performance

    Hi,
    Our listgrid displays columns that contain image and text and we need to refresh the data every 10 seconds (see EPollingTimer). There is no such ListGridFieldType, and we need to use createRecordComponent to create the custom widget.
    When we don't use custom component in "createRecordComponent", listgrid's performance is ok. I can scroll up and down the table. But when we use createRecordComponent and we create a canvas that contains an image and a label, the table stops responding (EImageWithText). Can you explain what is wrong and is it possible to improve the performance. We use SmartGWT 2.5, Firefox and IE browsers.

    Thanks,
    Olga.

    Code:
     
    package gwt.client;
    
    import com.google.gwt.core.client.EntryPoint;
    import com.google.gwt.user.client.Timer;
    import com.smartgwt.client.data.DSCallback;
    import com.smartgwt.client.data.DSRequest;
    import com.smartgwt.client.data.DSResponse;
    import com.smartgwt.client.data.DataSource;
    import com.smartgwt.client.data.Record;
    import com.smartgwt.client.data.ResultSet;
    import com.smartgwt.client.rpc.RPCManager;
    import com.smartgwt.client.types.Alignment;
    import com.smartgwt.client.types.FetchMode;
    import com.smartgwt.client.types.ImageStyle;
    import com.smartgwt.client.types.ListGridFieldType;
    import com.smartgwt.client.types.Overflow;
    import com.smartgwt.client.types.PromptStyle;
    import com.smartgwt.client.types.TextMatchStyle;
    import com.smartgwt.client.types.VerticalAlignment;
    import com.smartgwt.client.util.Page;
    import com.smartgwt.client.widgets.Canvas;
    import com.smartgwt.client.widgets.Img;
    import com.smartgwt.client.widgets.Label;
    import com.smartgwt.client.widgets.grid.CellFormatter;
    import com.smartgwt.client.widgets.grid.ListGrid;
    import com.smartgwt.client.widgets.grid.ListGridField;
    import com.smartgwt.client.widgets.grid.ListGridRecord;
    import com.smartgwt.client.widgets.layout.HLayout;
    
    public class EDebugTable implements EntryPoint
    {
    	ListGrid listGrid = new EListGrid();
    	
    	@Override
    	public void onModuleLoad()
    	{
    		Page.setAppImgDir(EFileUtils.getImagesDir());
    		
    		listGrid.setShowRecordComponents(true);
    		listGrid.setShowRecordComponentsByCell(true);
    		
            RPCManager.setPromptStyle(PromptStyle.CURSOR);
            DataSource dataSource = ItemSupplyXmlDS.getInstance();
            
            ListGridField rowNum = new ListGridField("itemNum", "Item No.");
            rowNum.setWidth(65);
            rowNum.setCellFormatter(new CellFormatter()
    		{
    			
    			@Override
    			public String format(Object aValue, ListGridRecord aRecord, int aRowNum,
    					int aColNum)
    			{
    				return aRowNum +"";
    			}
    		});
    
            ListGridField itemName = new ListGridField("itemName", 100);
            ListGridField sku = new ListGridField("SKU", 100);
            ListGridField description = new ListGridField("description", 150);
            ListGridField category = new ListGridField("category", 100);
            ListGridField units = new ListGridField("units", 100);
    
            ListGridField unitCost = new ListGridField("unitCost", 100);
            unitCost.setType(ListGridFieldType.FLOAT);
    
            ListGridField inStock = new ListGridField("inStock", 100);
            inStock.setType(ListGridFieldType.BOOLEAN);
            
            listGrid.setWidth100();
            listGrid.setHeight100();
            
            listGrid.setAutoFetchData(true);
            listGrid.setDataSource(dataSource);
            
            listGrid.setFields(rowNum, itemName, sku, description, category, units, unitCost);
           
            EPollingTimer timer = new EPollingTimer();
            timer.scheduleRepeating(10000);
           
            listGrid.draw();
    	}
    	
    	class EListGrid extends ListGrid
    	{
    		@Override
    		protected Canvas createRecordComponent(ListGridRecord aRecord,
    				Integer aColNum)
    		{
                EImageWithText imageWithText = new EImageWithText();
                imageWithText.mText.setContents("Text!!!!");
                imageWithText.mImage.setSrc("alarm-blue_10.gif");
                return imageWithText;
    		}
    	}
    	
    	private static final int REFRESH_ROW_OFFSET = 15;
    
    	class EImageWithText extends HLayout
    	{
    		private Label mText = new Label();
    		private Img mImage = new Img();
    		
    		public EImageWithText()
    		{
    			setLayoutAlign(VerticalAlignment.CENTER);
    			setAlign(Alignment.LEFT);
    			// text alignment inside label 
    			mText.setAlign(Alignment.LEFT);
    			mText.setWidth100();
    			mText.setHeight100();
    			
    			mImage.setImageType(ImageStyle.NORMAL);
    			mImage.setAutoHeight();
    			mImage.setAutoWidth();
    			
    			addMember(mImage);
    			addMember(mText);
    			setOverflow(Overflow.HIDDEN);
    			setMargin(0);
    			
    			setHeight(20);
    			setBackgroundColor("pink");
    		}
    	}
    	
    	class EPollingTimer extends Timer
    	{
    		@Override
    		public void run() 
    		{
    			final Integer row[] = listGrid.getVisibleRows();
    			final DSRequest request = new DSRequest();
    			request.setStartRow(Math.max(0, row[0] - REFRESH_ROW_OFFSET));
    			request.setEndRow(row[1] + REFRESH_ROW_OFFSET);
    			request.setSortBy(listGrid.getSort());
    			request.setTextMatchStyle(TextMatchStyle.SUBSTRING);
    
    			final DataSource ds = listGrid.getDataSource();
    			ds.setShowPrompt(false);
    			ds.fetchData(listGrid.getCriteria(), new DSCallback() {
    
    				@Override
    				public void execute(DSResponse response, Object rawData, DSRequest request)
    				{
    					final ResultSet resultset = new ResultSet();
    					resultset.setDataSource(ds);
    					resultset.setCriteria(listGrid.getCriteria());
    					resultset.setFetchMode(FetchMode.PAGED);
    					resultset.setInitialLength(response.getTotalRows());
    
    					Record[] responseData = response.getData();
    					Record[] initialData = new Record[response.getEndRow()+1];
    					for (int i = 0; i <= response.getEndRow(); i++) {
    					    if (i < response.getStartRow()) 
    				    	{
    				    		initialData[i] = null;
    				    	}
    					    else 
    				    	{
    					    	initialData[i] = responseData[i-response.getStartRow()];
    				    	}
    					}
    					resultset.setInitialData(initialData);
    					listGrid.setData(resultset);
    				}
    			}, request);
    		}
    	}
    }

    #2
    Without having reviewed your code - there is no need to use createRecordComponent to enable refresh. Use a CellFormatter to output the HTML you want, and use refreshCell() to cause the formatter to be run again.

    Comment


      #3
      Hi Isomorphic,
      The purpose of using createRecordComponent is not to refresh. We used it in order to create a custom control. Is it simple to create controls using CellFormatter? e.g. a control that contains an image and text next to it (like in the sample code).
      Thank you

      Comment


        #4
        Yes, if you don't need anything more than an image and text and you don't need separate click events for the image and text, this is quite trivial. It can also be done via valueIcons.

        Comment


          #5
          Hi,
          Is there a sample that does that?
          Is the performance of such a solution better than with createRecordComponent?

          Comment


            #6
            Auto refresh on ListGrid with Record Component Causes Memory Issues

            Smart GWT: v8.3p_2012-11-25/PowerEdition Deployment (built 2012-11-25)

            Firefox: 18.0.2

            Piggy-backing off the previous post since it's somewhat related.

            I have an app with a ListGrid. In that ListGrid I have a field that contains **5** ImgButtons. When clicked, each button performs a particular action using data from that particular record (see attached pic).

            I need to refresh this listgrid so have setup a timer for every 5 seconds.

            My refresh code (cobbled together with examples and suggestions on the forums to use updateCaches)
            Code:
            new Timer() {
            	@Override
            	public void run() {
            		_gridDevices.getGrid().getDataSource().fetchData(null, new DSCallback() {
            
            			public void execute(DSResponse response, Object rawData, DSRequest request) {
            				request.setOperationType(DSOperationType.UPDATE);
            
            				String selectedState = _gridDevices.getGrid().getSelectedState();
            
            				_gridDevices.getGrid().getDataSource().updateCaches(response, request);
            
            				_gridDevices.getGrid().setSelectedState(selectedState);
            			}
            
            		});
            	}
            }.scheduleRepeating(5000);
            Looking at the SmartClient Developer console, I can see that on every refresh 5 controls are being destroyed:

            Code:
            01:04:16.984:DSQ8:INFO:destroys:isc_HLayout_393:destroy() (5 children) 
            01:04:16.986:DSQ8:INFO:destroys:isc_ImgButton_1955:destroy()
            01:04:16.990:DSQ8:INFO:destroys:isc_ImgButton_1956:destroy()
            01:04:16.995:DSQ8:INFO:destroys:isc_ImgButton_1958:destroy()
            01:04:16.999:DSQ8:INFO:destroys:isc_ImgButton_1957:destroy()
            01:04:17.003:DSQ8:INFO:destroys:isc_ImgButton_1959:destroy()
            The Canvas count stays the same, however, over time the draw, clear, destroy count all reach into the 1,000s.

            After 600 clears are hit, I see this warning in my Eclipse GWT development tab:

            Code:
            [ERROR] [App] - 00:52:11.632:TMR3:WARN:Canvas:Auto allocation of tab-indices has reached native browser ceiling - tab-order cannot be guaranteed for widgets on this page.
            In looking through these forums, previous Responses said that happens when too many controls/canvases are being drawn in the browser.

            Not only that, but on every refresh, my CPU (2.7 Intel Core i7, 16 GB RAM) goes up to 40% and my RAM for Firefox has slowly been increasing.

            My application also comes to an unbearable level of user interaction after a while even with a small set of data.

            It appears the code to destroy these controls either has a memory leak, or the Browser is keeping something around (caching?).

            When I disable the timer, my app runs fine and I never get the warning above so it's definitely timer refresh. If I remove the createRecordComponent code and my 5 buttons everything runs fine again.

            I switched my first image column already to value icons since it doesn't need click events, but these columns do.

            Few questions:
            1) Is this a known bug?

            2) Is there any way using my current grid refresh or new update code to tell Smart GWT to ignore refreshing these components and/or deleting them? They never change after the record is created.

            3) I changed one of my columns to use a ValueMap (since it doesn't need any click action). Is this more, less, or the same efficiency when updating a ListGrid as using a RecordComponent? I liked the way my record Component canvas looked better, but if it's more efficient to use ValueMap icons in a column than create a Canvas, I'm all for it!

            4) I stumbled upon setRecordComponentPoolingMode and setRecordComponentPoolingMode halfway through this post.

            <---- UPDATE ----->

            Setting setRecordComponentPoolingMode == RECYCLE seems to have FIXED the issue. No more destroy being called and no more performance hit/lag.

            If you need to draw sophisticated Canvases in a listgrid and auto-refresh the listgrid DEFINITELY look into PoolingMode.

            I'm posting this for anyone's future reference, but Isomorphic, if you could chime in about #1, #2, and #3 for my own knowledge and others on the forum that would be great!

            Thanks!
            Attached Files

            Comment


              #7
              1) No. Your Canvas count / destroy logs show that the grid is managing the components as expected. Your result of getting rid of the leak through RECYCLE mode suggests that something is holding onto to the destroyed components, but it's unclear whether it's framework code or app code. A runnable test case would be definitive.

              2) Yes. There's the RECYCLE mode you already discovered; another approach would be to avoid updateCaches (which basically tells the grid there's an entirely new record) and instead fetch the existing record from the ListGrid's ResultSet and update it in place with setAttribute(), using refreshCell/Row to cause new data values to appear. This avoids re-construction of recordComponents.

              3) You mention using ImgButton because you need click events, but what we recommend is just using a field of type "icon" plus a CellClick handler. At that point, there's no rollover effect, but otherwise it's similar to an ImgButton (and the rollover effect can be re-created with some extra work).
              Last edited by Isomorphic; 6 Feb 2013, 08:30.

              Comment


                #8
                Originally posted by Isomorphic View Post
                3) You mention using ImgButton because you need click events, but what we recommend is just using a field of type "icon" plus a CellClick handler. At that point, there's no rollover effect, but otherwise it's similar to an ImgButton (and the rollover effect can be re-created with some extra work).
                I'm trying to avoid having a visual column (cell borders and a separate column header) for each of my 5 "buttons".

                Would I still be able to do this with a "ICON" field?

                Thanks as always for your help ISOMORPHIC! Looking forward to the next big release!

                Comment


                  #9
                  If you want them all under one column header, then no, an icon field won't do that. You'd need to write out a series of <img> tags with native event handlers that call your logic.

                  Comment


                    #10
                    Originally posted by Isomorphic View Post
                    If you want them all under one column header, then no, an icon field won't do that. You'd need to write out a series of <img> tags with native event handlers that call your logic.
                    First, thanks as always for your answers Isomorphic. They help save a lot of time.

                    As a somewhat tangent, do you mean using like a HTMLFlow or HTMLPane, then setting the content to be raw HTML with <img> tags and say javascript hooks into my Smart GWT functions using JSNI? Or something else that I completely missed?

                    Comment


                      #11
                      We mean using a CellFormatter, which can return arbitrary HTML to be placed inside the grid cell.

                      Comment


                        #12
                        Originally posted by Isomorphic View Post
                        We mean using a CellFormatter, which can return arbitrary HTML to be placed inside the grid cell.
                        Ah ok...and those <img> tags could contain javascript that will handle the individual click event on each html element and then call either Java functions via JSNI pattern or other javascript functions included in the page.

                        I like the use of the royal we... =)

                        Comment

                        Working...
                        X