Table with variable row height

Hi,

Are all rows within a Table always the same height, or can a Table have rows with variable height?

I have an editable Table with 2 columns/properties; the 1st one is of type String, and the 2nd of type String. I created a custom field (by using the CustomField addon) to render the string array as a vertical list of TextFields with a button to add additional fields. The problem I’m experiencing is that when adding new fields the row does not expand to accommodate the new components, and thus they become unreachable.So, is there any way for the Table to dynamically adjust the row height based on its contents, or at least a way where I can assign a height dynamically based on the number of components?

Thanks,
Luis Delgado

You have to repaint the complete Table with
table.requestRepaintAll()
after your add operation.

Hi,

Thanks for your response. I tried your suggestion but it doesn’t seem to work on mi side. I added code to fire a custom event every time a new TextField is added and then added the corresponding event listener on the Table where I put the table.requestRepaintAll() . The method is being called but the table does not refresh… or at least the row height is not adjusted.

Maybe you have to check if the CustomComponent gets invoked.
And does your CustomComponent resizes properly when its content changes?

After a few tweaks, this is where I’m at (not far from where I was, anyway):

Table tblAttributes = new Table();
tblAttributes.setSelectable(false);
tblAttributes.setPageLength(0);
tblAttributes.addContainerProperty("NAME", String.class, "", "Name", null, null);
tblAttributes.addContainerProperty("VALUES", String[].class, new String[]
 { "" }, "Value", null, null);
tblAttributes.setEditable(true);
tblAttributes.setSortDisabled(true);

tblAttributes.setTableFieldFactory(new TableFieldFactory () {
    public Field createField(Container container, Object itemId, Object propertyId, Component uiContext) {
        Field field;

        if ("NAME".equals(propertyId)) {
	        TextField tf = new TextField();
            tf.setWidth("170px");
            tf.setInputPrompt("attribute name");
            tf.setData(itemId);
            field = tf;
        }
        else {
        	TextFieldList tfl = new TextFieldList();
            tfl.setWidth("300px");
            tfl.setInputPrompt("attribute value");
            tfl.setData(itemId);
            field = tfl;
        }

        return field;
    }
});

tblAttributes.addGeneratedColumn("DELETE", new Table.ColumnGenerator() {
	@Override
	public Component generateCell(Table source, Object itemId, Object columnId) {
		Button b = new Button("X");
		b.setData(itemId);
		b.setStyleName(Reindeer.BUTTON_LINK);
		b.setDescription("Delete Attribute");
		b.setImmediate(true);
		b.addListener(new ClickListener() {
			@Override
			public void buttonClick(ClickEvent event) {
				Integer itemId = (Integer)event.getButton().getData();
				tblAttributes.removeItem(itemId);
			}
		});
		return b;
	}
});
tblAttributes.setColumnHeader("DELETE", "");
tblAttributes.setColumnWidth("DELETE", 20);
tblAttributes.setColumnAlignment("DELETE", Table.ALIGN_CENTER);

Then I have the following line to add one row:

tblAttributes.addItem(new Object[] {"name", new String[]
{"value1","value2","value3"}}, new Integer(1));

But when the page gets rendered the table only shows the default value (array with empty string) in the second column. In other words, it adds the new row with the default values but then ir doesn’t refresh the custom field component after setting the assigned property value.
I also have a button for the user to add more rows. When you click the button then the entire table refreshes after adding the new row and all the values are shown correctly. After this, if you click on the custom field button to add more values what happens is that a new TextField is added within the custom field as expected, but then the row expands its height and the table does not expand its overall dimensions accordingly, so it the table ends up showing scrollbars.

When the table shall resize itself, you have to tell the table’s parent to repaint. Because the ComponentContainer handles the size an positon of its children.

And I don’t know whether your TextFieldList handles the “setPropertyDataSource” method correct or not.

I really appreciate your time helping me on this. Here is my TextFieldList component/field. I’m using the CustomField add-on, so I’m relying on its implementation for the setPropertyDataSource() method. Anyway, here’s my code…it’s not 100% functional yet; it still has some problems…


public class TextFieldList extends CustomField
{
	private HorizontalLayout layout;
	private VerticalLayout list;
	private String inputPrompt = "";
	private Button bAdd;
	
	private String[] values;

