Calling a method of a global object from a function - javascript

I found out how to solve the problem but I could not understand what the problem was although, I know why it is working now.
Here is the code that works:
function onReceive(json) {
for (var i = 0; i < json.length; i++) {
var m = $("<p/>", {
"class": "message",
html: json[i].message
});
$("#messages").append(m);
}
}
I quote from here:
var carName = " Volvo";
// code here can use carName
function myFunction() {
// code here can use carName
}
My question is, why wouldn't this work?
messages = $("#messages");
function onReceive(json) {
for (var i = 0; i < json.length; i++) {
var m = $("<p/>", {
"class": "message",
html: json[i].message
});
messages.append(m);
}
}

I guess you are using messages = $("#messages"); before DOM ready. So at that time your javaScript not able to get $("#messages") and you are using onReceive() function after DOM load so it's working inside your function.

You need to get element when it is part of page already.
You could wrap $ call with document ready callback, like this:
$(document).ready(function () {
messages = $("#messages");
});
Otherwise selector return empty collection (no element with id messages found).
Such wrapper is not required in event handlers, because event callbacks are always called after document is ready.

Related

Referring to the calling object in JavaScript using addEventListener

I'm new to JavaScript/HTML/CSS and I can't spot the mistake I'm doing in this JavaScript function. Our teacher told us to use the addEventListener method cause it has some notable advantages.
This is my entire script with the problematic function
var espandi = function (e) {
var toHide = document.getElementsByClassName("optional");
for (var index = 0; index < toHide.length; index++)
toHide[index].style.display = "none";
var toShow = e.target.getElementsByClassName("optional");
for (index = 0; index < toShow.length; index++)
toShow[index].style.display = "block";
}
var expansibleObjects = document.getElementsByClassName("singleresult");
for (var index = 0; index < expansibleObjects.length; index++)
expansibleObjects[index].AddEventListener("click",espandi);
The fact is the line e.target.getElementsByClassName gets me an error of this type
Uncaught TypeError: Cannot read properties of undefined (reading 'getElementsByClassName').
On the contrary, if I set the function with the property "onclick" directly on the element the function works perfectly. So I think the problem it's about referring to the calling object using e.target
Update 1
First of all, I want to say that this is a project for university and I cannot publish the whole code, it would be risky for my exam.
Then there are more issues apparently. First of all with the method getElementsByClassName() applied on document it seems that he can get the Collection of Elements but then if I try to print the single Element it gives me undefined on the log. Here's the code:
var list = document.getElementsByClassName("prova");
if (list[0]) {
console.log("Assigning using \"list[0]\" as check");
list[0].onclick = espandi();
list[0].addEventListener("click",espandi);
console.log("finished adding listeners");
}
else if(list.item(0)){
console.log("Assigning using \"list.item(0)\" as check");
list.item(0).onclick = espandi();
list.item(0).addEventListener("click",espandi);
console.log("finished adding listeners");
}
else console.log("failed assignment");
console.log("printing list");
console.log(list);
console.log("printing list[0]");
console.log(list[0]);
console.log("printing list.item(0)");
console.log(list.item(0));
and here's the log output:
log output
Apparently the only way I succesfully make my script work is editing the function this way:
function espandi (caller) {
var toHide = document.getElementsByClassName("optional");
for (var index = 0; index < toHide.length; index++)
toHide[index].style.display = "none";
var toShow = caller.getElementsByClassName("optional");
for (index = 0; index < toShow.length; index++)
toShow[index].style.display = "block";
}
and then assigning it to elements directly using the "onclick" HTML attribute, like this:
<table class="prova" onclick="espandi(this)">
so that the parameter "caller" refers to the element who actually triggered the function espandi. The problem is I really want to know 2 things:
how to refer to the caller using an EventHandler function (in the case of method .addEventListener()) and a normal function (in the case of the attribute .onclick of the desired element) in JS.
how to manage the method getElementByClassName() and the collection returned by itself.
In the end, just to sum up, now I have problems with assigning the event listeners and also with referring to the caller without using a parameter like this in the HTML code I showed you.
You are calling espandi instead of assign it to onclick handler.
So you need to remove the () and do expansibleObjects[index].onclick = espandi;
Anyway in your question I don't see any e.target.addEventListener() so when you say The fact is the line e.target.addEventListener() gets me an error I don't understand what you mean, maybe you have to add more code.

