getElementById().appendChild() refuses to work! - javascript

Im trying to append some JSON data from the last.fm API,
I have been using alert() at several stages to verify the API is being parsed correctly and it is,
This has led me to the conclusion that getElementById().appendChild() doesn't work, below is the URL to the test page I have set up:
http://mutant-tractor.com/tabtest.html
Code here
function calculateDateAgo(secAgo) {
var agoString, agoRange, agoScaled;
if(secAgo >= (agoRange = 60*60*24))
agoString = (agoScaled = Math.floor(secAgo/agoRange))+" "+(agoScaled>1?"days":"day") + " ago"
else if(secAgo >= (agoRange = 60*60))
agoString = (agoScaled = Math.floor(secAgo/agoRange))+" "+(agoScaled>1?"hours":"hour") + " ago"
else if(secAgo >= (agoRange = 60))
agoString = (agoScaled = Math.floor(secAgo/agoRange))+" "+(agoScaled>1?"minutes":"minute") + " ago"
else if(secAgo >= -60)
agoString = "blastin' out now";
else
agoString = "soon ;)";
return agoString
}
function truncateName(name, l) {
return name.length > l ? name.substr(0,l-2) + "\u2026" : name
}
function lfmRecentTracks(JSONdata) {
try {
var eImg, eLink, eSpan, divTag, eWrapper;
var oTracks = new Array().concat(JSONdata.recenttracks.track);
for (var i = 0; i [lessthanhere] oTracks.length; i++) {
//insert track link
spanTag = document.createElement("span");
spanTag.className = "lfmTrackInfoCell tabslider";
eLink = document.createElement("a");
eLink.appendChild(document.createTextNode( truncateName(oTracks[i].name, 25) ));
//alert(truncateName(oTracks[i].name, 25));
spanTag.appendChild(eLink);
eLink.href = oTracks[i].url;
//alert(oTracks[i].url);
eLink.target = "new";
eLink.className = "lfmTrackTitle";
document.body.appendChild(spanTag);
//insert artist name
eSpan = document.createElement("span");
eSpan.appendChild(document.createTextNode(truncateName(oTracks[i].artist["#text"], 22) ));
//alert(truncateName(oTracks[i].artist["#text"], 22));
eSpan.className = "lfmTrackArtist";
document.body.appendChild(eSpan);
//insert date
eSpan = document.createElement("span");
spanTag.appendChild(eSpan);
eSpan.appendChild(document.createTextNode( (typeof oTracks[i].date=="undefined"?"now playing":calculateDateAgo(new Date().getTime()/1000 - oTracks[i].date.uts)) ));
//alert((typeof oTracks[i].date=="undefined"?"now playing":calculateDateAgo(new Date().getTime()/1000 - oTracks[i].date.uts)));
eSpan.className = "lfmTrackDate";
document.body.appendChild(eSpan);
}
} catch(e) {}
}
The only way it works is by using document.body.appendChild()
I'm calling the script in the head if that makes a difference?
The div I'm trying to attach them to are 4 different divs i.e. in the for loop each loop needs to reference a different element,
Thanks in advance!
Myles

I'm calling the script in the head if that makes a difference?
You won't be able to getElementById() if the document body hasn't even been parsed. In other words, you need to run your code in an window.onload function, or place it at the very bottom of your body.
Also, remove the try/catch while testing, it will only hide errors.

Are you sure that the element you're trying to get has been loaded into the DOM? You said that your script runs in the head tag (which loads before the rest of the body loads). It's possible that your script is being run before the DOM element you're searching for exists, and therefore it can't find it and therefore it can't add to it.

There's no guarantee that the HTML will have finished parsing by the time the JavaScript executes. There are several ways of doing what you want, with different performance characteristics.
You can put your code in a function and assign it as the load event handler for the window object. This has the downside of waiting until all resources for the page have finished loading, not just the HTML. This often slows down page load times, as you need to wait for things like slow ad servers etc.
You can put your code in a function and call it from bottom of the document.
You can use a JavaScript library such as jQuery to execute your JavaScript when the DOM has finished loading. There isn't a simple way of doing this that works cross-browser, so it's simplest not to reinvent the wheel and just to use what is already a mature solution.

