Performance issue with $.before() on styled DOM - javascript

I've come across an interesting issue, which may be browser related (but I'm not sure about that). I'm working on a very large webpage, which is a word-processor style app. I have this structure:
<article>
<div>...</div>
<div>...</div>
<div>...</div>
... 5000 more divs ...
</article>
When I first load the page, I have a function that does two things. First, it analyses the content of each div's html using regex and applies a class to the div if it matches a particular regex, resulting in something like this:
<article>
<div class="type1">...</div>
<div class="type2">...</div>
<div class="type3">...</div>
... 5000 more divs ...
</article>
The second part of the function then calculates the height of each div and adds it to a counter. Once it passes a certain number, I insert a piece of HTML that serves as a page break, then rinse and repeat:
// Pagination variables
var document_current_page_height = 0;
var constant_default_page_height = 1000;
var page_number = 0;
// Iterate over page elements
object_range.each(function()
{
// Check whether page is too long
if (document_current_page_height + $(this).outerHeight(true) > constant_default_page_height)
{
// Create page break
$(this).before("<section contenteditable=\"false\"><h3 class=\"page-footer\">" + document_page_footer" + "</h3><figure><hr /><hr /></figure><h1 class=\"page-header\">" + document_page_header + "</h1><h2 class=\"page-number\">" + page_number + "</h2></section>");
// Adjust variables
page_number++
document_current_page_height = 0;
}
// Increment current page height
document_current_page_height += $(this).outerHeight(true));
});
This code works perfectly, but here's the issue...
When run as it is supposed to be run, it takes about 2 seconds. However, if I run the same code, but skip the first part of the process (which adds classes to the divs), the code runs in 0.2 seconds.
I was further able to isolate the problem by commenting the $(this).before line of code. When this is done, both speed tests are near enough identical (within 100ms).
What I don't get, is that the $(this).before line of code does not reference the heights / styles / classes of the divs, so why the delay when the divs have a class?
Is it simply a case of the browser struggling with the divs when they have classes? Any thoughts? (I'm using Safari 9.1).
Thanks!

I found a solution to my issue, though it doesn't actually address why the browser behaves differently...
Instead of using $(this).before, I ended up adding the relevant $(this) to a vanilla JS array and then calling before later on:
// Pagination variables
var document_current_page_height = 0;
var constant_default_page_height = 1000;
var page_number = 0;
var page_break_array = [];
// Iterate over page elements
object_range.each(function()
{
// Check whether page is too long
if (document_current_page_height + $(this).outerHeight(true) > constant_default_page_height)
{
// Create page break
page_break_array.push([$(this), page_number]);
// Adjust variables
page_number++
document_current_page_height = 0;
}
// Increment current page height
document_current_page_height += $(this).outerHeight(true));
});
// Create page breaks (done separately for performance benefit)
for (var i = 0; i < page_break_array.length; i++) { page_break_array[i][0].before("... HTML content ..."); }

Related

Select all text frames and ALIGN RIGHT in all ODD numbered pages of InDesign (Javascript)

I want to select all text frames and then ALIGN its content to the right of the page of all the ODD numbered pages of my document in InDesign using only Javascript.
here's my progress so far and I know I can determine the odd numbers but still can't make selecting a page work thus no progress to select its text frames too.
main();
function main() {
var myDocument = app.documents.item(0);
var myPage = myDocument.pages.item(0);
var i = 0;
for (i = 1; i < myDocument.pages.count(); i = i + 2) {
\\ select the page, then find all text frames in that page, then align right
}
}
any help is appreciated. thank you.
To get all of the frames on a page:
var myFrames = myDocument.pages[i].textFrames;
Then you can loop through the frames and their paragraphs and apply (use different counter variables, e.g "c" and "b")
myFrames[c].paragraphs[b].justification = Justification.RIGHT_ALIGN;
You can also try everyItem()
myDocument.pages[i].textFrames.everyItem().paragraphs.everyItem().justification = Justification.RIGHT_ALIGN;
Here is a simplest solution:
var pages = app.activeDocument.pages;
for (var i = 1; i < pages.length; i = i + 2) {
app.select(pages[i].textFrames);
try {
app.menuActions.item("$ID/Horizontal Page Align Left").invoke()
} catch(e) {}
}
It relies on selection objects and invoke the menu action. Sometimes this is not the best idea (that is why try/catch). The solution can be more complicated and robust. It depends on your limitations and additional details.
Update
I didn't get that you need to align CONTENT of the frames rather than the frames. It can be done, but the solution differs for linked and non-linked text frames. Except when one paragraph belongs two neighboring pages.

