Liquid layout with 100% height div at the center that has overflow, along with resizable divs surrounding it

StackOverflow https://stackoverflow.com/questions/13322706

Question

So here is the relevant markup to make it clearer (I've also put this into a jsFiddle):

HTML

<div id="header">
    Header
</div>

<div id="container">
    <div id="tableWrapper">
        <div id="tableRowWrapper">
            <div id="nav">
                Navigation
            </div>
            <div id="main">
                <div id="details">
                    Details
                </div>
                <div>
                    Button1 Button2 Button3 Button4 Button5 Button6 Button7
                </div>
                <div id="editor">
                    <div class="paragraph">
                        Vestibulum ante ipsum primis in faucibus orci
                    </div>
                    <div class="paragraph">
                        Vestibulum ante ipsum primis in faucibus orci
                    </div>
                    <div class="paragraph">
                        Vestibulum ante ipsum primis in faucibus orci
                    </div>
                    <div class="paragraph">
                        Vestibulum ante ipsum primis in faucibus orci
                    </div>
                    <div class="paragraph">
                        Vestibulum ante ipsum primis in faucibus orci
                    </div>
                </div>
            </div>
        </div>            
    </div>
</div>

<div id="footer">
    Footer
</div>​

CSS

#header{
    position: fixed;
    top: 0px;
    height: 30px;
}

#footer{
    position: fixed;
    bottom: 0px;
    height: 30px;
}

#container{
    position:fixed;
    top:30px;
    bottom:30px;
    left:0;
    right:0;
    overflow:hidden;
    z-index:-1;
    background-color: #dddddd;
}

#tableWrapper
{
    display: table;
    width: 100%;   
    height: 100%;
}

#tableRowWrapper
{
    display: table-row;
}

#nav
{
    display: table-cell;
    background-color: #ccffff;
    resize: horizontal;
    overflow: auto;
    width: 100px;    
}

#main
{
    display: table-cell;
    background-color: #ffffcc;            
}

#details
{
    background-color: #faccfa;
    resize: vertical;
    overflow: auto;
    height: 40px;

}

#editor
{
    background-color: #fddfaf;   
    height: 100%;
    overflow-y: auto;
}

.paragraph
{
    min-height: 150px;
}

There are a number of key points here:

  • I am in the fortunate position of having to target Google Chrome on Windows only so I don't have to worry about IE8 etc.
  • The central 'editor' div has overflow set such that the vertical scrollbar should come into play when the content gets too big.
  • The 'nav' div to the left is resizable, in that you can grab the handle at the bottom right of it and use this to resize it horizontally.
  • The 'details' div near the top is also resizable; you can grab the handle at the bottom right of it to resize it vertically.
  • I don't want to give the 'details' and 'toolbar' divs a fixed height.

The problem I am having is that the central 'editor' div is too large even though it has overflow-y : scroll set. Although the scrollbar does appear, the bottom extent of it goes beyond the footer and indeed off the edge of the page.

If I decrease the overall page height (e.g. by adjusting the horizontal splitter in jsFiddle) I can see that the size of the central 'editor' div does decrease (the scrollbar grows accordingly). However, its overall resulting height is too big.

Increasing the width of the 'nav' div to the left (using the resize-handle) also causes the scrollbar of the central 'editor' div to grow accordingly, which is great. Aside from the fact that, again, its overall resulting height is too big.

The contents of the 'details' and 'toolbar' divs should wrap. E.g, if you increase the width of the 'nav' div enough (using the resize-handle), you'll see the 'Button1 / Button2 / etc' contents start to wrap and the 'editor' div is displaced down accordingly. The height of the 'editor' div needs to take account of this too.

Lastly, if I increase the height of the 'detail' div (again using the resize-handle) I find it has no affect on the central 'editor' div height at all; it just gets pushed down beyond the edge of the page and the scrollbar height doesn't change.

I think this is happening because the height: 100% I've set on the 'editor' div is using the height of something in its ancestor elements which is not correct, perhaps the body element. But I don't know what to do about it.

Ideally I'd use a pure CSS solution because if I were to resort to JavaScript I'd probably have to handle a number of different events, such as resizing the navigation and detail divs, resizing the screen, page load etc. But I would be open to a JavaScript solution if it were simple.

Indeed, simplicity is really what I am after here more than anything.

I've seen different solutions to various aspects of this in isolation but nothing which brings them all together.

Was it helpful?

Solution

I've come up with a JavaScript solution, with a little help from JQueryUI and Knockout.

I realised that I wanted any adjustments made to be persisted across page refreshes. As soon as state came into it an MVVM approach such as that supported by Knockout seemed like a good choice. It also means I can subscribe to JQueryUI Resizable events for relevant values and do a recalculation of the 'editor' div height accordingly.

