Announcement

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

  • CanvasItem value management

    Hello,

    Could you please explain how dynamic form value management for form items works ? I tried going through the documentation but could not find a clear explanation how this works.

    I've implemented a custom CanvasItem based form item and I'm trying to get the CanvasItem behave correctly in my dynamic form. I managed to get the canvasitem delegate the disabled state correctly to my custom canvas by overriding the "public void setDisabled(Boolean disabled)" method on CanvasItem.

    I'm having trouble getting the value from my (datasource bound) dynamic form to be delegated to my custom canvas though. I've tried overriding the "public Object getValue()" and "public void setValue(String value)" methods on my CanvasItem but it seems I'm not getting any calls to these methods at all. Could you please explain the pattern that should be used here to

    a) get the value from dynamic form's datasource to be displayed on my custom canvas(item) and
    b) deliver the edited value from my custom canvas(item) back to the datasource when the dynamic form submits ?

    I'm using the latest smartgwt 2.2 and gwt 2.0.3 on mac.

    Thanks in advance,
    Marko

  • #2
    Anyone ? Is this even possible with the current set of APIs available ?

    Comment


    • #3
      It should work. I modified the showcase example, http://www.smartclient.com/smartgwt/showcase/#layout_form_databinding and it works without problem for me.

      Code:
      public class MyItem extends CanvasItem {
      	public MyItem(String name) {
      		setName(name);
      		final DynamicForm f = new DynamicForm();
      		TextItem s = new TextItem("theNewItem");
      		s.addChangedHandler(new ChangedHandler() {
      			@Override
      			public void onChanged(ChangedEvent event) {
      				setValue(event.getValue().toString());
      			}
      		});
      		f.setItems(s);
      		setCanvas(f);
      	}
      
      	@Override
      	public void setValue(String value) {
      		super.setValue(value);
      	}
      	
      	@Override
      	public Object getValue() {
      		return super.getValue();
      	}
      };

      Comment


      • #4
        Thanks for your reply!

        I have trouble undestanding the example code you sent. Is it a requirement to have a DynamicForm as canvas item's canvas for the values to be passed correctly ?

        The code I'm trying is more like

        Code:
        public class TestCanvasItem extends CanvasItem {
        
            public TestCanvasItem(String name) {
                setName(name);
                Canvas myCustomCanvas = new Canvas();
                setCanvas(myCustomCanvas);
                setShouldSaveValue(true);
            }
        
            @Override
            public void setValue(String value) {
                SC.say("setValue called: " + value);
                super.setValue(value);
            }
        
            @Override
            public Object getValue() {
                SC.say("getValue called, returning " + super.getValue());
                return super.getValue();
            }
        
        }
        I have a custom canvas (currently a rich text editor) as the custom item's canvas. This canvas item is used in a DynamicForm which is bound to a datasource. I am sure the form itself works 100% because I have other fields (TextItems etc) in the form which work ok. When I use this TestCanvasItem with the form I never get any calls to getValue or setValue methods which I then could use to pass the values to myCustomCanvas. I don't really understand how your example MyItem could work in a DynamicForm which is bound to a datasource ? Any ideas ?

        br,
        Marko

        Comment


        • #5
          To follow up on my own post just to add that everything works if I _manually_ call the setValue before saving the form.

          Code:
          ...
              myTestCanvasItem.setValue(((MyCustomRTECanvas)myTestCanvasItem.getCanvas()).getEditorValue());
              myForm.saveData();
          ...
          And similarly when fetching the data:

          Code:
          ...
              myForm.fetchData(criteria, new DSCallback() {
                      public void execute(DSResponse response, Object rawData, DSRequest request) {
                          ((MyCustomRTECanvas)myTestCanvasItem.getCanvas()).setEditorValue(response.getData()[0].getAttribute("editor"));
                          ...
                      }
              });
          ...
          But surely there must be a way to do this automatically somehow on datasource bound forms ?
          Last edited by markok; 12th Jun 2010, 02:39.

          Comment


          • #6
            I have the exact same problem. In fact, I flat out don't understand how SmartGWT does it's form submission. Consider the following example:



            The application:

            Code:
            public class TomcatGWT implements EntryPoint
            {
            
            public TomcatGWT()
            {
            
            	  DataSource dataSource= DataSource.get("order");
            	  
            	  HLayout layout= new HLayout();
            	  layout.setWidth(800);
            
            
                     DynamicForm form = new DynamicForm(){
            		  
            		  @Override
            		  public Object getValue(String fieldName)
            		  {
            			  Log.debug("Form.getValue(" + fieldName + ") called.");
            			  return super.getValue(fieldName);
            		  }
            		  
            		  @Override 
            		  public String getValueAsString(String fieldName)
            		  {
            			  Log.debug("Form.getValueAsString(" + fieldName + ") called.");
            			  return super.getValueAsString(fieldName);
            		  }
            		  
            		  @Override 
            		  public Map getValues(){
            			  Log.debug("Form.getValues() called.");
            			  return super.getValues();
            		  }
            		  
            		  @Override
            		  public Record getValuesAsRecord(){
            			  Log.debug("Form.getValuesAsRecord() called.");
            			  return super.getValuesAsRecord();
            		  }
            		  
            		  @Override
            		  public Boolean hasErrors()
            		  {
            			  Log.debug("Form.hasErrors() called.");
            			  return super.hasErrors();
            		  }
            		  
            		  @Override
            		  public Boolean hasFieldErrors(String fieldName)
            		  {
            			  Log.debug("Form.hasFieldErrors(" + fieldName + ") called.");
            			  return super.hasFieldErrors(fieldName);
            		  }
            	  };
            	  form.setDataSource(dataSource);
            
                      TextItem normalTextItem = new TextItem("type"){
            		  
            		@Override
            		public Object getValue(){
            			Log.debug("TextItem.getValue() called.");
            			return super.getValue();
            		}
            	  };
            	  normalTextItem.setTitle("Normal Text.");
            	  
            	  CustomTextItem textItem = new CustomTextItem("title");
            	  textItem.setTitle("Title");
            	  textItem.setShouldSaveValue(true);
            	  textItem.setRequired(true);
            	  
            	  SubmitItem submit = new SubmitItem();
            	  submit.setTitle("Submit");
            	  form.setFields(textItem, submit);
            	  
                      layout.addMember(form);
            
                     layout.draw();
            }
            
            }

            The custom CanvasItem, again, really basic, Proof of Concept type stuff:

            Code:
            public class CustomTextItem extends CanvasItem {
            
            
                    TextItem textItem;
            	
            	public CustomTextItem(String name)
            	{
            		super(name);
            		
            		DynamicForm form = new DynamicForm();
            		 textItem = new TextItem();
            		
            		form.setFields(textItem);
            		
            		this.setCanvas(form);
            	}
            	
            	@Override
            	public Object getValue()
            	{
            		Log.debug("GetValue() called ");
            		
            		return textItem.getValue();
            	}
            	
            	@Override
            	public void setValue(String value)
            	{
            		Log.debug("Set value() called");
            		
            		textItem.setValue(value);
            	}
                     
                    @Override
            	public Boolean validate(){
            		Log.debug("Validate() called");
            		return super.validate();
            	}
            
            
            }

            Now, when clicking the Submit button, the form never submits because CustomTextItem has been set as required and for some reason, SmartGWT always sees the CustomTextItem as empty. The interesting part is that the NormalTextItem works, but in NEITHER CASE DOES FormItem.getValue() or Form.getValue(fieldName) EVER GET CALLED. Nor do any of the other methods on the DynamicForm that I added debug statements too.

            The only thing I can think of at this point is that the DynamicForm is doing some javascript magic under the hood that allows it to retrieve the entered values from the formitems which isn't exposed through the Java API. And that's basically the root of the problem with CanvasItem - you can override getValue() all you want, it never actually get invoked; so unless you manually invoke it and then add the value to the Record underlying the form, it will never get submitted. It also means that you cannot do validation against the CanvasItem because while you can "fake" adding the data to the Record in the way I just described, you cannot "fool" the validation - it will always fail on a CanvasItem, no matter how you implement validate() or getValue().

            Someone from Isomorphic needs to take a look at this - I'm at the end of my rope.

            Incidentally, while you're at it - can you explain to me the following inconsistencies in the API?

            Record has the following methods (inherited from the DataClass):

            Code:
            Record.setAttribute(String propertyName, String[] values)
            Record.setAttribute(String propertyName, int[] values)

            but DynamicForm does not have matching methods for these; i.e. there are no:
            Code:
            DynamicForm.setValue(String fieldName, String[] values)
            DymamicForm.setValue(String fieldName, int[] values)
            You also cannot get directly at the Record underlying a Form; you only get a COPY of that record; i.e.:

            Code:
            Record record = form.getValuesAsRecord;
            
            //do some stuff to the record, for instance add a multivalue array
            
            String[] emailArray= new String[2];
            
            emailArray[1]=email1@acmecorp.com;
            emailArray[2]=email2@acmecorp.com;
            
            record.setAttribute(myMultiValueStringField, stringArray);
            
            form.editRecord(record);
            
            form.saveData();
            However, the call to form.editRecord() puts the form into OperationType=update; so you ALSO need to hack the form.getOperationType() method; for instance:

            Code:
            //check whether to call update or add prior to saving
            Record record = this.getFormRecord();
            			
            			if(record.getAttributeAsInt("ID")!= null)
            			{
            				this.mainForm.setSaveOperationType(DSOperationType.UPDATE);
            			}
            			else
            			{
            				this.mainForm.setSaveOperationType(DSOperationType.ADD);
            			}
            			
            			this.mainForm.saveData();

            All this JUST to submit a multi-value property? In fact, if you look at the posts of the other people trying to use CanvasItem, most of them are doing this precise requirement: they're adding a ListGrid to a Form because they need to handle a multi-value property; something like a list of emails; for instance. That's a very common, basic requirement but the way SmartGWT wants you to handle this is by defining two different domain objects (one for your main record, one for the list of email addresses) and then use the foreign-key magic in the datasource to tie the two together. In other words; you now need to set up DataSource for every single multi-value property that you may have and, if you're using DMI, as I am, that's a HUGE amount of work.

            I really feel there's times where SmartGWT is trying to be a little bit too smart, no pun intended. Some more basic functionality and more control over the atomic behavior would be better than all this magic-out-of-the-box stuff that never quite fits a real-world scenario and you can't actually use in a real application; i.e. something real, as opposed to idealized situations from the showcase.

            Comment


            • #7
              If you're using the latest, you'll notice the advice in the docs for CanvasItem to call setValue() rather than anything you've attempted with getValue() et al overrides. It's true that int[] and String[] convenience methods should be added, however, note the setValue() variant that takes a JavaScript Object - you can use this for now via creating a JSArray of int or String.

              Comment


              • #8
                Ok, so that works for submitting data; i.e. calling super.setValue(). I think it's terrible design, personally - you need to now add ChangedHandlers all over the place to ensure super.setValue() gets called (not to mention that ChangedHandlers and things like EditorValueFormatters don't work together). Frankly, override getValue() and actually have the form call getValue() on each FormItem during saveData() would be much better, but whatever: it works.

                What about the other side of the binding equation? How do you actually populate a CanvasItem with data from a dataSource?

                What I mean is this:

                CanvasItem implementation (basically a CanvasItem wrapper around a TextItem):

                Code:
                public class CustomTextItem extends CanvasItem {
                	
                	TextItem textItem;
                	
                	public CustomTextItem(String name)
                	{
                		super(name);
                		
                		DynamicForm form = new DynamicForm();
                		
                		textItem = new TextItem();
                		textItem.setShowTitle(false);
                		
                		textItem.addChangedHandler(new ChangedHandler()
                		{
                
                			@Override
                			public void onChanged(ChangedEvent event) {
                				CustomTextItem.super.setValue(event.getValue().toString());
                				
                			}
                			
                		});
                		
                		form.setFields(textItem);
                		
                		this.setCanvas(form);
                	
                		
                	}
                	
                	@Override
                	public void setValue(String value)
                	{
                		GWT.log("setValue called: " + value);
                		
                		textItem.setValue(value);
                		
                		super.setValue(value);
                		
                	}
                
                }
                Databound form:

                Code:
                public class BuiltInDS implements EntryPoint {
                	private Layout layout;
                	private DynamicForm form;
                
                
                    public  void onModuleLoad () {
                	DataSource dataSource= ClientOnlyDataSource.getInstance();
                		
                		layout= new HLayout();
                		layout.setWidth(800);
                		
                		form = new DynamicForm();
                TextItem textField = new TextItem("userId");
                		CustomTextItem customTextField =new CustomTextItem("userId");
                		
                		form.setDataSource(dataSource);
                		form.setFields(textField, customTextField);
                		
                		ListGrid listGrid = new ListGrid();
                		listGrid.addSelectionChangedHandler(new SelectionChangedHandler(){
                
                			@Override
                			public void onSelectionChanged(SelectionEvent event) {
                				form.editRecord(event.getRecord());
                				
                			}
                			
                			
                		});
                		
                		listGrid.setDataSource(dataSource);
                		listGrid.fetchData();
                		
                		layout.addMember(listGrid);
                		layout.addMember(form);
                		
                		
                		layout.draw();
                		
                		
                		
                    }
                }

                When you now select a record in the listGrid, the textField has data, but the customTextField does not - again, because apparently, DynamicForm.editRecord(Record record) never actually calls setValue() on any of the FormItems that are part of the form, but instead injects values under the hood through some proprietary javascript solution. Correct?

                So what you're telling me is that inside the SelectionChangeHandler of the ListGrid; I now need to put this, right?

                Code:
                @Override
                			public void onSelectionChanged(SelectionEvent event) {
                				form.editRecord(event.getRecord());
                
                                          customTextField.setValue(event.getRecord().getAttribute("userId"));
                
                
                				
                			}
                Or, alternatively:

                Code:
                form = new DynamicForm(){
                			
                			@Override
                			public void editRecord(Record record)
                			{
                				customTextField.setValue(record.getAttribute("userId");
                				super.editRecord(record);
                
                			}
                			
                		};
                Both are terrible solutions; they go against every principle of black-boxing and reusable design. In the first solution, the ListGrid needs to know about the specific structure of the Form and what fields it has. In the second solution, the form has custom logic that deals with a specific FormItem. Basically, you now need to implement all this extra logic to make the CanvasItem work OUTSIDE the Canvasitem implementation itself.
                Last edited by SiccoNaets; 7th Jul 2010, 12:42.

                Comment


                • #9
                  Central change handling (dynamicForm.addItemChangedHandler()) is among many reasons why items must signal the form of value changes rather than have the form call getValue().

                  FormItem.setValue() is called when the form is populated with values and an override here is one way for a CanvasItem to be notified of calls like dynamicForm.editRecord(). However, it's not set up as a SmartGWT override point yet. The reason it hasn't been prioritized is that the most common reason to use a CanvasItem is for a complex external editor dealing with singular or multiple subobjects, like this use case, where there is already a need for some special case code.

                  It's not clear why you've headed down this path. If you want a custom TextItem, you can subclass TextItem - is there a reason you want to use CanvasItem here?

                  As an aside, it would be nice if you could be a little less caustic with your remarks. You're throwing around phrases like "terrible design", but mostly, you have a few misconceptions about how the design actually works.

                  Comment


                  • #10
                    I apologize if my comments gave offense; it's nothing personal, that's just the frustration talking.

                    The CustomTextItem was a hugely simplified example to try to figure out what's going on. Of course, this isn't something I would actually use.

                    I think perhaps I should show you the actual screens and widgets I'm working on, and I can try and show you why I think the current relationship between DynamicForm and FormItems isn't a good design - it's certainly caused me no end of headaches.

                    Here is a record detail screen from my application:

                    http://yfrog.com/05didorderdetailp

                    It contains several custom implementations of CanvasItem; I've numbered them in the graphic. All of them are fully functional at this point; but I'm not entirely happy with how I had to implement the underlying logic

                    1: NumberIncrementItem: this is a pretty simple CanvasItem implementation based on a textfield that ensures that the data entered is numeric. A plus and minus button allow you to increment or decrease the number that was entered.

                    2: EmailSelector: Basically a ListGrid that takes an array of Strings, where each String is supposed to be an email address. Adding a non-email address generates a validation error

                    3: DIDSelector: This is, by far, the most complex of the CanvasItems I've built. In fact, the actual implementation you're seeing here is what I call a ComplexDIDSelector. It allows users to add DIDs (a DID is basically a phone extension) to an order.

                    However, DIDSelectors come in different flavors. Here is an other example:

                    http://yfrog.com/jvdidtransferp

                    These are two implementations of the basic DIDSelector. It allows users to make a subselection of DIDs from the master DIDOrder and add them to the DIDs for a specific child-carrier order. The buttons in the middle are a "TransferStack", a configurable set of buttons that moves records back and forth between two listgrids.

                    Now, the ComplexDIDSelector I showed you in the first screen is actually an extension of the basic DIDSelector. It maintains the basic functionality; but adds a toolbar with two buttons (again, configurable) to the two. Click those buttons opens two sliding windows (with a nice little animation effect) which allow users two different ways of adding DIDs to the list. Either by searching from the pool of existing DIDs, like so:

                    http://yfrog.com/6bdidsearchp

                    Or by adding new DIDs to the pool and then automatically adding those DIDs to the order you were working on, like so:

                    http://yfrog.com/j7didwizardp

                    Now, why am I having problems with the CanvasItem value management for this widget?

                    First of all, DIDs can actually be added to a DIDSelector in a number of different ways, as you can see from the graphic:

                    1) They can be added through a wizard like interface which allows you create whole new ranges of DIDs and add them to the listgrid all at once.
                    2) They can be dragged and dropped, either from the did pool in a different window or from another did selector on the same screen.
                    3) They can be transfered by clicking a button on a two different types of TransferStacks (OneWayTransferStack and TwoWayTransferStack)
                    4) They can be added by double-clicking a record; either in the DID pool or in a different DID selector on the same screen.

                    They can also be removed through a number of different ways:
                    1) They can be deleted by clicking the delete graphic in the ListGrid
                    2) They can be dragged out again
                    3) They can be transferred out by clicking a button on one of the transferstacks


                    Now, if I really wanted to make the DIDSelector work the way the SmartGWT design demands it; I would have to add changeHandlers for all those different data-entry methods and each time ensure that CanvasItem.super.setValue() gets called, or, alternatively, DynamicForm.steValue(). Also, because neither FormItem nor DynamicForm right now does not take a int or String arrays as an argument; I'd have to construct and deconstruct JSArrays each time to wrap the ids of the DIDsin the ListGrid.

                    The problem with that approach is that there are a LOT of different components that can add data to the ListGrid. I've already determine that I can't just add handlers to the listgrid itself because not every form of data entry is detected by the listgrid. So I'd have to add it to the widgets that initiate the data-entry in the first place (the transferstack buttons, for instance). The problem THERE is that not all of them have a reference to the CanvasItem OR to the Form and it's not practical to throw those references around.

                    So instead, I did this:

                    Code:
                    public abstract class AbstractDIDOrderRecordView extends Tab
                    {
                    	protected Record record;
                    	
                    	protected DataSource dataSource;
                    	protected DataSource carrierOrderDS;
                    	protected DataSource accountDS;
                    	protected DataSource csaDS;
                    	protected DataSource userDS;
                    	
                    	protected final static int RELATED_CARRIER_ORDER_LIST_INDEX = 1;
                    	protected final static int CONTACT_EMAIL_SELECTOR_INDEX=2;
                    	protected final static int DID_SELECTOR_INDEX=1;
                    	
                    	protected HStack layout;
                    	protected final VStack leftColumn;
                    	protected final VStack rightColumn;
                    	protected final HStack carrierControls;
                    	protected final DynamicForm mainForm;
                    	protected final DynamicForm buttonForm;
                    	protected final DynamicForm emailForm;
                    	protected final StaticTextItem id;
                    	protected final TextItem didOrderTitle;
                    	protected final TextAreaItem notes;
                    	protected final StaticTextItem type;
                    	
                    	protected final SelectItem status;
                    	protected final DateItem requestedDate;
                    	protected final DateItem closedDate;
                    	protected final ComboBoxItem account;
                    	protected final ComboBoxItem csa;
                    	protected final TextItem rejectedReason;
                    	protected final StaticTextItem createdBy;
                    	protected final SelectItem assignedTo;
                    	protected final SpacerItem spacer;
                    	protected final Label relatedLabel;
                    	protected final ShortCarrierOrderListGrid relatedCarrierOrders;
                    	protected final EmailSelector emailSelector;
                    	protected final ComplexDIDSelectorView didSelector;
                    	protected final ButtonItem saveButton;
                    	protected final ButtonItem cancelButton;
                    	protected final ImgButton createCarrierOrderButton;
                    	
                    	public AbstractDIDOrderRecordView(HandlerManager eventBus, TabSet tabSet, Record record)
                    	{
                    		this(eventBus, tabSet);
                    		this.record = record;
                    		
                    	}
                    	
                    	public AbstractDIDOrderRecordView(HandlerManager eventBus, TabSet tabSet)
                    	{
                    		super();
                    	
                    		dataSource = DataSource.get("didOrder");
                    		carrierOrderDS = DataSource.get("carrierOrder");
                    		accountDS = DataSource.get("account");
                    		csaDS = DataSource.get("csa");
                    		userDS = DataSource.get("user");
                    		
                    		
                    		this.setCanClose(true);
                    		
                    		layout= new HStack(10);
                    		leftColumn = new VStack();
                    		leftColumn.setAlign(Alignment.RIGHT);
                    		leftColumn.setAlign(VerticalAlignment.TOP);
                    		rightColumn = new VStack();
                    		carrierControls = new HStack();
                    		carrierControls.setHeight(25);
                    		carrierControls.setAlign(VerticalAlignment.BOTTOM);
                    		
                    		
                    		mainForm = new DynamicForm();
                    		mainForm.setDataSource(dataSource);
                    		mainForm.setWrapItemTitles(false);
                    		mainForm.setNumCols(4);
                    		buttonForm = new DynamicForm();
                    		buttonForm.setNumCols(4);
                    		
                    		emailForm = new DynamicForm();
                    		emailForm.setNumCols(1);
                    		
                    		id = new StaticTextItem(DIDOrderField.ID.getName());
                    		id.setTitle(DIDOrderField.ID.getDisplayTitle());
                    		
                    		id.setValueFormatter(new FormItemValueFormatter(){
                    
                    			@Override
                    			public String formatValue(Object value, Record record,
                    					DynamicForm form, FormItem item) {
                    				
                    				if(value==null)
                    				{
                    					return "Not saved.";
                    				}
                    				else
                    				{
                    					return value.toString();
                    				}
                    			}
                    			
                    			
                    		});
                    		
                    		didOrderTitle=new TextItem(DIDOrderField.TITLE.getName());
                    		didOrderTitle.setTitle(DIDOrderField.TITLE.getDisplayTitle());
                    		didOrderTitle.setWidth(420);
                    		didOrderTitle.setColSpan(4);
                    		didOrderTitle.setEndRow(true);
                    		
                    		
                    		type = new StaticTextItem(DIDOrderField.TYPE.getName());
                    		type.setTitle(DIDOrderField.TYPE.getDisplayTitle());
                    		type.setValueMap(DIDOrderType.getValueMap());
                    		
                    		
                    		status = new SelectItem(DIDOrderField.STATUS.getName());
                    		status.setTitle(DIDOrderField.STATUS.getDisplayTitle());
                    		status.setValueMap(DIDOrderStatus.getValueMap());
                    		
                    		requestedDate = new DateItem(DIDOrderField.REQ_DATE.getName());
                    		requestedDate.setTitle(DIDOrderField.REQ_DATE.getDisplayTitle());
                    		requestedDate.setUseTextField(true);
                    		
                    		closedDate = new DateItem(DIDOrderField.CLOSED_DATE.getName());
                    		closedDate.setTitle(DIDOrderField.CLOSED_DATE.getDisplayTitle());
                    		closedDate.setUseTextField(true);
                    		
                    		account = new ComboBoxItem(DIDOrderField.ACCOUNT.getName());
                    		account.setTitle(DIDOrderField.ACCOUNT.getDisplayTitle());
                    		account.setValueField(AccountField.ACCOUNT_ID.getName());
                    		account.setDisplayField(AccountField.ACCOUNT_NAME.getName());
                    		account.setOptionDataSource(accountDS);
                    		account.setWidth(175);
                    		
                    		
                    		csa = new ComboBoxItem(DIDOrderField.CSA.getName());
                    		csa.setTitle(DIDOrderField.CSA.getDisplayTitle());
                    		csa.setValueField(CSAField.CSA_ID.getName());
                    		csa.setDisplayField(CSAField.CSA.getName());
                    		csa.setOptionDataSource(csaDS);
                    
                    		
                    		rejectedReason = new TextItem(DIDOrderField.REJECTED_REASON.getName());
                    		rejectedReason.setTitle(DIDOrderField.REJECTED_REASON.getDisplayTitle());
                    		rejectedReason.setStartRow(true);
                    		rejectedReason.setColSpan(4);
                    		rejectedReason.setEndRow(true);
                    		rejectedReason.setWidth(420);
                    		
                    		createdBy= new StaticTextItem(DIDOrderField.CREATED_BY.getName());
                    		createdBy.setTitle(DIDOrderField.CREATED_BY.getDisplayTitle());
                    		createdBy.setValueField(UserField.USER_ID.getName());
                    		createdBy.setDisplayField(UserField.FULL_NAME.getName());
                    		createdBy.setOptionDataSource(userDS);
                    		
                    		assignedTo = new SelectItem(DIDOrderField.ASSIGNED_TO.getName());
                    		assignedTo.setTitle(DIDOrderField.ASSIGNED_TO.getDisplayTitle());
                    		assignedTo.setValueField(UserField.USER_ID.getName());
                    		assignedTo.setDisplayField(UserField.FULL_NAME.getName());
                    		assignedTo.setOptionDataSource(userDS);
                    		
                    		notes = new TextAreaItem(DIDOrderField.NOTES.getName());
                    		notes.setTitle(DIDOrderField.NOTES.getDisplayTitle());
                    		notes.setTitleVAlign(VerticalAlignment.TOP);
                    		notes.setStartRow(true);
                    		notes.setColSpan(4);
                    		notes.setWidth(420);
                    		notes.setEndRow(true);
                    		
                    		didSelector = new ComplexDIDSelectorView(eventBus, layout, true);
                    		didSelector.init();
                    		didSelector.setTitle(DIDOrderField.DID_IDS.getDisplayTitle());
                    		didSelector.setTitleVAlign(VerticalAlignment.TOP);
                    		didSelector.setColSpan(4);
                    		
                    		saveButton = new ButtonItem("Save");
                    		saveButton.setWidth(100);
                    		saveButton.setStartRow(false);
                    		saveButton.setEndRow(false);
                    		cancelButton = new ButtonItem("Cancel");
                    		cancelButton.setStartRow(false);
                    		cancelButton.setWidth(100);
                    		spacer = new SpacerItem();
                    		spacer.setWidth(65);
                    		
                    		
                    		createCarrierOrderButton = new ImgButton();
                    		createCarrierOrderButton.setShowRollOver(true);
                    		createCarrierOrderButton.setShowDown(true);
                    		createCarrierOrderButton.setHeight(22);
                    		createCarrierOrderButton.setWidth(100);
                    		createCarrierOrderButton.setSrc("actions/newCarrierOrder.png");
                    		
                    		relatedLabel = new Label();
                    		relatedLabel.setContents("Related Carrier Orders");
                    		relatedLabel.setStyleName("sectionTitle");
                    		relatedLabel.setWidth(350);
                    		relatedLabel.setHeight(20);
                    		
                    		relatedCarrierOrders = new ShortCarrierOrderListGrid();
                    		relatedCarrierOrders.setDataSource(carrierOrderDS);
                    		relatedCarrierOrders.setHeight(225);
                    		relatedCarrierOrders.setWidth(450);
                    		
                    		emailSelector = new EmailSelector(DIDOrderField.CONTACT_EMAILS.getName(), eventBus, DIDOrderField.CONTACT_EMAILS.getName(), DIDOrderField.CONTACT_EMAILS.getDisplayTitle());
                    		emailSelector.setTitleOrientation(TitleOrientation.TOP);
                    		emailSelector.setTitle(DIDOrderField.CONTACT_EMAILS.getDisplayTitle());
                    		emailSelector.setTitleStyle("sectionTitle");
                    		emailSelector.init();
                    		emailForm.setFields(emailSelector);
                    		
                    		buttonForm.setFields(spacer, saveButton, spacer, cancelButton);
                    		buttonForm.setPadding(25);
                    		
                    		
                    		leftColumn.addMember(mainForm);
                    		leftColumn.addMember(buttonForm);
                    		
                    		carrierControls.addMember(relatedLabel);
                    		carrierControls.addMember(createCarrierOrderButton);
                    		
                    		rightColumn.addMember(carrierControls);
                    		rightColumn.addMember(relatedCarrierOrders);
                    		rightColumn.addMember(emailForm);
                    
                    		
                    		layout.addMember(leftColumn);
                    		layout.addMember(rightColumn);
                    		
                    		this.setPane(layout);
                    		
                    		
                    	}
                    	
                    	public void setEmails(String[] emails)
                    	{
                    		this.emailSelector.setEmails(emails);
                    	}
                    	
                    	public String[] getEmails()
                    	{
                    		return this.emailSelector.getEmails();
                    	}
                    	
                    	public Canvas getTabBody()
                    	{
                    		return this.layout;
                    	}
                    
                    	
                    	public String getPrincipal()
                    	{
                    		return "naetss";
                    		
                    	}
                    	
                    	public void clearFormField(String fieldName) {
                    		this.mainForm.clearValue(fieldName);
                    		
                    	}
                    	
                    	
                    	public void editRecord(Record record)
                    	{
                    		this.setTitle(this.getTabTitlePrefix() + record.getAttribute(DIDOrderField.TITLE.getName()));
                    		this.mainForm.editRecord(record);
                    	}
                    	
                    	public abstract String getTabTitlePrefix() ;
                    
                    	public void editNewRecord()
                    	{
                    		this.mainForm.editNewRecord();
                    		this.setTitle(this.getTabTitlePrefix()+ "New");
                    	}
                    
                    	
                    	
                    	
                    	public abstract class AbstractDIDOrderRecordController extends AbstractController implements CloseClickHandler
                    	{
                    
                    		protected DIDOrder didOrder;
                    		protected TabSet tabSet;
                    	
                    		public AbstractDIDOrderRecordController(HandlerManager eventBus, TabSet tabSet)
                    		{
                    			super(eventBus);
                    	
                    			this.tabSet = tabSet;
                    		}
                    		
                    		@Override
                    		public void init()
                    		{
                    			
                    			super.init();
                    			
                    			if(record!= null)
                    			{
                    				editRecord(record);
                    				
                    				//get the csa value before filtering
                    				String csaValue = record.getAttribute(DIDOrderField.CSA.getName());
                    				
                    				//filter csa selector
                    				this.filterCSAPicker(account.getValue());
                    				
                    				//set csa value
                    				csa.setValue(csaValue);
                    				
                    				//populate DIDSelector
                    				int[] didIntArray = record.getAttributeAsIntArray(DIDOrderField.DID_IDS.getName());
                    				if(didIntArray != null)
                    				{
                    					didSelector.setDIDIds(didIntArray);
                    				}
                    				
                    				//populate EmailSelector 
                    				
                    				String[] emails = record.getAttributeAsStringArray(DIDOrderField.CONTACT_EMAILS.getName());
                    				if(emails != null)
                    				{
                    					setEmails(emails);
                    				}
                    				
                    				//populate relatedCarrierOrders
                    				this.filterRelatedCarrierOrderListGrid();
                    			}
                    			else
                    			{
                    				createdBy.setValue(getPrincipal());
                    			}
                    		}
                    		
                    		@Override
                    		public void bind()
                    		{
                    			super.bind();
                    			
                    			/* Binding for relatedCarrierOrder selection */
                    			
                    			HandlerRegistration carrierOrderSelectionReg= relatedCarrierOrders.addRecordDoubleClickHandler(new RecordDoubleClickHandler(){
                    
                    				@Override
                    				public void onRecordDoubleClick(RecordDoubleClickEvent event) {
                    					Record record = event.getRecord();
                    					
                    					CarrierOrderType type = CarrierOrderType.valueOf(record.getAttribute(CarrierOrderField.TYPE.getName()));
                    					
                    					if(type != null)
                    					{
                    						eventBus.fireEvent(new OpenCarrierOrderEditorEvent(record, type));
                    					}
                    					else
                    					{
                    						Log.error("Selected record has unidentified CarrierOrderType.");
                    					}
                    					
                    				}
                    				
                    				
                    			});
                    			
                    			/* Binding for accountPicker */
                    			HandlerRegistration accountPickerReg = account.addChangedHandler(new ChangedHandler(){
                    
                    				@Override
                    				public void onChanged(ChangedEvent event) {
                    					filterCSAPicker(event.getValue());	
                    				}
                    				
                    			});
                    		
                    			
                    			/* Binding to for closeClickHandler */
                    			HandlerRegistration closeClickReg = tabSet.addCloseClickHandler(this);
                    			
                    			/* Binding for changes to values in the title field */
                    			HandlerRegistration titleChangedReg = didOrderTitle.addChangedHandler(new ChangedHandler(){
                    
                    				@Override
                    				public void onChanged(ChangedEvent event) 
                    				{
                    					
                    					String newValue;
                    					if(event.getValue()!= null && ((String)event.getValue()).length() > 0)
                    					{
                    						newValue = getTabTitlePrefix() + ((String)event.getValue());
                    					}
                    					else
                    					{
                    						newValue= getTabTitlePrefix() + " New";
                    					}
                    					
                    					
                    					tabSet.setTabTitle(AbstractDIDOrderRecordView.this, newValue);
                    				}
                    			});
                    			
                    			/* Binding for the save button */
                    			HandlerRegistration saveButtonReg = saveButton.addClickHandler(new com.smartgwt.client.widgets.form.fields.events.ClickHandler(){
                    
                    				@Override
                    				public void onClick(com.smartgwt.client.widgets.form.fields.events.ClickEvent event) {
                    					doSave();
                    					
                    				}
                    				
                    			});
                    			
                    			
                    			/* Add HandlerRegistrations for unbinding */
                    			this.addHandlerRegistration(carrierOrderSelectionReg,accountPickerReg, titleChangedReg, closeClickReg, saveButtonReg);
                    		}
                    		
                    		@Override
                    		public void onCloseClick(TabCloseClickEvent event) {
                    			if(event.getTab() == AbstractDIDOrderRecordView.this)
                    			{
                    				this.destroy();
                    			}
                    		}
                    		
                    		public Record getFormRecord()
                    		{
                    			/* Clear non-canonical values */
                    			
                    			if(account.getValue() instanceof String)
                    			{
                    				try
                    				{
                    					new Integer((String)account.getValue());
                    				}
                    				catch(NumberFormatException exc)
                    				{
                    					Log.debug("Clearing account value.");
                    					account.clearValue();
                    				}
                    			}
                    			
                    			if(csa.getValue()instanceof String)
                    			{
                    				try
                    				{
                    					new Integer((String)csa.getValue());
                    				}
                    				catch(NumberFormatException exc)
                    				{
                    					Log.debug("Clearing csa value.");
                    					csa.clearValue();
                    				}
                    			}
                    			
                    			return mainForm.getValuesAsRecord();
                    		}
                    		
                    		private void filterRelatedCarrierOrderListGrid()
                    		{
                    			if(id.getValue() != null)
                    			{
                    				Criteria criteria = new Criteria();
                    				criteria.addCriteria(CarrierOrderField.DID_ORDER.getName(), ((Integer)id.getValue()).intValue());
                    				relatedCarrierOrders.fetchData(criteria);
                    			}
                    		}
                    		
                    		private void filterCSAPicker(Object accountIdValue)
                    		{
                    			//Clear any previous selections the user may have made in the picker.
                    			clearFormField("csa");
                    			
                    			if(accountIdValue == null)
                    			{
                    				filterCSAPicker(new Criteria());
                    			}
                    			else
                    			{
                    				if(accountIdValue instanceof Integer)
                    				{
                    					Long accountId = new Long((Integer)accountIdValue);
                    					Criteria criteria = new Criteria();
                    					criteria.addCriteria(AccountField.ACCOUNT_ID.getName(), accountId.intValue());
                    					csa.setOptionCriteria(criteria);
                    					csa.fetchData();
                    				}
                    				else if(accountIdValue instanceof String)
                    				{
                    					Log.debug("User was typing, do nothing");
                    				}
                    				else
                    				{
                    					Log.error("Unknown data type being entered into account picker.");
                    				}
                    				
                    			}
                    		}
                    		
                    		
                    		public void filterCSAPicker(Criteria criteria)
                    		{
                    			csa.setOptionCriteria(criteria);
                    			csa.fetchData();
                    		}
                    
                    		protected void doSave() 
                    		{	
                    			
                    			/* Do value overrides before committing */
                    			Record recordCopy = getFormRecord();
                    			
                    			/* Get the list of email addresses */
                    			String[] emails = getEmails();
                    			
                    			recordCopy.setAttribute(DIDOrderField.CONTACT_EMAILS.getName(), emails);
                    			
                    			/* Get the list of DID Ids */
                    			int[] didIds = didSelector.getDIDIds();
                    			
                    			recordCopy.setAttribute(DIDOrderField.DID_IDS.getName(), didIds);
                    				
                    			editRecord(recordCopy);
                    			
                    			/* Set a default value on the didSelector to indicate it has contents */
                    			if(didIds.length!= 0)
                    			{
                    				didSelector.setValue(new Integer(1));
                    			}
                    			else
                    			{
                    				didSelector.clearValue();
                    			}
                    			
                    			//Perform validation
                    			if(mainForm.validate() && !emailSelector.getEmailListGrid().hasErrors())
                    			{
                    			
                    				//check whether to call update or add prior to saving
                    				Record record = this.getFormRecord();
                    				
                    				if(record.getAttributeAsInt(DIDOrderField.ID.getName())!= null)
                    				{
                    					mainForm.setSaveOperationType(DSOperationType.UPDATE);
                    				}
                    				else
                    				{
                    					mainForm.setSaveOperationType(DSOperationType.ADD);
                    				}
                    				
                    				mainForm.saveData();
                    			}
                    			else
                    			{
                    				SC.warn("Errors in the DID Order form need to be fixed before it can be saved.");
                    			}
                    			
                    		}
                    		
                    
                    	}
                    	
                    
                    
                    }
                    Let me point out the salient parts:

                    When the form loads, it's controller's init() method gets called:

                    Code:
                    @Override
                    		public void init()
                    		{
                    			
                    			super.init();
                    			
                    			if(record!= null)
                    			{
                    				editRecord(record);
                    				
                    				//get the csa value before filtering
                    				String csaValue = record.getAttribute(DIDOrderField.CSA.getName());
                    				
                    				//filter csa selector
                    				this.filterCSAPicker(account.getValue());
                    				
                    				//set csa value
                    				csa.setValue(csaValue);
                    				
                    				//populate DIDSelector
                    				int[] didIntArray = record.getAttributeAsIntArray(DIDOrderField.DID_IDS.getName());
                    				if(didIntArray != null)
                    				{
                    					didSelector.setDIDIds(didIntArray);
                    				}
                    				
                    				//populate EmailSelector 
                    				
                    				String[] emails = record.getAttributeAsStringArray(DIDOrderField.CONTACT_EMAILS.getName());
                    				if(emails != null)
                    				{
                    					setEmails(emails);
                    				}
                    				
                    				//populate relatedCarrierOrders
                    				this.filterRelatedCarrierOrderListGrid();
                    			}
                    			else
                    			{
                    				createdBy.setValue(getPrincipal());
                    			}
                    		}
                    I check whether a record was passed to the form (these typically come wrapped in an event from the eventBus, because the actiion to open a form can come from a totally different part of the application). If this is the case, I tell the form to edit the record, which populates all the standard formItem fields. I then MANUALLY get data out of the record and populate the custom CanvasItem implementations.

                    Not graceful, but it works. My beef here is that you can't just stick a custom canvasItem on a form, because of it's own accord, the CanvasItem does not work. Instead, the Form needs specialized logic to make it work; as opposed to the standard FormItems (TextItem, SelectItem, etc) which you just sick on the form and they work, period. That's what I mean with the design violating the principle of encapsulation.

                    For saving data, it gets messier:

                    Code:
                    protected void doSave() 
                    		{	
                    			
                    			/* Do value overrides before committing */
                    			Record recordCopy = getFormRecord();
                    			
                    			/* Get the list of email addresses */
                    			String[] emails = getEmails();
                    			
                    			recordCopy.setAttribute(DIDOrderField.CONTACT_EMAILS.getName(), emails);
                    			
                    			/* Get the list of DID Ids */
                    			int[] didIds = didSelector.getDIDIds();
                    			
                    			recordCopy.setAttribute(DIDOrderField.DID_IDS.getName(), didIds);
                    				
                    			editRecord(recordCopy);
                    			
                    			/* Set a default value on the didSelector to indicate it has contents */
                    			if(didIds.length!= 0)
                    			{
                    				didSelector.setValue(new Integer(1));
                    			}
                    			else
                    			{
                    				didSelector.clearValue();
                    			}
                    			
                    			//Perform validation
                    			if(mainForm.validate() && !emailSelector.getEmailListGrid().hasErrors())
                    			{
                    			
                    				//check whether to call update or add prior to saving
                    				Record record = this.getFormRecord();
                    				
                    				if(record.getAttributeAsInt(DIDOrderField.ID.getName())!= null)
                    				{
                    					mainForm.setSaveOperationType(DSOperationType.UPDATE);
                    				}
                    				else
                    				{
                    					mainForm.setSaveOperationType(DSOperationType.ADD);
                    				}
                    				
                    				mainForm.saveData();
                    			}
                    			else
                    			{
                    				SC.warn("Errors in the DID Order form need to be fixed before it can be saved.");
                    			}
                    			
                    		}

                    Quite frankly, I'm not happy with this code at all. It's ugly and it's messy. First of all, I get a copy of the record backing the form (through a call to DynamicForm.getValuesAsRecord()). I'd rather have worked with the record directly, but there's no method exposed to allow you to get that.

                    I then manually extract data from the custom CanvasItems and add them to the record.

                    Next, I need to set the DIDSelector to some bogus value because apparently, the validation logic of the DynamicForm checks whatever the backing value is behind CanvasItem.getValue() - BUT WITHOUT EVER CALLING DIDSelector.getValue() or DIDSelector.validate(). It doesn't look at the Record behind the form -which is what you'd expect - it looks somehow inside the super class of your custom CanvasItem at whatever hidden variable stores the data.

                    Next, I need to manually validate the form and the emailselector. Again, because I have no control whatso-ever over the validation logic that happens inside a DynamicForm; I have no way of telling the form that the EmailSelector is part of that it needs to check the ListGrid inside the EmailSelector for validation errors. So I have to do that manually.

                    Finally, I tell the form to re-edit the copy of the record I just obtained and modified. Unfortunately, calls to DynamicForm.editRecord() automatically switch the Form from DSOperationType.ADD to DSOperationType.UPDATE, so I have to undo that and switch it back (after checking whether the ID for the item has been set; this tells me whether or not I'm dealing with a new or an existing record.


                    Now, imagine how much easier this would be if the DynamicForm, on saveData() called FormItem.getValue() and FormItem.getValidate() on each of it's fields. All you would have to do would be override those two methods on your custom CanvasItem implemenation and you'd be good to go. No messing with Handlers and no hacking the validation and data-binding logic.

                    I understand the point you made about the centralized value management benefits DynamicForm provides - for instance, this way, you would know if a user modified the form at all and you get set a "not-saved indicator". But there's other ways of doing this, namely:

                    Code:
                    DynamicForm form = new DynamicForm();
                    TextItem text1 = new TextItem();
                    TextItem text2  = new TextItem();
                    text1.addChangeHandler(new NotifyFormHandler());
                    text2.addChangeHandler(new NotifyFormHandler());
                    
                    private class NotifyFormHandler extends ChangeHandler(){
                    
                    @Override
                    onChange(ChangeEvent event)
                    {
                     form.setUpdated(true);
                    }
                    
                    }
                    That's a LOT less code and lot more straighforward than the amount of code it takes to get a custom CanvasItem() to submit data. Not to mention that I may not always care about whether a form has been modified or not but I will certainly care about the values in the FormItems each and every time.

                    You know, you may be right - I probably had some misconceptions around how the design worked. There's certainly parts of the SmartGWT that I still don't get. But let's be fair - it's not like there's a lot of documentation and what does exist leaves a lot unsaid. I mean, here's the javadoc for CanvasItem():

                    http://www.smartclient.com/smartgwtee/javadoc/com/smartgwt/client/widgets/form/fields/CanvasItem.html

                    I doesn't mention anything about the need to manage the value of the super-class. Nor does the Javadoc on DynamicForm or FormItem mention that in fact, the value management of data by forms is done top-down by the form to its form-items, rather than bottom-up by the formItems to their form.

                    Any logical person is simply going to look at the API doc for FormItem and assume they have to override getValue() and validate() - which is precisely what a lot of the threads on CanvasItem on this forum show other users as doing; and then discovering that that doesn't actually work.

                    SmartGWT is no doubt very very powerful. Don't get me wrong, you guys have built some very powerful and attractive widgets. But the documentation leaves a lot to be desired and the development process is painstackingly slow with huge amounts of trial and error.

                    For instance, when I built the NumberIncrementItem, I - naively - assumed that a TextItem could have BOTH an EditorValueFormatter AND a ChangedHandler. But apparently, it can't, because once you add an EditorValueFormatter to a TextItem, none of it's ChangeHandlers or ChangedHandlers() ever get notified. That is again not mentioned in the documentation, you simply have to discover it the hard way. The end-result is that what should have been a really simple widget to built (I mean, a textfield with two buttons to increment/decrement a number by 1 is about as basic as it gets) but between that issue and the general "how-the-hell-do-I-get-my-custom-canvas-item-to-actually-show-data-from-the-datasource) it took a day and a half.

                    When I started this smartGWT project, our initial estimate was 8 weeks. It's now been 4.5 months and counting, most of which has been learning all the undocumented and undescribed in-and-outs of the inner workings of SmartGWT.

                    I'm not saying this to be negative or to attack SmartGWT. I'm trying to provide feedback as a paying customer that my experience with SmartGWT has been painful, but rewarding in the end - but most importantly - far, far too time-consuming; time that most organizations cannot afford. From my perspective, SmartGWTs API either needs to be a lot of self-evident than it is OR the documentation needs to be a lot more comprehensive and explain some of these inner complexities. And I'm pretty sure that I'm not the first person to point that out.

                    Comment


                    • #11
                      To review, the design of the form allows for central change handling, change tracking, and many other features not achievable unless items notify the form of changes.

                      However, the form is very flexible and will hold onto values for which there is no corresponding item. Therefore if you don't need central change handling you can just setValue() with whatever you want to store, like the Hibernate Master-Detail sample shows.

                      If you want to encapsulate that, you can make a subclass of DynamicForm that is aware of your special CanvasItem that isn't capable of change notifications, and retrieves its value right before save and validation. Or, encapsulate it at a higher level, such as a combined grid and form component like that shown in the Pattern Reuse sample.

                      So basically, you seem to have spent a lot of time chasing a particular design when other, simpler approaches were available. It's great and we appreciate that you invested in a license, but investing in support and asking questions early on would have saved all this effort and frustration, because we would have pointed you to the right pattern to use, and the relevant samples.

                      Note, of course docs can always be improved, and in fact recent improvements were made to the CanvasItem docs to steer more users to the right approach. Also, new events could be added to simplify and clarify this pattern, eg, items could be notified when the form is about to save or getValues() is called, so that if they cannot easily fire change events they can still participate in the form in a slightly degraded mode. This is not viewed as critical relative to other features many, many users want, but if it's important to you, there's Feature Sponsorship.

                      Just a couple further notes..
                      1. if you wanted an increment/decrement item, subclassing TextItem and adding FormItemIcons would yield that very quickly, no special value management required

                      2. you're assuming that because you cannot override methods they are not being called. Again, not all methods in SmartGWT are currently capable of being overridden. The methods are being called and the encapsulation is correct, but the override is not available in SmartGWT because it's not the right approach. While the online CanvasItem docs don't cover this, the latest docs do.

                      3. your proposed solution of adding change handlers to all items in lieu doesn't simplify anything because the items would all need to be able to detect changes in order fire change handlers

                      4. you can have both an EditorValueFormatter and fire change events, but you need an EditorValueParser which derives the logical value from the formatted value once the user has edited it. If you think there's a bug here, try putting together a test case.

                      Comment


                      • #12
                        Originally posted by Isomorphic
                        If you want to encapsulate that, you can make a subclass of DynamicForm that is aware of your special CanvasItem that isn't capable of change notifications, and retrieves its value right before save and validation. Or, encapsulate it at a higher level, such as a combined grid and form component like that shown in the Pattern Reuse sample.
                        I don't think that's always going to work and I'll show you why.

                        Lets say I have:

                        Code:
                        public class A extends CanvasItem
                        public class B extends CanvasItem
                        public class DynamicFormA extends DynamicForm
                        public class DynamicFormB extends DynamicForm
                        where DynamicFormA and DynamicFormB have specialized logc in the overriden editRecord() and saveData() methods to set/get the values from item A/B respectively.

                        I believe that is what you're suggesting, correct? I would then use DynamicFormA or DynamicFormB, depending on which CanvasItem implementation I have on the form.

                        This works, as long as I don't need to ever combine A and B in one form - at that point, the encapsulation breaks because you cannot have multiple inheritance in java. You would now have to write a DynamicFormA+B implementation which has logic to handle both A and B elements. And of course, you'de be copy-pasting code - which quickly becomes a nightmare to maintain.

                        I suppose you *could* instead spread the form across two DynamicForms and use a ValuesManager to bind the two together; but that creates other issues - for one, it's a bit of hack, for another, ValuesManager does not implement all of the utility methods from DynamicForm (for instance, there's no getValuesAsCriteria() method om the ValuesManager).

                        However, after giving this some more thought; I think I could see a way to use an extended form to solve this problem:

                        Code:
                        public abstract class AbstractCanvasItem extends CanvasItem {
                        	
                        	public abstract void setValue(JavaScriptObject jsObj);
                        
                        }
                        and

                        Code:
                        public class CanvasItemForm extends DynamicForm {
                        
                        	public CanvasItemForm() {
                        		super();
                        		// TODO Auto-generated constructor stub
                        	}
                        
                        	public CanvasItemForm(JavaScriptObject jsObj) {
                        		super(jsObj);
                        		// TODO Auto-generated constructor stub
                        	}
                        	
                        	@Override
                        	public void editRecord(Record record)
                        	{
                        		FormItem[] fields = this.getFields();
                        		for(FormItem field : fields)
                        		{
                        			if(field.getClass().isAssignableFrom(AbstractCanvasItem.class))
                        			{
                        				JavaScriptObject jsObj = record.getAttributeAsJavaScriptObject(field.getName());
                        				
                        				((AbstractCanvasItem)field).setValue(jsObj);
                        			}
                        		}
                        
                        super.editRecord(record);
                        
                        	}
                        	
                        	@Override
                        	public void saveData()
                        	{
                        		FormItem[] fields = this.getFields();
                        		
                        		for(FormItem field : fields)
                        		{
                        			if(field.getClass().isAssignableFrom(AbstractCanvasItem.class))
                        			{
                        				this.setValue(field.getName(), (JavaScriptObject)field.getValue());
                        			}
                        		}
                        		
                        		super.saveData();
                        	}
                        
                        }
                        look they would provide a generic solution that allows a DynamicForm to correctly set and get the values from custom canvasItems, provided those CanvasItems allow the setting and getting of their values as JavaScriptObjects.

                        I haven't actually tried this; I'd have to change quite a bit of code to make it work this way; but it looks like something that would work. Do you see any problems with this approach?

                        1. if you wanted an increment/decrement item, subclassing TextItem and adding FormItemIcons would yield that very quickly, no special value management required
                        I hadn't noticed the existence of FormItemIcons in the API; you're right, that's easier that the approach I took.


                        2. you're assuming that because you cannot override methods they are not being called. Again, not all methods in SmartGWT are currently capable of being overridden. The methods are being called and the encapsulation is correct, but the override is not available in SmartGWT because it's not the right approach. While the online CanvasItem docs don't cover this, the latest docs do.
                        I'm not sure I understand what you mean by this. I mean... of course I can override the methods - setValue() and getValue() are public, there's nothing preventing me from overriding their implementation in a child class - I have done so, the class compiles fine and when I call the method directly, it works.

                        However, when adding debug statements to setValue()/getValue(), I can see those methods are never invoked by the DynamicForm. I don't understand your comment that the methods DO get called but can't be overriden; my experience is the opposite: I can override them, I can call the override and it works; but without modifying DynamicForm, they're never invoked. I probably just don't get how DynamicForm and FormItems normally interact - as far as I can tell, you guys are doing some kind of magic under the hood - I'm assuming in the compiled JavaScript version of the code - to get and set values on FormItems; in a way that's not directly exposed in the Java API.

                        3. your proposed solution of adding change handlers to all items in lieu doesn't simplify anything because the items would all need to be able to detect changes in order fire change handlers
                        Don't all the out-of-the-box SmartGWT FormItems (textItem, selectItem, etc) currently have this capability in the form of ChangeHandlers/ChangedHandlers? You're right that this might be a problem for custom CanvasItems though - given that something like my DIDSelector has about 9 different ways that the underlying data can change, this would be a pain to implement.

                        4. you can have both an EditorValueFormatter and fire change events, but you need an EditorValueParser which derives the logical value from the formatted value once the user has edited it. If you think there's a bug here, try putting together a test case.
                        I'll look into that, thanks.
                        Last edited by SiccoNaets; 9th Jul 2010, 07:15.

                        Comment


                        • #13
                          Yes, making a subclass of DynamicForm that understands your CanvasItems and using that class pervasively is the right approach.

                          As far as overrides, SmartGWT is a hybrid architecture in which a GWT wrapper calls through to underlying SmartClient libraries. For performance reasons, not all JavaScript -> JavaScript calls in SmartClient are intercepted, instead, only methods designed as override points may be overridden in Java (these are marked "This is an override point" in JavaDoc). There are hundreds of these - substantially all methods that were designed as override points may be overridden (and overriding something that wasn't designed as an override point is usually a quick way to get into trouble anyway).

                          So what we're pointing out is that the underlying encapsulation in the JavaScript components is correct, but you are trying to override something that isn't set up an override point (because an override of that method is not the right approach).

                          Comment


                          • #14
                            On this last comment, shouldn't the Java methods that aren't meant to be 'override points' just be declared 'final'? Seems like it would prevent the confusion that follows when you override it and nothing happens.

                            Regarding the whole thread, I tend to agree with SiccoNaets. I came here looking for an answer to how my custom FormItem can get the value it's supposed to display when it's included in a bound form, and after reading this whole thread I still don't have an answer.

                            Stokes.

                            Comment


                            • #15
                              Originally posted by Stokesified
                              Regarding the whole thread, I tend to agree with SiccoNaets. I came here looking for an answer to how my custom FormItem can get the value it's supposed to display when it's included in a bound form, and after reading this whole thread I still don't have an answer.
                              Stokes.
                              I second this. I am glad I though to debug early in the process to see that my override of a smartclient class's method was never being called.

                              Any reasons why "final" is not being used to communicate to implementors the methods not available as override points?

                              Comment

                              Working...
                              X