This was an interesting question raised on the jtable discussion forum which intrigued me. The kernel of the solution was found on jsFiddle but it didn't apply directly to jTable, which uses ajax to update the table dynamically. So here is my take on a solution. As usual with these tutorials we'll start with standard jTable demonstration. Firstly an HTML element to contain the table.
A standard jtable definition, with a one line recordsLoaded
event handler. The handler just calls our new columnResize
function described below, every time a set of data is loaded. Note also the use of (non-breaking space) entities in the column titles. This prevents word wrapping after the resizing.
Next we need some css.
Ln 1. This is housekeeping. The standard jtable css sets the th padding at left:5 and right:5 while the td padding is left:3 and right:6 so a difference of just one pixel per column. Rather than change the heading which has the drag handles etc, we change the td padding to match. It is quite surprising what a difference this little change makes.
Ln 5. This styling sets the two command column headings the same width that the data columns will be when they have their edit and delete icons. This is a change we are going to adopt as a standard, as it prevents the column headings jumping about quite as much when the data records are loaded for the first time.
Ln. 9. This styling sets the border of the last heading column to be about the same width as the vertical scroll bar. We've tried to pick an RGB that is about the same colour as the vertical scroll, at least in Firefox, which we use.
Ln. 13. This styling destroys the normal HTML table behaviour, it breaks the link between table-head and table-body, which will have to re-implement ourselves.
Ln. 16. This styling make the table body limited in height and scrollable.
Ln. 22. This styling centres the table on the page and make it resize with the window
Now we need the script which is called from the recordsLoaded
handler, that actually resizes the table body to match the table head. We've made it a jQuery function so that we can apply it to a jQuery object.
Ln. 2. Onwards creates a resizing function as a jQuery function.
Lns. 3-6 Finds the first row of the table heading and grabs the column widths.
Lns. 9-11 Finds the first row of the table body and sets the column widths to match the headings.
Setting the width of an element sets the width of the inside content. The actual width has the padding, border and margin added. This explains why we had to align the padding of the table head and the table body in the CSS.
Lns. 14-17 Binds an event handler to the browser window, to recompute the widths on resizing.
So now below we have an almost working demonstration.
Traditionally using a normal HTML table, which jTable generates for us, the column widths are for guidance only. The browser uses column widths and column contents to construct the table. See mozilla explantion for more information. During contruction jTable makes the DOM elements of the table and populates the table head with column titles etc, and a single full span body row. The browser only has the column width guidelines and the heading contents with which to layout the table. Normally when jTable('load')
gets data from the servers and populates the table body with the data rows, the browser would re-layout the table, but by splitting the table head from the table body to make the body scrollable, we are preventing the layout algorithm from balancing the table. Therefore it is even more important than ever to get the initial jTable field width values suitable for the data.
We have our table with scrolling body, with table heading columns the same size as the table data columns, and it resizes as the window changes the size of the table container. jTable defaults columnResizable
and columnSelectable
options to true. This is the case in our demonstration above. Try dragging a heading column, or right click the heading bar and hide/show a column on the demonstration jTable above. It's broken.
Because the table is in two parts, only the table head gets rebalanced by the browser after column width dragging or column hiding/showing. Sadly there are no jTable events relating to column changes for us to handle. However we can spy on some of jTables internals, and address both these matter. Very satisfactorily in the case of column visibility, less so for column resizing.
During construction, jTable creates, among other things, two elements inside the jtable-container
, which we can exploit.
<div class='jtable-column-selection-container' />
containing the non-fixed column titles with checkboxes for hiding/showing. Let's create a new instance of the same jtable.
The <div class='jtable-column-selection-container' />
is dynamically populated on popup with a list of columns with visibily checkboxes. Because the checkboxes trigger a change event when any of them are changed, we simply use jQuery to bind a change
event handler to the container. jTable will handle the event on the individual colum first, and show/hide that colunn (both title and data). The event will then bubble up to the container, and trigger our event handler. We are not concerned with which column has changed, only the overall layout of the table head, so we just execute our resize function.
This solution is not so elegant, and requires a good understanding of jQuery events in general, and mouseup and mousemove events in particular. Without a long explanation of the reasoning, here are the general guidelines for implementing a drag type operation without using jQuery draggables.
mousedown
event handler to the element that is to be dragged. mousedown
event handler, apply both mousemove
and mouseup
event handlers. These can be computationally demanding so are only bound during individual drag operations. As the mouse can be dragged over elements, other than the one with the bound mousedown
event, it is normal to bind these two handlers to an element high up the DOM. In this case jTable binds the events to the document
itselfmouseup
event handler, the final action of the drag can be applied, and then both handlers unbound from the document.It is also worth mentioning that using jQuery we can be sure that on each element, event handlers are executed in the order in which they are bound. Sometimes event bubbling and event delegation may make this appear not to be so, but analysis will show they are.
The crux of our problem is to get a mouseup
handler bound AFTER the jTable mouseup
handler. To do that, ideally we need to execute our own mousedown
handler, immediately after the jTable mousedown
handler. Unfortunatley for us the jTable mousedown
handler calls the jQuery method event.stopPropagation()
, which means that even if we bind our own mousedown
handler, it will not be triggered.
We warned that the solution was not elegant. We are going to use a hack explained by Robee Shepherd to reorder the jQuery events on each of the div.jtable-column-resize-handler
elements. This places our mousedown
handler BEFORE the jTable one. Any mouseup
event handler we apply within it will be triggered BEFORE the jTable one, that resizes the column.
Rather than bind the event handler that actually performs the resizing we bind an interim handler to the body
. When the mouseup
event is triggered, it will bubble up the DOM from wherever it was triggered, until it reaches the body
, at which point our interim handler will execute. This will still be BEFORE the jTable mouseup
handler, but at a time when the jTable mouseup
handler is bound to the document
, so we bind our real mouseup
handler, to the document
, remove our interim handler from the body
, then let the mouseup
event bubble up to the document
. When it reaches the document
the jTable mouseup
handler will resize the table head, then our real mouseup
handler will resize the table body.
So now we have a fully working scrollable jTable. It adpats to window resizing, column visibily changes, and column width drags. Try dragging a column width, and move the mouse outside the table somewhere.
It is always satisfying to solve a problem. Splitting the table to make it scroll and the actual data column resizing work well, The column visibilty solution is simple and robust. The solution for column dragging, seems to work, that is the most positve thing we'd say about it. It uses a jQuery private function to manipulate internal data, which could easily break with new releases of jQuery. The whole event handler juggling has a fragility about it, that may crumble on more complex pages. Should a paying client ever require scrolling tables, we'd firstly suggest avoiding draggable column widths. If not we'd think about extending jTable to provide a proper columnsChanged
callback.
You've made it this far, we hope you've learnt something. We are available to offer local tutoring on a range of technologies, or to build you a website, standard or non-standard. The less standard the better! Contact us at tutor@swaleinternet.co.uk