	public TextFieldList()
	{
		super();
		
		layout = new HorizontalLayout();
		layout.setSpacing(true);
		layout.setWidth("100%");
		
		list = new VerticalLayout();
		list.setSpacing(false);
		list.setMargin(false);
		list.setImmediate(true);
		layout.addComponent(list);
		layout.setExpandRatio(list, 1);

		bAdd = new Button("+", new ClickListener() {
			@Override
			public void buttonClick(ClickEvent event) {
				addNewValue();
			}
		});
		bAdd.setStyleName(Reindeer.BUTTON_SMALL);
		bAdd.setImmediate(true);
		layout.addComponent(bAdd);
		layout.setComponentAlignment(bAdd, Alignment.BOTTOM_LEFT);
		
		setCompositionRoot(layout);
	}

	@Override
	public Class<?> getType()
	{
		return String[].class;
	}
	
	private void addNewValue()
	{
		String[] oldValues = (String[]
)getPropertyDataSource().getValue();
		String[] newValues = new String[oldValues.length + 1]
;
		System.arraycopy(oldValues, 0, newValues, 0, oldValues.length);
		newValues[newValues.length - 1]
 = "";
		getPropertyDataSource().setValue(newValues);
	}
	
	@Override
	public void setInternalValue(Object value)
	{
		values = (String[])value;
		list.removeAllComponents();
		for (int i = 0; i < values.length; i++) {
			list.addComponent(createInputField(new ObjectProperty<String>(values[ i ]
)));
		}
	}

	private Component createInputField(Property ds)
	{
		final TextField tf = new TextField(ds);
		tf.setWidth("100%");
		tf.setInputPrompt(inputPrompt);
		tf.addListener(new ValueChangeListener() {
			@Override
			public void valueChange(com.vaadin.data.Property.ValueChangeEvent event) {
				updateValues();
			 }
		});
		
		HorizontalLayout hl = new HorizontalLayout();
		hl.setMargin(false);
		hl.setSpacing(true);
		hl.setWidth("100%");
		hl.addComponent(createRemoveButton(new Integer(list.getComponentCount())));
		hl.addComponent(tf);
		hl.setExpandRatio(tf, 1);

		return hl;
	}
	
	private Button createRemoveButton(Object data)
	{
		Button b = new Button("x");
		b.setData(data);
		b.setStyleName(Reindeer.BUTTON_LINK);
		b.setDescription("Delete value");
		b.setImmediate(true);
		b.addListener(new ClickListener() {
			@Override
			public void buttonClick(ClickEvent event) {
				Integer itemId = (Integer)event.getButton().getData();
				removeValue(itemId.intValue());
 			}
		});
		return b;
	}
	
	private void removeValue(int index)
	{
		// Cannot leave the value empty
		if (list.getComponentCount() == 1) {
			getPropertyDataSource().setValue(new String[] { "" });
			return;
		}

		String[] oldValues = (String[]
)getPropertyDataSource().getValue();
		String[] newValues = new String[oldValues.length - 1]
;
		// Copy items before the removed index
		if (index > 0) {
			System.arraycopy(oldValues, 0, newValues, 0, index - 1);
		}
		// COpy items after the removed index
		if (index < oldValues.length - 1) {
			System.arraycopy(oldValues, index + 1, newValues, index, oldValues.length - index - 1);
		}
		getPropertyDataSource().setValue(newValues);
	}
	
	private void updateValues()
	{
		String[] newValues = new String[list.getComponentCount()]
;
		for (int i = 0; i < list.getComponentCount(); i++) {
			HorizontalLayout hl = (HorizontalLayout)list.getComponent(i);
			newValues[ i ]
 = (String)((TextField)hl.getComponent(1)).getValue();
		}
		getPropertyDataSource().setValue(newValues);
	}
	
	public void setInputPrompt(String prompt)
	{
		this.inputPrompt = prompt;
		for (int i = 0; i < list.getComponentCount(); i++) {
			HorizontalLayout hl = (HorizontalLayout)list.getComponent(i);
			((TextField)hl.getComponent(1)).setInputPrompt(prompt);
		}
	}
	
	public String getInputPrompt()
	{
		return inputPrompt;
	}
}

One thing I’m not clear is where/when I should repaint the components (within TextFieldList). Right now I’m doing it in setInternalValue() but I don’t know if that is a problem.

It seems to me that the Table component has problems with resizing.
I’m sorry, but I can’t help you any further.

I have a table, where one of the columns contains a horizontal layout.

I created an event handler as follows, and it seems to work. When “requestRepaint” is called, the horizontal layout and all its container/ancestors seem to redraw themselves as well.


public class MyClass extends HorizontalLayout implements FocusListener
{
//... during initialization, I add a focus listener to an inner component -- this is a simple example, not a practical one.
	public void focus(FocusEvent event)
	{
		this.setHeight(this.getHeight() + 1, this.getHeightUnits());
		this.requestRepaint();
	}
//...
}