Freeze table column

Hi!
I’m using Table component and excellent TableTree component. Is it possible to freeze first column in tables. If I have more then 10 columns I have to move/slide table scroller to the right. But then you cannot see first column no more.
Any ideas? Perhaps with CSS?
Thank you all for your help,
Janez

So at this moment I’m trying something with css, adding position:fixed for first column, and then margin-left for second column. It somehow worked, but not good for now. I’ll keep on searching for solution…

I’m almost there. I have a HorizontalSplitPanel now with left (freezed) columns and right component as a table. Now I need only top or bottom scroller for right component.

package com.example.tzui;

import com.vaadin.Application;
import com.vaadin.data.Item;
import com.vaadin.data.util.HierarchicalContainer;
import com.vaadin.data.util.IndexedContainer;
import com.vaadin.terminal.Sizeable;
import com.vaadin.ui.HorizontalSplitPanel;
import com.vaadin.ui.Panel;
import com.vaadin.ui.Table;
import com.vaadin.ui.VerticalLayout;
import com.vaadin.ui.Window;

public class TzuiApplication extends Application {
	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;

	@Override
	public void init() {

		Panel panel = new Panel(new VerticalLayout());
		panel.setWidth("100%");
		panel.setHeight("500");

		Table left = new Table();
		left.setContainerDataSource(getContainer(2));
		left.setPageLength(0);
		Table right = new Table();
		right.setContainerDataSource(getContainer(30));
		right.setPageLength(0);
		right.setSelectable(true);

		HorizontalSplitPanel hsp = new HorizontalSplitPanel();
		hsp.setFirstComponent(left);

		hsp.addComponent(right);
		hsp.setSplitPosition(100, Sizeable.UNITS_PIXELS);
		hsp.setLocked(true);
		panel.addComponent(hsp);

		Window window = new Window();
		window.addComponent(panel);

		setMainWindow(window);

	}

	public static IndexedContainer getContainer(int colCount) {
		HierarchicalContainer localeContainer = new HierarchicalContainer();
		for (int i = 0; i < colCount; i++)
			localeContainer.addContainerProperty("int" + i, String.class, null);
		String[] strings = new String[30]
;
		for (int i = 0; i < 30; i++)
			strings[i]
 = "str" + i;
		for (int i = 0; i < 30; i++) {
			Item item = localeContainer.addItem(i);
			for (int j = 0; j < colCount; j++)
				item.getItemProperty("int" + j).setValue(strings[j]
);
		}

		return localeContainer;
	}

}

Any ideas?
thnx,
Janez

If the table doesn’t have very many rows, you could have two tables horizontally, with the left table showing the first column and second table the rest. Use setPageLength(table.size()) to disable vertical scrollbars. The rows in both tables must have exactly the same height. Use CSS to join the two tables together visually.

If you want vertical scrolling, put the HorizontalLayout inside a Panel.

This disables all lazy loading, so you should make the table as light as possible (no components inside, just plain text columns). It will probably get heavy if there are more than a 100 or so rows.

Yeah, it’s hackish.

Ah ok, this solution is similar to what I suggested. I should have read the whole thread first.

Marko, thank you for your ideas. Yes I’have tried that but unfortunately I have more than a few rows:) I have this another idea. I will use PagedTable so that I do need to syncronize vertical scrollbars. I will post this code I hope today.


Yes!
I have found a solution. It’s a little bit ‘hackish’ but anyway.
I have extended
Table
class with two differences:


  • String scrollablePanelId
    added in constructor(s),

  • paintContent
    method adds this attribute into
    target
    attribute.
    Second I have extended
    VScrollableTable
    class which method
    updateFromUIDL
    reads this
    scrollablePanelId
    attribute. After that it sets the
    ID
    of
    scrollableBodyPanel
    and adds new attribute to
    scrollableBodyPanel
    named
    onScroll
    . See code:
scrollablePanelId = uidl.getStringAttribute("scrollablePanelId");
scrollBodyPanel.getElement().setId(scrollablePanelId);
scrollBodyPanel.getElement().setAttribute("onScroll", "synchronizeScroll();");

As you can see the last line defines that
scrollBodyPanel
has
onScroll
listener, and when you move scrollbars javascript method
synchronizeScroll
is called.
Now comes the ugly part. I have to add
synchronizeScroll
javascript method to main html so I need to extend
ApplicationServlet
and add this piece of code:

