Why is the existing content removed when I appendChild to an element? - javascript

I'm trying to create an implementation of "infinite scrolling". Here is my relevant code:
var postsIndex = 0;
var chunk = 5;
function getNextChunk() {
for (i=0; i<chunk; i++) {
postsIndex++;
document.write(getTimeLineElement(posts[postsIndex]));
}
}
if (postsIndex === 0) { // initial chunk.
var contentDiv = document.createElement("DIV"); // Create the div container.
var divTextNode = document.createTextNode(getNextChunk()); // Create a text node with the initial chunk.
contentDiv.appendChild(divTextNode); // Append the text node to the div container.
contentDiv.style.border="solid";
}
$(window).scroll(function() {
if($(window).scrollTop() + $(window).height() > $(document).height() - 100) {
alert("near bottom!");
contentDiv.appendChild(getNextChunk());
}
});
The initial chunk loads just fine and the loading of the 2nd chunk is triggered just fine. However, when the 2nd chunk gets loaded the original content is lost leaving only what was appended. As you can see I have placed a border around the div for testing purposes. I'm not seeing that border. I'm not sure why. This may be an indication that somehow this whole thing is not in the div element like I think that it is. Can anyone please tell me what I'm missing? Thanks for any input.
... doug

Your function, getNextChunk, which is called at various times after the document has loaded, itself calls document.write. As described in the notes in MDN's document.write documentation, when called against a document that has loaded, document.write makes an implicit call to document.open. The Notes in the MDN documentation for document.open state that when called against an existing document, document.open clears that document.
As such, your function is wiping out the document before inserting your content.
I don't know what you expect any of this to do anyway...you're calling appendChild while passing in a function call. This means that function's return value would be passed in--but it doesn't return anything...and it calls document.write in a loop...it's really all over the map.

We will start with the line that is not behaving the way you expect.
contentDiv.appendChild(getNextChunk());
What does this mean?
It means, execute the getNextChunk function and whatever it returns, add it to the DOM as a child of contentDiv.
So, what does getNextChunk return?
function getNextChunk() {
for (i=0; i<chunk; i++) {
postsIndex++;
document.write(getTimeLineElement(posts[postsIndex]));
}
}
As far as I can tell from that code, it does not actually return anything. Therefore, you are adding nothing as a child of your content div.
So what is getting added to the DOM?
You are calling getTimeLineElement, but adding its return value (assuming it is a string) directly to the HTML content of the page. This does not go into the div, but just gets appended to the document.
What did I do wrong?
Returns: You are passing the return value of a function which does not return anything.
Side effects: You have a function named get*, which suggests it exclusively reads data, but actually mutates the page (called a side effect).
Data types: The result of a function named getTimeLineElement, which suggests it returns a DOM element, actually returns a string.
Always understand what data types you are working with and how they move around from function to function.

Related

Custom Javascript Variable Returns Different Value on History Change

