Announcement

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

    TileGrid TestCase: Tile Recycling and Tile Hover Components

    Dear Isomorphic Team, I'm having trouble with the TileGrid-Component. My UI heavily relies on thsis component, so this is quite urgent and I hope, you can shed some light on a few things, see below.

    I've provide a test case for you. I'm working with the latest production build (5.0p) on GWT 2.6, testing on Chrome in Super Dev Mode.

    1. RecordContextClick on tiles throws an error
    - Related to http://forums.smartclient.com/showthread.php?t=31328&highlight=TileGrid
    - An error will be logged to the DevConsole on every RecordContextClick
    - On record context click, the respective tile will be selected and the selectionChangedHandler will fire. I have a use case where I want to show a context menu for a tile without the selectionChangedHandler to fire. Unfortunately it is not possible to cancel the recordContextClick-Event without suppressing the context menu at all. How can I accomplish this?
    Code:
    18:31:01.584:MDN3:WARN:Log:Call to Super for method: handleRightMouseDown failed on: [SimpleTile ID:isc_HelloWorld_1_0_tile_3]: couldn't find a superclass implementation of : SimpleTile.handleRightMouseDown	[a][c]SimpleTile.invokeSuper(clazz=>null, methodName=>"handleRightMouseDown", a=>undef, b=>undef, c=>undef, d=>undef, e=>undef, f=>undef, g=>undef, h=>undef, i=>undef, j=>undef, lastArg=>undef) @ Class.js:1556:47
    	[a][c]SimpleTile.Super(methodName=>"handleRightMouseDown", args=>[object Arguments], nativeArguments=>undef) @ Class.js:1404:21
    	[a]SimpleTile.handleRightMouseDown() @ TileGrid.js:1054:25
    	[c]EventHandler.bubbleEvent(target=>[SimpleTile ID:isc_HelloWorld_1_0_tile_3], eventType=>"rightMouseDown", eventInfo=>null, targetIsMasked=>false) @ EventHandler.js:5123:44
    	EventHandler.doHandleMouseDown(DOMevent=>[object MouseEvent], syntheticEvent=>undef) @ EventHandler.js:1810:28
    	EventHandler.observation() @ Class.js:2476:68
    	[c]EventHandler.handleMouseDown(DOMevent=>[object MouseEvent], syntheticEvent=>undef) @ EventHandler.js:1643:26
    	[c]EventHandler.dispatch(handler=>[c]EventHandler.handleMouseDown(), event=>[object MouseEvent]) @ EventHandler.js:6544:34
    	HTMLDocument.eval(event=>[object MouseEvent]) @ [no file]:3:127
    2. Tile Hover Components
    - Related to http://forums.smartclient.com/showthread.php?t=30898
    - Hover Components work for the TileGrid itself.
    - Tile Hover Components will not work when configured setTileProperties-API
    - Hover Components do work, when implemented by a child component of SimpleTile, e.g. a DynamicForm

    3. Tile Recycling
    - UseCase: I'd like to be able to define a tileContructor using the setTileContructor-API and use the updateTile-API to keep my CustomTiles up to date.
    - The new createTile-API and updateTile-API are not usable i.e. these two functions exist in the Java-API but NOT in the JavaScript-Modules (see TileGrid.js). Hence, overriding these methods is useless because they will never be called, right? So, what is meant by "If defined, this method will be called when a new tile is required." (from JavaDoc)? Should I use JSNI to define these methods on each TileGrid instance? I guess not. Could you please explain!
    - Tile recycling will only work reliably for the internally rendered DetailViewer that is showing tile record data. Have a look at TileGrid #2 in the TestCase.

    Here is the TestCase.

    Code:
    public class HelloWorld implements EntryPoint {
    
    	public static class CustomTileForm extends DynamicForm {
    
    		public CustomTileForm() {
    			super();
    			setBorder("1px solid black");
    			setHeight(HEIGHT);
    			setWidth(WIDTH);
    			setNumCols(1);
    			setColWidths("*");
    
    			setCanHover(true);
    			setHoverMoveWithMouse(true);
    			setShowHoverComponents(true);
    
    			FormItem idField = new StaticTextItem(TestRecord.ID);
    			idField.setShowTitle(false);
    			FormItem nameField = new StaticTextItem(TestRecord.NAME);
    			nameField.setShowTitle(false);
    			setFields(idField, nameField);
    		}
    
    		@Override
    		public Canvas getHoverComponent() {
    			return createDefaultHoverComponent("TileForm");
    		}
    	}
    
    	static class CustomTileGrid extends TileGrid {
    
    		public CustomTileGrid() {
    			super();
    			setDataSource(new DS());
    			setBackgroundColor("lightGray");
    			setBorder("1px solid black");
    			setWidth(300);
    			setHeight(300);
    			setTileHeight(HelloWorld.HEIGHT);
    			setTileWidth(HelloWorld.WIDTH);
    			setAutoFetchData(true);
    			addRecordContextClickHandler(new RecordContextClickHandler() {
    				private Menu createContextMenu() {
    					return new Menu();
    				}
    
    				@Override
    				public void onRecordContextClick(RecordContextClickEvent event) {
    					setContextMenu(createContextMenu());
    				}
    			});
    		}
    	}
    
    	public static class CustomTileWrapper extends SimpleTile {
    
    		private CustomTileForm editor;
    
    		public CustomTileWrapper() {
    			super();
    
    			setCanHover(true);
    			setShowHoverComponents(true);
    
    			addDrawHandler(new DrawHandler() {
    				@Override
    				public void onDraw(DrawEvent event) {
    					addChild(getOrCreateEditor());
    					updateContents(getTileRecord());
    				}
    			});
    		}
    
    		@Override
    		public Canvas getHoverComponent() {
    			return createDefaultHoverComponent("TileWrapper");
    		}
    
    		public CustomTileForm getOrCreateEditor() {
    			if (editor == null) {
    				editor = new CustomTileForm();
    			}
    			return editor;
    		}
    
    		public Record getTileRecord() {
    			return getAttributeAsRecord("record");
    		}
    
    		public void updateContents(Record record) {
    			getOrCreateEditor().editRecord(record);
    		}
    	}
    
    	static class DS extends DataSource {
    		public DS() {
    			setClientOnly(true);
    			DataSourceTextField id = new DataSourceTextField(TestRecord.ID);
    			id.setPrimaryKey(true);
    			DataSourceTextField name = new DataSourceTextField(TestRecord.NAME);
    			setFields(id, name);
    
    			int recCount = 1000;
    			Record[] data = new Record[recCount];
    			for (int i = 0; i < recCount; i++) {
    				data[i] = new TestRecord(String.valueOf(i), "Name_" + String.valueOf(i));
    			}
    			setCacheData(data);
    		}
    	}
    
    	public static class TestRecord extends Record {
    		public static final String ID = "id";
    		public static final String NAME = "name";
    
    		public TestRecord(String id, String name) {
    			setAttribute(ID, id);
    			setAttribute(NAME, name);
    		}
    	}
    
    	public interface TileMetaFactory extends BeanFactory.MetaFactory {
    		BeanFactory<CustomTileWrapper> getCustomTileWrapper();
    	}
    
    	static int HEIGHT = 120;
    	static int WIDTH = 120;
    
    	public static Canvas createDefaultHoverComponent(String contents) {
    		Canvas canvas = new Canvas();
    		canvas.setContents("Hover Generated by " + contents);
    		return canvas;
    	}
    
    	@Override
    	public void onModuleLoad() {
    
    		HLayout testLayout = new HLayout(10);
    
    		//use default approach with TileProperties-Config
    		final TileGrid tg1 = new CustomTileGrid() {
    			@Override
    			public Canvas getHoverComponent() {
    				return createDefaultHoverComponent("TileGrid#1");
    			}
    		};
    		tg1.setCanHover(true);
    		tg1.setShowHoverComponents(true);
    
    		Canvas c = new Canvas() {
    			@Override
    			public Canvas getHoverComponent() {
    				//WILL NOT WORK
    				return createDefaultHoverComponent("Plain Old SimpleTile");
    			}
    		};
    		c.setCanHover(true);
    		c.setShowHoverComponents(true);
    		tg1.setTileProperties(c);
    
    		//use factory-based approach
    		GWT.create(TileMetaFactory.class);
    		final TileGrid tg2 = new CustomTileGrid() {
    			@Override
    			public Canvas getHoverComponent() {
    				return createDefaultHoverComponent("TileGrid#2");
    			}
    
    			//WILL NEVER BE CALLED, BECAUSE NEITHER updateTile(..) NOR createTile(..) are defined on the TileGrid Instance. 
    			//However, HOW SHOULD THESE METHODS BE DEFINED?
    			@Override
    			public void updateTile(Record record, Integer tileIndex, Canvas reclaimedTile) {
    				CustomTileWrapper wrapper = (CustomTileWrapper) reclaimedTile;
    				wrapper.updateContents(record);
    			}
    		};
    		tg2.setCanHover(true);
    		tg2.setShowHoverComponents(true);
    		tg2.setTileConstructor(CustomTileWrapper.class.getName());
    
    		testLayout.addMember(tg1);
    		testLayout.addMember(tg2);
    		testLayout.draw();
    	}
    }

    It took me some hours to research these questions and elaborate on feasible workarounds, however I'm quite stuck at the moment. I really appreciate your suggestions.

    #2
    1. Already reported and we're working on a fix.

    2. Expected. If you implement a custom tile and you expect features provided by SimpleTile to work, you do need to subclass SimpleTile.

    3. updateTile and createTile exist in TileGrid.js and are documented SmartClient APIs. You seem to be looking at an outdated file, and may even be loading it at runtime. Once you've corrected this you should revisit all the issues you've been having.

    Comment


      #3
      First of all, thanks for the reply.
      1) ok

      2)I do subclass SimpleTile in the CustomTileWrapper class and it is sufficient for me that the child form is handling hover componets.

      3) I know, this is documented Smartclient AND SmartGWT Api. But how should that happen? I'm working on a fresh Smartgwt 5.0p Download. My module.gwt.xml is configured for Super Dev Mode, the index html is configured to use the smartclient debug sources. I have double checked that using Chrom Dev Tools, cleared browser cache etc. pp. This is what I see in the sources tab
      Code:
      File: TileGrid.js
      /*
       * Isomorphic SmartClient
       * Version v10.0p_2014-09-24 (2014-09-24)
       * Copyright(c) 1998 and beyond Isomorphic Software, Inc. All rights reserved.
       * "SmartClient" is a trademark of Isomorphic Software, Inc.
       *
       * licensing@smartclient.com
       *
       * http://smartclient.com/license
       */
      Again I've double checked the source file. I cannot find any functions named updateTile or createTile neither in TileGrid.js nor in TileLayout.js nor in the production sources. So what do you mean by 'outdated file'? I'm certainly working on the correct code base. To finally exclude any local misconfiguration I visited the smartclient ShowCase http://www.smartclient.com/smartgwt/showcase/#tiling_custom (SmartClient Version: v10.0p_2014-09-25/LGPL Development Only (built 2014-09-25)) and checked the sources using Chrome Tools: same Result. FInally I opend the opened DevConsole and tried the following:
      Code:
      isc.TileGrid.getInstanceProperty('updateTile')
      --> Evaluator: result of 'isc.TileGrid.getInstanceProperty('updateTile')' (1ms):
      undef
      
      isc.TileGrid.isMethodSupported('updateTile')
      --> Evaluator: result of 'isc.TileGrid.isMethodSupported('updateTile')' (1ms):
      false
      So, where have these functions gone? Please have a look at this again. Thank you.

      Comment


        #4
        We don't know how you are getting confused, but it's a certainty that updateTile is present in the .js files we ship with SmartGWT 5.0. The SmartGWT feature would not function without those APIs.

        Comment


          #5
          Dear Isomorphic Team, we finally got this to work using JSNI, however I'm pretty sure that this workaround does not reflect the API designers intentions. Nevertheless we are still interested in your feedback.

          To give a short summary: Our goal is to use the tileConstructor-API to create complex Tiles using a BeanFactory. This is pretty straight forward and well documented. Because our tile implementation uses quite complex rendering logic and datasource interactions we are very interested in the tile recycling mechanism that became part of the public SmartGwt API with release 5.0. As you may see from the previous discussion we had some trouble getting that to work, because the instance methods for tileCreate(...) and tileUpdate(...) do not seem to be available or reachable at runtime (reason:unknown). Taking this and the following API documentaion
          Code:
          /> @method tileGrid.updateTile()
          // If both this method and +link{tileGrid.createTile()} are defined and
          // +link{tileGrid.recycleTiles} is true, this method will be called when the
          // framework needs to recycle a tile to be used with a new record.  This
          // notification provides an opportunity to update any widget properties that
          // depend on the specifics of the record.
          ...led us to the idea to define one of the "missing" functions at initialization time of the TileGrid component.
          For those interested in our workaround, see the adapted testcase below. We are rendering a TileGrid with 1000 tiles where each tile is implemented as a DynamicForm backed by a SimpleTile. Scroll down the TileGrid to see that tiles are beeing recycled during scrolling. The number of currently rendered tiles (10) remains stable. If you deactivate tile recycling using
          Code:
          t.setRecycleTiles(false);
          see the devConsole you can see the number of created canvii rapidly growing during scrolling.

          Code:
          package com.mycompany.client;
          
          import com.google.gwt.core.client.EntryPoint;
          import com.google.gwt.core.client.GWT;
          import com.google.gwt.core.client.GWT.UncaughtExceptionHandler;
          import com.smartgwt.client.bean.BeanFactory;
          import com.smartgwt.client.data.DataSource;
          import com.smartgwt.client.data.Record;
          import com.smartgwt.client.data.fields.DataSourceTextField;
          import com.smartgwt.client.types.Overflow;
          import com.smartgwt.client.widgets.Canvas;
          import com.smartgwt.client.widgets.Label;
          import com.smartgwt.client.widgets.events.DrawEvent;
          import com.smartgwt.client.widgets.events.DrawHandler;
          import com.smartgwt.client.widgets.form.DynamicForm;
          import com.smartgwt.client.widgets.form.fields.FormItem;
          import com.smartgwt.client.widgets.form.fields.TextItem;
          import com.smartgwt.client.widgets.layout.VLayout;
          import com.smartgwt.client.widgets.menu.Menu;
          import com.smartgwt.client.widgets.tile.SimpleTile;
          import com.smartgwt.client.widgets.tile.TileGrid;
          import com.smartgwt.client.widgets.tile.events.RecordContextClickEvent;
          import com.smartgwt.client.widgets.tile.events.RecordContextClickHandler;
          import com.smartgwt.client.widgets.viewer.DetailViewerField;
          
          public class HelloWorld implements EntryPoint {
          
          	public static class CustomTileForm extends DynamicForm {
          
          		public CustomTileForm() {
          			super();
          			setBorder("1px solid black");
          			setHeight(HEIGHT);
          			setWidth(WIDTH);
          			setNumCols(1);
          			setColWidths("*");
          			FormItem idField = new TextItem(TestRecord.ID);
          			idField.setShowTitle(false);
          			FormItem nameField = new TextItem(TestRecord.NAME);
          			nameField.setShowTitle(false);
          			setFields(idField, nameField);
          		}
          	}
          
          	static class CustomTileGrid extends TileGrid {
          		public CustomTileGrid() {
          			super();
          			setDataSource(new DS());
          			setBackgroundColor("lightGray");
          			setBorder("1px solid black");
          			setWidth(300);
          			setHeight(300);
          			setTileHeight(HelloWorld.HEIGHT);
          			setTileWidth(HelloWorld.WIDTH);
          			setAutoFetchData(true);
          			addRecordContextClickHandler(new RecordContextClickHandler() {
          				private Menu createContextMenu() {
          					return new Menu();
          				}
          
          				@Override
          				public void onRecordContextClick(RecordContextClickEvent event) {
          					setContextMenu(createContextMenu());
          				}
          			});
          		}
          	}
          
          	public static class CustomTileWrapper extends SimpleTile {
          
          		private CustomTileForm editor;
          
          		public CustomTileWrapper() {
          			super();
          			addDrawHandler(new DrawHandler() {
          				@Override
          				public void onDraw(DrawEvent event) {
          					addChild(getOrCreateEditor());
          					updateContents(getTileRecord());
          				}
          			});
          		}
          
          		public CustomTileForm getOrCreateEditor() {
          			if (editor == null) {
          				editor = new CustomTileForm();
          			}
          			return editor;
          		}
          
          		public Record getTileRecord() {
          			return getAttributeAsRecord("record");
          		}
          
          		public void updateContents(Record record) {
          			getOrCreateEditor().setValues(record.toMap());
          		}
          	}
          
          	static class DS extends DataSource {
          		public DS() {
          			setClientOnly(true);
          			DataSourceTextField id = new DataSourceTextField(TestRecord.ID);
          			id.setPrimaryKey(true);
          			DataSourceTextField name = new DataSourceTextField(TestRecord.NAME);
          			setFields(id, name);
          
          			int recCount = 1000;
          			Record[] data = new Record[recCount];
          			for (int i = 0; i < recCount; i++) {
          				data[i] = new TestRecord(String.valueOf(i), "Name_" + String.valueOf(i));
          			}
          			setCacheData(data);
          		}
          	}
          
          	public static class TestRecord extends Record {
          		public static final String ID = "id";
          		public static final String NAME = "name";
          
          		public TestRecord(String id, String name) {
          			setAttribute(ID, id);
          			setAttribute(NAME, name);
          		}
          	}
          
          	public interface TileMetaFactory extends BeanFactory.MetaFactory {
          		BeanFactory<CustomTileWrapper> getCustomTileWrapper();
          	}
          
          	static int HEIGHT = 100;
          	static int WIDTH = 100;
          
          	@Override
          	public void onModuleLoad() {
          
          		GWT.setUncaughtExceptionHandler(new UncaughtExceptionHandler() {
          			@Override
          			public void onUncaughtException(final Throwable e) {
          				GWT.log("Exception occurred: ", e);
          			}
          		});
          
          		//use factory-based approach
          		GWT.create(TileMetaFactory.class);
          
          		VLayout layout = new VLayout();
          		layout.setOverflow(Overflow.VISIBLE);
          
          		final Label tileCacheSizeLbl = new Label();
          		tileCacheSizeLbl.setID("tileCacheSizeLbl");
          		tileCacheSizeLbl.setWidth100();
          
          		final Label lastUpdateTileIndexLbl = new Label();
          		lastUpdateTileIndexLbl.setWidth100();
          		lastUpdateTileIndexLbl.setID("lastUpdateTileIndexLbl");
          
          		final TileGrid t = new TileGrid() {
          
          			@Override
          			protected void onInit() {
          				super.onInit();
          				onInit_TileRecycling();
          			}
          
          			protected native void onInit_TileRecycling() /*-{
          
          		var self = this.@com.smartgwt.client.widgets.BaseWidget::getOrCreateJsObj()();
          
          		self.updateTile = $debox($entry(function(record, index, reclaimedTile) {
          			var jthis = this.__ref;
          			var jObj = reclaimedTile.__ref;
          			var jRec = @com.smartgwt.client.data.Record::getOrCreateRef(Lcom/google/gwt/core/client/JavaScriptObject;)(record);
          			jthis.@com.smartgwt.client.widgets.tile.TileGrid::updateTile(Lcom/smartgwt/client/data/Record;Ljava/lang/Integer;Lcom/smartgwt/client/widgets/Canvas;)(jRec, index, jObj);
          			$wnd.isc.Canvas.getById('lastUpdateTileIndexLbl').setContents(
          					'Last recycled Index: '.concat(index));
          			$wnd.isc.Canvas.getById('tileCacheSizeLbl').setContents(
          					'Current Tile Cache Size: '.concat(this.tiles.getLength()));
          		}));
          	}-*/;
          
          			@Override
          			public void updateTile(Record record, Integer tileIndex, Canvas reclaimedTile) {
          				CustomTileWrapper ctw = (CustomTileWrapper) reclaimedTile;
          				ctw.updateContents(record);
          			}
          		};
          
          		t.setFields(new DetailViewerField(TestRecord.NAME));
          		t.setDataSource(new DS());
          		t.setRecycleTiles(true);
          		t.setWidth(300);
          		t.setHeight(300);
          		t.setTileHeight(HelloWorld.HEIGHT);
          		t.setTileWidth(HelloWorld.WIDTH);
          		t.setAutoFetchData(true);
          		t.setTileConstructor(CustomTileWrapper.class.getName());
          		layout.addMember(t);
          		layout.addMember(tileCacheSizeLbl);
          		layout.addMember(lastUpdateTileIndexLbl);
          
          		layout.draw();
          	}
          }
          Last edited by hmueller; 26 Sep 2014, 03:08.

          Comment


            #6
            UPDATE:

            After upgrading to the latest nightly version 5.0p 15/01/15 it turns out that the complete createTile(...) and updateTile(...) APIs have been removed from TileGrid.

            The suspense continues ;-)
            Thread closed, I guess.

            Comment


              #7
              This capability is supported in SGWT through customizers. For example, http://www.smartclient.com/smartgwt/...ustomizer.html.

              Comment

              Working...
              X