Remove parent and all child elements not working

I am developing a plugin for my own usage, and I want to get rid of all the annoying wordpress messages that appear on top including updates, errors, info, etc. Given the obvious fact that the plugin's menu page is generated by php, I tried removing these messages with a php code which according to my research is this:
function remove_core_updates(){
global $wp_version;return(object) array('last_checked'=> time(),'version_checked'=> $wp_version,);
}
add_filter('pre_site_transient_update_core','remove_core_updates');
add_filter('pre_site_transient_update_plugins','remove_core_updates');
add_filter('pre_site_transient_update_themes','remove_core_updates');
I added this at the top of my plugin php file and should remove the notifications, but the error message for some plugins still appear. Apart from this, the code above removes the update notification for all menu pages I access in my dashboard. This I do not want.
As a workaround, I wrote a function which does what I want:
function remove_wp_messages(selector) {
var child = document.querySelector(selector),
parent = child.parentNode,
index = Array.prototype.indexOf.call(parent.children, child);
for (var i = 1; i < Number(index + 1); i++) {
var elements = document.querySelector("#wpbody-content > :nth-child(" + i + ")");
while (elements.firstChild) {
elements.removeChild(elements.firstChild);
elements.style.display = "none";
}
}
}
window.onload = remove_wp_messages("#wpbody-content > link");
Given that the first html element I add to the menu page is a link to an external stylesheet, the javascript function above returns the index of that link and removes all child nodes from the elements up to the link, hence the loop. Here is the issue, I want to not only remove the child elements but the parents as well which would be equivalent to removing the notifications above the content of my plugin. I tried replacing:
elements.style.display = "none";
With this:
elements.remove();
No luck. What I do not understand is, that the parent elements get hidden, but I replace it with remove() it removes my content instead of the notifications. Why? I am open to both php and javascript suggestions.
SOLUTION
According to #PetrSr answer, my code now looks like this:
function remove_wp_messages(selector) {
var child = document.querySelector(selector),
parent = child.parentNode,
index = Array.prototype.indexOf.call(parent.children, child);
for (var i = Number(index); i > 0; i--) {
document.querySelector("#wpbody-content > :nth-child(" + i + ")").remove();
}
}
Pretty straightforward.
Looping through the elements backwards like this should fix your issue:
for (var i = Number(index); i > 0; i--) {
//your code
}
You cannot loop forward when removing DOM elements the way you did, because after removing element1, element2 becomes the first one. If you then remove the second element, you are in fact removing what was originally element3. Etc.

How to set style of element loaded into <object> as data

