Unable to get property 'parentNode' of undefined or null reference - javascript

My javascript program basically creates a series of images and randomly picks one and sets its src to a particular path. However, for some reason, the code seems to get stuck sometimes. In my program, I'm calling element.parentNode of one of the images in order to surround it with an anchor tag but it throws a console error saying that it is unable to get property 'parentNode' of undefined or null reference despite the fact that sometimes, the code actually executes sometimes without any changes to the code. I have to refresh multiple times before I get lucky enough to have the error not be thrown and the rest of the code executed smoothly.
Here is my code:
var id = generateRandom(325);
var el = document.getElementById(id.toString());
var anchor = document.createElement("a");
var nextPage = "level" + (level + 1).toString() + ".html";
if (level == 3) {
nextPage = "win.html";
}
anchor.setAttribute("href", nextPage);
el.parentNode.insertBefore(anchor, el); // line 54
The error:
SCRIPT5007: Unable to get property 'parentNode' of undefined or null reference
scripts.js (54,2)
EDIT:
updated entire function
var div = document.getElementById("images");
var time = parseInt(document.getElementsByClassName("timer")[0].innerHTML);
var level = getLevel(time);
var rows = 13;
var cols = 23;
for (var i = 1; i <= rows; i++) {
var marquee = document.createElement("marquee");
marquee.setAttribute("scrollamount", 30);
for (var j = 1; j <= cols; j++) {
var img = document.createElement("img");
img.setAttribute("src", getPath());
img.setAttribute("id", (i * j).toString());
img.setAttribute("width", "80px");
img.setAttribute("height", "70px");
marquee.appendChild(img);
}
div.appendChild(marquee);
}
var id = generateRandom(rows * cols);
var el = document.getElementById(id.toString());
var anchor = document.createElement("a");
var nextPage = "level" + (level + 1).toString() + ".html";
if (level == 3) {
nextPage = "win.html";
}
anchor.setAttribute("href", nextPage);
el.parentNode.insertBefore(anchor, el);
var input = document.createElement("input");
input.setAttribute("type", "image");
input.setAttribute("width", "80px");
input.setAttribute("height", "70px");
input.setAttribute("src", "resources\\asotigue.jpg");
el.parentNode.replaceChild(input, el);
anchor.appendChild(input);
generateRandom function:
function generateRandom(n) {
return Math.floor((Math.random() * n) + 1);
}

This method will collect all the ids on items with a certain class available on the page right now, and will return one of the ids randomly.
You can see it at work in this fiddle. Check the console.
function getIdsListByClassName(className) {
var elements = document.querySelectorAll('[id].' + className); // get all items that have the id attribute and the defined class name
var ids = Array.prototype.slice.call(elements, 0).map(function (element) {
return element.getAttribute('id');
}); // convert the elements list to array, and map it to ids
var currentIndex = Math.floor(Math.random() * ids.length); // get an index between 0 and ids.length - 1
return ids[currentIndex];
}

