0
votes

Consider a GWT application whose UI has tabs. In one tab there are a few fixed size elements, followed by a DataGrid.

 _______________________________________________
|       ______                                  |
| Tab1 / Tab2 \ Tab3                            |
|_____/        \________________________________|
| Fixed size widgets here.                   || |
| Fixed size widgets here.                   || |
| ----------------------                     || |
| Datagrid headings         tab scrollbar -->|| |
| cell cell cell cell ||                     || |
| cell cell cell cell ||                     || |
| cell cell cell cell ||<-- datagrid         || |
| cell cell cell cell ||    scrollbar        || |
| cell cell cell cell ||                     || |
| cell cell cell cell ||                     || |
 -----------------------------------------------

I want the DataGrid to fill the remaining height in the window, and to change height if the browser window is resized, but with the constraint that it does not get smaller than a specified minimum height (say, 3 rows, not including headings). In this application, there are usually only about 10 rows in the data grid.

There are two scrollbars. I want them to behave like this:

The datagrid scrollbar allows navigation of the rows in the datagrid. It should not be displayed if all rows in the datagrid can be seen.

The tab scrollbar scrolls the contents of the tab, leaving the tab selectors visible. Usually this is hidden, because the datagrid is contained entirely within the browser window, and the datagrid scrollbar can be used to view the contents. However, the datagrid has a minimum height, and if there is not room for this in the browser window, then the tab scrollbar will be visible.

My question is: how do I achieve this?

I have some sample code. First, the module is loaded like this:

public class Proj implements EntryPoint {
  public void onModuleLoad() {
      RootLayoutPanel.get().add(new App());
      //RootPanel.get("app").add(new App());
  }
}

I think I need the RootLayoutPanel version rather than the RootPanel, but I'd welcome comments on that.

Now some UiBinder code:

<!DOCTYPE ui:UiBinder SYSTEM "http://dl.google.com/gwt/DTD/xhtml.ent">
<ui:UiBinder xmlns:ui="urn:ui:com.google.gwt.uibinder"
    xmlns:g="urn:import:com.google.gwt.user.client.ui"
    xmlns:d="urn:import:com.google.gwt.user.cellview.client"
    >
    <ui:style>
        .foo { 
        min-height: 100px; 
        max-height: 300px; 
        }

    </ui:style>
    <g:TabLayoutPanel ui:field="tabLayout" barUnit="PX" barHeight="30" >
        <g:tab>
            <g:header> Tab One </g:header>
            <g:ScrollPanel height="100%"> <!-- Hack? height="100%":-->
                <g:FlowPanel>
                    <g:Label text="Window.getClientHeight():"/>
                    <g:Label ui:field="label" text="Window.getClientHeight()"/>
                    <g:Label text="grid.getElement().getAbsoluteTop():"/>
                    <g:Label ui:field="label2" text="grid.getElement().getAbsoluteTop()"/>
                    <d:DataGrid styleName='{style.foo}' ui:field="grid"/>
                </g:FlowPanel>
            </g:ScrollPanel>
        </g:tab>
    </g:TabLayoutPanel>
</ui:UiBinder>

and finally the definition of class App:

public class App extends ResizeComposite {
    @UiTemplate("App.ui.xml")
    interface ThisUiBinder extends UiBinder<Widget, App> {
    }
    private static ThisUiBinder uiBinder = GWT.create(ThisUiBinder.class);
    @UiField
    TabLayoutPanel tabLayout;
    @UiField
    Label label;
    @UiField
    Label label2;
    @UiField (provided=true)
    DataGrid grid;

    static class Data {
        String foo;
        public Data(String foo) {
            this.foo = foo;
        }
    }
    private static final List<Data> DATA = Arrays.asList(
            new Data("A"), new Data("Q"), new Data("w"),
            new Data("e"), new Data("r"), new Data("t"),
            new Data("y"), new Data("u"), new Data("i"),
            new Data("o")
            );
    private final int MINSIZE = 100;    // of grid, in px