page.write("<script language=\"javascript\">" +
				"function synchronizeScroll() {" +
				"	document.getElementById('scrollerLeft').scrollTop=document.getElementById('scrollerRight').scrollTop;" +
				"}" +
				"</script>");

Those
scrollerLeft
and
scrollerRight
are div’s which are now
synchronized
. When you scroll the right one scroll position of the left one is updated. Now you can play with the code to have simultaneous scrolling, horizontal scrolling, etc…, without server-side hops.
Next step for me is doing the same on
TreeTable
.

Hi,

Great that you found a workaround. But as you said, that is very hackish.

Just to make not so hackish, I suggest you do the following:

There’s a special attribute that you can send from paintContent to the client: an external component reference. This allows you to connect to server side components also on the client side, so you don’t need to send your own generated ID to the client and (see this method:
PaintTarget.java#L253
).

paintContent(PaintTarget target) {
   ...
   target.addAttribute("scrollPane", myScrollableTable);
   ...
}

Then in the client you can do this:

updateFromUIDL(...) {
   super.updateFromUIDL(uidl, client);
   String id = uidl.getStringAttribute("scrollPane");

   VScrollTable scrollPane = (VScrollTable) client.getPaintable(id);
   ScrollPanel scrollBody = (ScrollPanel) scrollPane.getWidget(1);

   scrollBody.addScrollHandler(new ScrollHandler() {
      public void onScroll(ScrollEvent event) {
         VMyComponent.this.onScroll(event);
      }
   });
}

This way you can then get rid of the ugly JavaScript hack by using GWT’s own listener mechanism, which will prevent memory leaks for instance. Another way to simplify things, so that you don’t need to extend ApplicationServlet, is to use GWT’s JSNI (native JavaScript bridge) and add your custom JS in there (so you can keep your hacks isolated in one class/place).

Final note: I didn’t test the above in any way (other than it compiles), so I’m not totally sure it works correctly. The part that is the most volatile in this hack is the “getWidget(1)” part, since it relies on the internal implementation of VScrollTable (the order of added widgets).

Thank you Jouni,
your suggestion is better than my solution and I don’t have to do that JavaScript ‘hacking’ no more.
I have followed your advice so now I have extended
Table
component with overriden
paintContent
method. I want to have synchronized scrolling so I have added
setDependentTable
method which only sets internal
Table
field. Like this:
[code]
public void setDependentTable(MyTable table) {
this.table = table;
}

@Override
public void paintContent(PaintTarget target) throws PaintException {
	target.addAttribute("scrollPane", this);
	if (table != null)
		target.addAttribute("dependentTable", table);
	super.paintContent(target);
}

[/code]
The client
VScrollTable
is also extenended and the method
updateFromUIDL
is overriden like this:

super.updateFromUIDL(uidl, client);
		final String tableId = uidl.getStringAttribute("dependentTable");
		if (tableId != null) {
			String id = uidl.getStringAttribute("scrollPane");
			VScrollTable scrollPane = (VScrollTable) client.getPaintable(id);
			final FocusableScrollPanel scrollBody = (FocusableScrollPanel) scrollPane.getWidget(1);
			scrollBody.addScrollHandler(new ScrollHandler() {
				public void onScroll(ScrollEvent event) {
					VMyScrollTable.this.onScroll(event);
					VScrollTable dependentPane = (VScrollTable) client.getPaintable(tableId);
					FocusableScrollPanel scrollToBeBody = (FocusableScrollPanel) dependentPane.getWidget(1);
					scrollToBeBody.setScrollPosition(scrollBody.getScrollPosition());
				}
			});
		}

And it works. Both tables are now synchronized for scrolling up and down. No JavaScript hacks. That
getWidget(1)
could be avoided with minimal fix in
VScrollTable
class, we need only one getter method.

So now I have a table with freezed columns (on the left side) and I can scroll this table up and down.
Thank you for help, ideas and for clearing me how Vaadin widgets (client-server) works.
Janez

hello

have you create this frozen column as widget add-on for vaadin ?

best regards

Fred

This is very interesting, but I fear I have failed to implement your solution. I suppose that’s because I try and read too fast the Book of Vaadin and other blog posts… Or the information is perhaps just missing.

What I tried to do: in my test project, I just added a SplitTable class, extending Table. Then I added a VSplitTable component, extending VScrollTable. I declared @ClientWidget(VSplitTable.class) in the former.
The VSplitTable wasn’t recognized…

