Listen to DOM element change - javascript

When I do a search for images it loads images from Pixabay, which are added to a container via fetch without a page reload, I need to click on the newly generated images to get the ID of the image, which then converts the image ID to an URL, and gets added into the input.
My workaround is to find dom elements with the class ".dePixaImage" using the click, and mouseover event and then remove those events with removeEventListener which works fine to a certain point, but then when I apply a new search nothing happens because the new dom elements are being loaded and the event listener has been stopped.
If I do not remove the removeEventListener, then the for each loop keeps on multiplying, which results in a lot of fetch requests when the mouse is moved, or if a click occurs, which is completely unnecessary and uses up all the API requests.
window.addEventListener("click", deViewImage);
window.addEventListener("mouseover", deViewImage);
function deViewImage() {
let deViewImageAttr = "";
let dePixaImage = document.querySelectorAll(".dePixaImage");
if (dePixaImage) {
dePixaImage.forEach((e) => {
e.addEventListener("click", function () {
// id of the image from Pixabay, added via fetch
deViewImageAttr = e.getAttribute("id");
// call fetch function and pass ID
pixabayImageUrlFromId(deViewImageAttr);
});
window.removeEventListener("click", deViewImage);
window.removeEventListener("mouseover", deViewImage);
});
}
}
Maybe there is a better solution to listen for the dom elements when they get changed frequently?

I see from your comment what you are really looking for is attaching a click handler to dynamically added elements.
That is rather easy with event delegation and not having a click handler inside another click handler. This will work with objects in the DOM and new objects added to the DOM later. You only need to add the code once, and it won't be duplicated.
function pixabayImageUrlFromId(id) {
console.log(id)
}
document.body.addEventListener("click", deViewImage);
document.body.addEventListener("mouseover", deViewImage);
function deViewImage(e) {
let obj = e.target;
if (obj.classList.contains("dePixaImage")) {
pixabayImageUrlFromId(obj.dataset.id);
}
}
<div class="dePixaImage" data-id="1111111">ssss</div>

Thanks, #imvain, with little modification I no longer needed mouseover, here is the solution now with the above help.
function pixabayImageUrlFromId(id) {
let imageUrlInput = document.querySelector(
'[data-test-id="control-attributes-de_image_img_url"] input'
);
let URL = "https://pixabay.com/api/?key=" + API_KEY + "&id=" + id;
fetch(URL)
.then(function (resp) {
return resp.json();
})
.then(function (data) {
let imgUrl = data.hits[0].largeImageURL;
imageUrlInput.value = imgUrl;
imageUrlInput.dispatchEvent(new Event("input"));
});
}
document.addEventListener("click", deViewImage);
function deViewImage(e) {
let obj = e.target;
if (obj.classList.contains("dePixaImage")) {
pixabayImageUrlFromId(obj.getAttribute("id"));
}
}

Related

Undo .removeAtribute function

