How to copy an event listener in plain Javascript - javascript

So I am making some overrides on a Wordpress plugin. I need to copy the event listener on an element and then replace the element and add it back. The event listener is generated by the plugin.
I thought getEventListeners() would work but I have read that it only works in console. If that is this case I'm really astounded. We're in freaking 2020 and I am not finding an obvious solution to this.
What is the solution here people?
Below is the code I was trying to implement having assumed getEventListeners wasn't just a console function.
// Edit Affirm
(function replaceAffirm() {
if (document.querySelector(".affirm-modal-trigger")) {
const learnMore = document.querySelector("#learn-more");
const modalTrigger = document.querySelector(".affirm-modal-trigger");
const clickHandler = getEventListeners(modalTrigger).click[0].listener;
const substr = learnMore.innerHTML
.toString()
.substring(
learnMore.innerHTML.indexOf("h") + 2,
learnMore.innerHTML.length
);
learnMore.innerHTML = "Easy Financing with " + substr;
modalTrigger.addEventListener("click", clickHandler);
} else {
setTimeout(function () {
replaceAffirm();
}, 250);
}
})();
HTML
<p id="learn-more" class="affirm-as-low-as" data-amount="20000" data-affirm-color="white" data-learnmore-show="true" data-page-type="product">
Starting at
<span class="affirm-ala-price">$68</span>
/mo with
<span class="__affirm-logo __affirm-logo-white __ligature__affirm_full_logo__ __processed">Affirm</span>.
<a class="affirm-modal-trigger" aria-label="Prequalify Now (opens in modal)" href="javascript:void(0)">Prequalify now</a>
</p>

You can't copy event listeners, but it seems because of the structure of your HTML it's more likely that you shouldn't need to re-add it. Instead of editing the HTML and removing the event listener by doing so, the best bet would be to edit around it.
If you want to remove the text nodes you can iterate through childNodes and separate out what should be removed.
Then to rebuild the appropriate text where you want it you can use insertAdjacentText
if (document.querySelector(".affirm-modal-trigger")) {
const learnMore = document.querySelector("#learn-more");
const modalTrigger = document.querySelector(".affirm-modal-trigger");
const children = Array.from(learnMore.childNodes);
children.forEach(child => {
if (child.nodeType === Node.TEXT_NODE || child.matches(".affirm-ala-price")) {
if (learnMore.contains(child)) {
learnMore.removeChild(child);
}
}
});
learnMore.insertAdjacentText("afterBegin", "Easy Financing With ");
modalTrigger.insertAdjacentText("beforeBegin", " ");
} else {
setTimeout(function() {
replaceAffirm();
}, 250);
}
<p id="learn-more" class="affirm-as-low-as" data-amount="20000" data-affirm-color="white" data-learnmore-show="true" data-page-type="product">
Starting at
<span class="affirm-ala-price">$68</span> /mo with
<span class="__affirm-logo __affirm-logo-white __ligature__affirm_full_logo__ __processed">Affirm</span>.
<a class="affirm-modal-trigger" aria-label="Prequalify Now (opens in modal)" href="javascript:void(0)">Prequalify now</a>
</p>

Yes waiting for the Html element to be loaded and checking until it gets loaded is okay and this is one of the correct ways to wait for it.
As per my understanding of your issue, you just have to change the text of the learn-more element.
for that, it is not necessary to copy event listener and then again binding it.
Instead of replacing the whole element just change the text keeping the same element.
So it gets binded with the event listener by default.

Related

JavaScript Click-Event trigger fires several times per click

I have looked at similiar posts but I cant seem to find a Solution.
So the issue I am facing is that I dynamically add divs with content. If you click on that generated content, sth happens. Problem is that the clicklick Event fires several times. Interesting is, that it actually starts with only 1 trigger, goes up to 2,4,6,10,20,40 etc. triggers per click.
function AddArticle() {
let single_article = document.createElement("div");
single_article.setAttribute("class", "each-article");
single_article = `<div> ANY ARTICEL </div>`;
let prices_summary = document.getElementById('prices-summary');
prices_summary.appendChild(single_article);
//Refresh the Event since we added on DIV to the NodeList
RefreshClickEvent();
}
function RefreshClickEvent() {
let each_article = document.querySelectorAll(".each-article");
for (let article of each_article ) {
article.addEventListener('click', () => {
console.log("Trigger.");
});
}
}
Console Log OutPut:
Trigger.
[2]Trigger.
[4]Trigger.
.
.
.
[90]Trigger.
Appreciate any help.
When you add an element, the loop in RefreshClickEvent works for all elements (including the elements that were added). So, you should define a parameter to add event to an element. Another mistake innerHTML to assign content.
function AddArticle() {
let single_article = document.createElement("div");
single_article.setAttribute("class", "each-article");
single_article.innerHTML = `<div> ANY ARTICEL </div>`;
let prices_summary = document.getElementById('prices-summary');
prices_summary.appendChild(single_article);
//Refresh the Event since we added on DIV to the NodeList
RefreshClickEvent(single_article);
}
function RefreshClickEvent(element) {
element.addEventListener('click', () => {
console.log("Trigger.");
});
}

