Announcement

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

    Modal Editing with ListGrid and a CanvasItem

    Hello,

    We are using a ListGrid backed by a DS to display some data, which at a given point is edited by a user. All of the columns have custom editors which extend FormItem directly except one of them which extends CanvasItem.

    When we go into edit mode of a grid line, all editors are behaving as they should in regards to the click mask which is set up by the listgrid. Clicking outside of the grid disables the edit mode. However, once we touch the editor which extends from CanvasItem, this behavior is broken - the grid remains into edit mode when we click outside the grid.

    Is there any hint you can provide that could fix this behavior? Are we missing some code to make this working - I realize that the use of CanvasItem can complicate things.

    We use the CanvasItem to build a customized checkbox (provides 3-state functionality and alternate label orientation when used outside of grids). This checkbox is built by custom StatefulCanvas implementation which contains an instance of a Img widget.

    As far as I can tell, this behavior is caused by the fact that hovering over the 'check-box' is seen as a 'mouse out' event of the grid(row) which causes the click mask to be removed? When one hovers over the other (regular) grid cells this does not happen (no logging in dev console about mouse out).

    Here's some sample code to illustrate things.

    The checkbox implementation.
    Code:
    	private class ThreeStateImg extends StatefulCanvas {
    		private HLayout layout;
    		private Img img;
    		private Label label;
    		
    		public ThreeStateImg(final CanvasItem source, final MyCheckboxEditor editor) {
    The init handler in the editor which sets the components up.
    Code:
    public class MyCheckboxEditor extends CanvasItem {
    
    	public MyCheckboxEditor(boolean isReadOnly, boolean isThreeState, MyValueType theValueType) {
    		super();
    		//http://forums.smartclient.com/showthread.php?t=18110
    		this.setInitHandler(new FormItemInitHandler() {
    			
    			public void onInit(FormItem item) {
    				CanvasItem canvasItem = new CanvasItem(item.getJsObj());
    				// If a value is added on MyCheckboxItem, before the item was drawn,
                	// fetch this value and perform a setValue on the canvasItem.
                	// 'newValue' will be null if using a proper datasource (ok)
    				ThreeStateImg img = new ThreeStateImg(canvasItem, MyCheckboxEditor.this);
    				img.setCanFocus(true); // Need to set this to be able to get focus while tabbing
    				img.setCanHover(true);
    				img.setDrawState(MyCheckboxItem.getValueWhileNotCreated(item), false);
    				canvasItem.setCanvas(img);
    			}
    		});

    Many thanks for your insights.

    We are on SC_SNAPSHOT-2011-08-25/Pro Deployment (built 2011-08-25) and testing with FF 6.

    #2
    We'll take a look - but can you post the full source for the ThreeStateCheckbox class - it appears truncated in your post.

    Thanks
    Isomorphic Software

    Comment


      #3
      Thanks! Here's the full code of the ThreeStateImg implementation.

      Code:
      	private class ThreeStateImg extends StatefulCanvas {
      
      
      	private static final String UNDEF_URL = MyImageConstants.getURL(MyImageConstants.ICON_CHECKBOX_SQUARE_UNDEF, MyConstants.THREESTATE_CHECKBOX_IMG_SIZE);
      	private static final String FALSE_URL = MyImageConstants.getURL(MyImageConstants.ICON_CHECKBOX_SQUARE_FALSE, MyConstants.THREESTATE_CHECKBOX_IMG_SIZE);
      	private static final String TRUE_URL = MyImageConstants.getURL(MyImageConstants.ICON_CHECKBOX_SQUARE_TRUE, MyConstants.THREESTATE_CHECKBOX_IMG_SIZE);
      	
      
      		private HLayout layout;
      		private Img img;
      		private Label label;
      		
      		public ThreeStateImg(final CanvasItem source, final MyCheckboxEditor editor) {
      			layout = new HLayout();
      			img = new Img(UNDEF_URL, MyConstants.THREESTATE_CHECKBOX_IMG_SIZE, MyConstants.THREESTATE_CHECKBOX_IMG_SIZE);
      			
      			
      			//Check if we are in a grid: don't show a Label for the title
      			boolean isInGrid = false;
      			if (source.getContainerWidget() != null) {
      				//item.getContainerWidget() should be the Canvas ListGrid body
      				// so its parent should be the ListGrid
      				if (source.getContainerWidget() instanceof ListGrid) {
      					//We are in a ListGrid, do not show title
      					isInGrid = true;
      				} else {
      					Canvas parent = source.getContainerWidget().getParentElement();
      					if (parent != null && parent instanceof ListGrid) {
      						//We are in a ListGrid, do not show title
      						isInGrid = true;
      					}
      				}
      			}
      			
      			
      			if (isInGrid) {
      				label = null;
      				
      				source.setTitle("");
      				this.setWidth(1);
      				this.setHeight(1);
      				layout.setWidth100();
      				layout.setHeight100();
      				
      				this.setAlign(Alignment.CENTER);
      				img.setPadding(1); //Without it, you can see it moving between edit/non-edit mode in grid
      				//Also when setting this.setStyleName(MyConstants.MyCHECKBOX_STYLE) here, it doesn't align well
      				img.setAlign(Alignment.CENTER);
      				layout.setAlign(Alignment.CENTER);
      				
      				this.setValign(VerticalAlignment.CENTER);
      				img.setValign(VerticalAlignment.CENTER);
      				
      			} else {
      				//Form layout
      				
      				label = new Label(source.getTitle());	
      				label.setDisabled(editor.getDisabled());
      				label.setStyleName(MySkinStyles.FORMTITLE);
      				label.setWrap(false);
      				
      				//clear title and add space to align with other items
      				source.setTitle(" ");
      				//make sure the image and label are shown next to each other
      				layout.setHeight(1);
      				layout.setOverflow(Overflow.VISIBLE);
      				
      				this.setWidth(MyConstants.FORM_ITEM_WIDTH);
      				this.setHeight(MyConstants.FORM_ITEM_TEXT_HEIGHT);
      				this.setStyleName(MyConstants.MyCHECKBOX_STYLE);
      			}
      			
      			this.setDisabled(editor.getDisabled());
      			this.setShowDisabled(true);
      			this.setShowDisabledIcon(true);
      			this.addClickHandler(new ClickHandler() {
      				
      				public void onClick(ClickEvent event) {
      					if(ThreeStateImg.this.isVisible() && source != null && source.isDrawn() != null) {
      						editor.selectNext(source);
      					} else {
      						ThreeStateImg.this.hide();
      					}
      				}
      			});
      			this.addVisibilityChangedHandler(new VisibilityChangedHandler() {
      				
      				public void onVisibilityChanged(VisibilityChangedEvent event) {
      					if (source != null  && source.isDrawn() == null) {
      						ThreeStateImg.this.hide();
      					}
      				}
      			});
      			
      			if (label != null) {
      				layout.setMembers(img, label);
      			} else {
      				layout.setMembers(img);
      			}
      			this.addChild(layout);
      		}
      		
      		public void setDrawState(Object dataValue, boolean redraw) {
      			if (dataValue != null && dataValue instanceof JavaScriptObject) {
      				ThreeState state = getThreeState(MySimpleTypeUtil.getValue((JavaScriptObject) dataValue, getValueType()));
      				switch(state) {
      				case FALSE:
      					img.setSrc(FALSE_URL);
      					break;
      				case TRUE:
      					img.setSrc(TRUE_URL);
      					break;
      				case UNDEFINED:
      					img.setSrc(UNDEF_URL);
      					break;
      				}
      				if (redraw) {
      					this.show();
      				}
      			} else {
      				if (redraw) {
      					this.hide();
      				}
      			}
      		}
      		
      	}
      Do you need the code for MyCheckboxEditor too?

      Comment


        #4
        Ah - yes we didn't realize it was missing.
        Basically, from your description nothing is jumping out to explain this behavior so we need to reproduce it on our end.

        What we really need is a simple standalone test case we can actually run that demonstrates the problem. If you could put such a test case together for us that would allow us to get to the bottom of the problem very quickly. The process of creating a simple test case based on your usage may also reveal exactly what the problem is.

        Comment


          #5
          I was able to create a standalone test case reproducing the issue we face.
          Double clicking on the grid will start editing. Once the CanvasItem is clicked, the only way to stop editing (and discarding the changes) is to hit Esc or to click outside the window.

          Code:
          public class StandaloneTestcase implements EntryPoint {
          
          	private static final String FIELD1 = "textfield";
          	private static final String FIELD2 = "checkboxfield";
          	private static final String FIELD3 = "textfield2";
          	
          	public void onModuleLoad() {
          		
          		// Create field 1 with a textItem as editor
          		ListGridField field1 = new ListGridField();
          		field1.setCanEdit(true);
          		field1.setName(FIELD1);
          		field1.setEditorType(new TextItem());
          		
          		// Create field 2 with a the CustomCheckboxEditor as editor
          		ListGridField field2 = new ListGridField();
          		field2.setCanEdit(true);
          		field2.setName(FIELD2);
          		field2.setEditorType(new CustomCheckboxEditor(false));
          		field2.setCellFormatter(new CellFormatter() {
          
          			public String format(Object value, ListGridRecord record, int rowNum, int colNum) {
          				String result = "<null>";
          				if (value != null && value instanceof JavaScriptObject) {
          					String theEnumID = JSOHelper.getAttribute((JavaScriptObject) value, FIELD_VALUE_FIELD);
          					if (theEnumID != null) {
          						if (String.valueOf(CustomCheckboxEditor.THREESTATE_TRUE).equals(theEnumID)) {
          							result = Boolean.toString(true);
          						} else if (String.valueOf(CustomCheckboxEditor.THREESTATE_FALSE).equals(theEnumID)) {
          							result = Boolean.toString(false);
          						} else {
          							result = "Undefined";
          						}
          					}
          				}
          				return result;
          
          			}
          		});
          		
          		// Create field 3 with a textItem as editor
          		ListGridField field3 = new ListGridField();
          		field3.setCanEdit(true);
          		field3.setName(FIELD3);
          		field3.setEditorType(new TextItem());
          		
          		// Create the grid and set the fields
          		final CustomListGrid grid = new CustomListGrid();
          		grid.setWidth(400);
          		grid.setHeight(500);
          		grid.setFields(field1, field2, field3);
          		grid.setData(getData(5));
          		
          		// Add the grid to the CustomWindow
          		final CustomWindow window = new CustomWindow();
          		window.addItem(grid);
          		
          		// Create our button to show the window on click
          		Button b = new Button();
          		b.setTitle("click me");
          		b.addClickHandler(new ClickHandler() {
          			
          			public void onClick(ClickEvent event) {
          				window.show();
          			}
          		});
          		
          		// Draw our button
          		b.draw();
          	}
          	
          	private ListGridRecord[] getData(int number) {
          		ListGridRecord[] array = new ListGridRecord[number];
          		for(int i=0;i<number;i++) {
          			array[i] = new ListGridRecord();
          			array[i].setAttribute(FIELD1, "1_value" + i);
          			array[i].setAttribute(FIELD2, createThreeStateJSObject(CustomCheckboxEditor.THREESTATE_TRUE));
          			array[i].setAttribute(FIELD3, "2_value" + i);
          		}
          		return array;
          	}
          	
          	private class CustomListGrid extends ListGrid {
          		public CustomListGrid() {
          			setCanEdit(true);
          			setSaveLocally(true);
          			//DoubleClick starts edit mode
          			setEditEvent(ListGridEditEvent.NONE);
          			addCellDoubleClickHandler(new CellDoubleClickHandler() {
          
          				public void onCellDoubleClick(CellDoubleClickEvent event) {
          					if (!getCanEdit()) return;
          					startEditing(event.getRowNum(), event.getColNum(), false);
          				}
          			});
          			setRowEndEditAction(RowEndEditAction.NEXT);
          			setListEndEditAction(RowEndEditAction.DONE);
          			setModalEditing(true);
          			setEditByCell(false);
          			setAutoSaveEdits(true);
          			setShowRowNumbers(false);
          			setShowGridSummary(false);
          		}
          	}
          	
          	private class CustomWindow extends Window {
          		
          		public CustomWindow() {
          			setWidth(600);
          			setHeight(600);
          			setTitle("CustomWindow");
          			setShowMinimizeButton(false);
          			setAutoCenter(true);
          			setCanDragResize(true);
          			setCanDragReposition(true);
          			addCloseClickHandler(new CloseClickHandler() {
          				public void onCloseClick(CloseClickEvent event) {
          					CustomWindow.this.hide();
          				}
          			});
          		}
          	}
          
          	private static final String FIELD_VALUE_FIELD = "valueField";
          
          	private enum ThreeState {
          		TRUE, FALSE, UNDEFINED;
          	}
          
          	private class CustomCheckboxEditor extends CanvasItem {
          
          		public static final int THREESTATE_TRUE = 1;
          		public static final int THREESTATE_FALSE = 0;
          		public static final int THREESTATE_UNDEFINED = 2;
          
          		private static final String TRUE_URL = "http://footabc.net/hmenu/icons/checkbox-1.gif";
          		private static final String FALSE_URL = "http://tutorials.mennovanslooten.nl/2-inputs/gfx/checkbox.gif";
          		private static final String UNDEF_URL = "http://deskload.com/images/checkbox_off.gif";
          
          		public CustomCheckboxEditor(boolean isReadOnly) {
          			super();
          			this.setDisabled(isReadOnly);
          			this.setShouldSaveValue(true);
          			if (isReadOnly) {
          				this.setTabIndex(-1);
          				this.setGlobalTabIndex(-1);
          			}
          			this.addShowValueHandler(new ShowValueHandler() {
          
          				public void onShowValue(ShowValueEvent event) {
          					CanvasItem item = event.getItem();
          					ThreeStateImg view = (ThreeStateImg) item.getCanvas();
          					view.setDrawState(event.getDataValue());
          				}
          			});
          			this.setInitHandler(new FormItemInitHandler() {
          
          				public void onInit(FormItem item) {
          					CanvasItem canvasItem = new CanvasItem(item.getJsObj());
          					ThreeStateImg img = new ThreeStateImg(canvasItem, CustomCheckboxEditor.this);
          					canvasItem.setCanvas(img);
          				}
          			});
          		}
          
          		private void selectNext(CanvasItem source) {
          			Object oldValue = source.getValue();
          			if (oldValue != null && oldValue instanceof JavaScriptObject) {
          				if (oldValue instanceof JavaScriptObject) {
          
          					ThreeState currentState = getThreeState((JavaScriptObject) oldValue);
          					ThreeState newState = null;
          					switch (currentState) {
          					case FALSE:
          						newState = ThreeState.UNDEFINED;
          						break;
          					case TRUE:
          						newState = ThreeState.FALSE;
          						break;
          					case UNDEFINED:
          						newState = ThreeState.TRUE;
          						break;
          					}
          					storeNewValue(source, oldValue, newState);
          				}
          			} else {
          				if (oldValue != null) {
          					System.out.println("WARNING: Something other than a JavaScriptObject was given in the CheckBoxEditor!");
          				}
          			}
          		}
          
          		private void storeNewValue(CanvasItem source, Object oldValue, ThreeState state) {
          			// perform some logic to check if anything is changed...
          			doSetValue(source, createNewValue(state));
          		}
          
          		private native void doSetValue(FormItem item, JavaScriptObject value) /*-{
          			var self = item.@com.smartgwt.client.core.DataClass::getJsObj()();
          			if (self.setValue) {
          				self.setValue(value);
          			} else {
          				self.value = value;
          			}
          		}-*/;
          
          		public JavaScriptObject createNewValue(ThreeState state) {
          			int theEnumID = -1;
          			switch (state) {
          			case FALSE:
          				theEnumID = THREESTATE_FALSE;
          				break;
          			case TRUE:
          				theEnumID = THREESTATE_TRUE;
          				break;
          			case UNDEFINED:
          				theEnumID = THREESTATE_UNDEFINED;
          				break;
          			}
          			if (theEnumID != -1) {
          				return createThreeStateJSObject(theEnumID);
          			}
          			return null;
          		}
          
          		public ThreeState getThreeState(JavaScriptObject value) {
          			Integer theEnumID = JSOHelper.getAttributeAsInt(value, FIELD_VALUE_FIELD);
          			switch (theEnumID) {
          			case THREESTATE_TRUE:
          				return ThreeState.TRUE;
          			case THREESTATE_FALSE:
          				return ThreeState.FALSE;
          			case THREESTATE_UNDEFINED:
          				return ThreeState.UNDEFINED;
          			}
          			return null;
          		}
          
          		private class ThreeStateImg extends StatefulCanvas {
          
          			private HLayout layout;
          			private Img img;
          			private Label label;
          
          			public ThreeStateImg(final CanvasItem source, final CustomCheckboxEditor editor) {
          				setCanFocus(true); // Need to set this to be able to get focus while tabbing
          				
          				layout = new HLayout();
          				img = new Img(UNDEF_URL, 24, 24);
          				
          				this.setWidth(1);
          				this.setHeight(1);
          				layout.setWidth100();
          				layout.setHeight100();
          				
          				this.setAlign(Alignment.CENTER);
          				img.setPadding(1); //Without it, you can see it moving between edit/non-edit mode in grid
          				img.setAlign(Alignment.CENTER);
          				layout.setAlign(Alignment.CENTER);
          				
          				this.setValign(VerticalAlignment.CENTER);
          				img.setValign(VerticalAlignment.CENTER);
          				
          				setAlign(Alignment.CENTER);
          				setDisabled(editor.getDisabled());
          				addClickHandler(new com.smartgwt.client.widgets.events.ClickHandler() {
          
          					public void onClick(com.smartgwt.client.widgets.events.ClickEvent event) {
          						if (ThreeStateImg.this.isVisible() && source != null && source.isDrawn() != null) {
          							editor.selectNext(source);
          						} else {
          							ThreeStateImg.this.hide();
          						}
          					}
          				});
          				
          				addVisibilityChangedHandler(new VisibilityChangedHandler() {
          
          					public void onVisibilityChanged(VisibilityChangedEvent event) {
          						if (source != null && source.isDrawn() == null) {
          							ThreeStateImg.this.hide();
          						}
          					}
          				});
          				
          				if (label != null) {
          					layout.setMembers(img, label);
          				} else {
          					layout.setMembers(img);
          				}
          				this.addChild(layout);
          			}
          
          			public void setDrawState(Object dataValue) {
          				if (dataValue != null && dataValue instanceof JavaScriptObject) {
          					ThreeState state = getThreeState((JavaScriptObject) dataValue);
          					switch (state) {
          					case FALSE:
          						img.setSrc(FALSE_URL);
          						break;
          					case TRUE:
          						img.setSrc(TRUE_URL);
          						break;
          					case UNDEFINED:
          						img.setSrc(UNDEF_URL);
          						break;
          					}
          					show();
          				} else {
          					hide();
          				}
          			}
          		}
          	}
          
          	public static JavaScriptObject createThreeStateJSObject(Integer theEnumID) {
          		JavaScriptObject jsObject = JSOHelper.createObject();
          		if (theEnumID == null)
          			theEnumID = Integer.MIN_VALUE;
          		JSOHelper.setAttribute(jsObject, FIELD_VALUE_FIELD, theEnumID);
          		// add some other fields/identifiers/....
          		return jsObject;
          	}
          
          }

          Comment


            #6
            Thanks for test case. We were able to reproduce and fix the issue, and the fix will appear in the next nightly build.

            Comment


              #7
              Hi!

              Thx for the fix.
              The 'stops going out of edit mode' issue is solved.
              I do encounter a different issue now:

              I've debugged the editItemsDrawingNotification method with two testcases:

              1. Go into edit mode and click outside the listgrid:
              Code:
              isc_ListGrid__editItemsDrawingNotification([CanvasItem ID:isc_CanvasItem_0 name:checkboxfield],null,[GridBody ID:isc_StandaloneTestcase_CustomListGrid_0_body])
              [CanvasItem ID:isc_CanvasItem_0 name:checkboxfield] is placed on canvas
              [CanvasItem ID:isc_CanvasItem_0 name:checkboxfield] drawn method called
              Code:
              isc_ListGrid__editItemsDrawingNotification([CanvasItem ID:isc_CanvasItem_0 name:checkboxfield],[GridBody ID:isc_StandaloneTestcase_CustomListGrid_0_body])
              [CanvasItem ID:isc_CanvasItem_0 name:checkboxfield] cleared method called
              2. Go into edit mode, click the canvasItem and click outside the listgrid:
              Code:
              isc_ListGrid__editItemsDrawingNotification([CanvasItem ID:isc_CanvasItem_0 name:checkboxfield],null,[GridBody ID:isc_StandaloneTestcase_CustomListGrid_0_body])
              [CanvasItem ID:isc_CanvasItem_0 name:checkboxfield] is placed on canvas
              [CanvasItem ID:isc_CanvasItem_0 name:checkboxfield] drawn method called
              Code:
              isc_ListGrid__editItemsDrawingNotification(null,null,[GridBody ID:isc_StandaloneTestcase_CustomListGrid_0_body])
              [CanvasItem ID:isc_CanvasItem_0 name:checkboxfield] redrawn method called
              In this second case, the canvas (created in the CanvasItem) is not hidden. Because of this, the canvas is not moved on another row edit.

              What are your insights about the above?
              Thx!

              Comment


                #8
                We don't have the code that's producing these messages, but we did notice that your test case was seemingly using the same Canvas each time, which is not correct. You want to create a fresh instance in your initHandler.

                Also, just FYI, this whole approach is rather overkill. Something like a StaticTextItem with an <img> as content would handle this use case equally well without having to create a separate Canvas.

                Comment


                  #9
                  Hi,

                  Yes, that's correct: I'm using the same repro case.
                  Below is a small extract from this repro case with 2 extra debug statements:
                  Code:
                  this.addShowValueHandler(new ShowValueHandler() {
                  
                  	public void onShowValue(ShowValueEvent event) {
                  		System.out.println("ShowValueHandler.onShowValue called for FormItem: " + event.getItem());
                  		CanvasItem item = event.getItem();
                  		ThreeStateImg view = (ThreeStateImg) item.getCanvas();
                  		view.setDrawState(event.getDataValue());
                  	}
                  });
                  this.setInitHandler(new FormItemInitHandler() {
                  
                  	public void onInit(FormItem item) {
                  		CanvasItem canvasItem = new CanvasItem(item.getJsObj());
                  		System.out.println("FormItemInitHandler.onInit called for FormItem: " + canvasItem);
                  		ThreeStateImg img = new ThreeStateImg(canvasItem, CustomCheckboxEditor.this);
                  		canvasItem.setCanvas(img);
                  	}
                  });
                  When I doubleclick several rows to start editing, I receive the following output in my console:

                  Code:
                  FormItemInitHandler.onInit called for FormItem: com.smartgwt.client.widgets.form.fields.CanvasItem@389329d0
                  ShowValueHandler.onShowValue called for FormItem: com.smartgwt.client.widgets.form.fields.CanvasItem@560c48bd
                  ShowValueHandler.onShowValue called for FormItem: com.smartgwt.client.widgets.form.fields.CanvasItem@f84ebc5
                  ShowValueHandler.onShowValue called for FormItem: com.smartgwt.client.widgets.form.fields.CanvasItem@138d326d
                  ShowValueHandler.onShowValue called for FormItem: com.smartgwt.client.widgets.form.fields.CanvasItem@23a4b72a
                  ..
                  It looks like the FormItemInitHandler is only called just once.

                  Thx!

                  Comment


                    #10
                    That's correct, you should expect that the InitHandler is called once when the FormItem is created. It will not be called again unless the FormItem needs to be re-created.

                    Comment


                      #11
                      but we did notice that your test case was seemingly using the same Canvas each time, which is not correct. You want to create a fresh instance in your initHandler
                      Since the initHandler is only called just once, how can I create a fresh instance each time?

                      Thx!

                      Comment


                        #12
                        Sorry if we were unclear. There does not appear to be an issue with invalidly re-using a Canvas in your code. If you're still having an actual behavioral problem, please let us know, with details.

                        Comment


                          #13
                          Hi,

                          Yes, I'm still facing the following behavioral problem:
                          (Repro case with the stand alone testcase provided below)
                          1. click on the 'click me' button to open the window
                          2. double click on a row to go into edit mode
                          3. click on the CanvasItem (checkboxField) to change the value
                          4. click outside the edit row form
                          -> result: the CanvasItem is still visible

                          Thx!

                          Code:
                          public class StandaloneTestcase implements EntryPoint {
                          
                          	private static final String FIELD1 = "textfield";
                          	private static final String FIELD2 = "checkboxfield";
                          	private static final String FIELD3 = "textfield2";
                          
                          	public void onModuleLoad() {
                          
                          		// Create field 1 with a textItem as editor
                          		ListGridField field1 = new ListGridField();
                          		field1.setCanEdit(true);
                          		field1.setName(FIELD1);
                          		field1.setEditorType(new TextItem());
                          
                          		// Create field 2 with a the CustomCheckboxEditor as editor
                          		ListGridField field2 = new ListGridField();
                          		field2.setCanEdit(true);
                          		field2.setName(FIELD2);
                          		field2.setEditorType(new CustomCheckboxEditor(false));
                          		field2.setCellFormatter(new CellFormatter() {
                          
                          			public String format(Object value, ListGridRecord record, int rowNum, int colNum) {
                          				String result = "<null>";
                          				if (value != null && value instanceof JavaScriptObject) {
                          					String theEnumID = JSOHelper.getAttribute((JavaScriptObject) value, FIELD_VALUE_FIELD);
                          					if (theEnumID != null) {
                          						if (String.valueOf(CustomCheckboxEditor.THREESTATE_TRUE).equals(theEnumID)) {
                          							result = Boolean.toString(true);
                          						} else if (String.valueOf(CustomCheckboxEditor.THREESTATE_FALSE).equals(theEnumID)) {
                          							result = Boolean.toString(false);
                          						} else {
                          							result = "Undefined";
                          						}
                          					}
                          				}
                          				return result;
                          
                          			}
                          		});
                          
                          		// Create field 3 with a textItem as editor
                          		ListGridField field3 = new ListGridField();
                          		field3.setCanEdit(true);
                          		field3.setName(FIELD3);
                          		field3.setEditorType(new TextItem());
                          
                          		// Create the grid and set the fields
                          		final CustomListGrid grid = new CustomListGrid();
                          		grid.setWidth(400);
                          		grid.setHeight(500);
                          		grid.setFields(field1, field2, field3);
                          		grid.setData(getData(5));
                          
                          		// Add the grid to the CustomWindow
                          		final CustomWindow window = new CustomWindow();
                          		window.addItem(grid);
                          
                          		// Create our button to show the window on click
                          		Button b = new Button();
                          		b.setTitle("click me");
                          		b.addClickHandler(new ClickHandler() {
                          
                          			public void onClick(ClickEvent event) {
                          				window.show();
                          			}
                          		});
                          
                          		// Draw our button
                          		b.draw();
                          	}
                          
                          	private ListGridRecord[] getData(int number) {
                          		ListGridRecord[] array = new ListGridRecord[number];
                          		for (int i = 0; i < number; i++) {
                          			array[i] = new ListGridRecord();
                          			array[i].setAttribute(FIELD1, "1_value" + i);
                          			array[i].setAttribute(FIELD2, createThreeStateJSObject(CustomCheckboxEditor.THREESTATE_TRUE));
                          			array[i].setAttribute(FIELD3, "2_value" + i);
                          		}
                          		return array;
                          	}
                          
                          	private class CustomListGrid extends ListGrid {
                          
                          		public CustomListGrid() {
                          			setCanEdit(true);
                          			setSaveLocally(true);
                          			// DoubleClick starts edit mode
                          			setEditEvent(ListGridEditEvent.NONE);
                          			addCellDoubleClickHandler(new CellDoubleClickHandler() {
                          
                          				public void onCellDoubleClick(CellDoubleClickEvent event) {
                          					if (!getCanEdit())
                          						return;
                          					startEditing(event.getRowNum(), event.getColNum(), false);
                          				}
                          			});
                          			setRowEndEditAction(RowEndEditAction.NEXT);
                          			setListEndEditAction(RowEndEditAction.DONE);
                          			setModalEditing(true);
                          			setEditByCell(false);
                          			setAutoSaveEdits(true);
                          			setShowRowNumbers(false);
                          			setShowGridSummary(false);
                          		}
                          	}
                          
                          	private class CustomWindow extends Window {
                          
                          		public CustomWindow() {
                          			setWidth(600);
                          			setHeight(600);
                          			setTitle("CustomWindow");
                          			setShowMinimizeButton(false);
                          			setAutoCenter(true);
                          			setCanDragResize(true);
                          			setCanDragReposition(true);
                          			addCloseClickHandler(new CloseClickHandler() {
                          
                          				public void onCloseClick(CloseClickEvent event) {
                          					CustomWindow.this.hide();
                          				}
                          			});
                          		}
                          	}
                          
                          	private static final String FIELD_VALUE_FIELD = "valueField";
                          
                          	private enum ThreeState {
                          		TRUE, FALSE, UNDEFINED;
                          	}
                          
                          	private class CustomCheckboxEditor extends CanvasItem {
                          
                          		public static final int THREESTATE_TRUE = 1;
                          		public static final int THREESTATE_FALSE = 0;
                          		public static final int THREESTATE_UNDEFINED = 2;
                          
                          		private static final String TRUE_URL = "http://footabc.net/hmenu/icons/checkbox-1.gif";
                          		private static final String FALSE_URL = "http://tutorials.mennovanslooten.nl/2-inputs/gfx/checkbox.gif";
                          		private static final String UNDEF_URL = "http://deskload.com/images/checkbox_off.gif";
                          
                          		public CustomCheckboxEditor(boolean isReadOnly) {
                          			super();
                          			this.setDisabled(isReadOnly);
                          			this.setShouldSaveValue(true);
                          			if (isReadOnly) {
                          				this.setTabIndex(-1);
                          				this.setGlobalTabIndex(-1);
                          			}
                          			this.addShowValueHandler(new ShowValueHandler() {
                          
                          				public void onShowValue(ShowValueEvent event) {
                          					System.out.println("ShowValueHandler.onShowValue called for FormItem: " + event.getItem());
                          					CanvasItem item = event.getItem();
                          					ThreeStateImg view = (ThreeStateImg) item.getCanvas();
                          					view.setDrawState(event.getDataValue());
                          				}
                          			});
                          			this.setInitHandler(new FormItemInitHandler() {
                          
                          				public void onInit(FormItem item) {
                          					CanvasItem canvasItem = new CanvasItem(item.getJsObj());
                          					System.out.println("FormItemInitHandler.onInit called for FormItem: " + canvasItem);
                          					ThreeStateImg img = new ThreeStateImg(canvasItem, CustomCheckboxEditor.this);
                          					canvasItem.setCanvas(img);
                          				}
                          			});
                          		}
                          
                          		private ThreeState selectNext(CanvasItem source) {
                          			ThreeState newState = null;
                          			Object oldValue = source.getValue();
                          			if (oldValue != null && oldValue instanceof JavaScriptObject) {
                          				if (oldValue instanceof JavaScriptObject) {
                          
                          					ThreeState currentState = getThreeState((JavaScriptObject) oldValue);
                          					switch (currentState) {
                          					case FALSE:
                          						newState = ThreeState.UNDEFINED;
                          						break;
                          					case TRUE:
                          						newState = ThreeState.FALSE;
                          						break;
                          					case UNDEFINED:
                          						newState = ThreeState.TRUE;
                          						break;
                          					}
                          					source.storeValue(createNewValue(newState));
                          					return newState;
                          				}
                          			} else {
                          				if (oldValue != null) {
                          					System.out.println("WARNING: Something other than a JavaScriptObject was given in the CheckBoxEditor!");
                          				}
                          			}
                          			return newState;
                          		}
                          
                          		public JavaScriptObject createNewValue(ThreeState state) {
                          			int theEnumID = -1;
                          			switch (state) {
                          			case FALSE:
                          				theEnumID = THREESTATE_FALSE;
                          				break;
                          			case TRUE:
                          				theEnumID = THREESTATE_TRUE;
                          				break;
                          			case UNDEFINED:
                          				theEnumID = THREESTATE_UNDEFINED;
                          				break;
                          			}
                          			if (theEnumID != -1) {
                          				return createThreeStateJSObject(theEnumID);
                          			}
                          			return null;
                          		}
                          
                          		public ThreeState getThreeState(JavaScriptObject value) {
                          			Integer theEnumID = JSOHelper.getAttributeAsInt(value, FIELD_VALUE_FIELD);
                          			switch (theEnumID) {
                          			case THREESTATE_TRUE:
                          				return ThreeState.TRUE;
                          			case THREESTATE_FALSE:
                          				return ThreeState.FALSE;
                          			case THREESTATE_UNDEFINED:
                          				return ThreeState.UNDEFINED;
                          			}
                          			return null;
                          		}
                          
                          		private class ThreeStateImg extends StatefulCanvas {
                          
                          			private HLayout layout;
                          			private Img img;
                          
                          			public ThreeStateImg(final CanvasItem source, final CustomCheckboxEditor editor) {
                          				setCanFocus(true); // Need to set this to be able to get focus while tabbing
                          
                          				layout = new HLayout();
                          				img = new Img(UNDEF_URL, 24, 24);
                          
                          				this.setWidth(1);
                          				this.setHeight(1);
                          				layout.setWidth100();
                          				layout.setHeight100();
                          
                          				this.setAlign(Alignment.CENTER);
                          				img.setPadding(1); // Without it, you can see it moving between edit/non-edit mode
                          									// in grid
                          				img.setAlign(Alignment.CENTER);
                          				layout.setAlign(Alignment.CENTER);
                          
                          				this.setValign(VerticalAlignment.CENTER);
                          				img.setValign(VerticalAlignment.CENTER);
                          
                          				setAlign(Alignment.CENTER);
                          				setDisabled(editor.getDisabled());
                          				addClickHandler(new com.smartgwt.client.widgets.events.ClickHandler() {
                          
                          					public void onClick(com.smartgwt.client.widgets.events.ClickEvent event) {
                          						if (ThreeStateImg.this.isVisible() && source != null && source.isDrawn() != null) {
                          							ThreeState newState = editor.selectNext(source);
                          							setDrawState(newState);
                          							ThreeStateImg.this.redraw();
                          						} else {
                          							ThreeStateImg.this.hide();
                          						}
                          					}
                          				});
                          
                          				layout.setMembers(img);
                          				this.addChild(layout);
                          			}
                          
                          			public void setDrawState(Object dataValue) {
                          				if (dataValue != null) {
                          					ThreeState state = null;
                          					if (dataValue instanceof JavaScriptObject) {
                          						state = getThreeState((JavaScriptObject) dataValue);
                          					} else if (dataValue instanceof ThreeState) {
                          						state = (ThreeState) dataValue;
                          					}
                          					if (state != null) {
                          						switch (state) {
                          						case FALSE:
                          							img.setSrc(FALSE_URL);
                          							break;
                          						case TRUE:
                          							img.setSrc(TRUE_URL);
                          							break;
                          						case UNDEFINED:
                          							img.setSrc(UNDEF_URL);
                          							break;
                          						}
                          						show();
                          						return;
                          					}
                          				}
                          				hide();
                          			}
                          		}
                          	}
                          
                          	public static JavaScriptObject createThreeStateJSObject(Integer theEnumID) {
                          		JavaScriptObject jsObject = JSOHelper.createObject();
                          		if (theEnumID == null)
                          			theEnumID = Integer.MIN_VALUE;
                          		JSOHelper.setAttribute(jsObject, FIELD_VALUE_FIELD, theEnumID);
                          		// add some other fields/identifiers/....
                          		return jsObject;
                          	}
                          
                          }

                          Comment


                            #14
                            Your Canvas should have been auto-hidden by the CanvasItem when the editor was dismissed, and there was an extremely subtle bug preventing this which has now been fixed. Fix will be present in the next nightly build.

                            Comment


                              #15
                              Hi!

                              I've retested the repro case (of 16th Nov 2011) on SmartGWT/3.x/Pro/2011-11-23 and the issue still occurs.

                              Is the fix of this subtle bug already available in the 2011-11-23 build?

                              Thx!

                              Comment

                              Working...
                              X