    public App() {
        grid = new DataGrid<Data>();
        TextColumn<Data> fooColumn = new TextColumn<Data>() {
            @Override
            public String getValue(Data object) {
                return object.foo;
            }
        };
        grid.addColumn(fooColumn, "foo");
        grid.setRowCount(DATA.size(), true);
        grid.setRowData(0, DATA);

        initWidget(uiBinder.createAndBindUi(this));
        //grid.setHeight("100%");   // hack, 100% of what?
        //tabLayout.setHeight("100%");  // hack, 100% of what?
        onResize(); // Hack!
    }
    public void onResize() {
        label.setText(String.valueOf(Window.getClientHeight()));

        // Note that this is 0 on the first call (from our constructor),
        // which does not give the desired result!
        label2.setText(String.valueOf(grid.getElement().getAbsoluteTop()));

        // Works very badly! Jumpy and eratic!
        int newsize = Window.getClientHeight() - grid.getElement().getAbsoluteTop();
        if (newsize > MINSIZE) {
            grid.setHeight(String.valueOf(newsize)) + "px");
        }
    }
}

The mechanism in onResize() not only smells bad to me (it was implemented as an experiment), but it also works laughably badly.

Is there a neat way to do this purely in CSS? Or does something have to be done in onResize()? One of my motivations for using GWT is to avoid problems caused by variations between browsers, so is pure CSS the right way to go?

I have set height to 100% in various places as a hack, to see what difference it makes. I would dearly love to understand properly how this all hangs together - any reading suggestions would be most welcome. I have also tried following the guidelines at http://www.dave-woods.co.uk/100-height-layout-using-css/, e.g. using the CSS:

html, body {
    height: 100%;
}

I also found this informative: https://stackoverflow.com/a/11866677/685715. But it didn't fix my problem.

2

2 Answers

0
votes

You have three options:

  1. Use a ResizeHandler attached to your Window. Calculate and set the size of your DataGrid when a window is resized.

  2. Use a LayoutPanel (or VerticalPanel0 inside your tab. Set the fixed width widgets, and then set the DataGrid widget:

    myLayoutPanel.setWidgetTopBottom(myDataGrid, 120, Unit.PX, 0, Unit.PX);
    

This solution does not require a resize handler, but introduces an extra widget (i.e. HTML code) to your layout.

  1. Use flexbox layout model (http://css-tricks.com/snippets/css/a-guide-to-flexbox/) if you do not care about the old browsers. This is a pure CSS solution.
0
votes

Assuming the height of the static widgets above the Datagrid is constant and known beoforehand I would do it this way:

UiBinder code:

<!DOCTYPE ui:UiBinder SYSTEM "http://dl.google.com/gwt/DTD/xhtml.ent">
<ui:UiBinder xmlns:ui="urn:ui:com.google.gwt.uibinder"
    xmlns:g="urn:import:com.google.gwt.user.client.ui"
    xmlns:d="urn:import:com.google.gwt.user.cellview.client"
    >
    <ui:style>
        .foo { 
        min-height: [HEIGHT OF STATIC WIDGETS + 100px]; 
        max-height: [HEIGHT OF STATIC WIDEGET + 300px; 
        }

    </ui:style>
    <g:TabLayoutPanel ui:field="tabLayout" barUnit="PX" barHeight="30" >
        <g:tab>
            <g:header> Tab One </g:header>
            <g:ScrollPanel >
                <g:LayoutPanel addStyleNames="{style.foo}">
                    <g:layer top="0" height="[HEIGHT OF STATIC WIDGETS]">
                        <g:FlowPanel> 
                          <g:Label text="Window.getClientHeight():"/>
                          <g:Label ui:field="label" text="Window.getClientHeight()"/>
                          <g:Label text="grid.getElement().getAbsoluteTop():"/>
                          <g:Label ui:field="label2" text="grid.getElement().getAbsoluteTop()"/>
                         </g:FlowPanel>
                    </g:layer>
                    <g:layer top="[HEIGHT OF STATIC WIDGETS] bottom="0">
                        <d:DataGrid  ui:field="grid"/>
                    </g:layer>
               </g:LayoutPanel>
            </g:ScrollPanel>
        </g:tab>
    </g:TabLayoutPanel>
</ui:UiBinder>

This should work purely with UiBinder code (no java changes or size calculations are necessary).

Of course this only works if the total height of your fixed sized widgets above the DataGrid are know (just replace the "[HEIGHT OF STATIC WIDGETS]" with the corresponding size.

Alternatively you can also try to wrap your DataGrid inside a ResizeLayoutPanel which will provide explicit sizes to the DataGrid and doesn't require an unbroken chain of LayoutPanels up to the RootLayoutPanel. However I am not sure if this work with min-height on the DataGrid. You might want to move that to the ResizeLayoutPanel itself.