Break UL into multiple columns - javascript

It is difficult to extract code and post it here, so I'm posting a sample site which demonstrates my problem. I've been thinking about this, and there are several ways to solve this problem (HTML modification, CSS + HTML modification, CSS + JS modification), but since I'm a n00b at web coding, I want to know what is the best plan of attack here.
Anyways, the problem:
Have a look here: http://321cart.com/sellya/
Open the Categories Navigation menu, and you get this:
All those links under the categories are ul containing lots of li. I want to break the list and spread the list of multiple columns, so that there are only a maximum of 3 li in one column. Here is an example of what I want to do:
This is the overall thing that I want:
This will cause the menu width to increase (virtually double, in theory), but that is okay for me. I can deal with this on my own.
There are several methods to do this, but I would prefer if there's some way to do this using only CSS modification. If not, then maybe through CSS and JavaScript modification ? I want to reserve changes to actual HTML code as a last resort, because that is the original PHP HTML generation OpenCart code which I don't want to modify.
Please note that I did try to resolve this problem on my own, but it proved to be a little complex .. So don't think I didn't try .. Secondly, posting this in a JSFiddle would have been difficult to, and it would not have truly represented the actual problem fully, which is why I'm posting a link to a sample site.
Please have a look guys. Site: http://321cart.com/sellya/
So I need some answers for this problem that has me baffled.
EDIT:
Forgot to point this out. In the example I provided, all uls had more than 3 lis each, but in my actual case, only a few uls have more than 3 lis each, and I only want to extend those ones to 2 columns, not the ones will 1, 2 or 3 lis each.

CSS3 introduced columns, which should do what you want. In theory, simply apply a height and column-width to your ul. In practice, you might need to use vendor prefixes on the column-width, giving you -webkit-column-width, -moz-column-width, etc. Try it out.

You can set the outer divs width (the ones with "span2" class in the provided link) to something like 350px and set LIs width to 100px and float:left directive. This is the simplest approach you can use.

take a look at this:
var $columns = $("#columns");
var $longlist = $("#longlist");
var columnscount = 3;
//calculate sigle column size
var columnsize = Math.ceil($("p", $longlist).size() / columnscount);
//iterate number of columns, take [columnsize] elements, move them to div#columns and wrap with div.column
for (var i = 0; i < columnscount; i++) {
$("p:lt(" + columnsize + ")", $longlist).appendTo($columns).wrapAll("<div class='column'/>")
}
you would need to add some condition to avoid spliting list shorter that 3 elements.
and this is how it works:
http://jsfiddle.net/hGWjd/

Related

Find out how wide a set of children is

I am working on a WP theme, and am having some trouble with the navigation.
The basic markup looks like this:
#navbar
ul.main-nav
li.menu-item.drop-submenu
ul.submenu
li.menu-item.drop-submenu
ul.submenu
li.menu-item.drop-submenu
etc. (any menu item can have unlimited submenus)
li.menu-item.drop-submenu
ul.submenu
li.menu-item
li.menu-item
li.menu-item
Now the problem I'm having is that a menu item with 2 or more submenus spanning to the right will eventually overflow off viewport. I'm using jquery to calculate the width and offset of the submenus and apply a class that will cause the submenus to drop on the left instead should they cause overflow. This was relatively easy to do for the "top-level" submenus, but i'm drawing a blank for the nested submenus.
Basically, i'm looking for a way to find out what set of submenus spans most to the right, but not the collective width of all nested subs, if that makes any sense...
I'm not even sure how to explain this properly, so if something is unclear i will try my best to clarify. Thank you in advance, any push in the right direction will be much appreciated.
EDIT: Made a JSFiddle
Basically, i'm looking for a way to find out what set of submenus
spans most to the right, but not the collective width of all nested
subs, if that makes any sense...
So, this will select all of the innermost submenus, which in your case would*(right?)* be those furthest to the right.
var $rightmost = $(".main-nav").find('.submenu:not(:has(.submenu))');
You could repeatedly select elements in the following way, adding a .left class to all outlying .submenus until all pass the in-viewport check you're running. Maybe put it in a while loop and see if your query results have a length, then run your checking and handling logic within.
var $rightmost = $(".main-nav").find('.submenu:not(:has(.submenu)):not(.left)');
lol. it's late here and i'm delirious, so if this sucks or doesn't make a clear argument, speak up!
Admittedly: I did not check this, so also speak up if it's funky.
Assumption
because of this,
I'm using jquery to calculate the width and offset of the submenus and
apply a class that will cause the submenus to drop on the left instead
should they cause overflow.
I'm operating under the assumption that you've already coded the viewport logic.
Edit: this works
though it could be optimized... And I didn't at all do it in the I think cool way I proposed. GSD
You could have a look at the Jquery positionCalculator that allows your script to check for collisions automatically (See the Bootstrap dropdown example at the bottom of the page).

