Interactive Grid column widths

Since the first Early Adopter release of APEX 5.1 I have seen a number of questions or complaints about how column widths are handled in Interactive Grid. I think the complaints may be based on misunderstandings of how things work. I hope to explain here everything you need to know about column widths in Interactive Grids. Still, I’m not 100% sure that in implementing Interactive Grid we got it all exactly right. You can let me know in the comments after reading this.

[Update 5.1.1 30-Mar-2017] This article has been updated to reflect patch release 5.1.1

First lets review how Interactive Reports handled the column width. Basically it didn’t. It used a table with CSS table-layout: auto. This means that the browser decides how wide each column is and it generally takes into consideration the data that is in all the rows and all the columns. The algorithm isn’t standardized so it is possible that different browsers do it differently. It is also a little less efficient since the browser has to see the whole table markup before it can settle on the column widths. For the most part people seemed pretty happy with how the browser did things. It gives more width to columns with lots of text and less width to columns with less text and tries to fit as best it can before giving up and adding a horizontal scroll bar (by being wider than its container). It would also wrap text that didn’t fit. This means that the column widths are different depending on which rows you are looking at. When paging through a report I think the changing widths are distracting. Some people wanted control over the width of Interactive Reports columns and if you search you can find lots of advice on this; some better than others.

But Interactive Grid (IG) is a different beast. It uses fixed table layout and lets the user adjust column widths. It also fixes the row height of all rows. The consistent fixed row height is often very nice when looking over many rows but it can be annoying if some cells have lots of data you want to see. We choose a fixed row height because it is often what you want, especially for editing, but also because it made many of the fancy things that IG does, such as frozen columns and scroll paging, easier to implement and more efficient. We hope to have an option in the future for variable height rows.

I know this is supposed to be about column widths but permit me to go on a row height tangent for a moment. The row height is set with CSS. When there are frozen columns there are separate tables for the frozen and not frozen columns so if the heights of rows in the two tables don’t match the rows get out of sync and it looks terrible and becomes unusable. The browser’s table rules seem to care more about showing what is in a cell than any rules about cell or row height or overflow settings. So keeping the row heights in sync is a shared responsibility. If you add markup to a column you need to make sure it isn’t taller than what the default IG CSS rules allow for. Or you can override the row height for the grid using CSS. More on this in a moment.

I made this very mistake in the Sample Interactive Grids app. If you go to the Reporting: Icon and Detail Views page and freeze a column you will see that the heights don’t match because of the button. The button padding plus the cell padding is too much. (If you are reading this far enough in the future the problem may be fixed.) If you add your own buttons you should add class has-button to the column Appearance: CSS Classes attribute. This takes away the top and bottom cell padding and could be used for other cell markup; not just buttons. This should work with buttons using the t-Button* classes but it is still a little off. I found that using the following for the button class worked much better:

a-Button a-Button--icon a-Button--noLabel a-Button--action js-actionButton

Lets say you have a column that generally contains a lot of text that you want to see more than just one line but not necessarily all of it. You can make all the rows a little higher and let the text wrap for just that column. Here is how.

Its nice to target the CSS rules just to the grid they apply to. So give your IG region a Static ID. You could instead use a class. If you want to follow along you can modify the Icon and Detail Views page of Sample Interactive Grids app. It already has a static id of emp for the grid.

Select the NOTES column and change the Type to HTML Expression. Then enter the following for the HTML Expression attribute:

<div class="wrap-cell">&NOTES.</div>

Add the following to the page CSS Inline attribute.

#emp .a-GV-cell {
    height: 80px;
}
.wrap-cell {
    max-height: 64px;
    white-space: normal;
    overflow: hidden;
}

This sets the fixed height to 80px. The reason for wrapping the notes column in a div is that if we just turned off white-space: nowrap; for the cell the table layout rules would show all the text no matter how much making the row arbitrarily high.

This shows how simple it is to customize the height of IG rows. Setting the row height can be used for other purposes such as if the report contains thumbnail images. The technique for letting text wrap relies on using an HTML Expression so precludes editing that column.

[Update 5.1.1] If you want to use markup for the cell content while in navigation mode but still allow editing the cell value you can use the cellTemplate column option. For the Notes column change the type back to Textarea and in the column attribute Advanced: JavaScript Code enter:

function(config) {
    config.defaultGridColumnOptions = {
        cellTemplate: '<div class="wrap-cell">&NOTES.</div>'
    };
    return config;
}

While on the topic of cell editing and row height I should explain that the decision to edit the cell in place or as a popup is based on the height of the edit control compared to the height of the row. If you increase the row height things such as textareas or radio groups that are normally popups may now be edited inline. This may or may not be desired. For a textarea you can use the Height attribute to make it taller than the row so that it will be a popup or you can use CSS to make it fit exactly in the cell. For example add these CSS rules to the page.

#C_NOTES { /* give the column a static id */
    height: 60px; /* leave room for padding */
    width: 100%;
}

You may also want to take a look at the Sample Interactive Grids Advanced: Tooltips page to see how the full text of a cell can be displayed in a tooltip.

Now back to column widths. The first point of confusion is the Appearance: Width attribute on column types such as Text Field. This does not affect the column width. As the help text says, it is the “width of the form element” so it affects the width of the input element just like it does for a page item. But when editing in the grid it is more important for the width to match the column than what is specified for Width attribute. So the only place the column’s Width attribute comes into play is in Single Row View.

The important thing to realize is that the column widths are a report setting. That is to say they are a runtime setting not design time metadata. So just like other report settings, such as sorting or column order, to set the widths you run the page and then adjust each of the column widths (and any other report settings) and then save the report. As a developer you can save the default (primary) report that is used for all users. You can also create an alternate report. So part of creating an Interactive Grid is setting the default report settings. This is no different from Interactive Reports.

To set the width of columns you can drag the end (right for left to right languages) border of the column header. Or you can focus the column header with the keyboard and use the Ctrl-Right and Ctrl-Left keys. You can also set the widths in the Columns dialog. Choose Actions > Columns to open the dialog.

Once you have set all the widths choose Actions > Report > Save to save the default (primary) report. When you first create an Interactive Grid there is just the primary report. If you are editing the widths after other reports have been saved make sure the intended report is selected first. Each saved report can have different width settings. If allowed, end users can save their own reports.

Some advice about setting the column widths. You don’t know how wide the users browser window will be so try to pick the smallest reasonable width that fits the typical data that will be in the column. For some columns this will be easier than others. Using reasonably small widths by default will reduce the chance of their being a horizontal scroll bar when the end user views the grid. Also don’t forget to set the width of columns that are hidden by default.

The next point of confusion, complaint or frustration is the way widths are distributed across the columns. This is most notable on a grid with just a few columns. You can try this out on the Sample Interactive Grids app Master Detail page. The Departments grid has only 3 columns. When all the columns fit (no horizontal scroll bar), if you make any column smaller (less wide) then the extra width is given to the other columns. Also if you make the browser window wider the extra width is distributed among the columns. The reason for this is that we though that in general it looks odd to have extra dead space at the end (right or left depending on direction) of the grid. That extra space may as well be used for the columns even if it makes them wider than needed. If you you make a column wider eventually a horizontal scroll bar is added.

From this it should be clear that what you are actually adjusting is the minimum width of the column. If you open the Columns dialog you can see that indeed the field label is “Minimum Column Width”.

So columns have two width settings. One is the minimum width and the other is the current width. When you drag or use the keyboard to set the width you are setting the minimum width and also the current width. Then any substantial extra width is distributed to the other stretchable columns in proportion to their current width but it doesn’t change their minimum width. If there are just a few extra pixels it is given to the last stretchable column. If the browser window is resized then the current widths are cleared so that horizontal scrolling can be removed if possible or minimized. The current widths are never saved as part of the report settings.

Hopefully this explains the possibly confusing behavior of column widths. But what if you don’t agree with us that dead space at the end should get put to good use. Or perhaps you are fine with some columns getting stretched but stretching other like a Yes/No switch or date column just looks silly. A hint of the solution is in the term “stretchable column” I just used. The grid widget has a column property called noStretch and if it set to true then that column will not be bigger than its minimum width. [Update 5.1.1] This was supposed to be is an advanced column configuration setting. You should have been able to set it with a column Advanced JavaScript Code attribute as follows:

function(config) {
    config.defaultGridColumnOptions = {
        noStretch: true
    };
    return config;
}

[Update 5.1.1] The following workaround is no longer needed and as mentioned in the comments didn’t work completely anyway.
But sadly this doesn’t work. Here is the best workaround I can think of. Add code similar to the following to the Execute When Page Loads page attribute.