Change location.href with jQuery

I need to change the location.href of some URLs on my site. These are product cards and they do not contain "a" (which would make this a lot easier).
Here is the HTML:
<div class="product-card " onclick="location.href='https://www.google.com'">
I mean it is pretty simple, but I just cannot get it to work. Did not find any results from Google without this type of results, all of which contain the "a":
$("a[href='http://www.google.com/']").attr('href', 'http://www.live.com/')
Any ideas on how to get this to work with jQuery (or simple JS)?
I cannot change the code itself unfortunaltely, I can just manipulate it with jQuery and JS.
To change the onClick for all the class='product-card', you can do something like this:
// All the links
const links = document.getElementsByClassName('product-card');
// Loop over them
Array.prototype.forEach.call(links, function(el) {
// Set new onClick
el.setAttribute("onClick", "location.href = 'http://www.live.com/'" );
});
<div class="product-card " onclick="location.href='https://www.google.com'">Test</div>
Will produce the following DOM:
<div class="product-card " onclick="location.href = 'http://www.live.com/'">Test</div>
Another option, is to loop over each <div> and check if something like google.com is present in the onClick, if so, we can safely change it without altering any other divs with the same class like so:
// All the divs (or any other element)
const allDivs = document.getElementsByTagName('div');
// For each
Array.from(allDivs).forEach(function(div) {
// If the 'onClick' contains 'google.com', lets change
const oc = div.getAttributeNode('onclick');
if (oc && oc.nodeValue.includes('google.com')) {
// Change onClick
div.setAttribute("onClick", "location.href = 'http://www.live.com/'" );
}
});
<div class="product-card" onclick="location.href='https://www.google.com'">Change me</div>
<div class="product-card">Don't touch me!</div>

Click and toggle between two classes on an array (no JQuery)

I need to make a site where I can add something to a shoppingcart. I do not need to store any data, so just changing a class to 'addedInCart' is enough. This is what I have so far, but it's not working yet. I know all the classnames I got are coming back in an array. I just dont know how to change them if the button is clicked. I tried a lot with the addEventListener and the toggle, but I just started coding, not everything is clear for me yet. I am not alloud to use Jquery, only HTML and Javascript.
This is what I have in Javascript:
var buyMe = document.getElementsByClassName("materials-icon");
function shoppingcart() {
for(let i = 0; i < buyMe.length; i++){
buyMe[i].classList.toggle("addedInCart");
buyMe[i].addEventListener("click", toggleClass, false)
}
}
This is what my button looks like:
<button class="material-icons" onclick="shoppingcart()"></button>
Thank you for your time!
Use event delegation to be able to use one handler. Use a data-attribute to verify the button as being a button for adding to cart. Something like:
document.addEventListener("click", handle);
function handle(evt) {
const origin = evt.target;
if (origin.dataset.cartToggle) {
// ^ if element has attribute data-cart-toggle...
origin.classList.toggle("addedInCart");
// ^ ... toggle the class
}
};
.addedInCart {
color: red;
}
.addedInCart:after {
content: " (in cart, click to remove)";
}
<button class="material-icons" data-cart-toggle="1">buy</button>
<button class="material-icons" data-cart-toggle="1">buy</button>
<button class="material-icons" data-cart-toggle="1">buy</button>

Get element on which keydown happened in contenteditable