So I did a more sensible setup, moving SplitTable to a widget.splittable sub-package of my project, and VSplitTable to widget.splittable.client.ui
I also put a SplitTableWidgetset.gwt.xml next to SplitTable.java
If I recompile my widgetset, I get an error message:

Multiple widgetsets in project CurrentProjetName. Select a widgetset file (…widgetset.gwt.xml) to compile.

So, I just remove the .gwt.xml file. The compilation runs, but I have a warning:

[WARN]
Widget class xxx.widget.splittable.client.ui.VSplitTable was not found. The component xxx.widget.splittable.SplitTable will not be included in the widgetset.

So, it just doesn’t work.

Does that mean that even for a “simple” existing component override, I have to create a separate project to make the custom component?
I don’t have a major problem with this, but that’s something that doesn’t stand out clearly from the various sources I searched today…

Mmm, I tried to use the widget wizard, but the first time, it went with a weird error “No widgetset in project.”, perhaps because I was not in the right place in Eclipse when I started the wizard (it asked me for the path).

The second time, it picked the current project, and created dummy widgets. The server-side component in the place I indicated it in the project, the client-side one in widgetset.client.ui where widgetset was created previously to hold the project’s widgetset.gwt.xml file.

I pasted the code above in both files, and after recompiling the widgetset, it worked.
So, it is easier than I feared to add some custom widget extending existing widgets.

Cool philippe B)

I have also difficulty to make frozen column , i cannot wait to see your widget :stuck_out_tongue:

Good, I was about to summarize my experience anyway.

To be clearer: I was trying to add such widget to an existing project, based on the one hour (I’d say more like 1 day -_-, but whatever…) tutorial. So I just use the containers defined in this project.
My problem was that I was trying to reproduce the directory structure given at
Chapter 11 of the Book
, figure 11.1., in a sub-package of this project. Ie. my application was at xxx.tutorials, and I manually reproduced this hierarchy at xxx.widget.
I failed with messages complaining to have several widgetsets. These messages are a bit confusing as I wasn’t sure if they referred to the widgetset folder, the file, or something else…

I finally removed my directory, and could run the widget creation wizard in Eclipse. After specifying I wanted it in xxx.widget.splittable (but perhaps I should have put it just at xxx.widget, as we often have only one file), and that I wanted to override com.vaadin.ui.Table instead of default com.vaadin.ui.AbstractComponent, I saw it created the SplitTable.java in my specified package, and VSplitTable.java in xxx.tutorial.widgetset.client.ui, where widgetset was previously automatically created by the project creation wizard to hold my VaadintutorialWidgetset.gwt.xml (also automatically generated/managed).

I hope it is a bit clearer, now. My error was to try to reproduce the settings of a standalone widget (to be packaged in a jar) in the context of a full application. I suppose that when you want to create a standalone widget, you first create a simple application (demo), then you add a widget to it, and you end with a structure similar to the one was see in existing add-ons.

It might be interesting if the Book mentioned this use case, as well as how to override an existing widget, both on server (common) and client (less common!) side.

To be more concrete, here are the main files in my test project:


TestBench.java

package xxx.tutorials;

import com.vaadin.Application;
import com.vaadin.ui.*;

import xxx.tutorials.data.PersonContainer;
import xxx.tutorials.ui.MainTable;
import xxx.widget.splittable.SplitTable;

@SuppressWarnings("serial")
public class TestBench extends Application
{
   private PersonContainer dataSource = PersonContainer.createWithTestData();

   @Override
   public void init()
   {
      final Window mainWindow = new Window( "Vaadin Test Bench" );
      setMainWindow( mainWindow );
      setTheme( "simple" );

      final HorizontalSplitPanel splitPanel = new HorizontalSplitPanel();
      mainWindow.setContent( splitPanel );

      final VerticalLayout left = new VerticalLayout();
      left.setWidth( "150px" );
      splitPanel.addComponent( left );

      Label label = new Label( "Hello Vaadin user" );
      left.addComponent( label );
      left.addComponent( new Label( "Glad to meet you!" ) );

      CssLayout layoutTables = new CssLayout();

      SplitTable splitTable = new SplitTable( this );
      MainTable mainTable = new MainTable( this );
      splitTable.setDependentTable( mainTable );
      layoutTables.addComponent( splitTable );
      layoutTables.addComponent( mainTable );
      splitTable.setWidth( "300px" );
      mainTable.setWidth( "800px" );
      splitTable.setStyleName( "m-float-left" );

      splitPanel.addComponent( layoutTables );
   }