Edit now that you've included more code: You are generating 13 * 23 = 299 elements, yet selecting a random element up to 325 so sometimes you don't have enough elements for your random selection.
So, what this code is telling you is that the el variable is undefined or null. There are a couple reasons why that could be:
Your generateRandom(325) is generating an id value that is not present in the page.
You are calling document.getElementById(id.toString()); before the page has finished loading and parsing and thus some page elements are not yet present. Your script should not be run until after the page has been loaded either by moving it to the end of the <body> or by waiting until the DOMContentLoaded event fires.
FYI, if your issue is the second, you can read about various solutions using plain Javascript here pure JavaScript equivalent to jQuery's $.ready() how to call a function when the page/dom is ready for it.
If you know that sometimes generateRandom(325) will pick an element id that is not in the page and you want to allow that, but defend against it stopping execution of your code, then you can just check for that condition:
var id = generateRandom(325);
var el = document.getElementById(id.toString());
var anchor = document.createElement("a");
var nextPage = "level" + (level + 1).toString() + ".html";
if (level == 3) {
nextPage = "win.html";
}
anchor.setAttribute("href", nextPage);
if (el) {
el.parentNode.insertBefore(anchor, el);
}
To help you track down your occasional error, right after this:
var id = generateRandom(rows * cols);
var el = document.getElementById(id.toString());
You can add this extra debug code:
// add this to detect what situation you get a missing element
if (!el) {
console.log("did not find id when: rows = " + rows + ", cols = " + cols");
}

Related

Updating value in for loop / Reseting a for loop?

I'm working on my first school project so I don't have much experience in doing such web applications, that's why I decided to ask here.
How can I update the value in the for loop syntax or reset it entirely, so it iterates again, like I just reloaded it? I have another function that I decided not to show, simply because it would be useless to. What it does in the end is increments the taskCount.length by one. This part technically works but problem is, the function I'm going to show you now, once iterated, will always keep the default taskCount.length value, once the page is loaded, it never changes there. Is there any way I can update it?
Here's an example: The function above makes taskCount.length = '5' but when the page started it was taskCount.length = 4, and when I do alert(taskCount.length) from the console, I get 5. But the for loop doesn't want to change.
for (var i = 0; i < taskCount.length; i++) {
document.getElementsByClassName('task')[i].addEventListener('click', ((j) => {
return function() {
var shadow = document.createElement('div');
// Styling
var changingWindow = document.createElement('div');
// Styling
var changingTitle = document.createElement('p');
// Styling
var changingText = document.createElement('p');
// Styling
var changingTitleNode = document.createTextNode('Промяна');
var changingTextNode = document.createTextNode('Моля, изберете действие.');
var deleteTask = document.createElement('button');
var goUp = document.createElement('button');
var goDown = document.createElement('button');
var unchange = document.createElement('button');
// Styling
var deleteElementNode = document.createTextNode('Премахни задачата');
var goUpNode = document.createTextNode('Премести нагоре');
var goDownNode = document.createTextNode('Премести надолу');
var unchangeNode = document.createTextNode('Отказ');
var justBreak = document.createElement('br');
var justBreakAgain = document.createElement('br');
var justBreakOneMoreTime = document.createElement('br');
body.appendChild(shadow);
shadow.appendChild(changingWindow);
changingWindow.appendChild(changingTitle);
changingTitle.appendChild(changingTitleNode);
changingWindow.appendChild(changingText);
changingText.appendChild(changingTextNode);
changingWindow.appendChild(deleteTask);
deleteTask.appendChild(deleteElementNode);
deleteTask.onclick = function() {
document.getElementsByClassName('task')[j].parentNode.removeChild(document.getElementsByClassName('task')[j]);
shadow.parentNode.removeChild(shadow);
localStorage.setItem("listContent", document.getElementById('list').innerHTML);
}
changingWindow.appendChild(justBreak);
changingWindow.appendChild(goUp);
goUp.appendChild(goUpNode);
goUp.onclick = function() {
if (j !== 0) {
var saveThisTaskValue = document.getElementsByClassName('task')[j].innerHTML;
var savePreviousTaskValue = document.getElementsByClassName('task')[j - 1].innerHTML;
document.getElementsByClassName('task')[j].innerHTML = savePreviousTaskValue;
document.getElementsByClassName('task')[j - 1].innerHTML = saveThisTaskValue;
}
shadow.parentNode.removeChild(shadow);
localStorage.setItem("listContent", document.getElementById('list').innerHTML);
}
changingWindow.appendChild(justBreakAgain);
changingWindow.appendChild(goDown);
goDown.appendChild(goDownNode);
goDown.onclick = function() {
if (j !== document.getElementsByClassName('task').length - 1) {
var saveThisTaskValue = document.getElementsByClassName('task')[j].innerHTML;
var saveNextTaskValue = document.getElementsByClassName('task')[j + 1].innerHTML;
document.getElementsByClassName('task')[j].innerHTML = saveNextTaskValue;
document.getElementsByClassName('task')[j + 1].innerHTML = saveThisTaskValue;
}
shadow.parentNode.removeChild(shadow);
localStorage.setItem("listContent", document.getElementById('list').innerHTML);
}
changingWindow.appendChild(justBreakOneMoreTime);
changingWindow.appendChild(unchange);
unchange.appendChild(unchangeNode);
unchange.onclick = function() {
shadow.parentNode.removeChild(shadow);
}
}
})(i))
}
As a matter of the page reloading, you can always save the value as a cookie and reuse it again and again. You can update it whenever you want.
I don't fully understand you question, but maybe some recursion is what you need. Something along the lines of:
loop(5);
function loop(xTimes) {
for (var i = 0; i < xTimes; i++) {
if (newXTimes !== xTimes) {
loop(newXtimes);
break;
}
}
}
Maybe set newxTimes as a global variable that can be accessed inside loop.
In case someone "from the future" reads this question and it doesn't have any answers, I came up with the solution to reload the page everytime you change the value. Still, I'd like to do it without reloading.

Why can an event not be attached in the script but in the console?

I want to dynamically create, populate and clear a list with html and javascript. The creation and population of the list work just fine, but when I want to add the delete-button to the list item I can't attach the onclick event to the newly created element. Here is my complete function, it is called every time some changes happen to the printlist array:
var printlist = [];
var awesome = document.createElement("i");
awesome.className = "fa fa-minus";
function addToList(stationid, stationname)
{
var object = {id: stationid, name: stationname};
printlist.push(object);
drawList();
}
function removeFromList(id)
{
printlist.splice(id, 1);
drawList();
}
function drawList()
{
if (printlist.length > 0)
{
document.getElementById("printListDialog").style.visibility = 'visible';
var dlg = document.getElementById("DlgContent");
dlg.innerHTML = "";
for (var i = 0; i < printlist.length; i++)
{
var item = document.createElement("li");
item.className = "list-group-item";
var link = document.createElement("a");
link.href = "#";
link.dataset.listnumber = i;
link.style.color = "red";
link.style.float = "right";
link.appendChild(awesome);
link.onclick = function(){onRemove();};
item.innerHTML = printlist[i].name + " " + link.outerHTML;
dlg.appendChild(item);
}
}
else
{
document.getElementById("printListDialog").style.visibility = 'hidden';
}
}
function onRemove(e)
{
if (!e)
e = window.event;
var sender = e.srcElement || e.target;
removeFromList(sender.dataset.listnumber);
}
I tried:
link.onclick = function(){onRemove();};
as well as
link.addEventListener("click", onRemove);
Neither of those lines successfully adds the event from the script. However when I call any of the 2 lines above from the console it works and the event is attached.
Why does it work from the console but not from the script?
link.onclick = function(){onRemove();};
doesn't work because you're not passing through the event argument. link.onclick = onRemove should work just as your addEventListener call.
However, both of them don't work because of the line
item.innerHTML = printlist[i].name + " " + link.outerHTML;
which destroys the link element with all its dynamic data like .dataset or .onclick, and forms a raw html string that doesn't contain them. They're lost.
Do not use HTML strings!
Replace the line with
item.appendChild(document.createTextNode(printlist[i].name + " "));
item.appendChild(link); // keeps the element with the installed listener

Array is not accessible outside of Casper repeat function

My goal is to get each job link of a job site, go to each Job detail page by following Job link, download and save the detail in html through CASPERJS.
As id of each Job link change each time we back & forth between job link and job detail page, I need to get all the Job id each time under casper.repeat . But NoOfLink array is become empty outside of repeat function [I comment that part in code]. What is the problem?
var casper = require('casper').create();
var noOfRecordsToLoop = 0;
var TotalNoofNullElement = 0;
var NoOfLink = [];
var x = require('casper').selectXPath;
casper.echo('\nStart loding site......');
//---------------------------------------------Load and Scroll the site---------------------------------------//
casper.start('https://........../...../.......Careers/');
casper.wait(10000, function () {
//---------Total no of Job posting------//
var noOfRecords = this.fetchText(x('//*[#id="...........................jobProfile......"]'));
noOfRecordsToLoop = noOfRecords.replace(/[^0-9]/g, "");
var totalNoOfPage = Math.ceil(parseInt(noOfRecords) / 50);
casper.echo('\nStart scrolling site......');
casper.repeat(totalNoOfPage, function () {
this.scrollToBottom(); //-----------------------Scroll down
casper.wait(10000, function () {})
})
})
//------------------------------------------------Load and Scroll the site---------------------------------------//
casper.then(function () {
//-----------------------------------------Get all the link elements --------------------------//
var countForLink = 0;
var numTimesForRpt = noOfRecordsToLoop;
var numTimes = noOfRecordsToLoop;
casper.repeat(numTimesForRpt, function () {
RetElement = this.evaluate(function () {
var startingRow = '//*[contains(#id, "...-uid-")]'
var element = __utils__.getElementByXPath(startingRow).getAttribute('id');
return element;
});
var count = RetElement.replace(/[^0-9]/g, "");
casper.repeat(numTimes, function () {
var MatchElements = this.evaluate(function (count) {
var xp = '//*[contains(#id, "...-uid-' + count + '")]'
var element = __utils__.getElementByXPath(xp).getAttribute('id');
return element;
}, count++);
if (!MatchElements) {
TotalNoofNullElement = TotalNoofNullElement + 1
} else {
NoOfLink.push(MatchElements);
}
//**Here array elements are accessible**
for (var k = 0; k < NoOfLink.length; k++) {
this.echo(NoOfLink[k]);
}
});
//**But here array elements are not accessible outside of repeat** function
this.echo("Size of array is" + NoOfLink.length);
for (var q = 0; q < NoOfLink.length; q++) {
this.echo(NoOfLink[q]);
}
//-----------------------------------------Get all the link elements----------------------------//
//------------------------------------Go to the Job Detail Page Extract HTML and Save---------------------------//
this.echo("\n Inside repeat to Generate HTML");
var num = NoOfLink[countForLink];
this.echo("\nLink id is " + NoOfLink[countForLink]);
num = parseInt(num.replace(/[^0-9]/g, ""));
this.echo("\nNum is " + num);
//-----------------Click to go to the Job Detail Page------------------//
casper.thenClick(x('//*[#id="..-uid-' + num + '"]/div/div'));
casper.wait(5000, function getJobDetail() {
var content = this.getElementInfo(x(".//*[contains(#id,'......t-uid-')]")).html;
var divStart = '<div id="extrdHtml">'
var divEnd = '</div>'
var body = divStart + content + divEnd
this.echo("\nContent of Job detail :" + body);
var fs = require('fs');
fs.write('extractedJob' + NoOfLink[countForLink] + '.html', body, 'w');
this.echo("\nFile saved");
//------------------------------------Go to the Job Detail Page Extract HTML and Save---------------------------//
}); //casper.wait
casper.back();
casper.wait(5000);
countForLink++
}); //casper.repeat
}); //casper.then
//-------------------------------------------Get all the link elements------------------------------//
casper.run();
There are two repeat loops.
casper.repeat(numTimesForRpt, function () { - This is main outer loop , where the 2nd loop resides.
casper.repeat(numTimes, function () – Where I am getting the link and populating NoOfLink array. I am trying to get the array element value outside of this 2nd loop(within main outer loop) but it is not working.
All then* and wait* functions are asynchronous step functions. If you call them, you're scheduling a step that is executed at the end of the current step. casper.repeat() is a function that uses casper.then() and is therefore also asynchronous. Every synchronous code that comes after casper.repeat() will be executed before the contents of the repeat callback.
You have two options:
Wrap everything that comes after casper.repeat() in a casper.then() to make it asynchronous or
Use a normal synchronous loop instead of repeat, if the callback of repeat doesn't need to be evaluated asynchronously like in your case.
By the way, you can oftentimes reduce your code a bit by utilizing the helper functions that CasperJS provides. For example, you don't need to use evaluate() only to get the id attributes of some elements by XPath. You can do that with casper.getElementsAttribute().
Example:
var count = RetElement.replace(/[^0-9]/g, "");
for(var i = count; i < (numTimes + count); i++) {
var MatchElements = this.getElementsAttribute(x('//*[contains(#id, "...-uid-' + i + '")]'), 'id');
if (!MatchElements) {
TotalNoofNullElement = TotalNoofNullElement + 1
} else {
NoOfLink.push(MatchElements);
}
//**Here array elements are accessible**
for (var k = 0; k < NoOfLink.length; k++) {
this.echo(NoOfLink[k]);
}
}

Javascript: Passing Value of Tag to a Variable

Hope you can help me find a solution to this issue. I have a page with a number of anchor tags that contain an ID with a unique element. Here's a sample of the links:
<a href="#" class="button" id="widget_1"
onclick="$(this).parent().submit(); return false;">Button</a>
<a href="#" class="button" id="widget_2"
onclick="$(this).parent().submit(); return false;">Button</a>
<a href="#" class="button" id="widget_3"
onclick="$(this).parent().submit(); return false;">Button</a>
Below is the code that I tried to create to do collect the values in "id":
var num = [];
for (var i = 0; i<11; i++) {
num[i] = document.getElementsByTagName("id")[i].textContent;
if (num[i] == "widget_1"){ var y = "39.00"; return y;}
else if (num[i] == "widget_2"){ var y = "59.00"; return y;}
else if (num[i] == "widget_3"){ var y = "85.00"; return y;}
else { var y = "0";}
return y; }
What I'm trying to do is capture what's on id and use it to pass it to the array and if the contents match, then return the value of "y". For example, if I click on the second link, then 59.00 is returned. Any help that can be provided will be greatly appreciated.
Thanks,
Ridder
Another Approach
This approach also allows you to remove the onclick events in markup and improve readability, separating behavior from structure. (aka Separation of Concerns)
// put your prices on an array;
var prices = [39.0, 59.0, 85.0];
// match all widget(s) (aka anchors), add click handler.
$("[id^=widget]").click(function(element) {
// calculate the index into prices array based on anchor id.
var index = parseInt(this.id.substring(7)) - 1;
// get the pirce
var price = prices[index];
alert("Price is: " + price);
// here you could call
// this.$parent.submit();
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
Button
Button
Button
Pure Javascript
This version without jQuert. Enjoy!
Note: There are some limitations on old browsers (IE < 8 && FF3.0)
// put your prices on an array;
var prices = [39.0, 59.0, 85.0];
var elements = document.querySelectorAll('[id^=widget]');
Array.prototype.forEach.call(elements, function(element) {
var index = parseInt(element.id.substring(7)) - 1;
var price = prices[index];
element.onclick = function(event) {
alert(price);
};
});
Button
Button
Button
Extract just the number
var r = /\d+/;
var s = "add_to_cart_sku_ThisItemA1_CEB";
var index = s.match(r);
alert (index);
Extract a substring (for instance "ItemA1")
var code = "add_to_cart_sku_ThisItemA1_CEB";
var strIndex = code.substring(code.indexOf("Item"), code.lastIndexOf('_'));
alert (strIndex);
if we can define the function :
function MatchID(id) {
var y = "0";
if (id == "widget_1") {
y = "39.00";
} else if (id == "widget_2") {
y = "59.00";
} else if (id == "widget_3") {
y = "85.00";
}
return y;
}
Button
use this
switch(document.getElementById(this.id)){
case "widget_1":
var value = "59.00";
break;
case "widget_2":
var value = "60.00";
break;
case "widget_3":
var value = "61.00";
break;
}
return value;
that is what you need to get the basic job done, now you just need to get the click to go there, if it is another page you will need to know how it was sent and how it is recieved
hope this helps.
document.getElementsByTagName should be given html tag name instead of id and than you can access its id attribute as document.getElementsByTagName("a")[i].id
your code should be as follow :
var num = [];
for (var i = 0; i<11; i++) {
num[i] = document.getElementsByTagName("a")[i].id;
if (num[i] == "widget_1"){ var y = "39.00"; return y;}
else if (num[i] == "widget_2"){ var y = "59.00"; return y;}
else if (num[i] == "widget_3"){ var y = "85.00"; return y;}
else { var y = "0";}
return y; }
document.getElementsByTagName("id") this line in your code is not correct because there's no tags in html that is called `.
To retrieve all of the <a> tags in your document, there's a property of the HTMLDocument interface which is referred to as the document object which represents the entire html document. To retrieve all of the <a> tags. use this.
document.links this method returns a collection of the <a> tags, you can access this collection just like you do an array. For example, to access the first child in that collection.
document.links[0].href to get its href value or to get its id, you do document.links[0].id . Hope this helps

Break javascript loop

I have following code to use google images search API:
google.load('search', '1');
function searchComplete(searcher) {
// Check that we got results
if (searcher.results && searcher.results.length > 0) {
// Grab our content div, clear it.
var contentDiv = document.getElementById('contentimg');
contentDiv.innerHTML = '';
// Loop through our results, printing them to the page.
var results = searcher.results;
for (var i = 1; i < results.length; i++) {
// For each result write it's title and image to the screen
var result = results[i];
var imgContainer = document.createElement('div');
var newImg = document.createElement('img');
// There is also a result.url property which has the escaped version
newImg.src = result.tbUrl;
imgContainer.appendChild(newImg);
// Put our title + image in the content
contentDiv.appendChild(imgContainer);
The problem is, it gives me 3 image results. How to break a loop and show only the 1st one instead of 3 images?
if I change for (var i = 1; i < results.length; i++) to for (var i = 3; i < results.length; i++) it shows only one image, but image shown is the 3rd one and I need to show 1st one :)
Please advice
Don't use a for loop at all. Just replace all instances of i with 0.
google.load('search', '1');
function searchComplete(searcher) {
// Check that we got results
if (searcher.results && searcher.results.length > 0) {
// Grab our content div, clear it.
var contentDiv = document.getElementById('contentimg');
contentDiv.innerHTML = '';
var result = searcher.results[0];
var imgContainer = document.createElement('div');
var newImg = document.createElement('img');
// There is also a result.url property which has the escaped version
newImg.src = result.tbUrl;
imgContainer.appendChild(newImg);
// Put our title + image in the content
contentDiv.appendChild(imgContainer);
0 means the first item returned (almost all number sequences in programming start at 0!) so all other results will be ignored.
When you only want one element, you don't need a for loop. You can access the first element of an array with
result = results[0];
Arrays are zero-based. So when it contains three images, the images are named results[0], results[1] and results[2].
use break statement. It will terminate the loop once the image is found and hence you will have only the first one.
for (var i = 1; i < results.length; i++) {
// For each result write it's title and image to the screen
var result = results[i];
var imgContainer = document.createElement('div');
var newImg = document.createElement('img');
// There is also a result.url property which has the escaped version
newImg.src = result.tbUrl;
imgContainer.appendChild(newImg);
// Put our title + image in the content
contentDiv.appendChild(imgContainer);
//Berore the end of the loop
if(i==1)
{
break;
}
}

Categories

Resources