Converting javascript element to html while keeping the associated events - javascript

I have created a checkbox directly in javascript, and binded a click event the following way :
let checkBox = document.createElement('input');
checkBox.onclick = (e) => {
console.log("click", e);
};
Now I would like to convert this element to plain html, while keeping the associated event. I now I can call checkBox.outerHTML to get the associated html, but the event would disappear.
Is there a way to do the same thing without removing the attached event ?

I don't know why would you need such an approach when you simply can append that element where ever you want. Yet, it is still simple to just fix it the way it is.
Instead of assigning an event, you should assign an attribute like this:
const checkBox = document.createElement('input');
checkBox.setAttribute("onclick", "cbxClicked(event)");
function cbxClicked(e) {
console.log("click", e);
};
console.log(checkBox.outerHTML); // <input onclick="cbxClicked(event)">
Tested well on chrome.

The recommended way is this
window.addEventListner("load",function() {
document.getElementById("checkboxContainer")
.addEventListener("click",function(e) {
const tgt = e.target;
if (tgt.type && tgt.type==="checkbox") {
console.log("click",tgt)
}
});
});
Now you can create your checkboxes before or after load
window.addEventListener("load", function() {
const container = document.getElementById("checkboxContainer");
container.addEventListener("click", function(e) {
const tgt = e.target;
if (tgt.type && tgt.type === "checkbox") {
console.log("click", tgt)
}
});
const inp = document.createElement("input")
inp.type = "checkbox";
inp.value = "dynamic";
container.appendChild(inp);
});
<div id="checkboxContainer">
<input type="checkbox" value="static" />
</div>

Related

How to detect a file input has been cleared programaticaly

I have file input that is added by a third party plugin, the uploaded file has a custom preview, which i can click on a remove button within the preview element to clear the input, and this preview is injected to the Dom uppon file addition. when i try to detect the action of clearing the input , neither change nor ìnput events are triggered.
Please note that i could normally listen to the change if file has been added only.
I have tried this:
const fileInput = document.getElementById('thumb');
fileInput.addEventListener('input', (e) => {
if( fileInput.files.length > 0 )
{
const file = fileInput.files[0]; // Get the first selected file
const blobURL = URL.createObjectURL(file);
}else{
console.log('removed');
}
});
And sure i tried to replace input with change but no result
Since you are having problems addressing the remove button and listen for its click event, and since a regular MutationObserver would be listening to the change of an attribute and not strictly an object's property value being changed,
you may try redefining the setter for the value property so that every time it will be changed programmatically, it will also perform your code.
You will find further details here (among the posted answers):
Detect input value change with MutationObserver
Here in this demo there's both the change event listener and the new property setter defined. When you'll try to clear the input using the corresponding button, the attempt to change its value property will be intercepted printing the value was changed programmatically! on console:
//adds a canonic change event handler to the #thumb element
document.getElementById('thumb')
.addEventListener('change',()=>{
console.log('file input has changed!');
});
//clear the input file programmatically (when the corresponding button is pressed)
function clearFile(){
document.getElementById('thumb').value = "";
}
//on document ready
document.addEventListener('DOMContentLoaded',()=>{
//overrides the setter for the value property of the #thumb element
const fileInput = document.getElementById('thumb');
const originalSetter = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype,'value').set;
Object.defineProperty(document.getElementById('thumb'), "value", {
set: function (newValue) {
//so that it will print on console..
console.log('the value was changed programmatically!');
//and perform its original designation
return originalSetter.call(this, newValue);
}
});
});
<input type="file" id="thumb">
<button onclick="clearFile();">CLEAR PROGRAMMATICALLY</button>
The solution I'm thinking of is to detect changes in the value of the input file by using a Javascript setInterval every 1 ms.
let fileInput = document.getElementById('thumb');
let check = document.getElementById('check');
let clear = document.getElementById('clear');
let currentValue;
let lastValue = '';
setInterval(()=>{
currentValue = fileInput.value;
if (lastValue !== currentValue && currentValue === '') {
console.log('interval: file removed');
} else if (lastValue !== currentValue && currentValue !== '') {
console.log('interval: file added');
}
lastValue = currentValue;
},1);
check.onclick = () => {
if(fileInput.files.length > 0) {
const file = fileInput.files[0]; // Get the first selected file
const blobURL = URL.createObjectURL(file);
console.log(blobURL);
console.log('onclick: file is exist');
} else {
console.log('onclick: file is empty');
}
}
clear.onclick = () => {
fileInput.value = '';
}
<input type="file" id="thumb">
<button id="check">Check</button>
<button id="clear">Clear</button>