Warning not to make function within a loop

I've written a code to create modal windows for div container. Once the button is clicked, I get the button's number and display a related modal window. Tested, works on all browsers.
myModalContent = new tingle.modal();
var myBtn = document.querySelectorAll("button.project__btn");
for (var i = 0; i < myBtn.length; i++) {
myBtn[i].addEventListener("click", function () {
myModalContent.open();
if (this.hasAttribute("data-btn")) {
myModalContent.setContent(document.querySelector(".project" + this.getAttribute("data-btn") + "-modal").innerHTML);
} else {
myModalContent.setContent(document.querySelector(".project1-modal").innerHTML);
}
});
}
A js validator gives one warning "Don't make functions within a loop."
Read some posts related to this topic, especially that the function must be created outside of the loop, I created a function:
function handler(modalDiv, trigBtn, index){
modalDiv.open();
if (trigBtn[index].hasAttribute("data-btn")) {
modalDiv.setContent(document.querySelector(".project" + trigBtn[index].getAttribute("data-btn") + "-modal").innerHTML);
} else {
modalDiv.setContent(document.querySelector(".project1-modal").innerHTML);
}
}
Then called it from within a loop:
for (var i = 0; i < myBtn.length; i++) {
myBtn[i].onclick = handler(myModalContent, myBtn, i);
}
It doesn't seem to work properly, it displays a last modal window right after the web page loads. My understanding that the function must be connected with the click event listener, ie when a button is clicked, the modal window should pop up. Now, the modal window pops up without any click event. Could you give me an idea how to properly write a function? Or if I should just simply ignore this js validation warning or not.
Keep it simple! You do not have to change anything about your code but to move the function expression to a named function declaration outside of the loop body:
var myModalContent = new tingle.modal();
var myBtn = document.querySelectorAll("button.project__btn");
function myHandler() {
myModalContent.open();
if (this.hasAttribute("data-btn")) {
myModalContent.setContent(document.querySelector(".project" + this.getAttribute("data-btn") + "-modal").innerHTML);
} else {
myModalContent.setContent(document.querySelector(".project1-modal").innerHTML);
}
}
for (var i = 0; i < myBtn.length; i++) {
myBtn[i].addEventListener("click", myHandler);
}
The warning is trying to prevent a problem with "modified closures". If your function did anything with the variable i, then you'd find that the value of the variable i at the time when users click the button is always myBtn.length because that's the value it ends up with at the end of the loop.
This:
for (var i = 0; i < myBtn.length; i++) {
...
Is treated like this:
var i;
for (i = 0; i < myBtn.length; i++) {
...
Since you don't use i anywhere in your function, you're technically safe, but there's a possibility that other developers in the future could change the code and end up running into this problem.
In order to fix this code in the way it looks like you're trying to fix it, you'd need to have the handler function return a function itself.
myBtn[i].addEventListener("click", createHandler());
function createHandler() {
return function() {
myModalContent.open();
if (this.hasAttribute("data-btn")) {
myModalContent.setContent(document.querySelector(".project" + this.getAttribute("data-btn") + "-modal").innerHTML);
} else {
myModalContent.setContent(document.querySelector(".project1-modal").innerHTML);
}
};
}
This has the same effect as your working code, but prevents someone from trying to use i inside of the closure. If someone needs i there, they can add it to the createHandler's argument list, where it's not reusing the same variable for each pass through the loop.
Alternatively, if you can use modern versions of javascript, you can use the let keyword instead of var.
This:
for (let i = 0; i < myBtn.length; i++) {
...
Is treated more like how this code would work in a language like C#:
for (var _ = 0; _ < myBtn.length; _++) {
var i = _;
...
In other words, the scope of the i variable is internal to the for loop, rather than global to the function you're in.

addEventListener not working in Javascript but jQuery Click is working

I am using modular pattern of javascript and trying to do things in Javascript way rather than Jquery
myapp.module1 = (function($){
"use strict";
var _config = {
backgroundImages : document.getElementsByClassName('img_paste'),
}
for(var i = 0;i < _config.backgroundImages.length; i++){
var imageElement = _config.backgroundImages[i];
imageElement.addEventListener('click',myapp.module2.addBackgroundImage(imageElement),false);
}
// $('.img_paste').click(function(){
// var img = this;
// console.log(this);
// console.log($(this));
// myapp.module2.addBackgroundImage(img);
// });
})(jQuery);
In the above code, the Jquery click function works but not the Javacript one.
When I tried to debug, I tried to console out the image in addBackgroundImage() function.
var addBackgroundImage = function(imageToBeAdded){
console.log(imageToBeAdded);//
_addImageToCanvas(imageToBeAdded);
}
The function seems to be executing even before onclick. Why is that happening?
First, the images elements appear to be empty in the console, then after some some the image elements are displayed in console.
Take a look at this simple code example:
function describeTheParameter(p) {
console.log("describeTheParameter invoked. p is of type " + typeof(p));
}
function stringFunction() {
return "Hello World!";
}
describeTheParameter(stringFunction());
describeTheParameter(stringFunction);
This results in
describeTheParameter invoked. p is of type string
describeTheParameter invoked. p is of type function
In the first call, we are calling stringFunction, and then passing the result to describeTheParameter.
In the second call, we are actually passing the function to describeTheParameter.
When you call addEventListener you must follow the pattern of the second call: pass the function without invoking it:
In the following line of code, you are invoking addBackgroundImage, and then passing the result (which will be undefined) to addEventListener.
imageElement.addEventListener('click',myapp.module2.addBackgroundImage(imageElement),false);
You need to pass a yet-to-be-called function into addEventListener.
The smallest step to make your code work is to employ a currying function:
function addImage(imageElement) {
return function() {
myapp.module2.addBackgroundImage(imageElement);
}
}
for(var i = 0;i < _config.backgroundImages.length; i++){
var imageElement = _config.backgroundImages[i];
imageElement.addEventListener('click', addImage(imageElement), false);
}
For much simpler code, make use of the this keyword. In this case, this will point to the element that's firing the event.
function imageClickHandler() {
var imageElement = this;
myapp.module2.addBackgroundImage(imageElement);
}
for(var i = 0;i < _config.backgroundImages.length; i++){
var imageElement = _config.backgroundImages[i];
imageElement.addEventListener('click', imageClickHandler, false);
}
The function seems to be executing even before onclick. Why is that happening?
Look at the statement you wrote:
myapp.module2.addBackgroundImage(imageElement)
You are calling the function and then passing its return value as the function argument.
You want something more along the lines of:
myapp.module2.addBackgroundImage.bind(myapp.module2, imageElement)
(or the function expression that you used in the commented out code)

Getting value on hover produces "undefined"

This jsfiddle demonstrates a basic mockup of what I'm trying to achieve. After clicking the link, I should be able to hover over the list elements and text should be appearing on the page, but they don't. When I print the values of the strings that should be appearing, they are "undefined." Why is this so?
Here's the js below, but I recommend looking at the fiddle.
$('#link1').click(function () {
var foolist = ["foo1", "foo2", "foo3"];
for (var i = 0; i < foolist.length; i++) {
var li = document.createElement('li');
li.innerHTML = "This is a link.";
$(li).hover(function () {
console.log(foolist[i]);
$('#p1').append(foolist[i]);
},
function () {});
$('#ul1').append(li);
}
});
You've discovered the need for something called closures! Congratulations - not many people get to see how cool they are. This is pulled from a great MSDN article - I highly recommend reading the rest of it.
The problem is that i variable no longer has the value it did when you called .hover - if you log it you'll see that i===3. Why would that be? The function that you're passing to .hover is a closure - which means it consists of the function itself and a sort of "snapshot" of the .click function's scope. You create a closure with each iteration of the loop, but they all share the same "snapshot". By the time you try to get access to i via the click event, the loop has already completed.
So how can you solve it? More closures!
function showText(i) {
$('#p1').append(foolist[i]);
}
function makeTextCallback(i) {
return function() {
showText(i);
};
}
for (var i = 0; i < foolist.length; i++) {
$(li).hover(makeTextCallback.call(this,i));
}
This is called a "function factory". You send i to makeTextCallback which captures i within the closure that it returns.
https://jsfiddle.net/aLofhaxp/28/
The simple rule is to avoid any closures within loops.
Another good way is to define such things in data attributes:
$('#link1').click(function () {
var foolist = ["foo1", "foo2", "foo3"];
foolist.forEach(function(x) {
$("<li/>")
.data('list', x)
.text("This is a link");
.hover(fooListItemOnHover, fooListItemOnHoverOff)
.appendTo('#ul1');
}
});
function fooListItemOnHover() {
var data = $(this).data('list');
console.log(data);
$('#p1').append(data);
}
function fooListItemOnHoverOff() {
}
This will produce elements with additional data-list attribute, which will store your custom data:
<li data-list='foo1'>This it a link</li>
Then, your script will read this data from it using jQuery data().
Use .data() method of jQuery to attach data of any type to DOM elements.
$('#link1').click(function () {
var foolist = ["foo1", "foo2", "foo3"];
for (var i = 0; i < foolist.length; i++) {
var $li = $("<li/>");
$li.data("VAL", foolist[i]);
$li.html("This is a link.");
$($li).hover(function () {
var value = $(this).data("VAL");
console.log(value);
$('#p1').append(value);
},
function () {});
$('#ul1').append($li);
}
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
<a id="link1" href="javascript:;">Click to show list</a>
<ul id="ul1"></ul>
<p id="p1"></p>

addEventListener for array elements

With this code I'm trying to iterate over a Array which is derived from a json Array.
There is a SVG inside the page.
When I click on a country, the name should be submitted to a URL.
Unfortunately I get the following error at the line indicated with -->error
Uncaught TypeError: Cannot call method 'addEventListener' of null
How can I overcome this error?? And why is it occurring?
The array is like this:
{"Countries":["india","switzerland","usa","alaska","germany","austria","netherlands","france","italy","spain","poland","hungary","czech","romania","russia","china","mexico","brazil","britain","thailand","sweden","uae","new_zealand_north_island","new_zealand_south_island","egypt"]}
var mySVG = document.getElementById("VectorMap");
var svgDoc;
mySVG.addEventListener("load", function () {
svgDoc = mySVG.contentDocument;
$.getJSON("http://www.someurl.com/jsonArray",
function (data) {
$.each(data, function (index, item) {
var i=0;
for (tot=item.length; i < tot; i++) {
var someElement = svgDoc.getElementById(item[i]);
//--->error
someElement.addEventListener("mousedown", function () {
$("#info").html(ajax_load).load("http://www.someurl.com/returnData"+"?country="+text);
}, false); //add behaviour
}
});
});
}, false);
You're not checking whether the element exists before attempting to attach the event handler; if the element doesn't exist, getElementById() would return null. This code would have that check.
var someElement;
for (var i = 0, tot = item.length; i < tot; i++) {
someElement = svgDoc.getElementById(item[i]);
if (someElement) {
someElement.addEventListener("mousedown", function () {
$("#info")
.html(ajax_load)
.load("http://www.someurl.com/returnData"+"?country="+text);
}, false); //add behaviour
}
}
The exception is happening because in the page there is no element with the id given. In such case, getElementById returns null: you can check by your web console / debugger in the browser, what is this id that doesn't exists, and if it is supposed to be there – and therefore you have an error or typo.
Anyway, you could take advantages of jQuery. Because in jQuery you can pass a list of ids, and the listener will be attached only to those elements that are actually in the page, without throwing exception. So in your case, instead of:
var i=0;
for (tot=item.length; i < tot; i++) {
var someElement = svgDoc.getElementById(item[i]);
//--->error
someElement.addEventListener("mousedown", function () {
$("#info").html(ajax_load).load("http://www.someurl.com/returnData"+"?country="+text);
}, false); //add behaviour
}
You can simply have:
$('#' + item.join(', #')).on('mousedown', function() {
$("#info").html(ajax_load).load("http://www.someurl.com/returnData"+"?country="+text);
});
So basically, assuming item is an array (and therefore I would call it items) with plain ids, like ['a', 'b', 'c'], with '#' + item.join(', #') you will obtain "#a, #b, #c" as string to pass to jQuery: if any on those elements doesn't exists in the page, the mousedown listener simply won't be attached, without raise any error.
Note: not sure where this text variable came from, I just put there because in your original example.

Categories

Resources