   public PersonContainer getDataSource()
   {
      return dataSource;
   }
}



SplitTable.java

package xxx.widget.splittable;

import com.vaadin.data.util.BeanItemContainer;
import com.vaadin.terminal.PaintException;
import com.vaadin.terminal.PaintTarget;
import com.vaadin.ui.Table;

import xxx.tutorials.TestBench;
import xxx.tutorials.data.Person;
import xxx.tutorials.data.PersonContainer;
import xxx.tutorials.ui.MainTable;

@SuppressWarnings("serial")
@com.vaadin.ui.ClientWidget(xxx.tutorials.widgetset.client.ui.VSplitTable.class)
public class SplitTable extends Table
{
   private MainTable table;

   public SplitTable( TestBench app )
   {
      super();
      setContainerDataSource( app.getDataSource() );
      // Quick hack: just take one column of the main table's data
      setVisibleColumns( new Object[] { PersonContainer.NATURAL_COL_ORDER[3]
 } );
      setColumnHeaders( new String[] { PersonContainer.COL_HEADERS_ENGLISH[3]
 } );
   }

   public void setDependentTable( MainTable table )
   {
      this.table = table;
   }

   @Override
   public void paintContent( PaintTarget target ) throws PaintException
   {
      target.addAttribute( "scrollPane", this );
      if ( table != null ) target.addAttribute( "dependentTable", table );
      super.paintContent( target );
   }

   class HeaderContainer extends BeanItemContainer<Person>
   {
      private static final long serialVersionUID = 1L;

      public HeaderContainer() throws InstantiationException, IllegalAccessException
      {
         super( Person.class );
      }
   }
}



VSplitTable.java

package xxx.tutorials.widgetset.client.ui;

import com.google.gwt.event.dom.client.ScrollEvent;
import com.google.gwt.event.dom.client.ScrollHandler;

import com.vaadin.terminal.gwt.client.ApplicationConnection;
import com.vaadin.terminal.gwt.client.UIDL;
import com.vaadin.terminal.gwt.client.ui.FocusableScrollPanel;
import com.vaadin.terminal.gwt.client.ui.VScrollTable;

public class VSplitTable extends VScrollTable
{
   public VSplitTable()
   {
      super();
   }

   @Override
   public void updateFromUIDL( final UIDL uidl, final ApplicationConnection client )
   {
      super.updateFromUIDL( uidl, client );
      final String tableId = uidl.getStringAttribute( "dependentTable" );
      if ( tableId == null )
         return;

      String id = uidl.getStringAttribute( "scrollPane" );
      VScrollTable scrollPane = (VScrollTable) client.getPaintable( id );
      final FocusableScrollPanel scrollBody = (FocusableScrollPanel) scrollPane.getWidget( 1 );
      scrollBody.addScrollHandler( new ScrollHandler()
      {
         @Override
         public void onScroll( ScrollEvent event )
         {
            VSplitTable.this.onScroll( event );
            VScrollTable dependentPane = (VScrollTable) client.getPaintable( tableId );
            FocusableScrollPanel scrollToBeBody = (FocusableScrollPanel) dependentPane.getWidget( 1 );
            scrollToBeBody.setScrollPosition( scrollBody.getScrollPosition() );
         }
      } );
   }
}



MainTable.java

package xxx.tutorials.ui;

import com.vaadin.ui.*;

import xxx.tutorials.TestBench;
import xxx.tutorials.data.PersonContainer;

@SuppressWarnings("serial")
public class MainTable extends Table
{
   public MainTable( TestBench app )
   {
      super();

      setContainerDataSource( app.getDataSource() );
      setVisibleColumns( PersonContainer.NATURAL_COL_ORDER );
      setColumnHeaders( PersonContainer.COL_HEADERS_ENGLISH );

      setSelectable( true );
      setImmediate( true );

      setColumnCollapsingAllowed( true );
      setColumnReorderingAllowed( true );
   }
}



simple/styles.css

.m-float-left
{
   float: left;
}

Very thank you much , il will test this and i will tell you soon if work for me

merci beaucoup -_-

I tried this in vaadin 7, but the functions are deprecated, any idea or suggestion how to make this work in vaadin 7+?

Hi,
Does Anyone know how to implement it in Vaadin 7+ ?

Regards,
Dawid