The navigation width can be changed, as can the details height. The editor div height adjusts accordingly. It also works on first page load and for window resizes. Lastly the adjustments survive page refreshes (I'm storing them in cookies).

The HTML and CSS have been simplified a great deal.

The result is in this jsFiddle, and also here:

HTML

<div id="header">
    Header
</div>
<div id="container">
    <div id="nav" data-bind="jqResizableWidth: navWidth, jqOptions: { handles: 'e', minWidth: 5 } ">
            Navigation
        </div>
        <div id="main">
            <div id="details" data-bind="jqResizableHeight: detailsHeight, jqOptions: { handles: 's', minHeight: 10 }">
                Details
            </div>
            <div id="toolbar">
                Button1 Button2 Button3 Button4 Button5 Button6 Button7
            </div>
            <div id="editor">
                <div class="paragraph">
                    Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Etiam quam sapien, volutpat ac iaculis ut, malesuada quis massa. Quisque quis risus eu tellus mattis sagittis vulputate at nisl. Donec at nibh non neque facilisis adipiscing. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Donec at condimentum est. Fusce gravida diam vel odio venenatis vitae cursus lorem cursus. Ut tristique, libero quis scelerisque semper, arcu velit faucibus dui, eu imperdiet nisl arcu id enim. Nullam eget placerat risus.
                </div>
                <div class="paragraph">
                    Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Etiam quam sapien, volutpat ac iaculis ut, malesuada quis massa. Quisque quis risus eu tellus mattis sagittis vulputate at nisl. Donec at nibh non neque facilisis adipiscing. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Donec at condimentum est. Fusce gravida diam vel odio venenatis vitae cursus lorem cursus. Ut tristique, libero quis scelerisque semper, arcu velit faucibus dui, eu imperdiet nisl arcu id enim. Nullam eget placerat risus.
                </div>
                <div class="paragraph">
                    Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Etiam quam sapien, volutpat ac iaculis ut, malesuada quis massa. Quisque quis risus eu tellus mattis sagittis vulputate at nisl. Donec at nibh non neque facilisis adipiscing. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Donec at condimentum est. Fusce gravida diam vel odio venenatis vitae cursus lorem cursus. Ut tristique, libero quis scelerisque semper, arcu velit faucibus dui, eu imperdiet nisl arcu id enim. Nullam eget placerat risus.
                </div>
                <div class="paragraph">
                    Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Etiam quam sapien, volutpat ac iaculis ut, malesuada quis massa. Quisque quis risus eu tellus mattis sagittis vulputate at nisl. Donec at nibh non neque facilisis adipiscing. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Donec at condimentum est. Fusce gravida diam vel odio venenatis vitae cursus lorem cursus. Ut tristique, libero quis scelerisque semper, arcu velit faucibus dui, eu imperdiet nisl arcu id enim. Nullam eget placerat risus.
                </div>
                <div class="paragraph">
                    Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Etiam quam sapien, volutpat ac iaculis ut, malesuada quis massa. Quisque quis risus eu tellus mattis sagittis vulputate at nisl. Donec at nibh non neque facilisis adipiscing. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Donec at condimentum est. Fusce gravida diam vel odio venenatis vitae cursus lorem cursus. Ut tristique, libero quis scelerisque semper, arcu velit faucibus dui, eu imperdiet nisl arcu id enim. Nullam eget placerat risus.
                </div>
            </div>
        </div>
</div>

<div id="footer">
    Footer
</div>

CSS

#header{
    position: fixed;
    top: 0px;
    height: 30px;
}

#footer{
    position: fixed;
    bottom: 0px;
    height: 30px;
}

#container{
    position:fixed;
    top:30px;
    bottom:30px;
    left:0;
    right:0;
    overflow:hidden;
    z-index:-1;
    background-color: #dddddd;
}

#nav
{
    display: table-cell;
    background-color: #ccffff;
}

#nav .ui-resizable-e {
    background: #cccccc;
    width:5px;
    height: 100%;    
}

#main
{
    display: table-cell;
    background-color: #ffffcc;            
}

#details
{
    padding-left: 5px;
    background-color: #faccfa;
    width: 100%;
}

#details .ui-resizable-s {
    background: #cccccc;
    width:100%;
    height: 5px;    
}

#toolbar
{
    padding-top: 5px;
    padding-left: 5px;    
}

#editor
{
    padding-left: 5px;
    background-color: #fddfaf;   
    overflow-y: scroll;
}

.paragraph
{
    min-height: 150px;
}

JavaScript

ko.bindingHandlers.jqResizableWidth = {
    init: function(element, valueAccessor, allBindingsAccessor) {
        var value = ko.utils.unwrapObservable(valueAccessor());
        $(element).width(value);

        var options = allBindingsAccessor().jqOptions || {};
        $(element).resizable(options);


        ko.utils.registerEventHandler(element, "resize", function(event, ui) {
            var observable = valueAccessor();
            var value = ui.size.width;
            observable(value);

            // Have to include the next line because otherwise JQueryUI Resizable
            // fixes the height to the currently resolved height.
            $(element).height('100%');
        });
    }
};

function adjustEditor() {

    // Extra 5px added because of 5px 
    var height = $(window).height() - $('#header').height() - $('#details').height() - $('#toolbar').height() - $('#details .ui-resizable-s').height() - $('#footer').height();

    $('#editor').height(height); 
}

ko.bindingHandlers.jqResizableHeight = {
    init: function(element, valueAccessor, allBindingsAccessor) {
        var value = ko.utils.unwrapObservable(valueAccessor());
        $(element).height(value);

        var options = allBindingsAccessor().jqOptions || {};
        $(element).resizable(options);


        ko.utils.registerEventHandler(element, "resize", function(event, ui) {
            var observable = valueAccessor();
            var value = ui.size.height;
            observable(value);

            // Have to include the next line because otherwise JQueryUI Resizable
            // fixes the width to the currently resolved width.
            $(element).width('100%');
        });
    }
};



var viewModel = function(navWidth, detailsHeight) {

    var self = this;

    self.navWidth = ko.observable(navWidth); 
    self.detailsHeight = ko.observable(detailsHeight);


    self.navWidth.subscribe(function(newValue) {
        $.cookie('navWidth', newValue, {
            expires: 7,
            path: '/'
        });

        adjustEditor();
    });

    self.detailsHeight.subscribe(function(newValue) {
        $.cookie('detailsHeight', newValue, {
            expires: 7,
            path: '/'
        });

        adjustEditor();
    });

};

var navWidth = $.cookie('navWidth') || 80;
var detailsHeight = $.cookie('detailsHeight') || 50;

ko.applyBindings(new viewModel(navWidth, detailsHeight));


$(window).resize(function() {adjustEditor();});
$(window).resize();​
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top