My text inputs are empty when I do a drag and drop

I did drag and drop with several divisions. Everything works correctly but when I do, my inputs are empty...
Do you know why I lose my data (before, after)?
Thanks a lot for your help.
Code :
const divs = document.querySelectorAll(".box")
let dragged
for (let div of divs)
{
div.ondragstart = (e) =>
{
dragged = div
e.dataTransfer.setData("text/plain", div.innerHTML)
};
div.ondragover = (e) => e.preventDefault()
div.ondrop = (e) =>
{
dragged.innerHTML = div.innerHTML
div.innerHTML = e.dataTransfer.getData("text/plain")
};
}
let divTable = document.createElement("div");
divTable.id = "DivTableGroupe" + numTableau;
divTable.className = "box" ;
divTable.draggable = "true";
Your problem is that you are using the div variable in your drop handler.
It is different from the target <div> on which the drop is taking place.
To refer to the target <div> you can use e.currentTarget instead.
Also, there should be a preventDefault() in the drop handler.
div.ondragstart = (e) =>{
dragged = e.currentTarget;
e.dataTransfer.setData("text/plain", div.innerHTML)
};
div.ondrop = (e) => {
e.preventDefault();
dragged.innerHTML = e.currentTarget.innerHTML;
e.currentTarget.innerHTML = e.dataTransfer.getData('text/plain');
};

addEventListener to ALL INPUT elements when DOM changes

At the moment I'm doing the following when the document is complete.
var passwordInputs = document.querySelectorAll("input[type=password]");
for (index = 0; index < passwordInputs.length; ++index) {
passwordInputs[index].addEventListener("focusin", activeWordsFocusIn);
passwordInputs[index].addEventListener("focusout", activeWordsFocusOut);
}
Which works as expected. However if the page has some additional script which modifies the DOM and adds more input elements then they are not hooked.
How can add event handlers for ALL input elements, even those added to the DOM thru script/ajax?
Not a duplicate I don't consider this a duplicate as this question Detect Changes in the DOM which focuses on detecting changes in the DOM. My questions focus is on adding an eventListener to all input elements even when the DOM changes. I have since added my own answer to this now.
You can use event delegation to add an event handler to the container of the inputs. When an element inside the container triggers an event,
we check if the element matches the selector, and if so, the event handler is called.
const delegate = (selector) => (cb) => (e) => e.target.matches(selector) && cb(e);
const inputDelegate = delegate('input[type=password]');
container.addEventListener('focusin', inputDelegate((el) => console.log('focus in', el.target.name)));
container.addEventListener('focusout', inputDelegate((el) => console.log('focus out', el.target.name)));
setTimeout(() => {
const input = document.createElement('input');
input.setAttribute('type', 'password');
input.setAttribute('name', 'input2');
container.append(input);
}, 2000);
<div id="container">
<input type="password" name="input1">
</div>
This is the solution I have found that works...
function activeWordsFocusIn(e) {
if (isPassword(e)) {
console.log("focus in");
}
}
function activeWordsFocusOut(e) {
if (isPassword(e)) {
console.log("focus out");
}
}
function isPassword(e) {
var e = window.e || e;
if (e.target.tagName !== 'INPUT')
return false;
if (e.target.type !== 'password')
return false;
return true;
}
document.addEventListener('focusin', activeWordsFocusIn, false);
document.addEventListener('focusout', activeWordsFocusOut, false);

Javascript on change for checkbox

