I’m currently working on a project that uses jQuery throughout the user interface to improve the user’s experience. In one section of the application there are three divs laid out next to each other horizontally as if they were three columns. The leftmost div contains a series of items to select from via check boxes. When you pick one (or more) items, the middle div is populated by a jQuery Ajax query and displays a list where the content is based on what was selected in the first (leftmost) group. Finally, you can click on an item in the middle div which will then bring up detailed information in the rightmost div.
Normally this wouldn’t be a problem, but in this case the issue is that the width of the columns needed to be dynamic. If we had made the column widths fixed, then whenever the data in any of the columns was wider than the containing div, a horizontal scrollbar would’ve popped up. While this wasn’t a showstopper, it’s wasn’t visually appealing. You might be thinking that using a table would be a good solution except for the fact that resizing table cells isn’t much easier than resizing multiple divs, plus we’re not displaying true tabular data. Another possible answer could lie in using frames, except for the fact that frames disappear in HTML5 and I haven’t used frames in over 10 years.
So back to our divs. To find a solution I realized that not only would I need the three main div containers, but I would also need two more divs to act as column separators. These two new divs should give me something to grab and drag since dragging the left, middle, and right container divs themselves wouldn’t look right. OK, so now we have five divs laid out horizontally. Three for content and two to act as drag handles.
So we have our layout but the real brain work still needed to be done. How can I manage all the divs so that only the two “handles” were draggable AND the three content divs would respond to the movement of either handle? By using jQuery I could make the two handles draggable – that was easy. The problem now was how to get the content divs to react to the handles being moved. After many failed experiments, I determined how to properly arrange the divs so that dragging the handles not only properly resized the container divs, but the handle divs were prevented from overlapping each other.
During some of my initial experiments I would drag the handles and the neighboring content divs would be resized, but they ended up pushing my handles while they were being dragged making them move much farther than they should have. After positioning the handle divs absolutely that solved the ghost movements, but introduced another problem. With the handles positioned absolutely, they had been removed from the flow of the document so I couldn’t rely on their dimensions and other CSS attributes to “push” the other content divs. Now I had to calculate their position and use that information to resize the other div’s width. When dragging the left handle we would change the width of the left and center content divs. When dragging the right handle we would change the width of the center and right content divs. There wasn’t any need to change the left position, margin, or padding for any of the elements and this was a big relief.
Not to go into too much technical detail, but jQuery’s draggable interaction has a containment option which allows you to restrict the movement of draggable elements. This would be critical to prevent the handles from being dragged too far. The problem is that you either pass it an element, like the parent of a draggable element, the body of the page, or give it a set of coordinates. The problem with the coordinate system is that it’s relative to the document, not an element. Had we been able to specify the coordinates relative to an element rather than the document, this would’ve been much easier. Since we had to use coordinates relative to the document we had to make our calculations relative to the document as well, which caused a conflict with webkit browsers when we wanted to center our columns. The problem with webkit browsers is that they don’t calculate the jQuery offset() function properly when you center something by using margin: 0 auto and you have a whole slew of “how am I ever gonna get this one to work?” problems to solve. Once I figure out that by solving the webkit centering issue I wouldn’t have to worry about the offset() issue it was smooth sailing.
Another issue involved the content divs themselves. Floating them didn’t work and absolute positioning didn’t either. I began to dig into the CSS display property. Did you know that the display property can be: block, inline-block, inline-table, list-item, run-in, table, table-caption, table-cell, table-column, table-column-group, table-footer-group, table-header-group, table-row, or table-row-group? Neither did I. As it turns out, inline-table did the trick. (Note: I later found out that inline-block also works just fine.)
The W3C says that the inline-table property is “a rectangular block that participates in an inline formatting context”. This basically means that you can have a block with a width, height, margins, etc. but it’s treated as an inline element instead of the usual block element. Hmmm. So far so good. Add that, width, and a little vertical-align:top to all the container divs and we’re looking good. The handle divs get positioned absolutely and get their left position set initially based on the container divs to their left (i.e. the first handle has a left position equal to the width of the first container div and the second handle has a left position equal to the width of the first two container divs plus the width of the first handle. That’s all the CSS you need, the rest is jQuery.
Since we can’t center the big div that contains everything and do our dragging because webkit browsers can’t calculate the correct jQuery offset, we center it manually (note that this isn’t necessary if you’re not going to center the whole shebang). Then we have to figure out the horizontal limits of our drag handles. We don’t want to have people dragging the left handle over the right handle and vice versa, plus we should also give the content divs a minimum width so that the content doesn’t become unreadable. Finally we calculate the containment area for each draggable handle once the opposite handle is finished being dragged. Phew.