Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to split a nested navigation into columns with almost equal heights

I'm working on a responsive website with an navigation overlay (nodes and subnodes). The navigation should be split to 4 columns for desktop view or 2 columns for tablet view. It's a nested list navigation, so a column is build with <ul /> around it. My idea was, simply write the navigation in 1 column and arrange it to 4 or 2 cols dynamic with jquery.

html:

<!-- overlay -->
<nav class="overlay"> 
    <!-- ul for column -->
    <ul>
        <!-- nodes & subnodes -->
        <li><a href="#">node</a>
            <ul>
                <li><a href="#">subnode</a></li>
                <li><a href="#">subnode</a></li>
                <li><a href="#">subnode</a></li>
                <li><a href="#">subnode</a></li>
            </ul>
        </li>
        <li><a href="#">node</a>
            <ul>
                <li><a href="#">subnode</a></li>
                <li><a href="#">subnode</a></li>
                <li><a href="#">subnode</a></li>
                <li><a href="#">subnode</a></li>                    
                <li><a href="#">subnode</a></li>
                <li><a href="#">subnode</a></li>
                <li><a href="#">subnode</a></li>
                <li><a href="#">subnode</a></li>
            </ul>
        </li>
        <li><a href="#">node</a> ...
    </ul>
</nav>

I wrote this function:

    $.fn.arrangeObjects = function(wrapWith, maxCols) {

        this.each(function() {
            if ($(this).parent(wrapWith).length) $(this).unwrap();
        });

        this.parent().each(function() {
            var $el = $(this).children();
                amount = $el.length,
                wrapAmount = amount / maxCols;

            for (var i = 0; i < amount; i += wrapAmount) {
                $el.slice(i, i + wrapAmount).wrapAll('<'+ wrapWith +'/>');
            }
        });
    };

Fired for desktops:

$(".overlay > ul > li").arrangeObjects('ul', 4);

Fired for tablets:

$(".overlay > ul > li").arrangeObjects('ul', 2);

This solution splits the nodes in equal parts into the cols. Unfortunately it looks not really nice this way:
http://bern09.ch/notgood.png

What i want to achieve is an arranging with almost the same column heights, like this:
http://bern09.ch/good.png

I have to respect the number of subnodes in a way, but I don't really know how to achieve that. Maybe anyone has the great tip, a little help would be appreciated.

like image 846
Thomas Avatar asked Feb 18 '26 08:02

Thomas


2 Answers

Ok. I've got it. It was a little bit tricky. Last day I started out with calculating heights in every column, but I believe this approach is more flexible:

    this.parent().each(function () {
        var $subnodes       = $(this).children();

        // true will cause counter increment
        // false will cause counter decrement
        var inc     = true;
        var cols    = [];

        for (var i = 0; i < maxCols; i++) {
            cols.push($('<ul></ul>'));
            cols[i].appendTo($(this));    
        }

        function sortByHeight(a, b) {
            return $(a).height() > $(b).height() ? 0 : 1;
        }

        $subnodes = $subnodes.sort(sortByHeight);

        var i = 0;
        $subnodes.each(function () {
            // logic for left and right boundry
            if (i < 0 || i === maxCols) {
                inc = !inc;
                // this will cause node to be added once again to the same column
                inc ? i++ : i--;
            }

            cols[i].append($(this));

            inc ? i++ : i--;
        });
    });

It's a concept. First I sort all the NODES by their height (from the heighest to the lowest) and then I append them to columns (specified by the maxCols) going from left to right and back. It's not as pretty as yours and unfortunately breaks wrapAll usage (I like it so much). I'm already working on a litte optimization which will use threshold parameter. When the difference in height between the shortest and the longest column is greater than threshold the last element of the longest column will be move to the shortest column. It should look more natural then. Let me know if this is what You are looking for.

like image 136
op1ekun Avatar answered Feb 19 '26 23:02

op1ekun


First and foremost - sorting and grouping should happen in the back-end whenever possible and in this situation it is.

When you have sorted and grouped results in each column UL you can use CSS3 columns with jQuery fallback for older browsers, explained here.

.overlay {
    -moz-column-count: 4;
    -moz-column-gap: 10px;
    -webkit-column-count: 4;
    -webkit-column-gap: 10px;
    column-count: 4;
    column-gap: 10px;
}

However, if you insist on using JavaScript for everything, you could use Multi Column List jQuery plugin.
It seems that it provides the solution you need. It was however written while ago so you will probably have to modify parts of it for the newest jQuery version and maybe slightly to cater to your needs.

like image 43
Grzegorz Widła Avatar answered Feb 19 '26 21:02

Grzegorz Widła



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!