I am changing state of check boxes with following code:
document.getElementById('checkall').onclick = function(){
inputs = VARIABLE.querySelectorAll('input[type=checkbox]');
for(i=0; i<inputs.length; i++)
inputs[i].checked = true;
}
This section work fine.
and i am creating checkboxes with(these codes call on for):
mainFrameInput = document.createElement("input"); mainFrameInput.className = "item"; mainFrameInput.style.display='none'; mainFrameInput.setAttribute('type', 'checkbox'); mainFrameInput.setAttribute('id', GnId);
this section work fine too
At this time i want to have a function which run when check boxes changed because it can change on several way.
I am creating check boxes with JavaScript and want to handle onchange with JavaScript NOT JQUERY.
I tested CHECKBOX_VARIABLE.onchange = function{} but it does not call when i change with above code and just CHECKBOX_VARIABLE.onclick work when i click on each checkbox.
I found solution and posted as answer.
one way to do this is by using the native onchange attribute and give it a function
<select id="mySelect" onchange="alert('change')">
<option value="Audi">Audi</option>
<option value="BMW">BMW</option>
<option value="Mercedes">Mercedes</option>
<option value="Volvo">Volvo</option>
</select>
here's a fiddle showing this
https://jsfiddle.net/r4aj8zh2/
You can do this like that:
HTML:
<button id="checkall">check all</button><br>
a: <input type="checkbox" name="a" value="a"><br>
b: <input type="checkbox" name="b" value="b"><br>
c: <input type="checkbox" name="c" value="c">
JavaScript:
var inputs = document.querySelectorAll('input[type=checkbox]');
document.getElementById('checkall').onclick = function(){
for(var i=0; i<inputs.length; i++) {
inputs[i].checked = true;
}
somethingChanged();
}
for(var i=0; i<inputs.length; i++) {
inputs[i].addEventListener('change', somethingChanged);
}
function somethingChanged(evt) {
if (evt) {
console.log(evt.srcElement.name, 'changed');
}
else {
console.log('all changed');
}
}
Fiddle: https://jsfiddle.net/1m3rcvw9/
Explanation: When I tried it I could reproduce your problem - the change listener was not called when clicking the check-all button. So my idea is to just call the function manually after a click occurs on check-all. You can even distinguish between single checkbox clicks and check-all clicks by checking if there is a event-parameter.
EDIT: If you dynamically add <input> tags then just add the somethingChanged change listener right after creation of new elements and update the inputs variable by reselecting all checkboxes:
mainFrameInput = document.createElement("input");
mainFrameInput.addEventListener('change', somethingChanged);
// ... insert the element into DOM here
inputs = document.querySelectorAll('input[type=checkbox]');
You can addEventListener to these checkboxes
// Get all checkbox. Use more specific selector using name or class
var getAllCheckBox = document.querySelector('input[type=checkbox]');
// Adding event listener change to each checkbox
getAllCheckBox.addEventListener('change', function (event) {
if (getAllCheckBox.checked) {
// do something if checked
} else {
// do something else otherwise
}
});
Add event listener to element when element is created. Make sure the D is lower case d at .getElementById VARIABLE = document.getElementById('#div-id');
mainFrameInput = document.createElement("input");
mainFrameInput.addEventListener("change", function() {
// do stuff
})
FINALLY I RESOLVED THE ISSUE:
first of all i developed a function:
function fireEvent(element,event){
if (document.createEventObject){
var evt = document.createEventObject();
return element.fireEvent('on'+event,evt)
}
else{
var evt = document.createEvent("HTMLEvents");
evt.initEvent(event, true, true ); // event type,bubbling,cancelable
return !element.dispatchEvent(evt);
}
}
and called that when changed state of check box:
fireEvent(inputs[i],'change');
and added on change event when creating check boxes:
mainFrameInput.onchange = function(){
if (this.checked)
{
console.log('checked');
}
else
{
console.log('un checked');
}
}
I think it is more easy just define a onchange function into the input element like this:
const wrapperElement = document.querySelector('.wrapper')
const fruits = ['apple', 'orange', 'banana']
fruits.forEach(f => {
const item = document.createElement('div')
item.className = 'item'
const fruit = document.createElement('input')
fruit.type = 'checkbox'
fruit.id = f
fruit.onchange = handleOnChange
const label = document.createElement('label')
label.className = 'checkbox-label'
label.setAttribute('for', f)
label.textContent = f
item.append(fruit)
item.append(label)
wrapperElement.append(item)
})
function handleOnChange(e) {
const element = e.srcElement
element.parentElement.classList.toggle('checked')
}
.item.checked {
background: red;
}
<div class="wrapper"></div>

Detect click outside element (vanilla JavaScript)

