I want to paginate a text in some div so it will fit the allowed area
Logic is pretty simple:
1. split text into words
2. add word by word into and calculate element height
3. if we exceed the height - create next page
It works quite good
here is JS function i've used:
function paginate() {
var newPage = $('<pre class="text-page" />');
contentBox.empty().append(newPage);
var betterPageText='';
var pageNum = 0;
var isNewPage = false;
var lineHeight = parseInt(contentBox.css('line-height'), 10);
var wantedHeight = contentBox.height() - lineHeight;
for (var i = 0; i < words.length; i++) {
if (isNewPage) {
isNewPage = false;
} else {
betterPageText = betterPageText + ' ' + words[i];
}
newPage.text(betterPageText + ' ...');
if (newPage.height() >= wantedHeight) {
pageNum++;
if (pageNum > 0) {
betterPageText = betterPageText + ' ...';
}
newPage.text(betterPageText);
newPage.clone().insertBefore(newPage)
betterPageText = '...';
isNewPage = true;
} else {
newPage.text(betterPageText);
}
}
contentBox.craftyslide({ height: wantedHeight });
}
But when i add an image it break everything. In this case text overflows 'green' area.
Working fiddle: http://jsfiddle.net/74W4N/7/
Is there a better way to paginate the text and calculate element height?
Except the fact that there are many more variables to calculate,not just only the word width & height, but also new lines,margins paddings and how each browser outputs everything.
Then by adding an image (almost impossible if the image is higher or larger as the max width or height) if it's smaller it also has margins/paddings. and it could start at the end of a line and so break up everything again.basically only on the first page you could add an image simply by calculating it's width+margin and height+margin/lineheight. but that needs alot math to get the wanted result.
Said that i tried some time ago to write a similar script but stopped cause of to many problems and different browser results.
Now reading your question i came across something that i read some time ago:
-webkit-column-count
so i made a different approach of your function that leaves out all this calculations.
don't judge the code as i wrote it just now.(i tested on chrome, other browsers need different prefixes.)
var div=document.getElementsByTagName('div')[0].firstChild,
maxWidth=300,
maxHeigth=200,
div.style.width=maxWidth+'px';
currentHeight=div.offsetHeight;
columns=Math.ceil(currentHeight/maxHeigth);
div.style['-webkit-column-count']=columns;
div.style.width=(maxWidth*columns)+'px';
div.style['-webkit-transition']='all 700ms ease';
div.style['-webkit-column-gap']='0px';
//if you change the column-gap you need to
//add padding before calculating the normal div.
//also the line height should be an integer that
// is divisible of the max height
here is an Example
http://jsfiddle.net/HNF3d/10/
adding an image smaller than the max height & width in the first page would not mess up everything.
and it looks like it's supported by all modern browsers now.(with the correct prefixes)
In my experience, trying to calculate and reposition text in HTML is almost an exercise in futility. There are too many variations among browsers, operating systems, and font issues.
My suggestion would be to take advantage of the overflow CSS property. This, combined with using em sizing for heights, should allow you to define a div block that only shows a defined number of lines (regardless of the size and type of the font). Combine this with a bit of javascript to scroll the containing div element, and you have pagination.
I've hacked together a quick proof of concept in JSFiddle, which you can see here: http://jsfiddle.net/8CMzY/1/
It's missing a previous button and a way of showing the number of pages, but these should be very simple additions.
EDIT: I originally linked to the wrong version for the JSFiddle concept
Solved by using jQuery.clone() method and performing all calculations on hidden copy of original HTML element
function paginate() {
var section = $('.section');
var cloneSection = section.clone().insertAfter(section).css({ position: 'absolute', left: -9999, width: section.width(), zIndex: -999 });
cloneSection.css({ width: section.width() });
var descBox = cloneSection.find('.holder-description').css({ height: 'auto' });
var newPage = $('<pre class="text-page" />');
contentBox.empty();
descBox.empty();
var betterPageText = '';
var pageNum = 0;
var isNewPage = false;
var lineHeight = parseInt(contentBox.css('line-height'), 10);
var wantedHeight = contentBox.height() - lineHeight;
var oldText = '';
for (var i = 0; i < words.length; i++) {
if (isNewPage) {
isNewPage = false;
descBox.empty();
}
betterPageText = betterPageText + ' ' + words[i];
oldText = betterPageText;
descBox.text(betterPageText + ' ...');
if (descBox.height() >= wantedHeight) {
if (i != words.length - 1) {
pageNum++;
if (pageNum > 0) {
betterPageText = betterPageText + ' ...';
}
oldText += ' ... ';
}
newPage.text(oldText);
newPage.clone().appendTo(contentBox);
betterPageText = '... ';
isNewPage = true;
} else {
descBox.text(betterPageText);
if (i == words.length - 1) {
newPage.text(betterPageText).appendTo(contentBox);
}
}
}
if (pageNum > 0) {
contentBox.craftyslide({ height: wantedHeight });
}
cloneSection.remove();
}
live demo: http://jsfiddle.net/74W4N/19/
I actually came to an easier solution based on what #cocco has done, which also works in IE9.
For me it was important to keep the backward compatibility and the animation and so on was irrelevant so I stripped them down. You can see it here: http://jsfiddle.net/HNF3d/63/
heart of it is the fact that I dont limit height and present horizontal pagination as vertical.
var parentDiv = div = document.getElementsByTagName('div')[0];
var div = parentDiv.firstChild,
maxWidth = 300,
maxHeigth = 200,
t = function (e) {
div.style.webkitTransform = 'translate(0,-' + ((e.target.textContent * 1 - 1) * maxHeigth) + 'px)';
div.style["-ms-transform"] = 'translate(0,-' + ((e.target.textContent * 1 - 1) * maxHeigth) + 'px)';
};
div.style.width = maxWidth + 'px';
currentHeight = div.offsetHeight;
columns = Math.ceil(currentHeight / maxHeigth);
links = [];
while (columns--) {
links[columns] = '<span>' + (columns + 1) + '</span>';
}
var l = document.createElement('div');
l.innerHTML = links.join('');
l.onclick = t;
document.body.appendChild(l)
Related
I am trying to work out a way to detect wordwrap in a specific span tag inside a banner. If it wraps to 2 lines then increase the overall height of the container by 56px. There is also a sub headline (headline2) which also needs to increase (or decrease) the height by 40px.
I have written some basic JS code here which checks the div height of the span but its not great & also will only work for 3 lines.
// Variable banner heights
var hl11sub = 368;
var hl21sub = 448;
var hl31sub = 548;
var hl12sub = 416;
var hl22sub = 496;
var hl32sub = 576;
var hLFontSizeCSS = window.getComputedStyle(headlineText, null).getPropertyValue("font-size");
var hL2FontSizeCSS = window.getComputedStyle(headline2text, null).getPropertyValue("font-size");
var bannerHeightCSS = window.getComputedStyle(banner, null).getPropertyValue("height");
var headlineHeight = headlineText.offsetHeight;
var hL2HeadHeight = headline2text.offsetHeight;
var headHeight = headlineText.style.lineHeight = parseInt(hLFontSizeCSS) + 10 + "px";
var hL2Height = headline2text.style.lineHeight = parseInt(hL2FontSizeCSS) + 10 + "px";
// Text Height values
var hL1LineHeight = parseInt(headHeight); // 8 is line height & padding
var hL2LinesHeight = 140;
var hL3LinesHeight = 195;
// HL2 height values
var hL2TextOver1LineHeight = parseInt(hL2Height); // 8 is line height & padding
var hL2TextOver2LineHeight = 84;
if(hL2HeadHeight == hL2TextOver1LineHeight && headlineHeight == hL1LineHeight){
banner.style.height = hl11sub + "px";
}
else if(hL2HeadHeight == hL2TextOver1LineHeight && headlineHeight == hL2LinesHeight){
banner.style.height = hl21sub + "px";
}
else if(hL2HeadHeight == hL2TextOver1LineHeight && headlineHeight >= hL3LinesHeight){
banner.style.height = hl31sub + "px";
}
else if(hL2HeadHeight == hL2TextOver2LineHeight && headlineHeight == hL1LineHeight){
// Single headline with 2 lines sub
banner.style.height = hl12sub + "px";
}
else if(hL2HeadHeight == hL2TextOver2LineHeight && headlineHeight == hL2LinesHeight){
// 2 headlines with 2 lines sub
banner.style.height = hl22sub + "px";
}
else {
banner.style.height = hl32sub + "px";
// 3 headlines with 2 lines sub
It needs to only change the height of the banner depending on if the span words wrap once, twice, three times etc.
Any suggestions or help with this would be greatly appreciated.
Here is a very basic implementation on how to detect when a line is wrapped hopefully this gives you a good idea where to start and integrate it into your app.
Heres the docs for stuff used
https://developer.mozilla.org/en/docs/Web/API/EventTarget/addEventListener
https://developer.mozilla.org/en-US/docs/Web/API/Window/getComputedStyle
https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver
You mentioned the height changing and you needing to know when its wrapped you can use a mutation observer to check when the style has changed then check if its wrapped.
Resize the demo window to see results
any questions i'll try get to them asap if i've misunderstood i'll happily change :)
const h1 = document.querySelector('h1');
const banner = document.querySelector('.banner');
//handles style changes on banner to check wrapping
const observer = new MutationObserver(mutations =>
mutations.forEach(mutationRecord => onLineWrapDoSomething())
);
observer.observe(banner, { attributes : true, attributeFilter : ['style'] });
// handles window resize events
window.addEventListener('resize', onLineWrapDoSomething)
function onLineWrapDoSomething() {
const { lineHeight } = getComputedStyle(h1);
const lineHeightParsed = parseInt(lineHeight.split('px')[0]);
const amountOfLinesTilAdjust = 2;
if (h1.offsetHeight >= (lineHeightParsed * amountOfLinesTilAdjust)) {
console.log('your h1 now wrapped')
} else {
console.log('your h1 on one line')
}
}
// shows it logs when style changes and it wraps, ignore the disgusting code below
setTimeout(() => {
banner.style.width = '50%'
setTimeout(() => {
banner.style.width = '100%'
}, 1500)
}, 1500)
.banner {
width: 100%;
}
h1 {
line-height: 1.5
}
<div class="banner">
<h1>This is some text that will eventually wrap</h1>
</div>
I am generating pdf from jsPDF api , I want to add footer to each page with page number .
How to achieve this . It is having option of adding footer from fromHTML plugin , but I am writing without HTML.
var doc = new jsPDF("portrait","px","a4");
You have to implement it yourself. You can do something like this:
var doc = new jsPDF();
doc.page=1; // use this as a counter.
function footer(){
doc.text(150,285, 'page ' + doc.page); //print number bottom right
doc.page ++;
};
// and call footer() after each doc.addPage()
Stephen Collins is the best answer! It works well with jspdf-autotable plugin.
With this is made after all is added to the doc, so we can use easy the total page number!
Add some style to the Stephen Collins answer: "page x of total"
const addFooters = doc => {
const pageCount = doc.internal.getNumberOfPages()
doc.setFont('helvetica', 'italic')
doc.setFontSize(8)
for (var i = 1; i <= pageCount; i++) {
doc.setPage(i)
doc.text('Page ' + String(i) + ' of ' + String(pageCount), doc.internal.pageSize.width / 2, 287, {
align: 'center'
})
}
}
let doc = new jsPDF()
doc.text(...)
doc.autoTable(...)
addFooters(doc)
doc.save()
I know this post is old but I'm going to offer another solution.
First define your total amount of pages. There's multiple ways to determine this so I won't go into that.
var doc = new jsPDF('p', 'pt', 'letter');
doc.page = 1; // use this as a counter.
var totalPages = 10; // define total amount of pages
// HEADER
doc.setFontSize(20);//optional
doc.setTextColor(40);//optional
doc.setFontStyle('normal');//optional
doc.text("Report", 50, 22);// set your margins
// FOOTER
var str = "Page " + doc.page + " of " + totalPages;
doc.setFontSize(10);// optional
doc.text(str, 50, doc.internal.pageSize.height - 10);//key is the interal pageSize function
// Add Page content
.....
//Add new page and increase page count
doc.addPage();
doc.page ++;
//Begin process all over again.
This works well in a loop as you can set your page count by taking your array.length + 1 (as it's zero based).
Run this function before you run doc.save()
function addFooters() {
const pageCount = doc.internal.getNumberOfPages();
for(var i = 1; i <= pageCount; i++) {
doc.text(String(i), 196, 285);
}
}
It's work for me:
I just put coordenates for A4 Paper;
Just add the for before doc.save() like this;
// Create a document
var doc = new jsPDF('p','mm','a4');
// Some stuff
doc.text("Some text", 74, 150);
doc.addPage();
doc.text("Some text", 74, 150);
doc.addPage();
doc.text("Some text", 74, 150);
doc.addPage();
doc.text("Some text", 74, 150);
doc.addPage();
doc.text("Last page", 74, 150);
// PAGE NUMBERING
// Add Page number at bottom-right
// Get the number of pages
const pageCount = doc.internal.getNumberOfPages();
// For each page, print the page number and the total pages
for(var i = 1; i <= pageCount; i++) {
// Go to page i
doc.setPage(i);
//Print Page 1 of 4 for example
doc.text('Page ' + String(i) + ' of ' + String(pageCount),210-20,297-30,null,null,"right");
}
// Save the doc
doc.save('test.pdf');
If you need something like "current page / totalPage" displaying for each page.
Using "Total page number" plugin available in jspdf v1.0+
How to use:
var doc = new jsPDF();
doc.page=1; // use this as a counter.
var totalPagesExp = "{total_pages_count_string}";
function footer(){
var str = "Page " + doc.page;
if (typeof doc.putTotalPages === 'function') {
str = str + "/" + totalPagesExp;
}
doc.text(150,285, str); //print number bottom right
}
// call footer() after each doc.addPage()
// and before doc.save() do not forget put
if (typeof doc.putTotalPages === 'function') {
doc.putTotalPages(totalPagesExp);
}
It should work. Hope this helps.
After digging into the code, I think the feature you ask is not implemented. But there is a function to generate a footer from html and you can use this code to fullfill your need. But beware some part of the code is marked as "bad hack".
From plugins/from_html.js
checkForFooter = function (elem, renderer, elementHandlers) {
//check if we can found a <footer> element
var footer = elem.getElementsByTagName("footer");
if (footer.length > 0) {
footer = footer[0];
//bad hack to get height of footer
//creat dummy out and check new y after fake rendering
var oldOut = renderer.pdf.internal.write;
var oldY = renderer.y;
renderer.pdf.internal.write = function () {};
DrillForContent(footer, renderer, elementHandlers);
var footerHeight = Math.ceil(renderer.y - oldY) + 5;
renderer.y = oldY;
renderer.pdf.internal.write = oldOut;
//add 20% to prevent overlapping
renderer.pdf.margins_doc.bottom += footerHeight;
//Create function render header on every page
var renderFooter = function (pageInfo) {
var pageNumber = pageInfo !== undefined ? pageInfo.pageNumber : 1;
//set current y position to old margin
var oldPosition = renderer.y;
//render all child nodes of the header element
renderer.y = renderer.pdf.internal.pageSize.height - renderer.pdf.margins_doc.bottom;
renderer.pdf.margins_doc.bottom -= footerHeight;
//check if we have to add page numbers
var spans = footer.getElementsByTagName('span');
for (var i = 0; i < spans.length; ++i) {
//if we find some span element with class pageCounter, set the page
if ((" " + spans[i].className + " ").replace(/[\n\t]/g, " ").indexOf(" pageCounter ") > -1) {
spans[i].innerHTML = pageNumber;
}
//if we find some span element with class totalPages, set a variable which is replaced after rendering of all pages
if ((" " + spans[i].className + " ").replace(/[\n\t]/g, " ").indexOf(" totalPages ") > -1) {
spans[i].innerHTML = '###jsPDFVarTotalPages###';
}
}
//render footer content
DrillForContent(footer, renderer, elementHandlers);
//set bottom margin to previous height including the footer height
renderer.pdf.margins_doc.bottom += footerHeight;
//important for other plugins (e.g. table) to start rendering at correct position after header
renderer.y = oldPosition;
};
//check if footer contains totalPages which shoudl be replace at the disoposal of the document
var spans = footer.getElementsByTagName('span');
for (var i = 0; i < spans.length; ++i) {
if ((" " + spans[i].className + " ").replace(/[\n\t]/g, " ").indexOf(" totalPages ") > -1) {
renderer.pdf.internal.events.subscribe('htmlRenderingFinished', renderer.pdf.putTotalPages.bind(renderer.pdf, '###jsPDFVarTotalPages###'), true);
}
}
//register event to render footer on every new page
renderer.pdf.internal.events.subscribe('addPage', renderFooter, false);
//render footer on first page
renderFooter();
//prevent footer rendering
SkipNode['FOOTER'] = 1;
}
};
I've got a textarea where the user inputs there data, but as they add it when the cursor gets to the end it reduces the size of the font. All works quite well but I cannot seem to set a limit on how small it goes. Also is there any way I can set the point at which the text starts to go small, because the text tends to disappear at the end before shrinking? Please could someone help with this, seem to be pulling my hair out on this one?
function textAreaChange(){
var textArea1 = document.getElementById("textarea");
var sizeText1 = document.getElementById("size_text");
while (textArea1.scrollWidth > textArea1.clientWidth)
{
var fs = parseFloat(textArea1.style.fontSize) - 1;
textArea1.style.fontSize = fs + 'px';
sizeText1.value = (fs | 0) + 'px';
document.getElementById("a1").innerHTML = ((parseInt(textArea1.style.fontSize))/textboxscale);
sizeText1.value = ((parseInt(textArea1.style.fontSize))/textboxscale);
}
}
textAreaChange();
Thanks in advance.
Davetoff
Right I have added the if (fs>15) to this code, but now it lock up when it gets to 15:
function textAreaChange(){
var fss = 15;
var textArea1 = document.getElementById("textarea");
var sizeText1 = document.getElementById("size_text");
while (textArea1.scrollWidth > textArea1.clientWidth)
{
var fs = parseFloat(textArea1.style.fontSize) - 1;
if (fs >15){
textArea1.style.fontSize = fs + 'px';
sizeText1.value = (fs | 0) + 'px';
document.getElementById("a1").innerHTML = ((parseInt(textArea1.style.fontSize))/textboxscale);
sizeText1.value = ((parseInt(textArea1.style.fontSize))/textboxscale);
} else {
textArea1.style.fontSize = fss + 'px';
sizeText1.value = (fss | 0) + 'px';
document.getElementById("a1").innerHTML = fss + 'px';
}
}
}
Please help really appreciated spent a couple of days on this now and my head is beginning to hurt:
Thanks.
Davetoff
After the line where you assign "fs", just add a statement to make sure that if fs is too small, you don't assign it to the textArea1 font size, something like:
if (fs > 7) //only perform statements below if fs > 7
{
...
}
Right found the problem, indeed the answer was to add the if statement, however I was being a fool as I had a scale factor in there that was causing all sorts of problems, also I've added a keycode in there to stop additional key inputs when the end of the text area is reached, updated code below:
function textAreaChange(){
var fss = <?=$min_text_size?>*textboxscale;
var textArea1 = document.getElementById("textarea");
var sizeText1 = document.getElementById("size_text");
if (textArea1.scrollWidth > (textArea1.clientWidth))
{
var fs = parseFloat(textArea1.style.fontSize) - (2);
if (fs >(14*textboxscale)){
textArea1.style.fontSize = (fs) + 'px'; //this is the textarea style
document.getElementById("a1").innerHTML = (parseInt(textArea1.style.fontSize)/textboxscale); //this is some text to show the user what size the text is
sizeText1.value = ((parseInt(textArea1.style.fontSize))/textboxscale); //this is a slider I put to chage the text height manually (input range)
} else{
textArea1.style.fontSize = (fss) + 'px';
document.getElementById("a1").innerHTML = fss/textboxscale;
sizeText1.value = fss /textboxscale;
if (event.keyCode == 8, 32) {
event.preventDefault();
}
}
}
}
However the keycode 8,32 works well in IE but not in Firefox, any additional help would be really appreciated?
D
My problem is that I've been working on a "accordion menu", which uses CSS3 transition on the height property to give it an animation when the <li> is expanding, which contains a submenu.
The expansion of the <li> happens when calling a function, triggered by a onclick event.
The function calculates the new height for the containing <li> based on how many <li>'s the submenu contains. All this works fine, but I can't figure out how to reset the height on the second click, to close the submenu.
function accordion(ul){
{
// Make new height happen
var howManyChildren = document.getElementById(ul).children.length;
var calcSubHeight = 30 + howManyChildren * 31 + 'px'; // "top-padding" + amount of <li>'s * each <li> height + unit
var elementId = ul.replace('_ul','');
document.getElementById(elementId).style.height = subHeight;
}
else // Code to be executed on secound click.
{
document.getElementById(elementId).style.height = "";
}
}
I want the "else" block to execute on the second click, as the comment states.
So I might have been a bit to elaborate here. And pardon for the bad English, I'm quite Swedish.
Try this, it sees if the height returns false.
function accordion(ul) {
var elementId = ul.replace('_ul', '');
if (!document.getElementById(elementId).style.height) {
var howManyChildren = document.getElementById(ul).children.length;
var calcSubHeight = 30 + howManyChildren * 31 + 'px'; // "top-padding" + amount of <li>'s * each <li> height + unit
document.getElementById(elementId).style.height = subHeight;
} else {
document.getElementById(elementId).style.height = "";
}
}
To determine whether it's the first or second click, you'll need to keep a variable that changes on each click. A closure is ideal for this:
var accordion = ( function accordionClosure(){
var count = 0;
return function accordion( ul ){
if ( count === 0 ){
var howManyChildren = document.getElementById(ul).children.length;
var calcSubHeight = 30 + howManyChildren * 31 + 'px'; // "top-padding" + amount of <li>'s * each <li> height + unit
var elementId = ul.replace('_ul','');
document.getElementById(elementId).style.height = subHeight;
count = 1;
}
else {
document.getElementById(elementId).style.height = "";
count = 0;
}
};
}() );
I am building a timelined comments feature for part of my app. Inspired by facebook's new timelined profiles.
As an example here is a screenshot of the current display.
As you can see the comment/reply boxes fill up all vertical space.
In order to do this I have used jQuery to do the following to the <li> elements in the ordered list of comments with nested replies.
collect the items into an object
iterate over each one and get its individual height all the while building a left and right variable with the total height of the left
hand side boxes and the total of the right hand side boxes
I then check which out of the left and right have the higher total height and apply either a timeline-left or timeline-right class to
the next comment box.
This all seems very simple however I ran into a huge problem which I am at a loss to explain.
If I apply the class within the initial loop (iteration over the elements) The height variable gets effected within the loop. It appears to be adding them together rather than replacing the current one. This of course causes the boxes to be switched to the incorrect sides and therefore fails to close the gaps. Although not relevant to the functionality I added the heights as an attribute to each element in order to better see what was going on. And without a shadow of a doubt. As soon I add the class 'timeline-left' within the same loop as the height detection, the height variable combines each loop rather than reflecting the individual elements value.
To work around this I have come up with what I consider to be a very ugly method solve the issue.
I iterate through the objects and build an array of values (height,side) for each box.
I then iterate AGAIN through this new array and apply the classes to each box.
Now this actually works fine and performance is also fine. While I usually adhere to the whole pre-optimization is bad mentality. In this case I just cannot let it lie as surely there should be no reason to iterate through the elements more than once?
Here is the current (ugly) code I am using which collects the data about each element and then re-loops a second time to apply the classes:
var side = "left",
$timeline = $("#timeline"),
$elements = $timeline.children("li").not(".timeline-spine"),
newwidth = parseInt(($("#rightcolumnwrapper").width() / 2) - 26),
length = $elements.length,
array = [],
$i = 0,
leftheight = 0,
rightheight = 0,
side = "left";
while ($i < length) {
var $this = $elements.eq($i),
height = $this.outerHeight(true);
if (leftheight > rightheight) {
side = "right";
} else {
side = "left";
}
array.push({
"side": side,
"height": height
});
if (side == "right") {
var rightheight = rightheight + height + 25;
} else if (side == "left") {
var leftheight = leftheight + height + 25;
}
$i++;
}
$.each(array, function (index, value) {
var $timelineItem = $("#timeline").children("li").not(".timeline-spine").eq(index);
$timelineItem.attr("data-height", array[index]["height"]).addClass("timeline-" + array[index]["side"]).children(".window-wrapper").width(newwidth).siblings("#pointer").addClass("pointer-" + array[index]["side"]);
//The code below is not relevant to the question but is left in for completeness
if ($timelineItem.is("#timeline-bottom")) {
$timelineItem.find("#pointer").css({
"left": (newwidth + 21) + "px"
});
} else if ($timelineItem.is("#loves-capsule")) {
if ($timelineItem.find("#love-main").height() > $this.find("#loves-wrapper").height() - 33) {
$timelineItem.height("176px").find("#loves-wrapper").css({
"height": "174px",
"margin-bottom": "-35px"
}).end().find("#love-footer").show();
}
}
});
Is there anything I can do to get this into one loop without (and I really cannot explain why) effecting the heights reported for each element?
Note: There is some other stuff going on at the end is not really relevant to the question however I have left it in for code completeness.
EDIT: Just to be clear there is no css styling other than clear:left; float:left and clear:right; float:right; in either of the css rules that I am adding. Just in case anyone thought I may be styling them to modify the height.
This is a possible "one loop" implimentation:
jQuery(document).ready(function($){
var $timeline, $elements, $wrapper, columns;
$timeline = $("#timeline");
$elements = $timeline.children("li").not(".timeline-spine");
$wrapper = $("#rightcolumnwrapper");
columns = {
left : {
height : 0,
stack : []
},
right : {
height : 0,
stack : []
}
};
$elements.each(function(index){
var $obj = $(this), col, side, obj_height;
obj_height = $obj.outerHeight(true);
if ( index === 0 || columns.left.height < columns.right.height ) {
// create a local reference to the column
col = columns.left;
side = "left";
}
else {
col = columns.right;
side = "right";
}
// Do stuff with this node
$obj.addClass('timeline-'+side); //...
// Add object height to container height
col.height = col.height + obj_height;
// push raw dom node to stack
col.stack.push(this);
});
});
However manipulating an entire "stack" of nodes may be faster than doing the same operations on every iteration of .each()
$.each(columns,function(index){
// index is left or right
var $stack = $( this.stack );
$stack.addClass('timeline-'+index)//.doMoreThings()...
});
UPDATE: After further review, I think your problem with height is this part:
.attr("data-height", height)
Try calling "data-height" something else, or use .data() instead of .attr();
I believe this (single loop) is equivalent to what you posted. Updated source, comments in source below.
var side = "left",
$timeline = $("#timeline"),
$elements = $timeline.children("li").not(".timeline-spine"),
newwidth = parseInt(($("#rightcolumnwrapper").width() / 2), 10) - 26,
length = $elements.length,
array = [],
$i = 0,
leftheight = 0,
rightheight = 0,
side = "left",
$this = {},
height = 0;
while ($i < length) {
$this = $elements.eq($i);
height = $this.outerHeight(true);
side = (leftheight > rightheight) ? "right" : "left";
// unnecessary with only one loop
/*
array.push({
"side": side,
"height": height
});
*/
// var $timelineItem = $("#timeline").children("li").not(".timeline-spine").eq(index); === var $this = $elements.eq($i)
$this.attr("data-heightY", height).addClass("timeline-" + side).children(".window-wrapper").width(newwidth).siblings("#pointer").addClass("pointer-" + side);
if ($this.is("#timeline-bottom")) {
$this.find("#pointer").css({
"left": (newwidth + 21) + "px"
});
} else if ($this.is("#loves-capsule")) {
if ($this.find("#love-main").height() > ($this.find("#loves-wrapper").height() - 33)) {
$this.height("176px").find("#loves-wrapper").css({
"height": "174px",
"margin-bottom": "-35px"
}).end().find("#love-footer").show();
}
}
if (side == "right") {
rightheight += height + 25;
} else if (side == "left") {
leftheight += height + 25;
}
$i++;
}
// now unnecessary
/*
$.each(array, function (index, value) {
var $timelineItem = $("#timeline").children("li").not(".timeline-spine").eq(index);
$timelineItem.attr("data-height", array[index]["height"]).addClass("timeline-" + array[index]["side"]).children(".window-wrapper").width(newwidth).siblings("#pointer").addClass("pointer-" + array[index]["side"]);
if ($timelineItem.is("#timeline-bottom")) {
$timelineItem.find("#pointer").css({
"left": (newwidth + 21) + "px"
});
} else if ($timelineItem.is("#loves-capsule")) {
if ($timelineItem.find("#love-main").height() > $this.find("#loves-wrapper").height() - 33) {
$timelineItem.height("176px").find("#loves-wrapper").css({
"height": "174px",
"margin-bottom": "-35px"
}).end().find("#love-footer").show();
}
}
});
*/