Javascript to turn an unordered list into multiple columns - javascript

There doesn't seem to be an easy way in (well supported) css to do this. I'm looking for a javascript solution, preferably jQuery.
I have an unordered list like this:
<ul>
<li>A</li>
<li>B</li>
<li>C</li>
<li>D</li>
<li>E</li>
...etc
</ul>
I want each column to have a height for example four items and fill vertically rather than horizontally like a css float:
A E
B F
C
D

You will want to use a combination of CSS and jQuery, but in theory it is very simple. Render a complete single list in HTML, then provide a wrapper via jQuery and split the list up as desired. The following function does just that. Be sure to use a more specific selector than just ul when actually using the script. An id would be ideal.
View demo here.
jQuery(function ($) {
var size = 4,
$ul = $("ul"),
$lis = $ul.children().filter(':gt(' + (size - 1) + ')'),
loop = Math.ceil($lis.length / size),
i = 0;
$ul.css('float', 'left').wrap("<div style='overflow: hidden'></div>");
for (; i < loop; i = i + 1) {
$ul = $("<ul />").css('float', 'left').append($lis.slice(i * size, (i * size) + size)).insertAfter($ul);
}
});

See this article:
One of the minor holy grails of XHTML
and CSS is to produce a single,
semantically logical ordered list that
wraps into vertical columns.
I’ll warn you up front. If you want to
present a list in multiple columns,
you’ll have to compromise. You can
sacrifice W3C web standards and use
deprecated markup, you can live with
markup that’s less than semantically
logical, you can tolerate a mixture of
presentation with content, you can say
goodbye to browser compatibility, or
you can use markup that’s heavy with
attributes and styling that’s heavy
with rules. Every road exacts a toll.
http://www.alistapart.com/articles/multicolumnlists/
The "best" solution is subjective, but I'd be inclined towards arbitrary classes.

Doug's solution is nice if you want to split the list up into sub lists.
Instead I chose to position the list elements without changing the dom.
This is a bit messy, basically it puts a left margin on each element which is the column number multiplied by the column width.
This will result in a staircase layout so the next step was to add some negative top margin to bring each element up to the top.
Basically this displays as a grid. I am using this for drop down menus so it worked well. Avoid using this if you need each list item to have a dynamic height. The col_height variable could be set to the height of the largest item to make the code a bit more general purpose.
var col_max_height = 6; //Max Items per column
var col_width = 200; //Pixels
var col_height = 33; //Pixels
$('.header ul li ul').each(function() {
$(this).find('li').each(function(index){
column = parseInt(index/col_max_height);
$(this).css('margin-left', column * col_width + 'px')
if(column > 0) {
$(this).css('margin-top', (index - (col_max_height * column) + 1) * -col_height + 'px').addClass('col_'+column);
}
});
});

#Keyo, thanks for your helpful answer.
Just a few modification needed to make it work on my end.
(Formula was changed, perhaps it could help someone)
var col_max_item = 2; //Max Items per column
var col_width = $('.header ul li').css('width').replace("px", ""); //Pixels, get width from CSS
var col_height = $('.header ul li').css('height').replace("px", ""); //Pixels, get height from CSS
$('.header ul').each(function() {
$(this).find('li').each(function(index){
column = parseInt(index/col_max_item);
$(this).css('margin-left', column * col_width + 'px')
if(column > 0 && (index / col_max_item) == column) {
$(this).css('margin-top', (col_max_item * col_height * -1) + 'px').addClass('col_'+column);
}
});
});

for this I use a plugin called "Easy List Splitter". here is the link:
http://www.madeincima.it/en/articles/resources-and-tools/easy-list-splitter-plugin/

Just a little different moves up the next column instead of each li.
var col_max_height = 6; //Max Items per column
var col_width = 120; //Pixels
var prevCol = 0;
$('.header ul').each(function() {
$(this).find('li').each(function(index){
column = parseInt(index/col_max_height);
$(this).css('margin-left', column * col_width + 'px')
if(prevCol != column) {
$(this).css('margin-top', '-92px').addClass('col_'+column);
prevCol = column;
} else {
$(this).css('margin-top', '0px').addClass('col_'+column);
}
});
});

