I have onmouseover and onclick in my html and I would like to move them to separate js file (in js folder).
with onmouseover i do try however i can't make it work. it is supposed to be changing (just once) from img/cacti-edit.jpg to img/cacti.jpeg.
my html :
<div class="pic">
<img src="img/cacti-edit.jpg" alt="close-up of a cactus and its spikes"/>
</div>
my js:
let picture = document.getElementsByClassName("pic");
picture.addEventListener("mouseover", function( event ) { picture.src='img/cacti.jpeg'; }, false);
console.log(picture)
log is : TypeError: picture.addEventListener is not a function.
HTML:
<ul>
<li onclick="swapStyleSheet('css/fixed.css')">Fixed width</li>
<li onclick="swapStyleSheet('css/responsive.css')">Responsive</li>
</ul>
and in JS I have:
function swapStyleSheet(sheet) { document.getElementById("pagestyle").setAttribute("href", sheet); }
and I dont know at all how to move this onclicks into seperate js.
Thanks
Your issue is not with the file, as you have shown you are getting an error from the event listener.
The reason is because getElementsByClassName() returns an array. You cannot assign a listener directly to an array. You need to access an element in the array and assign the listener to that.
let picture = document.getElementsByClassName("pic");
picture[0].addEventListener("mouseover", function( event ) {
picture.src='img/cacti.jpeg'; }, false);
Next, to add it to a separate file, simply add those functions to a file and import the file into your html.
For example:
Filename: test.js
Import line:
<script src="test.js" type="text/javascript">
The document.getElementsByClassName() selector will return an array, even if you have just one element to fit that query. You are trying to add an EventListener in a array, wich is impossible. 2 possible solutions are:
Using a for loop in case you have more than 1 element with pic class:
let picture = document.getElementsByClassName("pic");
for(let pictureEl of picture) //You can use any loop that you find most convenient
pictureEl.addEventListener("mouseover", function( event ) { pictureEl.src='img/cacti.jpeg'; }, false);
Select the array first element. This way you'll work with that specific div. Just add something in your query.
let picture = document.getElementsByClassName("pic")[0]; /*Since we are dealing with an array, index 0 represents its first element*/
This should solve your problem. Comment if you need any clarification.
Related
I would like to attach an event listener (that calls a function) to a button that is not yet present in the DOM. I don't want to use inline-call in the HTML how can I do it?
So far i could achieve what I wanted by creating a global event listener that checks if the element clicked has a specific Id. But I found this solution to be dirty and not optimal.
const messageForm = document.querySelector("#message-form")
const messageTextarea = document.querySelector("#message-textarea");
const messageList = document.querySelector("#message-list");
const messageEmpty = document.querySelector("#message-empty");
let messageNumber = 0;
const messageFormatted = messageText => {
return `
<li class="message-item">
<img class="message-avatar" src="./icons/boy.png" alt="Username">
<article class="message-content">
<p class="author">Ernesto Campese</p>
<p class="message">${messageText}</p>
<p class="datetime">A moment ago</p>
</article>
<div class="message-actions">
<img id="message-edit" class="action-button" src="./icons/edit.png" alt="" width="22" height="22">
<img id="message-delete" class="action-button" src="./icons/delete.png" alt="" width="22" height="22">
</div>
</li>
`;
}
document.addEventListener("click", (e) => {
if (e.target.id === "message-delete") {
e.target.parentNode.parentNode.remove();
messageNumber--;
messageEmptyCheck();
}
})
messageForm.addEventListener("submit", (e) => {
e.preventDefault();
if (messageTextarea.value !== "") {
messageList.insertAdjacentHTML("beforeend", messageFormatted(messageTextarea.value));
messageTextarea.value = null;
messageTextarea.focus();
messageNumber++;
messageEmptyCheck();
}
});
As you can see in the code, inside the <li> that I'm creating there are two IMG, one is for deleting and the other for editing. I want to add an event listener to the delete IMG, so when the user clicks it, the li gets eliminated.
The problem is that I cannot create any function if the element does not exist yet. I would love to do something like:
const messageDeleteButton = document.querySelector("#message-delete");
messageDeleteButton.addEventListener("click", (e) => {
e.parentNode.parentNode.remove();
}
})
Hope I was clear enough, thank you guys!
You could parse your HTML string to a document in JS and then add the event listener to that element.
So you've got your Template Literal string.
const messageFormatted = messageText => {
return `
<li class="message-item">
<img class="message-avatar" src="./icons/boy.png" alt="Username">
<article class="message-content">
<p class="author">Ernesto Campese</p>
<p class="message">${messageText}</p>
<p class="datetime">A moment ago</p>
</article>
<div class="message-actions">
<img id="message-edit" class="action-button" src="./icons/edit.png" alt="" width="22" height="22">
<img id="message-delete" class="action-button" src="./icons/delete.png" alt="" width="22" height="22">
</div>
</li>
`;
}
You can transform your string into HTML by writing a function like the one below.
It uses the DOMParser API which creates a new document that will contain all of your HTML you've written in your string.
const parseStringToHTML = (str) => {
const parser = new DOMParser();
return parser.parseFromString(str, 'text/html');
};
And this document works like the one you have in your page, its just a new one that only exists in your JS memory for the time being. Give your string as argument to parseStringToHTML to create HTML.
const message = messageFormatted('This is the message'); // Example message
const messageHTML = parseStringToHTML(message); // Returns a document object with all the features a document object has.
So now that your string is a document you can use methods on it like getElementById, querySelector, etc. But first you must select the element that you need.
const messageDeleteButton = messageHTML.querySelector("#message-delete");
See that I've used messageHTML instead of document. In this scenario messageHTML is a document and therefor we can query inside of it. And now you've found your element inside this document you can add an event listener to it.
messageDeleteButton.addEventListener("click", (e) => {
e.parentNode.parentNode.remove();
});
Okay, so now the event listener has been added. All you have to do now is append the HTML you need from the messageHTML into the document of your page. The HTML can be found in the messageHTML.body property.
Now instead of insertAdjacentHTML use insertAdjacentElement to insert the element in the position of your choosing. The element you want to append is the <li class="message-item"> which would be the firstElementChild in the body of the messageHTML.body property.
So in your submit event listener of your form change the following line from:
messageList.insertAdjacentHTML("beforeend", messageFormatted(messageTextarea.value));
To all that I've explained above.
// Create string
const message = messageFormatted(messageTextarea.value);
// String to document
const messageHTML = parseStringToHTML(message);
// Select delete element.
const messageDeleteButton = messageHTML.querySelector("#message-delete");
// Add event listener to delete element.
messageDeleteButton.addEventListener("click", (e) => {
e.parentNode.parentNode.remove();
});
// Append the <li> element to the messageList.
messageList.insertAdjacentElement("beforeend", messageHTML.body.firstElementChild);
Don't forget to include the parseStringToHTML function in your code. I understand that this is a lot to work out so if you have any question please do ask.
I hope that this will help you out.
Note I see that event delegation (global event listener) is not something you want to do, although as other have stated, it is more performant and easier to implement than other methods, even my own answer. It would also solve listening for elements that you've added or even removed from your list. You could set it even on the <ul id="message-list"> element like T.J. Crowder suggested.
You're on the right track. The identifying characteristic doesn't have to be an id, it can be anything about the element — a class is a common approach.
There are three DOM methods that will help with making the implementation flexible:
closest - starting with the element you call it on, it finds the nearest ancestor matching a given CSS selector
contains - checks to see if a node contains another node
matches - checks to see if the element you call it on matches a given CSS selector
For instance, your current handler requires that the button be the element that was clicked (event.target). That's fine for your img use case, but what if it were a button with an img inside it? Then event.target might be the button of the img, depending on where the user clicked. closest and contains help with that:
document.addEventListener("click", (e) => {
const button = e.target.closest("#message-delete");
if (button) {
button.closest("li").remove();
messageNumber--;
messageEmptyCheck();
}
})
You'll also want to hook the events on the nearest container that will work. Sometimes, you're stuck with it being document or document.body. But oftentimes, you can do better than that. For instance, when listening for button clicks on buttons that are in the cells of a table, you can listen at the table or tbody/thead level rather than document. That's where contains comes into the equation, it makes sure closest didn't go too far up the document tree:
document.querySelector("selector-for-the-table").addEventListener("click", (e) => {
const button = e.target.closest("#message-delete");
if (button && e.currentTarget.contains(button)) {
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
button.closest("li").remove();
messageNumber--;
messageEmptyCheck();
}
})
It's not likely in your case that the search for #message-delete will have gone out of the containing element, so you may not need that bit, but sometimes it can happen and so you want that contains check.
If you really want to add event listener to that element itself then I think you may want to try MutationObserver - https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver
You can observe the events with passing config for childList, subtree and on the event you can check whether the element you are looking for has been added or it is there. If you find that you can add the event listener.
You may want to look at following articles -
https://eager.io/blog/three-real-world-use-cases-for-mutation-observer/
https://blog.sessionstack.com/how-javascript-works-tracking-changes-in-the-dom-using-mutationobserver-86adc7446401
I hope it would be helpful.
I've been trying to learn js (and a tad of jquery) and I have run into two difficulties when trying to find a way to combine solutions that I find.
Just a little warning that this code is a mix of a few tutorials that I have recently done. I am very new to js.
So I start with a basic html with a few li.
<body>
<ol id="liste">
<li class="active">
</li>
<li>
</li>
<li>
</li>
</ol>
<div id="main_ima">
</div>
<script src="js/main.js"></script>
</body>
I want to create ids for each "li" so in my main.js I add this:
var idVar = $("#liste").find("li").each(function(index){
$(this).attr("id","num-li-"+index);
});
This works great so far. Everytime I add a new li, it gets a new id. I also put it into a var because I will need to use it later.
In th console, If I type idVar, it gives me the whole list of li. If I type idVar[3]. it only gives me the li associated to the [3]. Perfect.
Now I want to get something to appear when one of the li is clicked. For example, I will use the [3]. So I add this to my main.js
var imaContainer = document.getElementById('main_ima')
var listed = document.getElementById('liste');
idVar[3].addEventListener("click", appar);
function appar(){
$(idVar[3]).addClass("active").siblings().removeClass("active");
var imaSel = new XMLHttpRequest();
imaSel.open('GET', 'https://domain.link.to.file.json');
imaSel.onload = function() {
var imaLo = JSON.parse(imaSel.responseText);
renderHTML(imaLo);
};
imaSel.send();
};
function renderHTML(data) {
var htmlS = "";
for (i = 0; i < data.length; i++) {
htmlS += "<p>" + data[i].name + " is a " + data[i].species + ".</p>";
}
imaContainer.insertAdjacentHTML('beforeend', htmlS);
}
Just a side note, I added the add/remove "active" class for CSS.
So when I click the li[3], it works almost as expected. The only thing is when I reclick [3] it produces the result a 2nd time. And again, if I click it a 3rd time, it produces the result a 3rd time, without remove the past results. (which is not totally what I want. Just the 1st result would be better.)
But that is not the main problem I am facing.
I would like the [number] to be dynamically detected, based on the id of the clicked li. I could, in a very ugly way, copy and past this code for every [number] I have. and it would work. But then, what if I want to add more li elements, I would need to add more copy and paste of the above code, giving me possibly huge files for nothing. This is surely not the best way, although it would work.
I'm sure this can be done dynamically.. but that is mostly why I am here. :)
Afterwards, once the dynamic has been added to the clicked li, I would also like the link to be changed dynamically based on the li id. For example, instead of :
imaSel.open('GET', 'https://domain.link.to.file.json');
something like:
imaSel.open('GET', "https://domain.link.to.file" + var +".json");
the var being equal to the [3] number of the clicked li.
In this case, when I try to add a var with a for loop, I always get the "var = max.length" instead of the "var = [id of clicked item]".
So there you have it. Do you need more details?
This is my first JS and/or Jquery try. I've been playing with it for a few days but when I search for answers, when I implement the "solutions" it alwas gives me some new problem. So I am showing you the code that is the closest, IMO, to what I am looking for.
Hopefully, I am not too far away of somehting that works and is not as big as my solutions. :)
Thanks for your time and all help is appreciated.
Here are some suggestions:
You don't need to assign id attributes to your li. You actually never need that id. This will work just as well (note also the > in the selector which makes the find call unnecessary):
var $li = $("#liste > li");
Already now you can address each of the li as $li[3], although that is not the "best practise". Better is $li.get(3). I also like the convention to start the variable with $ when it is the result of a jQuery selection. It gives a clue that you can apply jQuery methods to it.
You don't need to assign a click handler to each li separately. With jQuery on (instead of the native addEventListener) you can assign one event handler for all of them at once.
$li.on('click', apar)
The callback you define for on will have this set to the particular li element that has been clicked, so you can do:
$(this).addClass("active").siblings().removeClass("active");
... without any array lookup.
jQuery offers easy functions for several types of HTTP requests, so you don't need to use XMLHttpRequest. In fact, there is one specifically for getting JSON, so you don't even have to parse the response:
$.getJSON('https://domain.link.to.file.json', renderHTML);
The jQuery index() method can give you the sequence number of that li:
$.getJSON('https://domain.link.to.file' + $(this).index() + '.json', renderHTML);
To replace the inner HTML of a certain element, the jQuery html method can be used:
$('#main_ima').html(htmlS);
Note also how you don't need the DOM native getElementById method, jQuery can look that up for you with the short $('#main_ima').
Example
Here is a working example with a fake JSON serving server:
$("#liste > li").on('click', apar);
function apar() {
$(this).addClass("active").siblings().removeClass("active");
$.getJSON('https://jsonplaceholder.typicode.com/posts/'
+ (1+$(this).index()), renderHTML);
}
function renderHTML(data) {
// This particular JSON request returns an object with body property
var htmlS = data.body;
$('#main_ima').html(htmlS);
}
// On page load, click on the first `li` to automatically load the data for it
$('#liste > li:first').click();
#liste { width: 40px }
.active { background: yellow }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<ol id="liste">
<li class="active">load 1</li>
<li>load 2</li>
<li>load 3</li>
</ol>
<div id="main_ima"></div>
The following answers your main concern, how to dynamically get the ID with jquery:
$('.listen-to-me').click(function() { //Add event listener to class
var elementId = $(this).attr('id'); //Get the 'id' attribute of the element clicked
var idNumber = elementId.substring(elementId.indexOf("-") +1); //Get the index of the "-" in the string, and then cut everything prior
alert(idNumber); //The final result
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<ul>
<li id="test-1" class="listen-to-me">1</li>
<li id="test-2" class="listen-to-me">2</li>
<li id="test-3" class="listen-to-me">3</li>
<li id="test-4" class="listen-to-me">4</li>
<li id="test-5" class="listen-to-me">5</li>
</ul>
I have a web page that is structured like this:
<canvas id="myCanvas"></canvas>
#for(var i=0; i<10; i++) {
<div class="my-element">
<canvas></canvas>
</div>
}
This code generates one main canvas. Below it, 10 other dynamically generated divs are being generated. In reality, this loop is just used to show that I have some code being dynamically generated. The main thing to understand is the my-element piece.
In my javascript, I have the following:
$(function() {
var mainCanvas = document.getElementById('myCanvas');
initializeCanvas(mainCanvas); // This works.
var dynamicElements = $('.my-element');
for (var i=0; i<dynamicElements.length; i++) {
initializeCanvas($(dynamicElements[i])[0]); // This does not work
}
});
function initializeCanvas(canvas) {
// do stuff
}
The first call to initializeCanvas works because I'm passing in an actual HTML Dom element. But the second initializeCanvas, which is called multiple times, fails because it's passing in something else. How do I get the same type of element as what's returned by document.getElementById in this case?
Thanks!
First of all, this doesn't make sense:
$(dynamicElements[i])[0]
You are getting jQuery element, unwrapping it, then wrapping again in jQuery...
what you simply need to do is get canvas from the element
dynamicElements.eq(i).find('canvas')[0] should do the job
You can't use the same element for this purpose. I suggest you to clone it. Like;
var dynamicElements = $('.my-element').clone();
Also when you add more element with my-element class this will be messy. You should make sure to select only one element with $('.my-element') selection.
I've built a page with 3 elements, each of which looks like this:
<div class="col-md-4 event-type">
<a href="{{ pathFor 'step2' }}" id="eventchoice" name="eventchoice" value="corporate">
</a>
</div>
I'm trying to pass the value or name or id of the the <a> element on to a collection using the following code:
EventsController.events({
'click #eventchoice' : function(event) {
console.log(event.target.getAttribute("id"));
console.log(event.target.getAttribute("name"));
console.log(event.target.getAttribute("value"));
var eventchoice = event.target.value;
var params = {
eventchoice: eventchoice
}
//Insert Event
Meteor.call('addEvent', params);
FlashMessages.sendSuccess('Event Added');
}
});
I added the console.log's to see if I can get the id/name/value of the <a> element, but the console outputs 'null' for all of these. Therefore, there is nothing to pass to the collection in the eventAdd method.
I don't believe the problem is with the EventsController, the addEvent method or the Events collection. Any ideas how I can pass these values through?
Thank you for your help!
I think there must be something wrong with your controller then, because if you check the Meteorpad here, it works just fine.
Although you might want to use a class instead of an id if you have many similar elements.
There are several ways of solving your problem but the way I consider as "The Meteor Way" is to use a separate template for every choice (or just use #each loop), if you do that your "this" inside the event code will contain the values you need in your scope, so you won't have to rely on the event.target for them.
Okay so, I want to make an OnClick function in JavaScript that makes it so when a user clicks on it, it will change the word. Is there a replaceword() function or something that which will let me do so? I know this is not real code, but for example:
<p>Quickly <span onclick="replaceword('Surf');">Search</span> The Web!</p>
If there is, then can someone tell me also how to reverse the code maybe? So when they click on it the second time, it will change back to "Search"?
If you want to jump between multiple words, you'll need to store them someplace. You could have two words in the sentence, and toggle the visibility of one or the other (which doesn't scale well), or you could even store them as values on an attribute placed on the element itself.
<p>Hello, <span data-values="World,People,Stack Overflow">World</span>.</p>
I have placed all possible values within the data-values attribute. Each distinct value is separated from the other values by a comma. We'll use this for creating an array of values next:
// Leverage event-delegation via bubbling
document.addEventListener( "click", function toggleWords ( event ) {
// A few variables to help us track important values/references
var target = event.target, values = [], placed;
// If the clicked element has multiple values
if ( target.hasAttribute( "data-values" ) ) {
// Split those values out into an array
values = target.getAttribute( "data-values" ).split( "," );
// Find the location of its current value in the array
// IE9+ (Older versions supported by polyfill: http://goo.gl/uZslmo)
placed = values.indexOf( target.textContent );
// Set its text to be the next value in the array
target.textContent = values[ ++placed % values.length ];
}
});
The results:
The above listens for clicks on the document. There are numerous reasons why this is a good option:
You don't need to wait for the document to finish loading to run this code
This code will work for any elements added asynchronously later in the page life
Rather than setting up one handler for each element, we have one handler for all.
There are some caveats; you may run into a case where the click is prevented from propagating up past a particular parent element. In that case, you would want to add the eventListener closer to your target region, so the likeliness that bubbling will be prevented is less.
There are other benefits to this code as well:
Logic is separated from markup
Scale to any number of values without adjusting your JavaScript
A demo is available for your review online: http://jsfiddle.net/7N5K5/2/
No, there isn't any native function, but you can create on your own.
function replaceword(that, word, oword) {
that.textContent = that.textContent == oword ? word : oword;
}
You can call it like this:
<p>Quickly<span onclick="replaceword(this,'Surf','Search');">Search</span>The Web!</p>
Live Demo: http://jsfiddle.net/t6bvA/6
<p id="abc">Hello</p>
<input type="submit" name="Change" onclick="change()">
function change(){
var ab=document.getElementById('abc').value;
ab.innerHTML="Hi, Bye";
}
I think so this should help you, you should go to site such as w3schools.com, its basic and it will answer your doubt
You can try something like this if you wanna use jQuery
http://jsfiddle.net/R3Ume/2/
<script src="//code.jquery.com/jquery-1.10.2.js"></script>
<body>
<p>Hello <a id='name'>John<a></p>
<input id="clickMe" type="button" value="replace" onclick="onClick();" />
<script>
function onClick() {
$('#name').text('world');
}
</script>