I've recently inherited some code that I need to maintain, part of which is a web-app meant to be used on iPad.
Unfortunately, the recent iOS 8 update broke a part of said web-app.
In one section, the web-app contained an iframe which loaded in a list of items. This iframe used to scroll with only one finger when run as a web-app, but now only scrolls with two fingers. * The one finger vs. two finger scrolling of iframe content in iOS web-apps is a whole other can of worms, and NOT what I'm looking for help with though * - I need help with implementing my solution.
If I place the content in a div I can get it to scroll as I want, however, I then also have to use an object to load in the content (coming from a separate page) - this is where my jQuery problem arises.
When an item is selected from the loaded list it is highlighted - once the user is finished and his/her selections are submitted the "highlighted" items are then "deselected" using jQuery to remove the "highlighted" style.
This worked fine with the iframe, but I can't manage to change the styling of the items now that they are contained within a div and object.
Here are some code snippets to illustrate what's going on:
ORIGINAL (works correctly with iframe)
<iframe id="myiframe" src="item_list.html"></iframe>
<script>
function deselect()
{
var itemcount = $('#ItemCount').val();
for (var i = 0; i < itemcount; i++) {
// item ids are named a1, a2, a3, etc.
$("#myiframe").contents().find("#a" + i).css("font-size", "26px");
$("#myiframe").contents().find("#a" + i).css("font-weight", "normal");
$("#myiframe").contents().find("#a" + i).css("color", "#3A3B3B");
}
}
</script>
MY ATTEMPT (not working)
<div id="myiframe">
<object id="myloader" type="text/html" data="item_list.html" ></object>
</div>
<script>
function deselect()
{
var itemcount = $('#ItemCount').val();
for (var i = 0; i < itemcount; i++) {
// item ids are named a1, a2, a3, etc.
//doesn't work - I think contents() is specifically for iframes?
$("#myiframe, #myloader").contents().find("#a" + i).css("font-size", "26px");
// none of these work
var path = "#myiframe, #myloader, #a" + i ;
var path = "#myiframe, #a" + i ;
var path = "#myloader, #a" + i ;
var path = "#a" + i ;
$(path).css("font-size", "26px");
// none of these work
var path = "#myiframe, #myloader" ;
var path = "#myiframe" ;
var path = "#myloader" ;
$(path).find("#a" + i).css("font-size", "26px");
// someone suggested using data() but it didn't work for me
// either AND I don't think that that's how it's used anyhow
}
}
</script>
Admittedly, I'm not really a jQuery guy, but this seems like it should be fairly easy...
Thanks in advance for any help you can offer.
Even though I think it's better to use ajax to load the content into a div (and a single DOM), I found a way to access the document within the <object> element.
$("#a0", $("#myloader")[0].contentDocument) will select the a0 element in the document held within the object data.
The second parameter to the jquery selection is the context in which to search, $("#myloader)[0] returns the HTMLObjectElement (and is equivalent to document.getElementById('myloader'), and contentDocument returns the HTML document retrieved via data attribute.
function deselect()
{
var itemcount = $('#ItemCount').val();
for (var i = 0; i < itemcount; i++) {
// item ids are named a1, a2, a3, etc.
//searches the content document within the object tag
$("#a"+i, $("#myloader")[0].contentDocument).css("font-size", "26px");
// I still think you should use .addClass() and .removeClass()
// (or data attributes if you want to be super fancy),
// changing the styles with CSS,
// rather than manipulate the styles directly in javascript.
}
}
Is there a reason for using an <object> element? Jquery has a method .load() which will request and insert the returned data/html into an element. http://api.jquery.com/load/
<div id="myiframe">
<div id="myloader"></div>
</div>
Then you would load the data:
<script type='text/javascript'>
$(function() {
$('#myloader').load("item_list.html", function() {
//at this point, the elements are in the document:
$('#a0').css("font-size", "26px");
//...
});
});
function deselect() {
var itemcount = $('#ItemCount').val();
for (var i = 0; i < itemcount; i++) {
$('#a'+i).css("font-size", "26px");
//etc.
}
}
</script>
I would add that instead of manually editing the css in javascript, you use jquery to .addClass(), removeClass(), or .toggleClass() css classes which contain the styling you are looking for.

Cycle with interval stops acting

Have been trying to learn some basics of web designing involving some simple HTML, CSS, JS/Jquery and have been coming up to certain obstacles that I haven't been able to find a way to work around.
One of the things I'm trying to implement is rotating a small number of divs. At the lack of some proper manner for it, what I rigged up was to .toggle off one of them while toggling on another div that was hidden from page load.
Not the prettiest thing, but it kind of works though oddly enough it only works twice before for some reason the cycle stops working.
function moveSide(){
var intervalId;
var childCount = 2;
var preLast = childCount + 2;
var newLast = childCount + 3;
intervalId = setInterval(function () {
$(".column:nth-of-type(" + childCount + ")").toggle("slide", function(){
$(".column:nth-of-type(" + preLast + ")").removeClass("last").delay(1, function(){
$(".column:nth-of-type(" + newLast + ")").addClass("last").delay(1).toggle("slide", function(){
childCount++;
preLast = childCount + 2;
newLast = childCount + 3;
//alert(childCount);
});
});
});
},5000);
}
I'm not sure if using nth-of-type is the right choice, but it seemed to be allowing me to pick amongst the divs. childCount is to pick which div is to be the first to be toggled off, pre(vious)Last is to identify what was the last div of those displayed in order to remove a class used for some properties, newLast is to identify the div that will become visible and give it the class to add CSS properties.
The alert cycle runs twice entirely (increasing childCount), but doesn't process a third time.
Any ideas what I'm doing wrong?
i don't now, if it's my not cured cold or the fact, that i just got up a few hours ago, but i don't understand your code.
what i understand, is what you want to achieve - and i suggest another method:
$(".column:gt(0)").hide(); //first of all, hide all columns but the first one
setInterval(function() {
$('.column:first') //select the first column
.toggle("slide") //slide it out
.next().next() //select the 3rd column
.toggleClass("last") //remove class "last"
.next() //select first invisible column
.toggleClass("last") //add class last
.toggle("slide") //slide it in
.end().end().end() //end the chain, to reselect the first element
// since we used .next() three times, we have to end it three times
.appendTo('#column-content'); //move the first element inside the dom to the end
}, 5000);
your interval should be running infinite now - always sliding out the first element, sliding in the next element end appending the first element to the end. therefore, the current element is always the first one...
see the fiddle: http://jsfiddle.net/sv5j85df/2/

Table .append() occurring all at once

I've got a for loop in which I am appending rows to a table. Although I append each tr one at a time in the loop, the browser does not render any of them until the loop is finished.
At first, I thought it might be the browser rendering too quickly for me to notice, but after increasing the number of rows to, say, 10000, there's a significant slowdown, then the entire table shows at once.
Link: http://jsfiddle.net/xyan/ad2tV/
Increment counter to increase the number of rows.
Also, if you change counter to 3 (or another small number) and uncomment the alert(), it will pause the loop and show each row being added one at a time.
HTML:
<div></div>
Javascript:
var table = $('<table />').appendTo($('div'));
var counter = 1000;
var html = [];
var j = 0;
for (var i = 1 ; i < (counter + 1) ; i++) {
html[j++] = '<tr>';
html[j++] = '<td>'+i+'-1</td><td>'+i+'-2</td><td>'+i+'-3</td>';
html[j++] = '<tr>';
table.append(html.join(''));
//alert('pause');
html = [];
j = 0;
}
CSS:
table td {
padding: 3px;
border: 1px solid red;
}
Note:
I've found a way to force the loop to slow down, allowing the rows to be added one at a time.
Link: http://jsfiddle.net/xyan/8SCP9/
var html = '';
var numbertorun = 15;
var delay = 500;
(function loop (i) {
setTimeout(function () {
html = '<tr><td>'+i+'-1</td><td>'+i+'-2</td></tr>';
$('table').append(html);
if (--i) loop(i);
}, delay)
})(numbertorun);
So I guess my question isn't how to do this, but why the rows aren't inserted one at a time in the original for loop.
I'm guessing some background is needed to understand this question.
Each time you add a new row to the table in your webpage, your script will alter the model of your document in your browsers memory. The API you are using for this is called DOM (Document Object Model).
An individual update to DOM doesn't always necessarily mean that a page will be redrawn. It is up to the browser to decide, when a given set of DOM alterations will cause the browser to re-render the page.
I don't know for fact, but I'm guessing that browsers typically update views perhaps somewhere around 40-60 times per second. The thing is this: if your table printing takes less time than 1/60th of a second, you don't get to see the partial updates.
By adding a timer you of course can slow down the progress of creating the table. As you perceived, this will allow you to see the page redrawn at different points during the for loop.
If you wish to force browser to redraw the UI, there are some JavaScript hacks you could try applying. One such hack for instance is documented in Ajaxian: Forcing a UI redraw from JavaScript.
If you deliberatively wish to run your program slowly and add rows slowly, say one row per second, while potentially doing some other stuff with JavaScript at the same time, you need to run the loop either by triggering new iterations somehow or by using another (JavaScript) thread.
A schematic example of using setTimeout to slow down printing (prints out 1 row per sec, just tweak second parameter in window.setTimeout to change delay):
var table = $('<table id="table1"/>').appendTo($('div'));
var i = 0;
var counter = 1000;
function addRow() {
table.append('<tr><td>'+i+'-1</td><td>'+i+'-2</td><td>'+i+'-3</td></tr>');
i++;
if(i<counter) {
window.setTimeout( addRow, 1000 );
}
}
addRow();
jQuery append method is similar to javascript appendChild method. Both these methods traverse through the DOM and insert the element at the end of the parent node. This task takes time and doing this inside a Loop will cause frequent changes in DOM Structure which the DOM will not able to replicate. So by inserting your rows using settimeout gives the browser ample time to re-load the content. Hence you see the one by one insertion of rows in the second approach.
Each time an element is added to the page, the browser needs to redraw the entire page. In rendering terms, this is an extremely intensive process and is quite slow. So if you make small changes little by little that will make the page appear really slowly. What the browser is doing is to group the little changes into big changes. The end result is that the page will render much more quickly in aggregate.
The browser is doing this:
http://www.tvidesign.co.uk/blog/improve-your-jquery-25-excellent-tips.aspx#tip6

Categories

Resources