[Table] Next cell loses focus on Tab with ColumnFooter

Hi all :slight_smile:

I use a Table component. I set a Property.ValueChangeListener on each TextField in the Table (with setImmediate=true). The listener calculates the sum of each field in the row and store it in a generated column called “Total”. The listener calculates also the footer balance for each column.

My problem : when I select a cell, change the value and press Tab, I lose the focus on the next cell and I had to focus manually a second time.

The listener of each TextFields of the table :

final Property.ValueChangeListener listener = new Property.ValueChangeListener() {

	public void valueChange(Property.ValueChangeEvent event) {
		double sum = 0;

		BudgetFormDTO budget = (BudgetFormDTO) ((TextField) event
				.getProperty()).getData();
		sum = budget.getInitialization() + budget.getConception()
				+ budget.getImplementation() + budget.getIntroduction();

		for (int i = 0; i < totals.size(); i++)
			if (totals.get(i).getData().equals(budget))
				totals.get(i).setValue(sum);
				
		// Footer
		calculateFooter();				
	}
};

INFO: The “budget” variable is the beanItem of the row.
INFO: If I comment the line “calculateFooter();”, I haven’t this problem and the next cell is correctly focused with Tab key.

The calculateFooter() method :

protected void calculateFooter() {

		Double balance=0.0;
		Double totalBalance=0.0;
		
		// Footer "name"
		this.setColumnFooter("name", "Solde");
		
		// Footer "initialization"
		balance = calculateSolde(budgetMandate.getInitialization(), budgetRevised.getInitialization(), 
								 budgetSpent.getInitialization(), budgetEngaged.getInitialization());
		totalBalance += balance;
		this.setColumnFooter("initialization", String.valueOf(balance));
		
		// Footer "conception"
		balance = calculateSolde(budgetMandate.getConception(), budgetRevised.getConception(), 
				 budgetSpent.getConception(), budgetEngaged.getConception());
		totalBalance += balance;
		this.setColumnFooter("conception", String.valueOf(balance));
		
		// Footer "implementation"
		balance = calculateSolde(budgetMandate.getImplementation(), budgetRevised.getImplementation(), 
				 budgetSpent.getImplementation(), budgetEngaged.getImplementation());
		totalBalance += balance;
		this.setColumnFooter("implementation", String.valueOf(balance));
		
		// Footer "introduction"
		balance = calculateSolde(budgetMandate.getIntroduction(), budgetRevised.getIntroduction(), 
				 budgetSpent.getIntroduction(), budgetEngaged.getIntroduction());
		totalBalance += balance;
		this.setColumnFooter("introduction", String.valueOf(balance));
		
		// Footer "total"
		this.setColumnFooter(BUDGET_TOTAL_COLUMNNAME, String.valueOf(totalBalance));	
	
	}

The TotalColumnGenerator inner class :

class TotalColumnGenerator implements Table.ColumnGenerator {

	private static final long serialVersionUID = -3307604339645408677L;

	private List<Label> totals;

	public TotalColumnGenerator(List<Label> totals) {
		this.totals = totals;
	}

	public Component generateCell(Table source, Object itemId,
			Object columnId) {

		if (columnId.equals(BUDGET_TOTAL_COLUMNNAME)) {
			Label ltemp = new Label();
			ltemp.setWidth("100%");
			BudgetFormDTO budget = (BudgetFormDTO) itemId;
			ltemp.setData(budget);

			totals.add(ltemp);

			if (budget != null)
				ltemp.setValue(budget.getInitialization()
						+ budget.getConception()
						+ budget.getImplementation()
						+ budget.getIntroduction());
				
			return ltemp;
		}
		return null;
	}
}

So I think I can find a workaround but I need your opinion before that.
Thank you in advance.
Best regards,
Jachen.

Hi,