I have setup a Custom JavaScript variable that works intermittently. The function is simply designed to return true or false if a text is contained on the page.
The below code works fine when the page is loaded directly from the URL bar and when executed in the developer tools console. When running the function in the console, the function indeed turns true. When the code is executed within debug mode in GTM, the value returns false when a history change event occurs.
function() {
var content = document.body.innerText;
var query = "text to search";
if (content.search(query) > -1 ) {
return true;
} else {
return false;
}
}
Any assistance/insight is very much appreciated!
To me this seems like expected behavior. Since you are talking about history changes, you are probably working with a single page application, or some other page where the DOM is changed after the initial page load.
Custom Javscript variables evaluate a function and return the result each time you reference it. How I imagine the flow of operations goes is this.
Page Loads (target text is in the page body) -> Custom JS evaluates on page view and returns true -> User presses some button -> DOM is modified to display new content (target text is removed and no longer present -> History change occurs -> Custom JS evaluates again, the text is no longer present so returns false.
If the target text is still present after the history change, then I can understand why we have some unexpected behavior. The history change trigger is based off of the push state api so what could be happening is that the pushState() function is called before the DOM is finished being modified. In this case, the text isn't present at time of the history change event even though it is shortly afterwards.
You could change the page so pushState() is only called after the DOM is done being modified, use a custom event as a trigger instead (again, pushing it after the DOM is done being modified), or use a different trigger like the element visibility trigger that will only fire after the new DOM elements you want to target appear on-screen.

Javascript TypeError: Cannot read property when nested inside a window.onload function

SOLVED: Will move to close post but for future reference here is what happened. The children div in the parent div I was referencing were dependent on an AJAX call. So I set an interval to keep checking if the element existed with...
var checkExist = setInterval(function() {
if ($('parent').length) {
// do work on child elements
clearInterval(checkExist);
}
}, 100);
ORIGINAL POST BELOW
I am getting into writing some scripts to make testing tasks simple. I need to wait to run some of these until the page is fully loaded. Naturally I nest my code in ...
window.onload = function () { code in here }
So I am trying to store an element in a var that I know exists and I know works without being nested in onload. The var is written like this.
var element = document.getElementById('userPanel').getElementsByTagName('div')[0];
I can run this in console and it returns the element I need. When I run my script nested in the onload function. It throws a type error. Is there some fundamental principle of javascript I am overlooking here? Thanks!
For sake of clarity here is the whole code section glued together
window.onload = function () {
var element = document.getElementById('userPanel').getElementsByTagName('div')[0];
}
SOLVED: Will move to close post but for future reference here is what happened. The children div in the parent div I was referencing were dependent on an AJAX call. So I set an interval to keep checking if the element existed with...
var checkExist = setInterval(function() {
if ($('parent').length) {
// do work on child elements
clearInterval(checkExist);
}
}, 100);

Two methods of looping through Ajax requests for jQuery Then When - which to use?

I've got a deceptively simple blog project in the works, where I'm trying to bring together Isotope Jquery (for layout/filtering/sorting), Infinite Scroll, and dynamic loading of all blog excerpts via Ajax (so filtering and sorting is applied to all excerpts before the user scrolls down the page (after which time they're loaded into the dom and then accessible)).
This question primarily deals with getting the blog post excerpt data via Ajax, to then be passed into Isotope filtering code. I'm not sure of the best way to do this, but am currently trying to loop through each page (of blog posts excerpts) with an ajax request and then access the data as one whole.
I've come across two different methods to loop through the ajax requests, each using then when jquery statements. The first is using the method give in this SO answer, the other is simply putting the entire then when statement inside of an $.each statement.
Method 1:
var pageCount = 15;
var pageCountArray = [];
for (var i = 1; i != pageCount; ++i) pageCountArray.push(i);
var deferreds = [];
$(pageCountArray).each(function() {
var pageNumber = this;
deferreds.push(
$.get('/page/' + pageNumber)
)
$.when.apply($, deferreds)
.then(function(data){
console.log(data);
// this outputs data as a string from the first page, then a list of objects
console.log(typeof(data));
// string
// 13 - object
});
});
Slight aside: Any ideas as to why this is outputting one string and then objects?
Method 2:
var pageCount = 15;
var pageCountArray = [];
for (var i = 1; i != pageCount; ++i) pageCountArray.push(i);
$(pageCountArray).each(function(data) {
var pageNumber = this;
$.when(
$.get('/page/' + pageNumber)
).then(function() {
console.log(data);
// this outputs 14 strings of data
console.log(typeof(data));
// 14 - string
})
});
I haven't yet figured out how to incorporate the Ajaxed data into my Isotope filter function, but I think I'll need to parse this into HTML first. Still getting my footing with javascript... in this case is one of these data types (objects vs strings) easier to parse into HTML? I suppose that's the key to my answer?
Much obliged for insights.
PS: Bonus points for anyone who might know of a better way to achieve this in a different way that somehow dovetails into Isotope/Infinite Scroll nicely (perhaps in a way that's more intended to play nice with these plugins... I've been unsuccessful in my searching).
PPS: The second method feels much cleaner... anyone know of a reason that it's not a good approach (using when then inside of an .each loop)?
Wow, this is a largely scoped question no wonder there aren't any responses. This is a massive question so I will do my very best to help. I have created many sites that include the sort/filtering of Isotope while using AJAX preload's with infinite scrolling so here is one of the simplest examples I have already written out...
First I must mention that this whole thing works much better with David DeSandro's ImagesLoaded plugin. This is mostly because it allows you to place a callback function (function to be executed once an event occurs) attached to the loading event of the final image in a given container. Wow that was wordy. How to put that better... It basically asks the container, are you done loading yet? No? How about now? You're loaded? Ok please do this function now then...
With that being implemented I would start with this code in my onLoad event like so...
$(function() {
extendJQ_PreLoad(); //I Will Get To This Function In A Min
//Use ImagesLoaded Plugin To Control Load Time Sync
$(container).imagesLoaded(function() {
cont.isotope({
itemSelector: ".box", //This is the class I use on all my images to sort
layoutMode: "masonry",
isOriginLeft: true,
isFitWidth: true,
filter: "*",
masonry: {
columnWidth: ".box"
}
});
preLoadNextImgSet(); //I Will Get To This Function In A Min
});
});
Ok so let's break this down. The ImagesLoaded plugin stops the Isotope plugin instantiation from happening before there are images present to sort/filter/load and/or handle. This is step 1. Step 2 would be to then start looking at the actual isotope plugin instantiation. I am telling it to use Masonry plugin as its layout style and then I am passing in an object literal with options under the array key 'masonry'. The array key here that is named masonry is the same as any instantiation you would have normally done in the past with the stand alone Masonry plugin (non-isotope or isotope-2).
Step 3 to look at here would be my beginning call to extendJQ_PreLoad();. This function is the function I wrote to let JQuery know that I need to extend it's core functionality in order to capacitate preloading any images I give it, as an array. Like so...
function extendJQ_PreLoad() {
$.preloadImages = function(args) {
for (var i = 0; i < args.length; i++) {
$("<img />").attr("src", args[i]);
}
}
} //end function
This is just a simple iterator and nothing fancy, it allows the images to be preloaded by using a neat trick associated with the DOM. If you load images in this way it loads then into memory but not into the DOM meaning it is loaded and hidden. Once you then insert this image anywhere it will insert very quickly as it is now loaded in cache and awaiting placement. You can view more about this here.
Finally the last to look at is my call to my preload function. This is a very simple call to a php file that simply goes and looks for the next set of images in order, if there is any to find. If it gets some images then it begins adding it to a temporary div in memory (again not on the DOM to be seen) and is now setup for simple DOM traversal. Let's view the function to dissect its functionality...
function preLoadNextImgSet() {
$.post('AjaxController/ajaxPreload_Gallery.php', {currStart: start, currSize: loadSize}, function(data) {
if(data!="") {
var y = $(document.createElement("div")).append(data).find("a"),
found = [];
y.each(function() {
found[found.length] = "img/gallery/" + $(this).text();
});
$.preloadImages(found);
}
});
} //end function
In this example I have two global variables living in my browser window from JavaScript that I would have declared. A start and a loadSize variable. The start variable represents the current place in our list of images that we currently are at and the loadSize variable sets a limit on how many images to preload each time.
Now that the variables are set and sent in to the PHP file via the $.post function, we can use the PHP file to find the appropriate images in order and have them loaded into memory awaiting usage. Whatever is returned here to the y variable gets iterated over by the each function and then preloaded. Once this functions scope is exited the imaginary div will be deleted and sent to garbage as it is not used simple iterated over.
Ok, now. Its been a journey but we are almost ready to begin the final method here. Let's first go back and look at what the first imagesLoaded call was doing now that we know the new functionality added in these functions. The imagesLoaded call in the DOM-Ready event has a call in its very bottom piece that preloads the images.... why? This is because once the page loads and the initial images are loaded into the isotope container, we need the page to now use this idle time to begin already loading the next set. So in other words once the images are placed and sorted and happy to just sit there, the next loadSize amount of images will be loaded and waiting for you to place them.
Now for the final function. This function is a generic function thats sole purpose is to load in the current preloaded images into the DOM officially and then to ask for the next set to be loaded. However what on earth would be calling this function? This is where the lazyloading or infinitescroll becomes useful to us. Somewhere in your page you need to add this function in...
$(window).scroll(function(){
scrollTop = $(window).scrollTop(),
windowHeight = $(window).height(),
docuHeight = $(document).height();
//AJAX Data Pull
if(((scrollTop + windowHeight)+35) >= docuHeight){
getNextImages();
}
});
This function is the magic function that allows the infinitescroll effect to occur. I have added 35 pixels or so of padding (the +35 randomly in my code) because sometimes you want it to load close to the end of the page but not quite the actual end of the page.
Ok so now that this is setup when we reach the end of the page this function will want to get all of the next images generically like we had mentioned. The function of mine looks like this...
function getNextImages() {
cont = $(container);
$.post('AjaxController/ajaxPortfolio_Gallery.php', {currStart: start, currSize: loadSize}, function(data) {
if(data!="") {
//Append New Photos Inside <a> Element Tag
var y = $(document.createElement("div")).append(data).find("a");
cont.append(y);
//Fix Image Layouts
cont.imagesLoaded(function() {
//Feed Isotope Layout The New Items
cont.isotope("appended", y);
cont.find("a").css({"opacity":"1"});
});
} else { unFilled = false; }
});
}
I have included the unFilled variable simply so that there is a flag that can be set when you have reached the end of the images. You don't want it to keep trying to load forever if there are no images left to show.
Ok, so. This is a lot of information so I will try to keep answering as much as possible.

Create new window using jquery

I'm working on a little project in JavaScript/jQuery.
In order to display results from a calculation done in javascript, I'd like to open a new window with some predefined content and modify this content in order to display the results: Im using code like this:
var resultwindow = window.open('result.html')
var doc = $('body', resultwindow.document);
doc.append("<p>Result</p>")
This is not working since the result document is not yet loaded when I append the content, so it is overwritten with the contents of 'result.html'.
I also tried
$(resultwindow.document).ready(function() {
// ... Fill result document here
})
and
$(resultwindow.document).load(function() {
// ... Fill result document here
})
but ready() works only on the current document (it is called immediately, if the current document is already loaded), and load doesn't get called at all.
Perhaps someone can point me to the right direction. Thanks in advance!
EDIT:
I finally solved this by creating the new document "by hand" in Javascript like:
w = window.open('','newwinow','width=800,height=600,menubar=1,status=0,scrollbars=1,resizable=1);
d = w.document.open("text/html","replace");
d.writeln('<html><head>' +
'<link rel="stylesheet" type="text/css" href="style.cs"/></head>' +
+'<body></body></html>');
// use d to manipulate DOM of new document and display results
If I were to do the same thing today (two years of experience later), I'd use some Javascript template library like Handlebars to maintain a template and compile it to javscript.
Your load call doesn't work because you're attempting to handle the load of the document, and chances are document does not even exist at this point. Which means you are passing null into jQuery, and it gracefully ignores you. Handle the load event of the raw window reference instead, and then you should be good to go...
var win = window.open("result.html");
$(win).load(function() {
$("body").append("<p>Result</p>");
});
The problem you have is that load() doesn't do what you think it does.
Instead, use bind("load", function() { /* Your function here */ }); then everything should work.
Correction:
load() is actually a dual-use function -- if it's called with a function as its first parameter, then it binds it to the load event of the object (or objects) in question, otherwise it loads the returned data (if any) into the elements in question. See Josh's answer for the real reason why it's not working.
Send the data to result.html in the querystring and then have the result.html display the data from there. If you want to be less obvious about it going through you could hash the data in the querystring and have the result page dehash it.

dojo.parser.parse doesn't always return

I have this bit of code. It is used to update the form after a select element changes. onChange an "ajax" call is made and this bit of code takes care of the response.
The first time everything works as expected. However it the dojo.parser.parse fails to return about 50% of the time.
At first it looked like this:
var targetNode = dojo.byId(node);
targetNode.innerHTML = data;
dojo.parser.parse(targetNode);
Then I read something about the objects existing. So I thought that maybe destroying them would help:
if(dojo.byId(node)) dojo.destroy(node);
dojo.create('div', { id: node }, afternode, 'after');
var targetNode = dojo.byId(node);
targetNode.innerHTML = data;
dojo.parser.parse(targetNode);
That didn't help any. What the h3ll is going on? Sometimes it parses some of the elements. Is this a known problem with the dojo.parser?
If you create the dijits declaratively and use dojo.parser.parse to parse them on-the-fly and you specify the ID of the dijit, once you parse the same HTML fragment twice, you'll get an error says that the dijit's ID has been registered.
<div dojoType="dijit.form.Button" id="myButton" />
The cause is that the dijits have not been destroyed yet and you can not reuse the ID. If you don't specify the ID when declaring it, you won't get this error, but you actually have memory leaks.
The correct way is to destroy the dijits before parsing the HTML fragment again. The return value of dijit.parser.parse is an array list that contains the references of all the dijits it parsed from the HTML fragment. You can keep the list and destroy the dijits first.
if (dijits) {
for (var i = 0, n = dijits.length; i < n; i++) {
dijits[i].destroyRecursive();
}
}
dijits = dojo.parser.parse(targetNode);

Categories

Resources