I'm looking for a solution to restore a removed attribute. I'm not an experienced programmer, so I'm not sure where to start when sharing my code, so I'll try to give some context below.
I have an image of a map that has several hidden overlays. These overlays are activated by a series of adjacent buttons.
Each of these buttons has a mouseover and mouseout event, which temporarily reveals the overlay. They also have an onclick event that permanently displays the overlay. I've used a .removeAtribute function to remove the mouseout event so that my overlay is permanent.
All other layers are still visible with the mouseover and mouseout events (so that you can make comparisons).
When I onclick another overlay button, it clears the previous one, however, now the mouseout event for the previously selected button is still inactive, so hovering over it causes the overlay to appear permanently.
How can I restore the mouseout event after I've removed it?
I have tried to use .setAttribute("onmouseout"), but I've had no luck in making that work.
Hopefully, this all makes some sense; I'll post some of my code below, which might help give further context.
function btn01On() {
document.getElementById("btn01").removeAttribute("onmouseout");
}
function btnClear() {
document.getElementById("btn01").setAttribute("onmouseout");
}
<button id="btn01" class="map-button map-button1"
onclick="MM_showHideLayers('InfoCurrentExc','','show','OverlayCurrentExc','','show');btn01On();" onmouseover="MM_showHideLayers('OverlayCurrentExc','','show')" onmouseout="MM_showHideLayers('OverlayCurrentExc','','show')">
Current Excavation
</button>
Usually setting up event handlers using onevent attribute, (although oldest method for event handling) is not the recommended way, See https://developer.mozilla.org/en-US/docs/Web/Events/Event_handlers#dom_event_handler_list.
I recommend using EventTarget.addEventListener and EventTarget.removeEventListener for your case. Try the code below
const mouseoutHandler = function() {
MM_showHideLayers('OverlayCurrentExc', '', 'show');
};
const mouseOverHandler = function() {
MM_showHideLayers('OverlayCurrentExc', '', 'show');
};
const button01 = document.getElementById("btn01");
button01.addEventListener("mouseover", mouseOverHandler);
function btn01On() {
if (!mouseoutListener)
button01.addEventListener("mouseout", mouseoutHandler);
}
function btnClear() {
if (mouseoutListener) {
button01.removeEventListener("mouseout", mouseoutListener);
mouseoutListener = null;
}
}
const clickHandler = function() {
MM_showHideLayers('InfoCurrentExc', '', 'show', 'OverlayCurrentExc', '', 'show');
btn01On();
}
button01.addEventListener("click", clickHandler);
I was lucky enough to find someone who had a fix for this problem. I'll share the code below for anyone who might land here with a similar request.
I don't fully understand how this code works so if someone has a good explanation feel free to share it.
// Remove mouse outs
function btn01On() {
document.getElementById("btn01").removeAttribute("onmouseout");
}
// keep mouse outs
const buttonIds = ["btn01"];
const mouseOuts = {};
buttonIds.forEach((id) => {
const el = document.getElementById(id);
if (el) {
mouseOuts[id] = el.getAttribute('onmouseout');
}
});
const restoreMouseOutEvent = () => {
buttonIds.forEach((id) => {
const el = document.getElementById(id);
if (el && mouseOuts[id]) {
el.setAttribute('onmouseout', mouseOuts[id]);
}
});
}

Is there a difference between "click" function vs e.target in javascript? Performance speed?

