3

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.

Thomas
  • 1,444
  • 1
  • 13
  • 25
  • It seems that number of nodes is just only one of criteria You should take into account. You probably need to calculate amount of content inside. A little more information about what can be found inside these node will be helpful. I like your idea and I'm working on my solution right now :) I will share as soon as it's finished :) – op1ekun Dec 27 '12 at 17:28
  • Thank you for your effort op1ekun. You asked for more information about node content, what information do you mean exactly? my question is only about split the navigation entries (that's what I mean with "nodes"). Please let me know if I should edit my code above for more information. – Thomas Dec 27 '12 at 18:07
  • Ok, You need it only for navigation and it's all about links :) I got fixed on more generic solution that's why I asked for the content. – op1ekun Dec 27 '12 at 21:58

2 Answers2

3

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.

Grzegorz Widła
  • 196
  • 1
  • 3
  • I cannot agree with You... while sorting can be done (and in most cases it is a better solution) on a backend side, I believe grouping seems like a frontend job. – op1ekun Dec 28 '12 at 10:25
  • However You get +1 for CSS columns and implicit ;) link to Masonry plugin. Thanks! – op1ekun Dec 28 '12 at 23:25
  • 1
    +1 for the great masonry in CSS tutorial. To work with CSS is a straightforward and fast solution, but unfortunately not 100% supported. So I choose JavaScript, we have to use it anyway. – Thomas Dec 30 '12 at 21:58
  • Another layout library http://metafizzy.co/blog/packery-preview/ which fixes a couple of masonry issues :) – op1ekun Feb 12 '13 at 14:29
3

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.

op1ekun
  • 1,918
  • 19
  • 26
  • 1
    Thousand thanks mate :) This looks like a very smart solution. I had a similar idea, especially to work with threshold parameter. at least it failed due to the lack of attention to small but important details. I'll give your beauty snippet a try as fast I can and come back! – Thomas Dec 30 '12 at 21:45
  • No problem :) It was fun ;) – op1ekun Dec 30 '12 at 22:21
  • 1
    First, I wish you a happy new year op1ekun :) I tried your solution out and to my surprise, it works by pasting it one-on-one, nice work mate! The blocks are arranged by almost same height and it looks much better. The only issue is (and it's my fault because I forgot to mention) the navigation blocks should respect the order in the HTML. I will try to change your script to make it work. If you have the master idea to solve this issue, it would be nice to update the solution, otherwise I'll mark it as accepted, because you show me the way to go. Thank you for this. I'll keep you informed. – Thomas Jan 02 '13 at 16:43
  • Great :) I'm glad I could help! About your issue. Does it mean sorting is no longer an option? – op1ekun Jan 02 '13 at 17:07
  • ...or we have to change the parameter function to sort the height in respect to the order? – Thomas Jan 02 '13 at 17:53
  • I will try the other solution, it should be much easier to respect all the constraints. By default sorting sounds like order changing ;) ... or I don't get your idea ;) – op1ekun Jan 02 '13 at 18:39