You can do this really easily with the jQuery-Columns Plugin for example to split a ul with a class of .mylist you would do
$('.mylist').cols(2);
Here's a live example on jsfiddle

Related

How to set the width of each header cell in a table (with fixed header) to be the same to the biggest in its column, and make the browser respect it?

Here is the infamous table:
My boss wants me, at this time of the year, to make the header of the table fixed, so the user can scroll down the table and continue reading the header. I want to preserve the original precomputed dimensions of the table, I mean, the width that every column has at the moment of its creation (widths aren't established by CSS) and then, adapt the header so its columns match the columns of the body. Following some of the answers I found in Stackoverflow, I started making the header and the body of the table display: block. And after that, I wrote this:
function setTableHeadDimensions() {
var $taskTable = $('.tablaTareas_PEMVISUALIZA'),
$firstRow = $taskTable.find('tbody > tr:first-child'),
$firstRowTds = $firstRow.find('td'),
$firstRowHead = $taskTable.find('thead > tr:first-child'),
$secondRowHead = $taskTable.find('thead > tr:eq(1)'),
$firstRowHeadThs = $firstRowHead.find('th'),
$secondRowHeadThs = $secondRowHead.find('th'),
i = 0,
cells = [];
//We prepare CSS, so we can specify width.
$taskTable
.css('table-layout', 'fixed')
.find('td, th').each(function () {
var $tdh = $(this);
$tdh.css('box-sizing', 'border-box');
$tdh.css('overflow', 'hidden');
});
//Cells of the first row of the table head.
for (i = 0; i < 3; i++) {
cells.push($($firstRowHeadThs[i]));
}
//Cells of the second row of the table head.
for (i = 0; i < $secondRowHeadThs.length; i++) {
cells.push($($secondRowHeadThs[i]));
}
//Rest of the cells for the first row.
for (i = 5; i < $firstRowHeadThs.length; i++) {
cells.push($($firstRowHeadThs[i]));
}
//Try to set the width of the current column's header cell
//to the biggest cell width in the column.
for (i = 0; i < cells.length; i++) {
var maxWidth = 0;
$taskTable.find('td:nth-child(' + (i + 1) + ')').each(function () {
var $el = $(this);
maxWidth = Math.max(maxWidth, $el.width());
});
cells[i].width(maxWidth);
}
}
But, as you can see in the picture, the browser doesn't want to cooperate. What's more, it establishes the width of the cell, but to a number that doesn't match the width of it's corresponding column:
What's more, it doesn't match the width of the row it should match:
So I have two questions:
Why does the browser behave in the way it does?
How can I solve this problem in a way compatible with IE8? (no fancy CSS3 solution, please)
Here's a codepen with the example cut down to the minimum necessary: Codepen example
I solved it. In reality, there were two problems:
The first one is that jQuery.width() returns only the width of the content of the cell, without padding and margin (even if you specify border-sizing: border-box). I found more natural to use jQuery.css('width') and then take borders and padding into account in my calculations, without specifying border-sizing: border-box because retrieving the width with border-sizing: border-box and then setting it in another element with the idea of matching both widths can be error prone (I had problems with it).
The second one is if you use rowspan in the header of the table. In that case, you have to establish the width of the rows envolved doing the proper calculations, not only one of them hopping that the rest of the rows will adapt.
Here's the codepen with the solution: http://codepen.io/PolarKuma/pen/BQXMbO

How to measure and set the width?

I would like to make sub-navigation to measure its parents width and then set its own width accordingly. At the moment every sub-navigation (.primary-navigation ul ul) gets an individual class (customWidth-0 + i). Then using this class I measure its parent's width and set the width minus the padding. It's all working nice and fine, but I'm learning and I'd like to shorten the script. I was trying to loop this, use "this", but seem to get stuck at every point. It would be nice to learn to do this in a proper, robust way. Any help is very much appreciated. Thanks.
jQuery(document).ready(function( ) {
jQuery(".primary-navigation ul ul").each(function(i) {
i = i+1;
jQuery(this).addClass("customWidth-0" + i);
});
a = jQuery(".customWidth-01").prev().parent().width();
b = jQuery(".customWidth-02").prev().parent().width();
c = jQuery(".customWidth-03").prev().parent().width();
d = jQuery(".customWidth-04").prev().parent().width();
jQuery(".customWidth-01").css("width", a-31);
jQuery(".customWidth-02").css("width", b-31);
jQuery(".customWidth-03").css("width", c-31);
jQuery(".customWidth-04").css("width", d-31);
});
I took your look and added my code after commenting your sections out and got the same thing.
<script type="text/javascript">
jQuery(document).ready(function( ) {
jQuery(".primary-navigation ul ul").each(function(i) {
i = i+1;
var _this = jQuery(this), w = _this.parents('ul').width();
_this.css("width", (w-31)+"px");
//jQuery(this).addClass("customWidth-0" + i);
console.log(i);
});
/*
a = jQuery(".customWidth-01").prev().parent().width();
b = jQuery(".customWidth-02").prev().parent().width();
c = jQuery(".customWidth-03").prev().parent().width();
d = jQuery(".customWidth-04").prev().parent().width();
jQuery(".customWidth-01").css("width", a-31);
jQuery(".customWidth-02").css("width", b-31);
jQuery(".customWidth-03").css("width", c-31);
jQuery(".customWidth-04").css("width", d-31);
*/
});
</script>
It will find the div above given ul and take its width take off 31px and then apply it to given elements below. The LI elements inside of the UL will already have a 100% width and conform to that standard. If you wanted to you could just apply a position: relative; on the LIs of the .primary-navigation and then position: absolute; left: 0; top: ~20px; (top is a little skewed according to your em size.. This will do the same thing except it won't wrap your children and make it look weird.
If you gave me an exact image of what you wanted your menu to look like (not using javascript or anything maybe a designed version??) I could probably do a better job as this is still kinda hard to answer. Hopefully the code I provided helps you if not leave another comment and let me know if you have questions.
Below line is old answer.
Set a variable
var _this = jQuery(this), w = _this.parent().parent().width();
_this.css("width", (w-31)+"px");// just to be safe added px use w/e or leave blank it assumes it
This should work for you the same as you have it in the foreach.
you could also use .parents('ul')
w = _this.parents('ul').width();
function() {
$('.primary-navigation').children('li').each(
function(index) {
var parentWidth = $(this).width();
$(this).children('ul').width(parentWidth/2);
}
);
Is this what you are looking for? now all the sub menu is 1/2 of parent. you need to fix it to your specific need.
DEMO http://jsfiddle.net/La07pvf2/

Dynamically percentage width assignment with jQuery

I've got a simple piece of jQuery running to assign a width to my list items:
$(subMenu).each( function() {
var noLi = $(this).children('li').length,
itemSize = (10/noLi)*10;
$(this).children('li').each( function() {
$(this).css('width', itemSize +'%');
});
});
I know there are always less than 10 list-items in this case so I am generating the percentage width by the simple itemSize calculation. However if more items were added, the whole thing would fail & the layout would not be as intended.
I also believe this may not be the most efficient way to achieve this in the first place, but am unsure of a better way. Can someone point me in the right direction?
You mean itemSize gets to big? If so, you can do this:
itemSize = Math.min(100, (10/noLi)*10); // Get smallest value of the two. 100 is the max now
About adding sizes, you can replace the code to this:
$(this).children('li').css('width', itemSize +'%'); // no need for each
Combined would be:
$(subMenu).each( function() {
var $AllLi = $(this).children('li'); // Save'm all for later access
var noLi = $AllLi.length; // we saved all selected li´s so we dont need to reselect
itemSize = Math.min(100, (10/noLi)*10);
$AllLi.css('width', itemSize +'%'); // no need for each
});
Another way might be better, using css:
<style>
ul{
width: 100%;
display: table-row;
}
li{
display: table-cell;
}
li:gt(10){
display: none;
}
</style>
<ul>
<li>li part</li>
<li>li part</li>
<li>li part</li>
<li>li part</li>
</ul>
First, you don't need to make any assumptions about how many items you're going to have. If you just want to divide the 100% width into x number of items, you can simply use 100 / x.
Also, there is no need for the nested .each you have. jQuery does that automatically.
$(subMenu).each(function() {
var items = $(this).children('li');
var itemSize = 100 / items.length;
items.css('width', itemSize +'%');
});

CSS/JS Dynamic Width & Font Size

I have a client site with a navigation feature that has been very tightly designed (and not by me):
It consists of an unordered list, with three DIVs in each list item:
<ul id="application-tabs">
<li>
<div class="cv-home-applications-slideshow-tab-first"></div>
<div class="cv-home-applications-slideshow-tab"><h4>Coffee</h4></div>
<div class="cv-home-applications-slideshow-tab-right"></div>
</li>
<li>
<div class="cv-home-applications-slideshow-tab-left"></div>
<div class="cv-home-applications-slideshow-tab"><h4>Pet Food</h4></div>
<div class="cv-home-applications-slideshow-tab-right"></div>
</li>
</ul>
The content is in the center DIV, while the first/left and right DIVs create the angled tab buttons.
This client has also requested a Google Translate utility up at the top of the page.
My problem is that I need the navigation element to always fill that space from end to end. If the translation produces a shorter word – such as "Cafe" instead of "Coffee" – I need it to expand accordingly.
Likewise, if it results in a longer word, like "Cerveza," I'll need the font size to reduce.
I'm sure I'll need to employ some javascript, in combination with the CSS, but I'm not entirely sure where to start. Any assistance would be appreciated.
Thanks,
ty
here is a fiddle of a solution, it automatically spaces the menu to fit http://jsfiddle.net/nFRjc/
var $j = jQuery.noConflict();
$j(document).ready(function () {
var containerWidth = $j('#application-tabs').width();
var linksWidth = 0;
$j('#application-tabs li div + div').children().each(function () {
linksWidth += $j(this).width();
});
var linkSpacing = Math.floor((containerWidth - linksWidth) / ($j('#application-tabs').children('li').length));
$j('#application-tabs').children().not(':last-child').css('margin-right', linkSpacing + "px");
});​
Ok simple solution to make the font reduce in size if it's too large. See this fiddle, the links font size are 100px, but the script reduces them until they fit. http://jsfiddle.net/nFRjc/2/ I just added a loop that checks whether or not the total width of the individual links is greater than the container width, and reduces the font size by 1 if true.
var $j = jQuery.noConflict();
$j(document).ready(function () {
var containerWidth = $j('#application-tabs').width();
var linksWidth = 0;
$j('#application-tabs li div + div').children().each(function () {
linksWidth += $j(this).width();
});
while (linksWidth >= (containerWidth - 100)) {
$j('#application-tabs li div + div h4').css({'font-size': '-=1'});
var linksWidth = 0;
$j('#application-tabs li div + div').children().each(function () {
linksWidth += $j(this).width();
});
}
var linkSpacing = Math.floor((containerWidth - linksWidth) / ($j('#application-tabs').children('li').length));
$j('#application-tabs').children().not(':last-child').css('margin-right', linkSpacing + "px");
});​

Find DOM elements at top and bottom of scrolling div with jQuery

I have a scrolling div containing list items. I have this boilerplate scroll event defined
$("#scrollingDiv").scroll(function(e) {
});
Inside of this scroll event function, how can I figure out which elements are at the top and bottom of the currently visible area?
You could try computing the positions of the list items with respect to the scrolling <div> and then scan the positions to see which ones match up with the scrollTop of the <div>.
Something like this perhaps:
var base = $('#scrollingDiv').offset().top;
var offs = [ ];
$('li').each(function() {
var $this = $(this);
offs.push({
offset: $this.offset().top - base,
height: $this.height()
});
});
$("#scrollingDiv").scroll(function() {
var y = this.scrollTop;
for(var i = 0; i < offs.length; ++i) {
if(y < offs[i].offset
|| y > offs[i].offset + offs[i].height)
continue;
// Entry i is at the top so do things to it.
return;
}
});
Live version (open your console please): http://jsfiddle.net/ambiguous/yHH7C/
You'd probably want to play with the fuzziness of the if to get something that works sensibly (1px visible hardly makes an element the top one) but the basic idea should be clear enough. Mixing in the height of #scrollingDiv will let you see which <li> is at the bottom.
If you have a lot of list items, then a linear search might not be what you want but you should be able to solve that without too much effort.

Categories

Resources