Is there any major difference in using
document.getElementById("button").addEventListener("click", function() {
//kk
})
document.getElementById("button2").addEventListener("click", function() {
//kk
})
VS
document.addEventListener("click",function(e){
if(e.target.closest(#button){
//kk
}else if(e.target.closest(#button2){
//kk
}
})
Is there a performance benefit for looping though if statements or just attaching individual listener for each element that is clickable?
Dynamic elements
There's often times when a specific element is not yet present in the page - but we want to do something if a specific event happens in the future.
In such circumstances a common way to tackle the problem is to use an ancestor delegator (like document or a closest known Element).
// We don't have buttons yet, but might appear in the future
document.addEventListener("click", function(evt) {
const EL_btn = evt.target.closest("button");
if (!EL_btn) return; // No button was clicked. Play dead!
if (EL_btn.id === "foo") {
console.log("Button #foo was clicked!")
}
});
If those elements could be anywhere in such case we use document and call it a day. But if we know exactly the parent container that will hold those child elements always use that element as delegator. I.e: EL_asideMenu.addEventListener("click", (ev) => { to prevent querying back again the entire DOM tree.
Dynamic elements pt2:
When creating in-memory elements, assign at creation a click handler. Append your elements (when time comes) where needed - and that's it. No need to do DOM events querying or other stuff.
const NewEL = (tag, attr) => Object.assign(document.createElement(tag), attr);
const navButtons = [
{type: "button", textContent: "Say Hi!", onclick() { console.log("Hello World!"); }},
{type: "button", textContent: "Say Foo", onclick() { console.log("Foo!Bar!Baz!"); }},
].map(attr => NewEL("button", attr));
document.querySelector("#navee").append(...navButtons);
<nav id="navee"></nav>
Pro tip: in the above example, that's the only, proper and sole time you want to use the on* attribute handlers on an Element. Since the element is just being created. Every other time you should use Element.addEventListener() to attach additional handlers. But never again the on* to not override any prior handler.
Static elements
That's when direct Events assignment is preferrable
const myButtonHandler = (ev) => {
const EL_btn = ev.currentTarget; // Use currentTarget in that case!
if (EL_btn.id === "foo") {
console.log("Button #foo was clicked!")
}
};
// Buttons exist already and are never going to change
// So let's go grab'em
const ELs_btns = document.querySelectorAll("button");
// Assign a "click" Event handler
ELs_btns.forEach(EL => EL.addEventListener("click", myButtonHandler));
Now, regarding both the above examples and their if and possible lots of else statements, you could create a "map" with functions for every button - by storing the desired function name inside a data-* attribute:
const clickFn = (ev) => ({
sayHi() { console.log("Hello, World!") },
myOtherFn() { console.log("Something else!") },
}[ev.currentTarget.dataset.click](ev));
const ELs_btns = document.querySelectorAll("[data-click]");
ELs_btns.forEach(EL => EL.addEventListener("click", clickFn));
<button data-click="sayHi" type="button">Say hello!</button>
<button data-click="myOtherFn" type="button">Do something else</button>
or many other ways... like a switch .. case, if .. else etc...

Is it possible to have acces to innerHTML 'id' from otger part of the code?

I have following code, where, based on event, I add some html code. I would like to refer to 'id' from this dynamically injected html in other event (or just from other part of the code):
<div id="choice"></div>
var decisionList = document.getElementById("decisionList");
decisionList.addEventListener("change", function () {
var finalChoice = document.getElementById("choice");
finalChoice.innerHTML='<input id="finalDate" type="date">'
}
and other event referring to 'id' from innerHTML:
var payment = document.getElementById("finalDate");
payment.addEventListener("change", function () {
var textDate = payment.textContent;
alert(textDate);
})
The above is not working. Is it possible or not?
It is possible, but make that payment getter lazy. What that means is, instead of setting up that second change listener right away (in your other code), make that other code a function. Then in your first trigger, where you created the extra div or input or something, call that setup function.
decisionList.addEventListener("change", function () {
const finalChoice = document.getElementById("choice");
finalChoice.innerHTML='<input id="finalDate" type="date">'
createFinalDateListener();
}
function createFinalDateListener() {
const payment = document.getElementById("finalDate");
payment.addEventListener("change", function () {
const textDate = payment.textContent;
alert(textDate);
});
}
Here's a similar example. I do not have the input immediately. Or listener. And I only create a listener after I create the input.
// Here's the main trigger
function addExtraElements() {
// let's create a datepicker dynamically.
document.querySelector('#placeholder').innerHTML = '<input type="date" placeholder="pick date">';
listenDateChanges();
// TODO: don't forget to add cleanup code! Each time you fill that innerHTML, the old listener will remain
}
// Here's your datepicker listener
function listenDateChanges() {
const datePickerEl = document.querySelector('input[type="date"]');
if (!datePickerEl) {
console.log('no picker');
return;
}
datePickerEl.addEventListener('change', () => alert(datePickerEl.value));
}
<div id="placeholder">
Placeholder
</div>
<button onclick="addExtraElements()">Add extra elements</button>

Jquery Select Dynamically added element

Several similar question exist, but after fighting with this for a day or so I feel the need to ask because the vast majority of the answers refer to adding event handlers to elements.
I am not interested in adding an event handler to the elements in question, rather I am interested in adding additional dynamic content to dynamically generated content.
The app works thusly:
load a modal form dynamically upon the click of a static element (working properly)
function loadModal(target,modalId) {
console.log("==================> loadModal() Entry");
$.ajax({
type: "GET",
url: 'http://localhost/retrieve-modal/'+modalId,
success : function (text) {
$("#"+modalId)[0].innerHTML = text;
modalSaveIntercept($("#"+modalId)[0])
},
failure : function (e) {
console.log("something is wrong");
}
})
}
Then I have a save interceptor that overrides the default save behavior of my form here this is also working properly, (I suspect because I am loading this event handler at the time of loading the modal)
function modalSaveIntercept(eventTarget) {
if(eventTarget.hasChildNodes()) {
eventTarget.childNodes.forEach(function(e) {
if(e.tagName == "FORM") {
console.log("found the form: " + e.id + " applying save override listener");
$("#"+e.id).submit(function(event){
event.preventDefault();
submitForm(e);
});
modalSaveIntercept(e)
}
});
}
}
the above attaches a listener to the form loaded into my modal and rather than firing the default behavior of a Save button click, it fires my submitForm() function which is here:
function submitForm(form) {
let payload = constructPayloadFromFormData(form);
validate(payload).then(function(v) {
console.log("response Data:");
for(let p in v) {
if(v.hasOwnProperty(p)) {
constructInvalidFeedbackForProperty(p,v[p])
}
}
});
}
this function constructs a payload from the form data (working fine) then executes another ajax call inside of validate() - I wait for the return call from ajax and then iterate through an array of validation data to confirm the form's validity. However, here is where the problem is:
function constructInvalidFeedbackForProperty(prop,e) {
let el = $("#" + "ic-role-" + prop);
console.log(el);
el.append("<div class=\"invalid-feedback\">problem</div>");
}
the problem is the append - I cannot seem to fire that method. I can select the element as the console.log(el) writes to the log the correctly identified element in my dom.
What am I doing wrong?
I have created a contrived jsfiddle for a sample of the problem. I actually believe it may be that an input field is not something you can append to... perhaps? https://jsfiddle.net/jtango/xpvt214o/987051/
Okay, I messed around with your fiddle a bit. If you inspect the input element that is created you can see that your append does work. It's just not displaying. If you are trying to edit what is in the input box then you must use val()
Here is a copy of your fiddle that will display inside the input:
$("#top").on("click", function(){
$("#form").append("<label>some label: </label><input type=\"text\" id=\"myinput\">");
});
$("#btm").on("click",function(){
$("#myinput").val("<div>I will not appear</div>");
});
As your shared https://jsfiddle.net/jtango/xpvt214o/987051/ It will not appear, this is wrong way to append any HTML element inside "input box" or any of form elements. it should allow to set only new attribute or value.
check screenshot: https://i.stack.imgur.com/4FBgn.png
So verify your below code if it's similar then it will not work:
let el = $("#" + "ic-role-" + prop);
console.log(el);
el.append("<div class=\"invalid-feedback\">problem</div>");

attach event handler to dynamically added element

I have a javascript code, which adds an element dynamically and event handler to it.this event handler is called (and vanishes) when the element is added.when I see the dynamically added element through inspector(to see if the event handler was added successfully or not) I can't find onchnage="texttol(eletext)" function added it it.
eletext = document.createElement("input");
eletext.type="text";
eletext.placeholder = "Type Here";
eletext.onchange=texttol(eletext);
event.target.appendChild(eletext);
Add your event handling once the element is actually in the DOM.
var eletext = document.createElement("input");
eletext.type = "text";
eletext.placeholder = "Type Here";
eletext.id = "eletext";
document.body.appendChild(eletext);
eletext.addEventListener('change', function(e) {
texttol(eletext.value);
}, false);
function texttol(str) {
alert('texttol function passes: ' + str);
}
With this line, you are calling the method texttol and attaching whatever it returns to the event handler.
eletext.onchange=texttol(eletext);
You need to use a closure
eletext.onchange = function() { texttol(eletext) };
even better, use addEventListener
i have the same issue but the mentioned solutions do not work.
The event handler function theme_onChange is executed on adding each element.
I have a Bootstrap dropdwn menu (#themeDropdownMenu) and want to fill it dynamically with button elements via looping over the "Themes" object properties.
function setThemes () {
var lstThemes = document.querySelector('#themeDropdownMenu');
var newItem;
for ( x in Themes ) {
newItem = document.createElement('button');
newItem.innerHTML = x;
newItem.classList.add('dropdown-item');
newItem.type='button';
lstThemes.appendChild(newItem);
newItem.addEventListener('click', theme_onChange(newItem) );
}
}
function theme_onChange (item) {
/* console.log("Theme: " + item.innerHTML); */
console.log (item);
}
Result:
console logs each added button element.

Categories

Resources