Editable Table in Form

I confess that when I run the debugger of an editable Table that creates fields for data entry in a Form, there’s a lot of mystery going on.

The createField() callback is called a lot, over and over it seems, for the same fields.

STEP 1: Load the form
When the Form is first loaded, I call Table.addItem() to create the Table with one row. This results in createField() being called for each column as Table.addItem() calls IndexedContainer.addItem() which eventually calls Table.refreshRenderedCells() which calls Table.getPropertyValue() that ends up creating a new Field.

But it then does it all over again when IndexedContainer$IndexContainerProperty.setValue() is called internally so that Table.valueChange() is called, which then calls Table.refreshRenderedCells() and Table.getPropertyValue() to create the fields one more time.

But then I call Table.setEditable(true), which also ends up calling Table.refreshRenderedCells() that recreates them for the third time.

STEP 2: Click a button to add a new (second) row to the Table
Again, I call Table.addItem(), which creates all of the fields again, this time including the new row as expected.

But then IndexedContainer$IndexContainerProperty.setValue() is called internally, creating them all over again.

And then my code calls Table.setPageLength(), which creates them yet again.

STEP 3: Select a row and click a button to remove that (second) row from the Table
I call Table.removeItem(), which seems to recreate the original Fields for the two rows via AbstractSelect.unselect(), Table.setValue() to Table.refreshRenderedCells() and Table.getPropertyValue().

But it happens again when IndexedContainer.fireContentsChange() calls Table.containerItemSetChange() which renders it all again, but at least the removed row is now not being created.

But it happens again when Table.valueChange() is called, which again does Table.refreshRenderedCells() …creating them once more.

And then I call Table.setPageLength() and they are created yet again.


I am not sure if I can position my calls to avoid this constant recreation of fields. The only reason why this was even a bother for me is that because the Table is in a Form, I’d like my Form.isModified() to be able to tell me if any of the data inside the Table was changed, but the createField() doesn’t itself remember the Field that goes with the Item/Properties created, so I was keeping track of the Fields I created so I could call the field.isModified() on my list. But of course my list was huge because I was not clearing my list each time all of the fields were being recreated.

I have a hack now where if the itemId (Integer in my Table that represents the order since drag and drop can be used to reorder the rows) is “1” and the propertyId is the first column name “order”, I clear my Field tracking list. This works now so my List seems seems to stay in synch with the Table fields being created over and over.

Is this the way I should be doing this? Is there a better way to associate the Fields I create for an editable table so I can scan them to see if they’ve been modified?

Is there any plan for an editable table to be part of a form in a more integrated way?

Thanks for any tips or ideas…

Noted that my hack was just that, a hack, since it stopped working as soon as I removed the first row that had the Integer order of 1. If anybody else needs something, here’s a more general solution, mapping the itemId to a list of Fields:

In my Form subclass, I keep track of the fields created in the table using a map:

Map<Object,List<Field>> tableFieldsMap = new TreeMap<Object,List<Field>>();

Then, in the createField() callback of my DefaultFieldFactory, at the top I check for the “first property id” so that if I find the itemId in my map, I know I can clear it as we’re creating the fields again, and if I don’t have a field list, I create it.

		if ( propertyId.equals("name") ) {
			if ( tableFieldsMap.containsKey(itemId) ) { // If I already have the specified itemId in my map and this is the first property in our table, let's clear it and assume we're building it again
				tableFieldsMap.clear(); 
			}
		}
				
	   List<Field> fieldList = tableFieldsMap.get(itemId);
	   if ( fieldList == null ) fieldList = new LinkedList<Field>();

Then, wherever I actually create a field based on property id, I add it to the list and put it in the map:

					fieldList.add(tf);
					tableFieldsMap.put(itemId,fieldList);

I also found that if my table does drag and drop, or you can click a button to add/remove rows, you will want to sync the Fields back to the Table before you handle those request to avoid losing any other data changes made in the Table. I do this because I don’t want to save it back to my bean (the regular Form.commit() code calls this same routine to commit each field, but then that also saves the data back to my bean).

    private boolean commitFieldsToTableOnly() {
    	boolean isValid = true;
    	
    	// Only do this if we have something in our table since emptying a table will not have cleared our fields list.
    	if ( myTable.size() > 0 ) {
    		for( List<Field> fList : tableFieldsMap.values() ) {
    			for( Field f : fList ) {
    				if ( f.isValid() )
    					f.commit();
    				else {
    					isValid = false;
    				}
    			}
    		}
    	}
    	
    	if ( ! isValid )
    	{
    		vaadinApp.showWarning(vaadinApp.getMsg("form.save.invalid"));
    	}

    	return isValid;
    }

Then in order for the Form to validate the fields in the Table, I have:

    @Override
    public void validate() throws InvalidValueException {
    	super.validate();
    	
    	if ( myTable.size() > 0 ) {
    		for( List<Field> fList : tableFieldsMap.values() ) {
    			for( Field f : fList ) {
    				f.validate();
    			}
    		}
    	}
    }
    
    @Override
    public boolean isValid() {
    	if ( ! super.isValid() ) {
    		return false;
    	}

    	if ( myTable.size() > 0 ) {
    		for( List<Field> fList : tableFieldsMap.values() ) {
    			for( Field f : fList ) {
        			if ( ! f.isValid() ) return false;
    			}
    		}
    	}

		return true;
    }

Same for isModified() if you need for your form:

	@Override
	public boolean isModified() {
		if ( super.isModified() || myTableChanged ) {
			return true;
		}
		
		for( List<Field> fList : tableFieldsMap.values() ) {
			for( Field f : fList ) {
    			if ( f.isModified() ) return true;
			}
		}
		
		return false;
	}

The myTableChanged boolean here is used because I want to also detect a re-order of my table, new row added or row deleted, so I set that to true when those take place, and reset in my commit() and discard().

You can also clear the tableFieldsMap when setItemDataSource() is called on your Form. Or you can it whenever you populate your Table entirely from your data source (in our case, we used a bean).

Don’t know that this is fully correct, but it seems to work for me, even if I completely empty the Table. Hope it helps someone trying to incorporate an editable table in a form.