var grid = apex.region("dept").widget().interactiveGrid("getViews","grid");
grid.modelColumns.DEPTNO.noStretch = true;
grid.view$.grid("refreshColumns");
grid.view$.grid("refresh");

This will set the DEPTNO column of a grid with static id dept to not stretch. It relies on IG APIs that are not yet stable. Namely the getViews method and that the views have view$ and modelColumns properties.

And if you want all the columns to not stretch use this code.

var grid = apex.region("dept").widget().interactiveGrid("getViews","grid");
for (c in grid.modelColumns) {
    grid.modelColumns[c].noStretch = true;
}
grid.view$.grid("refreshColumns");
grid.view$.grid("refresh");

Now all the columns will be exactly the width you set and there may be dead space at the end.

[Update 5.1.1] You could add the noStretch option to all the columns but that is a lot of effort if you have many columns and you want the stretching behavior off completely. By popular demand, in 5.1.1, we added a configuration option stretchColumns to the view features object. (In the future this may become a declarative option.) Add the following code to the IG attribute Advanced: JavaScript Code:

function(config) {
    // Logically we want to do this: config.views.grid.features.stretchColumns = false;
    // but the server may not generate the views, grid, or features objects
    // so we must check for and add if needed each one using this code
    var o = config;
    "views.grid.features".split(".").forEach(function(p) { 
        if ( !o[p] ) {
            o[p] = {};
        }
        o = o[p];
    });
    o.stretchColumns = false;
    return config;
}

The stretchColumns option provides a default. You can still use the per column noStretch option to override it for a specific column. See the next Interactive Grid article for ways to consolidate the code so that it doesn’t have to be repeated on all IG regions in your app; assuming you want consistent column stretching behavior.

The absolute minimum width a column can be is hard coded to 40px not counting padding. We needed some minimum just so there would be room for column header controls. There is no maximum width for a column. We saw no need to restrict the user.

You can programmatically set the grid column width using code such as:

apex.region("dept").widget().interactiveGrid("getViews","grid").view$.grid("setColumnWidth", "DEPTNO", 180);

Change the region dept, column DEPTNO and width 180 as needed.

That’s everything I can think of about how column widths work.

8 thoughts on “Interactive Grid column widths”

  1. @Evelina It should be possible to have an editable textarea and use the technique described here to show a few lines but a small bug makes it not practical at this time. Likely in the first patch release it can be done. The key is the column property cellTemplate. It is not practical for the same reason that the code above to set noStretch is not robust.

  2. John, and if I wanted to make an editable (Textarea) column to display more than just one line when the grid is not in Edit mode, is that possible at all?

    And to join the people who commented before me – great reading, thank you so much!

  3. The code I give to set a column or all columns noStretch = true does not work perfectly. At this time I don’t have a robust solution. The problem is that the grid view is created and destroyed at various times such as when the report changes. When this happens the column settings are lost. I’ll update if I find a robust solution.

  4. Hi,
    thank you for the detailed explanation, however to reach this:
    “Now all the columns will be exactly the width you set and there may be dead space at the end.”
    … there should be an declarative way (option) – also and in particular! – for the end user.
    All these folks never may want or will read your explanation – they want it to work e.g they know it from their Excel experience.
    Usually they are less interested why things work as they do,
    but rather users wants to have things work as it makes sense for them and how they expect it has to work.
    If they adjust a column width they expect that it will live as long as they change it again
    and at least for the duration of the current session.
    I know you wanted to do things best and optimized, however sometimes the user wants to have the last word.
    I think IG in the first place is for our end users.
    So in this case please make a step back in your willing of optimization
    an a step toward the end user to fullfill what they want and expect.

    Once again – thank you very much for the great work and explanation
    and best regards.

  5. Me again .. on a Editable Grid, where by default the APEX$ROW_SELECTOR and APEX$ROW_ACTION columns are “Frozen” – how does one adjust the height of those columns to fit the rest of the row, given that the my other columns have LOVs and Radio Groups and/or a Calendar.
    In fact I am seeing different row heights – that makes it worse. I am seeing this on apex.oracle.com https://apex.oracle.com/pls/apex/f?p=76892:30 username /pwd guest/demo.
    My rows are different heights, depending on what I edited when. How to handle that? Suggestions?

Comments are closed.