How to set HTML Table column widths explicitly in JavaScript

I have hit a scaling problem with an HTML table, whilst manipulating it in JavaScript.
I have a JavaScript code blob that dynamically creates a table from a JSON array.
It fixes the header and allows the table body to scroll, and it works well.
The header is a separate table, and I strongly control cell widths to ensure that the header table's cells match up with the body table cells.
The body table cells all have their width explicitly set.
I iterate the rows in the table, and then interate the cells within that loop, and set each cells width.
This iteration is not scaling, as the rows grow to 100 and the columns get to 40 odd, I get complaints from Firefox that the script is hanging or unresponsive. It is unresponsive, because it is busy setting all the cell widths.
nz.dynatable.setTableColumnWidths = function (table, arrayColumnWidths) {
// Iterate the table and set width settings for each cell
for (var i = 0; i < table.rows.length; ++i) {
for (var j = 0; j < table.rows[i].cells.length; ++j) {
var width = arrayColumnWidths[j] || 0;
table.rows[i].cells[j].style.width = width.toString() + "px";
}
}
}
Q: Is it possible to to set cell widths for a table in one row and have all the other cells in the table fall into line with this? Is Firefox getting whacked because changing the cell widths is causing it to recalc the table size on each loop?
The full code chunk is in a public GitHub repo: https://github.com/Hiblet/DynaTable
Try this: I had have same problem and this is help me:
let tables = document.getElementsByClassName("table");
if(tables.length == 2){
let firstTableCol = tables[0].querySelectorAll('colgroup col');
let secondTableCol = tables[1].querySelectorAll('tr')[0].querySelectorAll('td');
if(firstTableCol.length == secondTableCol.length){
for(let index = 0; index < firstTableCol.length; index++){
firstTableCol[index].style.width = secondTableCol[index].offsetWidth + "px";
}
}else
console.log('cols count mismatch');
}else
console.log('count of tables are bigger than 2');
I tried a few ways to skin this cat and here were my findings. The final solution ended up being very clean and performant, with the caveats that I tested on Chrome, and not other browsers, and with moderately large, but not huge, tables. To summarize, use css such as to following to set the 5th column to width 70, and use javascript to modify that css:
table#my_table th:nth-child(5), table#my_table td:nth-child(5){ max-width:70px; min-width:70px;}
More details:
you need to set both min-width and max-width to avoid columns that you are NOT trying to resize from doing funky stuff in order to keep the table size constant
you can do this using element.style.xxxWidth, but a simple approach such as in the OP doesn't scale well, as the OP notes.
One way to deal with this is to only update the elements that are in a viewable area (let's call this the "viewable area approach"). You can do this by comparing the parent.getBoundingClientRect() results from the parent object and the object you are checking (see e.g. How to tell if a DOM element is visible in the current viewport?)
I was able to get this to work pretty well as long as calls to getBoundingClientRect() (which are expensive) were kept to a minimum (i.e. do it once per column, not per cell). Because I didn't want to maintain a cache of what cells were updated and what were not, after updating viewable cells, I would then update the non-viewable cells using an async function call. This way the UI appeared much more responsive even though it was still doing stuff in the background
However, anything involving direct changes to element.style.xxxWidth felt messy, especially after having to add the code to only update viewable elements. Furthermore, though performance was much better with that change, it still seemed possibly sub-optimal. So the final solution I ended up using was to do the following (let's call this the "dynamic stylesheet approach"):
assume each table will have a unique element ID
initialize the table by creating a new style (document.createElement('style')) for each column. The style that contain one rule that will only select that column for the table with that ID, e.g. for table with id "my_table", column 5 (cellIndex 4), to set width to 70:
table#my_table th:nth-child(5), table#my_table td:nth-child(5){ max-width:70px; min-width:70px;}
add the newly created styleElements to the table element (not the document), so that if the table is removed from the DOM, these extra styles go away also
to change a column width, the only thing that needs to change is the maxWidth / minWidth pieces of rule 0 of the related styleElement (note, you could also do this with a single stylesheet that has multiple rules, but I found it easier to use a separate stylesheet per column with one rule each).
Overall, using the dynamic stylesheet approach was the winner in my view for the following reasons:
it performs well with tables that are large enough to have performance issues under the "naive" approach described in the OP (though I haven't compared the performance of other approaches on very large tables)
it is clean: no element.style changes are required, and it doesn't clutter your basic document DOM as the relevant style elements are attached to the related table (need to test cross-browser)
if its performance isn't already optimized automatically by browsers, its design should make optimization straightforward by leaving the performance challenge up to the browser to handle

Freeze Top Row (up/down) In Table and Allow Scroll left/right

I have some wide tables in HTML that require scrolling left/right in the browser to view. I'd like to vertically keep the top row in place while allowing it to scroll left/right.
I have been scouring the web for something simple but I haven't found anything that allows for both of these conditions.
EDIT:
I restate my question in a possibly more clear way-
I am looking to alter existing html files with existing html tables so that the first row is fixed vertically but scrolls horizontally with the body. I don't have control over how these html files are initially made so I must resort to adding any styling that I can through python in order to get my desired output. In my mind there would only be one scroll bar. Can this be done?
Anyone have an idea?
To give more information:
I have tried many options including trying to add a css class for the first row and using overflow-y as well as using multiple divs. I would prefer to solve this in css formatting as modifying the css file is probably easier done with python than adding a bunch of divs to the already formulated html table (all of the editing is being done through python).
So far every approach at best looks a bit off and undesirably leaves a scroll bar directly under the first row.
I would like to keep the first row looking the same but just fixed vertically and allow it scroll with the rest of the rows horizontally.
I was able to solve my problem and post my solution here in case anyone else out there is working with webpages output from excel (or just any predetermined html table in general) and wants to make the first row (or any given row) freeze vertically but scroll horizontally. The solution relies on javascript/jquery/CSS and implementing the two div approach (I don't need a footer) discussed in the solution presented by Saechel. I tried just about every approach and this was the only satisfactory one:
CSS file include the following:
#header { position: fixed;}
javascript file include the following:
$(document).ready(function(){
function scroll_Table_Header()
{
var pos = $(this).scrollLeft();
$("#header").animate({left: "-" + pos},1);
//console.log("position: " + pos);
//alert("Hi");
}
var firstDiv = $("<div></div>");
firstDiv.attr("id","header");
var secondDiv = $("<div></div>");
secondDiv.attr("id","content");
var colformatting = $("table colgroup").clone();
var titlebar = $("table tr:first-child").clone();
$("table").attr("id", "origTable");
var newTable = $("<table id='newTable'></table>").attr("style", $("table").attr("style"));
newTable.attr("id", "newTable");
newTable.append(colformatting);
newTable.append(titlebar);
$("#origTable").before(firstDiv);
firstDiv.append(newTable);
secondDiv.append($("#origTable"));
$(firstDiv).after(secondDiv);
$(window).scroll(function(e)
{
scroll_Table_Header();
});
});
Enjoy

How to limit a <td> to only three lines?

I would like to achieve a unified look and feel across all my table rows.
When you look at my example below you can see that the note in the middle goes over 4 lines and thats not so pretty.
I was hoping to limit all <td> to 3 lines.
If there is more to be shown than three lines, then it should cut the content with ... [click for more] and put the content inside a collapseable element, so that when clicked on it it would show the whole content.
The latter shouldn't be a problem, but how do I limit the content to only three lines? Shall I count the characters to make the decision upon that? Is there a better strategy? I am using Django by the way,but I am happy to use javascript, jquery or any css magic instead to solve this.
Update:
The accepted answer is very good. However it comes with a caveat, which isn't easy to solve.
if you have a neighbouring td that already goes over three lines, while the current td is only two lines we will get an infinite while loop.
while($(this).innerHeight() / $(this).css('line-height').slice(0,-2) >= 3){ .. }
Since $(this).innerHeight() can't decrease because of the neighbouring cell holding the height up high. I think if it was possible to get the css of the current td and copy it across the content completely in a separate field, where neighbouring tds can't interfere, we would get the optimal solution.
Update 2:
As Assad mentioned, the solution is to put a div wrapper around the content of td and set the class on the div inside the td rather than on the td itself. It works flawlessly.
Assuming you are using jQuery, you could find all td elements that exceed a certain number of lines using:
$('td').filter(function(){
return $(this).innerHeight() / $(this).css('line-height').slice(0,-2) > 3; //more than 3 lines
});
You could then apply collapsible elements to these td elements.
Here is a demonstration (using paragraphs instead of tds): http://jsfiddle.net/jM4ZY/1/
Here is an example of cutting off content to fit 3 lines, then adding a more button: http://jsfiddle.net/jM4ZY/2/
As far as the edit is concerned, this is easily resolved by using an inner wrapper for your content; possibly a div element. You can then measure the height of this element, which is independent of the height of neighboring cells.
Another jQuery solution is described here
It is described how to change the Text by counting the number of letters in the displayed text. If you are unsure about the number of letters, or want to make it dependent of the text-length you can calculate it by using this snipped
$.fn.textWidth = function(){
var html_org = $(this).html();
var html_calc = '<span>' + html_org + '</span>';
$(this).html(html_calc);
var width = $(this).find('span:first').width();
$(this).html(html_org);
return width;
};
which I took from Calculating text width

Is it okay to rely on javascript for menu layout?

I have a website template where I do not know the number of menu items or the size of the menu items that will be required. The js below works exactly the way I want it to, however this is the most js I've every written. Are there any disadvantages or potential problems with this method that I'm not aware of because I'm a js beginner? I'm currently manually setting the padding for each site. Thank you!
var width_of_text = 0;
var number_of_li = 0;
// measure the width of each <li> and add it to the total with, increment li counter
$('li').each(function() {
width_of_text += $(this).width();
number_of_li++;
});
// calculate the space between <li>'s so the space is equal
var padding = Math.floor((900 - width_of_text)/(number_of_li - 1));
// add the padding the all but the first <li>
$('li').each(function(index) {
if (index !== 0)
{
$(this).css("padding-left", padding);
}
});
You can do this hackily in CSS, using display: inline-block, and text-align: justify
<ul>
<li>thing</li>
<li>thing2</li>
<li>thing3</li>
<li>thing4</li>
<li class="hack"></li>
</ul>
And then:
ul { text-align: justify }
li { display: inline-block }
li.hack { width: 100% } /* force the justified text to wrap */
Demo
​
Yes, there are disadvantages of using JS for formatting.
It is strongly recommended to avoid using JS for formatting and positioning, use CSS whenever possible.
Javascript is interpreted and run very differently from browser to browser, from OS to OS, from OS/browser version to version.
CSS rendering is a native browser engine function and its rendering priority is higher than that of JS.
CSS rendering is much more speedy than JS.
Etc.
What you are doing now I would never suggest doing. IMHO, this is a very wrong approach. JS is absolutely definetely misused in this case. You have to use CSS for this task, and I would suggest posting a question about how to use CSS correctly for this task.
I would suggest having a default spacing between them in a way that would not push them out of their container. The extra javascript to enable them to space equally should be an enhancement only.
I think the answer to your question is, if it works, then it works (and will continue to work), but that doesn't mean that this is the best way to handle it. If you *care about the best way, then investigate how to improve your approach using mostly (or even exclusively) CSS. If you're just looking to get the job done, and it's working, then you're good to go.
Depending on your site visitors, there will be around 3% who visit you with JS disabled. And you want the site to work for them to. Maybe not the unnessecary parts of the site but you want the critical parts to work. Navigation is one of the most important parts of a website.
Make sure the navigation works without JS (doesn't have to be as fancy as with JS) and then you could make some improvements with JS.
You don't need JavaScript as long as you can rely on a CSS algorithm that adapt width to its content: the table layout algorithm :)
See http://jsfiddle.net/r9yrM/1/ from my previous answer for examples.
Don't forget to have a minimum padding on each "cell", text stuck to a border isn't very readable (and ugly). You'll also probably want text-align: center on cells (last CSS rule).
With JS, you could decide of a maximum number of tabs (or a minimum "reasonable" width) and above (below) that number, add a class on the parent that will trigger each tab to render as float: left and block and not table-cell anymore. Then it'll occupy 2 or more lines (like the extension Tab Mix Plus on Firefox)
Note: there're at least 2 algorithms for table: with and without table-layout: fixed, depending on freedom left to the browser when adapting.
Note on your jQuery code above: "each li except the first" can be expressed by $('li + li') (the first one isn't preceded by a li)

Categories

Resources