Related

JavaScript works when setTimeout() is used, but it isn't working when document.eventListener('DOMContentLoaded', x) is used on a WordPress page. Why?

I have a few lines of JavaScript code that pick up heading texts from separate sections and place them into their respective input fields. They are also executed on single pages using wp_enqueue_script.
It works absolutely fine when setTimeout() is used:
function passengerElevator() {
var getProductName = document.querySelectorAll('[data-id="6657316"]');
getProductName.forEach(function(item) {
var productName = item.querySelector('.lift');
var inputArea = item.querySelector('input[name=product]');
inputArea.value = productName.innerText;
});
var getProductName = document.querySelectorAll('[data-id="e9c06d5"]');
getProductName.forEach(function(item) {
var productName = item.querySelector('.lift');
var inputArea = item.querySelector('input[name=product]');
inputArea.value = productName.innerText;
});
setTimeout(function() { passengerElevator() },3000);
However, there is problem of page size (some pages have more than 10 input fields) and I don't want to set an astronomically high ms to delay the script. So I decided to fire it on DOMContentLoaded:
document.addEventListener("DOMContentLoaded", passengerElevator);
function passengerElevator() {
var getProductName = document.querySelectorAll('[data-id="6657316"]');
getProductName.forEach(function(item) {
var productName = item.querySelector('.lift'); // heading text (ex:Panoramic Lift)
var inputArea = item.querySelector('input[name=product]');
inputArea.value = productName.innerText; //ouput here
});
var getProductName = document.querySelectorAll('[data-id="e9c06d5"]');
getProductName.forEach(function(item) {
var productName = item.querySelector('.lift'); // Heading text (ex:Home Lift)
var inputArea = item.querySelector('input[name=product]');
inputArea.value = productName.innerText; // Output here
});
}
As you may have already guessed, it is not working. Is my code too messy to be executed faster or is there any other problem I am missing?
I know similar questions have been asked previously, however, no existing answer I found was able to help me.
It seems like you try to loop through elements that are still not loaded. Perhaps they are being appended to the page via Ajax, so DOMContentLoaded can't help there.
You can create your own check for those elements using setInterval, so use something like this:
let dataIdCheck = setInterval(() => {
if (document.querySelectorAll('[data-id="6657316"]').length > 0 && document.querySelectorAll('[data-id="e9c06d5"]').length > 0) {
clearInterval(dataIdCheck);
// your code here
}
}, 500);
This code will run every 500 milliseconds and check if those two elements exists, using .length. Once they do exists, we stop the interval and run the code.
I also suggest to do console.log('in') to check that our interval stop running once the elements are found.

setInterval refuses to run my function after recent edit to my code... why?

To give you a grasp of what I mean in my title.
Take a look at this code which is before the setInterval stopped working.
var anime = function(){
_.each(db.get('','animedb'), function(site){
var ann = function(){
^ the function is in a var
for (var epid in eps) {
epid = parseInt(epid, 10);
var eptime = (new Date(eps[epid].pubDate[0])*1000)/1000;
if(eptime > site.lastbuilddate){
counter = counter+1;
if(counter < 6){
list.push(font(colors['normal'])+eps[epid].title[0] +' - ['+ utils.secondsToString((new Date() - (eptime+site.delay))/1000, 1)+' ago.]</f>');
}
}
};
^ this is the part that breaks everything after its been edited
var run = setInterval(ann, site.interval*60000);
^ here is the setInterval its at the bottom of the each
anime();
^ here is the call for the whole function that calls the setInterval
The above code is part of an anime announcement for chat rooms owned by anime sites owners using their rss feeds.
The above code works and excuse me for saying this but at this point.
I'm going to say "I have no idea why". Because i really have no idea why setInterval picks and chooses when to work.
I talked to a friend who had more knowledge than me in javascript and time based functions and he said that there are no "conditions" required for setInterval to run.
for (var epid in eps) {
epid = parseInt(epid, 10);
var eptime = (new Date(eps[epid].pubDate[0])*1000)/1000;
if(eptime > site.lastbuilddate){
counter = counter+1;
if(counter < 6){
var url = eps[epid].link.split('//')[1];
var keyword = '';
var words = url.substr(0, url.length-1).split('/').join('-').split('-');
for (var wid in words) {
keyword += words[wid].charAt(0);
}
http.get({hostname:'dev.ilp.moe', port:80, path:'/surl/yourls-api.php?username=usernameremovedforsecurity&password=passwordremovedforsecurity&format=json&action=shorturl&url='+url+'&title='+ctitle+' - '+eps[epid].title[0]+'&keyword='+keyword}, function(r) {
if(r.statusCode === 200) { //200 is success
var b = '';
r.on('data', function(c) {
b += c;
});
r.on('end', function() {
list.push(font(colors['normal'])+eps[epid].title[0] +' - ['+ utils.secondsToString((new Date() - (eptime+site.delay))/1000, 1)+' ago.] - http://dev.ilp.moe/surl/'+keyword+'</f>');
}
}
});
}
}
};
The above code is the part for creating shorturls.
Here is the json DB that is being loaded.
{"0":{"lastbuilddate":1426441081000,"delay":0,"host":"www.animerush.tv","path":"/rss.xml","chats":["animerushtv"],"interval":15},"1":{"lastbuilddate":1424068119000,"delay":28800000,"host":"dubbedanime.tv","path":"/feed/","chats":["dubbed-anime-tv"],"interval":15},"2":{"lastbuilddate":1426415086000,"delay":32400000,"host":"bestanimes.tv","path":"/feed/","chats":["bestanimestv"],"interval":15},"3":{"lastbuilddate":1426434866000,"delay":0,"host":"www.theanime.tv","path":"/feed/","chats":["timecapsule"],"interval":15}}
The recent edit to my code was supposed to implement Shortened links for each episode released using the links provided in the rss feeds from the sites in the database.
The domain http://ilp.moe is my domain.
I have console logged everywhere and tested as much as I possibly could.
At this point I do not understand why the edit is making code that used to be executed by setInterval no longer be executed.
The reason why the code wasn't executed is because the functions were assigned to a variable so they weren't run until it got to setInterval.
When they reach setInterval the errors prevent setInterval from executing (depends on the severity of the error).
after taking the function and just running it without putting it in a var or setInterval and console logging for a bit i found the error was caused by this line
var url = eps[epid].link.split('//')[1];
in this case
eps[epid].link; // === ["http://blah.com/animelolep1"]
my problem was that the var url was trying to split on a list and not a string
here is the fix
var url = eps[epid].link[0].split('//')[1]; // grabs the first item from the list and then splits

Appending and Preloading Images with JavaScript

I'm in the process of creating a site that preloads several large gifs. Due to the size of the images. I need them all to be loaded before displayed to the user. In the past I have done this numerous times using something basic like this:
var image = new Image();
image.onload = function () { document.appendChild(image); }
image.src = '/myimage.jpg';
However, i'm loading a group of images from an array, which contains the image source url. It should show a loading message and once they have all loaded it show perform a callback and hide the loading message etc.
The code I've been using is below:
var images = ['image1.gif', 'image2.gif', 'image3.gif'];
function preload_images (target, callback) {
// get feedback container
var feedback = document.getElementById('feedback');
// show feedback (loading message)
feedback.style.display = 'block';
// set target
var target = document.getElementById(target);
// clear html of target incase they refresh (tmp fix)
target.innerHTML = '';
// internal counter var
var counter = 0;
// image containers attach to window
var img = new Array();
// loop images
if (images.length > 0) {
for (var i in images) {
// new image object
img[i] = new Image();
// when ready peform certain actions.
img[i].onload = (function (value) {
// append to container
target.appendChild(img[value]);
// hide all images apart from the first image
if (value > 0) {
hide(img[value]);
}
// increment counter
++counter;
// on counter at correct value use callback!
if (counter == images.length) {
// hide feedback (loading message)
feedback.style.display = 'none';
if (callback) {
callback(); // when ready do callback!
}
}
})(i);
// give image alt name
img[i].alt = 'My Image ' + i;
// give image id
img[i].id = 'my_image_' + i
// preload src
img[i].src = images[i];
}//end loop
}//endif length
}//end preload image
It's really weird, I'm sure it should just work, but it doesn't even show my loading message. It just goes straight to the callback.. I'm sure it must be something simple, I've been busy and looking at it for ages and finding it a tad hard to narrow down.
I've been looking over stackoverflow and people have had similar problems and I've tried the solutions without much luck.
Any help would be greatly appreciated! I'll post more code if needed.
Cheers!
If I'm not totally wrong the problem is with you assignment to
// when ready peform certain actions.
img[i].onload = (function (value) {...})(i);
here you instantly call and execute the function and return undefined to the onload attribute, what can not be called when the image is loaded.
What you can do to have access to the value 'i' when the image is loaded you can try something like the following:
onload = (function(val){
var temp = val;
return function(){
i = temp;
//your code here
}
})(i);
this should store the value in temp and will return a callable function which should be able to access this value.
I did not test that if it is working and there maybe a better solution, but this one came to my mind :)
Try this for your onload callback:
img[i].onload = function(event) {
target.appendChild(this);
if (img.indexOf(this) > 0) {
hide(this);
}
// ...
};
Hope you can get it working! It's bed time for me though.
Edit: You'll probably have to do something about img.indexOf(this)... just realized you are using associative array for img. In your original code, I don't think comparing value to 0 is logical in that case, since value is a string. Perhaps you shouldn't use an associative array?

How to ensure my javascript function only executes after all external javascripts are loaded?

I am developing a WebPart in SharePoint,and I need to draw something in my WebPart using excanvas.js.But sometimes it shows nothing.The error message is:
Object doesn't support property or method 'getContext'
When I debug it,it breaks at here:
var ctxBg = document.getElementById(backgroundId).getContext("2d");
The "backgroundId" is the id of one canvas element.
This error happens not every times,just sometimes,so I think if my js function is executed before the excanvas.js is loaded.I register the excanvas.js with the code:
this.Page.ClientScript.RegisterClientScriptInclude("ExCanvasJs", "wpresources/MyWebPart/js/excanvas.js");
So how to ensure my function is executed after the excanvas.js is loaded?Or I'm wrong at this problem?Would you give me your advice?
my js function:
function DrawMeter(meter, contextCollection) {
var backgroundId = meter.meterbackground;
var pointerId = meter.meterpointer;
var containerId = meter.metercontainer;
if (contextCollection != null && contextCollection.length > 0) {
for (var i = 0; i < contextCollection.length; i++) {
DrawSingleMeter(backgroundId + "_" + i, pointerId + "_" + i, containerId + "_" + i, contextCollection[i]);
}
}
function DrawSingleMeter(backgroundId, pointerId, containerId, context) {
var ctxBg = document.getElementById(backgroundId).getContext("2d");
var ctxPointer = document.getElementById(pointerId).getContext("2d");
drawing...
}
you need to put it into a page ready handler. otherwise at the time the javascript is executed the element might not be available yet.
consider using jquery or another js library for this, if you can't use it here's a link on how to use the native js version of the onload event

How might I get the script filename from within that script?

I'm pretty sure the answer is no, but thought I'd ask anyway.
If my site references a scripted named "whatever.js", is it possible to get "whatever.js" from within that script? Like:
var scriptName = ???
if (typeof jQuery !== "function") {
throw new Error(
"jQuery's script needs to be loaded before " +
scriptName + ". Check the <script> tag order.");
}
Probably more trouble than it's worth for dependency checking, but what the hell.
var scripts = document.getElementsByTagName('script');
var lastScript = scripts[scripts.length-1];
var scriptName = lastScript.src;
alert("loading: " + scriptName);
Tested in: FF 3.0.8, Chrome 1.0.154.53, IE6
See also: How may I reference the script tag that loaded the currently-executing script?
I'm aware this is old but I have developed a better solution because all of the above didn't work for Async scripts. With some tweaking the following script can cover almost all use cases. Heres what worked for me:
function getScriptName() {
var error = new Error()
, source
, lastStackFrameRegex = new RegExp(/.+\/(.*?):\d+(:\d+)*$/)
, currentStackFrameRegex = new RegExp(/getScriptName \(.+\/(.*):\d+:\d+\)/);
if((source = lastStackFrameRegex.exec(error.stack.trim())) && source[1] != "")
return source[1];
else if((source = currentStackFrameRegex.exec(error.stack.trim())))
return source[1];
else if(error.fileName != undefined)
return error.fileName;
}
Not sure about support on Internet Explorer, but works fine in every other browser I tested on.
You can use...
var scripts = document.getElementsByTagName("script"),
currentScriptUrl = (document.currentScript || scripts[scripts.length - 1]).src;
currentScript() is supported by all browsers except IE.
Make sure it's ran as the file is parsed and executed, not on DOM ready or window load.
If it's an empty string, your script block has no or an empty src attribute.
In Node.js:
var abc = __filename.split(__dirname+"/").pop();
Shog9's suggestion more shorter:
alert("loading: " + document.scripts[document.scripts.length-1].src);
You can return a list of script elements in the page:
var scripts = document.getElementsByTagName("script");
And then evaluate each one and retrieve its location:
var location;
for(var i=0; i<scripts.length;++i) {
location = scripts[i].src;
//Do stuff with the script location here
}
As the "src" attribute holds the full path to the script file you can add a substring call to get the file name only.
var path = document.scripts[document.scripts.length-1].src;
var fileName = path.substring(path.lastIndexOf('/')+1);
I had issues with the above code while extracting the script name when the calling code is included inside a .html file.
Hence I developed this solution:
var scripts = document.getElementsByTagName( "script" ) ;
var currentScriptUrl = ( document.currentScript || scripts[scripts.length - 1] ).src ;
var scriptName = currentScriptUrl.length > 0 ? currentScriptUrl : scripts[scripts.length-1].baseURI.split( "/" ).pop() ;
You can try putting this at the top of your JavaScript file:
window.myJSFilename = "";
window.onerror = function(message, url, line) {
if (window.myJSFilename != "") return;
window.myJSFilename = url;
}
throw 1;
Make sure you have only functions below this. The myJSFilename variable will contain the full path of the JavaScript file, the filename can be parsed from that. Tested in IE11, but it should work elsewhere.
If you did't want use jQuery:
function getCurrentFile() {
var filename = document.location.href;
var tail = (filename.indexOf(".", (filename.indexOf(".org") + 1)) == -1) ? filename.length : filename.lastIndexOf(".");
return (filename.lastIndexOf("/") >= (filename.length - 1)) ? (filename.substring(filename.substring(0, filename.length - 2).lastIndexOf("/") + 1, filename.lastIndexOf("/"))).toLowerCase() : (filename.substring(filename.lastIndexOf("/") + 1, tail)).toLowerCase();
}
What will happen if the jQuery script isn't there? Are you just going to output a message? I guess it is slightly better for debugging if something goes wrong, but it's not very helpful for users.
I'd say just design your pages such that this occurrence will not happen, and in the rare event it does, just let the script fail.
The only way that is waterproof:
var code = this.__proto__.constructor.toString();
$("script").each(function (index, element) {
var src = $(element).attr("src");
if (src !== undefined) {
$.get(src, function (data) {
if (data.trim() === code) {
scriptdir = src.substring(0, src.lastIndexOf("/"));
}
});
}
});
"var code" can also be the function name, but with the prototype constructor you don't have to modify anything. This code compares its own content against all present scripts. No hard coded filenames needed anymore.
Korporal Nobbs was in the right direction, but not complete with the comparison.

Categories

Resources