I have searched for a good solution everywhere, yet I can't find one which does not use jQuery.
Is there a cross-browser, normal way (without weird hacks or easy to break code), to detect a click outside of an element (which may or may not have children)?
Add an event listener to document and use Node.contains() to find whether the target of the event (which is the inner-most clicked element) is inside your specified element. It works even in IE5
const specifiedElement = document.getElementById('a')
// I'm using "click" but it works with any event
document.addEventListener('click', event => {
const isClickInside = specifiedElement.contains(event.target)
if (!isClickInside) {
// The click was OUTSIDE the specifiedElement, do something
}
})
var specifiedElement = document.getElementById('a');
//I'm using "click" but it works with any event
document.addEventListener('click', function(event) {
var isClickInside = specifiedElement.contains(event.target);
if (isClickInside) {
alert('You clicked inside A')
} else {
alert('You clicked outside A')
}
});
div {
margin: auto;
padding: 1em;
max-width: 6em;
background: rgba(0, 0, 0, .2);
text-align: center;
}
Is the click inside A or outside?
<div id="a">A
<div id="b">B
<div id="c">C</div>
</div>
</div>
You need to handle the click event on document level. In the event object, you have a target property, the inner-most DOM element that was clicked. With this you check itself and walk up its parents until the document element, if one of them is your watched element.
See the example on jsFiddle
document.addEventListener("click", function (e) {
var level = 0;
for (var element = e.target; element; element = element.parentNode) {
if (element.id === 'x') {
document.getElementById("out").innerHTML = (level ? "inner " : "") + "x clicked";
return;
}
level++;
}
document.getElementById("out").innerHTML = "not x clicked";
});
As always, this isn't cross-bad-browser compatible because of addEventListener/attachEvent, but it works like this.
A child is clicked, when not event.target, but one of it's parents is the watched element (i'm simply counting level for this). You may also have a boolean var, if the element is found or not, to not return the handler from inside the for clause. My example is limiting to that the handler only finishes, when nothing matches.
Adding cross-browser compatability, I'm usually doing it like this:
var addEvent = function (element, eventName, fn, useCapture) {
if (element.addEventListener) {
element.addEventListener(eventName, fn, useCapture);
}
else if (element.attachEvent) {
element.attachEvent(eventName, function (e) {
fn.apply(element, arguments);
}, useCapture);
}
};
This is cross-browser compatible code for attaching an event listener/handler, inclusive rewriting this in IE, to be the element, as like jQuery does for its event handlers. There are plenty of arguments to have some bits of jQuery in mind ;)
How about this:
jsBin demo
document.onclick = function(event){
var hasParent = false;
for(var node = event.target; node != document.body; node = node.parentNode)
{
if(node.id == 'div1'){
hasParent = true;
break;
}
}
if(hasParent)
alert('inside');
else
alert('outside');
}
you can use composePath() to check if the click happened outside or inside of a target div that may or may not have children:
const targetDiv = document.querySelector('#targetDiv')
document.addEventListener('click', (e) => {
const isClickedInsideDiv = e.composedPath().includes(targetDiv)
if (isClickedInsideDiv) {
console.log('clicked inside of div')
} else {
console.log('clicked outside of div')
}
})
I did a lot of research on it to find a better method. JavaScript method .contains go recursively in DOM to check whether it contains target or not. I used it in one of react project but when react DOM changes on set state, .contains method does not work. SO i came up with this solution
//Basic Html snippet
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<div id="mydiv">
<h2>
click outside this div to test
</h2>
Check click outside
</div>
</body>
</html>
//Implementation in Vanilla javaScript
const node = document.getElementById('mydiv')
//minor css to make div more obvious
node.style.width = '300px'
node.style.height = '100px'
node.style.background = 'red'
let isCursorInside = false
//Attach mouseover event listener and update in variable
node.addEventListener('mouseover', function() {
isCursorInside = true
console.log('cursor inside')
})
/Attach mouseout event listener and update in variable
node.addEventListener('mouseout', function() {
isCursorInside = false
console.log('cursor outside')
})
document.addEventListener('click', function() {
//And if isCursorInside = false it means cursor is outside
if(!isCursorInside) {
alert('Outside div click detected')
}
})
WORKING DEMO jsfiddle
using the js Element.closest() method:
let popup = document.querySelector('.parent-element')
popup.addEventListener('click', (e) => {
if (!e.target.closest('.child-element')) {
// clicked outside
}
});
To hide element by click outside of it I usually apply such simple code:
var bodyTag = document.getElementsByTagName('body');
var element = document.getElementById('element');
function clickedOrNot(e) {
if (e.target !== element) {
// action in the case of click outside
bodyTag[0].removeEventListener('click', clickedOrNot, true);
}
}
bodyTag[0].addEventListener('click', clickedOrNot, true);
Another very simple and quick approach to this problem is to map the array of path into the event object returned by the listener. If the id or class name of your element matches one of those in the array, the click is inside your element.
(This solution can be useful if you don't want to get the element directly (e.g: document.getElementById('...'), for example in a reactjs/nextjs app, in ssr..).
Here is an example:
document.addEventListener('click', e => {
let clickedOutside = true;
e.path.forEach(item => {
if (!clickedOutside)
return;
if (item.className === 'your-element-class')
clickedOutside = false;
});
if (clickedOutside)
// Make an action if it's clicked outside..
});
I hope this answer will help you !
(Let me know if my solution is not a good solution or if you see something to improve.)

Categories