I have a div that is contenteditable and that div has some contents:
<div contenteditable="true" id="editable-div">
<p id="paragraph">
Lorem <em>ipsum</em> dolor sit <strong id="stong-el">amet</strong>
<p>
</div>
I want to intercept keydown events and find the element into which the users have tried to write.
I have already failed to find the specific element when listening on the content editable div:
const div = document.getElementById('editable-div');
div.addEventListener('keydown', event => { /* ??? */ }, false);
Problem: The event in the callback does not tell me which element has been written into, it only tells my I have written in #editable-div
My other attempt was to listen on the nested event, like so:
// either
const el = document.getElementById('paragraph');
// or
const el = document.getElementById('stong-el');
el.addEventListener('keydown', event => { /* ??? */ }, false);
The problem with this is that the event never fires. Apparently the event only fires on the element that has the contenteditable attribute, which kind of sucks.
The last resort I have would be to listen on #editable-div and check which element has the text selection, but I'd rather not if there is any other way.
UPDATE
Another way I found was to use a MutationObserver on the editable element, something like this:
const observer = new MutationObserver(mutations => {
mutations.forEach(function(mutation) {
console.log(mutation.target);
});
});
const config = { characterData: true, subtree: true };
const div = document.getElementById('editable-div');
observer.observe(div, config);
This logs the text element I edited.
I am not sure if my questions is answered with this, because in the end I want to do 2 things:
Catch and prevent the event to update an internal data structure
Render React components from the data structure
and this does not seem to be an appropriate solution for this.
You could attach the keydown event to the body of your document. Then on keydown event, use document.activeElement to return the element which currently has focus and will receive the keyboard input.
Here is a link that explains document.activeElement (https://developer.mozilla.org/en-US/docs/Web/API/Document/activeElement)
You can put listener on contenteditable element, and then check which element got updated, by comparing its new text with old one. And depending on that you can perform your action in respective branch.
check this codeblock -
<div id="ce" contenteditable="true">
<strong id="s">Abc</strong><br/>
<i id="i">Hello</i>
</div>
<script>
oldstext = document.getElementById('s').textContent;
olditext = document.getElementById('i').textContent;
document.getElementById('ce').addEventListener('keydown', event => {
//alert("ce Edited");
if(oldstext != document.getElementById('s').textContent){
console.log(oldstext + " v/s " + document.getElementById('s').textContent);
oldstext = document.getElementById('s').textContent;
}
else if (olditext != document.getElementById('i').textContent){
console.log(olditext + " v/s " + document.getElementById('i').textContent);
olditext = document.getElementById('i').textContent;
}
}, false);
</script>

jquery append element and keep reference for removing it later

what is the correct method to append a div to dom, and keep a handle for manipulating that dom later ? and case removing it will the refrence be deleted also ? if not how can i ?
this is an example code of what i came out with, please let me know your opinon and if there is a better more solid method for doing this.
note that i keep reference in an array because many elements can manipulate element.
var layovers=[];
function ajaxMe($e){
//do some ajax call
var lay=layoverThis($e);
layovers[lay].remove(); //does this remove added element from dom ? does it unset layouvers[lay] too ?
}
function layoverThis($e){
var p=layover.push($('<div class="overlay" ><span><i class="fa fa-spinner fa-spin"/>loading</span></div>')) - 1;
$e.append(layover[p]);
return p;
}
nearest example i can think of is like opening a folder in windows Os.
when you open a folder: a new window opens to your desktop, while also there is a taskbar tab that is added linked to same window that was appended to your desktop, allowing system to manipulate window (close-restore minimize etc..), so how do i do this in javasript.
my question is how to make such behavior in a manner that is flexible and not hacky or memory wasteful.
You can use a psudoClass and an index - add the event to the delete element as you create the element in the DOM. OR have an onclick that removes the parent div that was appended..
So, you're code will look like:
function layoverThis($e){
var p=layover.push($('<div class="overlay" ><span><i class="fa fa-spinner fa- spin"/>loading</span> <span class="closeThis" onclick="$(this).parent().remove();" > Close </span></div>')) - 1;
$e.append(layover[p]);
return p;
}
Let me know if this works for you, i have a number of other solutions.
Update:
To facilitate the object to be selectable, I would use a psudoClass..
document.ready(function() {
addADiv($('#myParent'));
getArrayOfDivs().css('background-color', 'red');
doAjax(lastClicked);
});
function doAjax(ctrl)
{
}
var lastClicked = "";
$('.dynamicDivs').click(function() {
lastClicked = $(this);
});
function getArrayOfDivs() {
return $('.dynamicDivs');
}
function addADiv($where){
var uniqueIdentifier = $('.dynamicDivs').count() + 1;
$where.append($('<div class="overlay dynamicDivs ' + uniqueIdentifier + ' " ><span><i class="fa fa-spinner fa- spin"/>loading</span></div>'));
}

Categories

Resources