I try to reproduce my problem with the sample wrote by Marko Grönroos (
http://magi.virtuallypreinstalled.com/book-examples/book/?restartApplication#component.table.headersfooters.footers.footer-sum
and unfortunately the table also lost the focus after update the footer value if I use Tab or ENTER key.

To reproduce, get the Marko’s sample and remove the ActionHandler. I use vaadin 6.4.8 and Firefox 3.6 / Chrome 7.

Code:

// Have a table with a numeric column
final Table table = new Table("Roster of Fear");
table.addContainerProperty("Source of Fear", String.class, null);
table.addContainerProperty("Fear Factor", Double.class, null);
        
// Put the table in edit mode to allow editing the fear factors
table.setEditable(true);
        
// Insert some data
final Object fears[][]
 =  {{"Psycho",       8.7},
                           {"Alien",        8.5},
                           {"The Shining",  8.5},
                           {"The Thing",    8.2}};
for (int i=0; i<fears.length; i++)
    table.addItem(fears[i]
, new Integer(i));
        
// Set the footers
table.setFooterVisible(true);
table.setColumnFooter("Source of Fear", "Sum of All Fears");

// Calculate the sum every time any of the values change
final Property.ValueChangeListener listener =
    new Property.ValueChangeListener() {
    public void valueChange(ValueChangeEvent event) {
        // Calculate the sum of the numeric column
        double sum = 0;
        for (Iterator<?> i = table.getItemIds().iterator(); i.hasNext();)
            sum += (Double) table.getItem(i.next()).
                                    getItemProperty("Fear Factor").getValue();
        sum = Math.round(sum*100)/100.0; // Round to two decimals
        
        // Set the new sum in the footer
        table.setColumnFooter("Fear Factor", String.valueOf(sum));
    }
};
        
// Can't access the editable components from the table so
// must store the information
final HashMap<Integer,TextField> valueFields =
    new HashMap<Integer,TextField>();

// Set the same listener of all textfields, and set them as immediate 
table.setTableFieldFactory(new TableFieldFactory () {
	@Override
	public Field createField(Container container, Object itemId,
            Object propertyId, Component uiContext) {
        TextField field = new TextField((String) propertyId);
        
        // User can only edit the numeric column
        if (propertyId.equals("Source of Fear"))
            field.setReadOnly(true);
        else { // The numeric column
            field.setImmediate(true);
            field.addListener(listener);
            field.setColumns(7);
            
            // The field needs to know the item it is in
            field.setData(itemId);
            
            // Remember the field
            valueFields.put((Integer) itemId, field);
            
            // Focus the first editable value
            if (((Integer)itemId) == 0)
                field.focus();
        }
        return field;
    }
});
        
		        
// Panel that handles keyboard navigation
Panel navigator = new Panel();
navigator.addComponent(table);
//navigator.addActionHandler(new KbdHandler());
        
// Adjust the table height a bit
table.setPageLength(table.size());	

Is that a bug or a feature ? :stuck_out_tongue:

I found a workaround to this problem, I share it with you. Tested and approuved with Firefox 3.6 and Chrome 9.
You have to declare a new FocusListener and do a selectAll() on the sender Textfield. See below :

/ Have a table with a numeric column
final Table table = new Table("Roster of Fear");
table.addContainerProperty("Source of Fear", String.class, null);
table.addContainerProperty("Fear Factor", Double.class, null);
        
// Put the table in edit mode to allow editing the fear factors
table.setEditable(true);
        
// Insert some data
final Object fears[][]
 =  {{"Psycho",       8.7},
                           {"Alien",        8.5},
                           {"The Shining",  8.5},
                           {"The Thing",    8.2}};
for (int i=0; i<fears.length; i++)
    table.addItem(fears, new Integer(i));
        
// Set the footers
table.setFooterVisible(true);
table.setColumnFooter("Source of Fear", "Sum of All Fears");

// Calculate the sum every time any of the values change
final Property.ValueChangeListener listener =
    new Property.ValueChangeListener() {
    public void valueChange(ValueChangeEvent event) {
        // Calculate the sum of the numeric column
        double sum = 0;
        for (Iterator<?> i = table.getItemIds().iterator(); i.hasNext();)
            sum += (Double) table.getItem(i.next()).
                                    getItemProperty("Fear Factor").getValue();
        sum = Math.round(sum*100)/100.0; // Round to two decimals
        
        // Set the new sum in the footer
        table.setColumnFooter("Fear Factor", String.valueOf(sum));
    }
};

[b]
// Focus listener declaration
final FocusListener focusListener = new FocusListener() {

	@Override
	public void focus(FocusEvent event) {
		
		// Workaround to avoid loss of focus 
		TextField focusedTextField = (TextField) event.getSource();
		focusedTextField.selectAll();
	}
};
[/b]
        
// Can't access the editable components from the table so
// must store the information
final HashMap<Integer,TextField> valueFields =
    new HashMap<Integer,TextField>();

// Set the same listener of all textfields, and set them as immediate 
table.setTableFieldFactory(new TableFieldFactory () {
    @Override
    public Field createField(Container container, Object itemId,
            Object propertyId, Component uiContext) {
        TextField field = new TextField((String) propertyId);
        
        // User can only edit the numeric column
        if (propertyId.equals("Source of Fear"))
            field.setReadOnly(true);
        else { // The numeric column
            field.setImmediate(true);
            field.addListener(listener);
[b]
            field.addListener(focusListener); 
[/b]
            field.setColumns(7);
            
            // The field needs to know the item it is in
            field.setData(itemId);
            
            // Remember the field
            valueFields.put((Integer) itemId, field);
            
            // Focus the first editable value
            if (((Integer)itemId) == 0)
                field.focus();
        }
        return field;
    }
});
        
                
// Panel that handles keyboard navigation
Panel navigator = new Panel();
navigator.addComponent(table);
//navigator.addActionHandler(new KbdHandler());
        
// Adjust the table height a bit
table.setPageLength(table.size());    

Thank you for this solution. It looks a bit weird. I think the browser executes the tab event immediately but the page is updated later, as an Ajax call is done. Thus the focus get lost. You have the focus again.

The solution of Marko Grönroos has also a problem:

java.lang.NullPointerException
at com.vaadin.book.examples.component.TableExample$1KbdHandler.handleAction(TableExample.java:930)
at com.vaadin.event.ActionManager.handleAction(ActionManager.java:228)
at com.vaadin.event.ActionManager.handleActions(ActionManager.java:198)
at com.vaadin.ui.Panel.changeVariables(Panel.java:346)
at com.vaadin.terminal.gwt.server.AbstractCommunicationManager.handleVariableBurst(AbstractCommunicationManager.java:1296)
at com.vaadin.terminal.gwt.server.AbstractCommunicationManager.handleVariables(AbstractCommunicationManager.java:1216)
at com.vaadin.terminal.gwt.server.AbstractCommunicationManager.doHandleUidlRequest(AbstractCommunicationManager.java:732)
at com.vaadin.terminal.gwt.server.CommunicationManager.handleUidlRequest(CommunicationManager.java:296)
at com.vaadin.terminal.gwt.server.AbstractApplicationServlet.service(AbstractApplicationServlet.java:483)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:717)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:290)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:233)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:191)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:128)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:102)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:109)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:286)
at org.apache.jk.server.JkCoyoteHandler.invoke(JkCoyoteHandler.java:190)
at org.apache.jk.common.HandlerRequest.invoke(HandlerRequest.java:283)
at org.apache.jk.common.ChannelSocket.invoke(ChannelSocket.java:767)
at org.apache.jk.common.ChannelSocket.processConnection(ChannelSocket.java:697)
at org.apache.jk.common.ChannelSocket$SocketConnection.runIt(ChannelSocket.java:889)
at org.apache.tomcat.util.threads.ThreadPool$ControlRunnable.run(ThreadPool.java:690)
at java.lang.Thread.run(Thread.java:619)

The events are captured to slow, as you keep on pressing the Tab key, the foucs goes to the Labels. If you press enter, the exception above is thrown.

Yes you’re right, this is ugly. But this is the only solution I found without modifying vaadin source code.

For the solution of Marko Grönroos I cannot help you, I don’t use handleAction, I use this code only for the footer utilisation sample.