Cypress selectFile without an input element in DOM - javascript

When the upload button is clicked, the file browser opens using the below method. As far as I'm aware, there is no element added to the DOM unless you explicitly append it to a DOM element.
const inputEl = document.createElement("input");
inputEl.type = "file";
inputEl.multiple = true;
inputEl.click();
inputEl.onchange = (e) => { ... }
Is it possible to select a file in Cypress using this method? selectFile requires the input element to be in DOM and chained off it. Otherwise, I'd have to use hidden input elements instead.

Solved. You can't do it in Cypress. I used an environment variable "DEVELOPMENT=1" to append the input element to the DOM, but only during testing.
const inputEl = document.createElement("input");
if (process.env.DEVELOPMENT) {
document.getElementById("root").appendChild(inputEl);
}
inputEl.type = "file";
inputEl.multiple = true;
inputEl.click();
inputEl.onchange = (e) => { ... }

Related

Clone function and addEventListener in button inside

I need to clone a div that contains an input file, and within the clone, there is a button to delete the created clone.
My problem is that once the clone is created I cannot add the function on the button to delete the clone.
The function does not work. Where am I wrong?
if (document.querySelector('.clona-input-file') !== null) {
var clonaInputFile = document.querySelector('.clona-input-file');
clonaInputFile.addEventListener('click', function(e) {
e.preventDefault();
var RowDaClonare = document.querySelector('#row-da-clonare');
var clone = RowDaClonare.cloneNode(true);
clone.children[0].lastElementChild.value = '';
clone.id = 'row-da-clonare-' + Date.now();
RowDaClonare.after(clone);
var _buttonDel = document.createElement("button");
_buttonDel.id = 'cancellaInputClone';
_buttonDel.type = 'button';
_buttonDel.setAttribute("data-id-da-eliminare", clone.id);
_buttonDel.classList.add("btn");
_buttonDel.classList.add("btn-danger");
_buttonDel.classList.add("cancellaInputClone");
_buttonDel.innerHTML = '<i class="bi bi-trash-fill"></i>';
clone.appendChild(_buttonDel);
});
}
var cloneSet = document.querySelectorAll(".cancellaInputClone");
for (var i = 0; i < cloneSet.length; i++) {
cloneSet[i].addEventListener('click', fx_button);
}
function fx_button() {
console.log(this)
}
The issue is because you're attempting to bind event handlers to elements which don't yet exist in the DOM. This can be addressed by delegating your event handlers to parent elements which do exist when the DOM loads, and interrogating the events to see if they were raised by the elements you created.
In addition there's some other issues in your code to address:
Firstly, don't use id attributes in dynamic content. It makes your logic more complex than it needs to be. Use classes instead, and relate elements to each other using DOM traversal methods, such as closest().
Secondly, use querySelector() to find the child element, not children/index accessors. It's more robust.
Lastly, you can provide multiple separate class names to classList.add() to save you having to call it repeatedly.
With that said, try this working example:
let cloneButton = document.querySelector('.clona-input-file');
if (cloneButton) {
cloneButton.addEventListener('click', function(e) {
e.preventDefault();
let rowDaClonare = document.querySelector('.row-da-clonare'); // querySelector will return first match only
let clone = rowDaClonare.cloneNode(true);
clone.querySelector('input').value = '';
rowDaClonare.after(clone);
let buttonDel = document.createElement("button");
buttonDel.type = 'button';
buttonDel.classList.add("btn", "btn-danger", "cancellaInputClone");
clone.appendChild(buttonDel);
let icon = document.createElement('i');
icon.classList.add('bi', 'bi-trash-fill');
buttonDel.appendChild(icon);
});
}
// icon click handler delegated to the .container element
document.querySelector('.container').addEventListener('click', e => {
let el = e.target;
if (!el.classList.contains('cancellaInputClone') && !el.closest('button')?.classList.contains('cancellaInputClone'))
return;
el.closest('.row-da-clonare').remove();
});
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons#1.3.0/font/bootstrap-icons.css">
<div class="container">
<button class="clona-input-file">Clone</button>
<div class="row-da-clonare">
<div>
Lorem ipsum dolor sit.
<input type="text" class="foo" />
</div>
</div>
</div>
According to your code, it looks like the button is created only when the clonaInputFile is clicked, but the querySelectorAll and loop and addEventListener executes right after you registered the callback for the clonaInputFil. So now there is no button you are querying, and the cloneSet should be empty, if you haven't created before.
Try log out the length of cloneSet. If it is 0, I recommend you to put the addEventListener('click', fx_button); right after the creation of that button.

How do I remove a newly created element in Javascript?

Html
<button onclick="myFunction()">Go</button>
<button onclick="removeone()">
Remove
</button>
Javascript
This lets me go to any website in an iframe window
function myFunction() {
let userdata= prompt("Enter site name", "http://");
if (userdata != null) {
const ifrm = document.createElement("iframe");
ifrm.setAttribute("src", userdata);
ifrm.style.width = "1620px";
ifrm.style.height = "880px";
document.body.appendChild(ifrm);
} else {}
}
This removes the iFrame
function removeone() {
const iframe = document.querySelector('iframe')
iframe.remove()`
My problem is whenever I remove the iframe, it removes the oldest element first, and not the newly created one as I want it to.
I'm new to programming in JavaScript.
Details are commented in example
// Bind "click" event to <button>
document.querySelector(".open").onclick = openIframe;
// Event handler passes event object by default
function openIframe(event) {
// Prompt user for url
let url = prompt("Enter site name", "https://");
// If url isn't null...
if (url != null) {
// Create <iframe> and <button>
const ifrm = document.createElement("iframe");
const btn = document.createElement("button");
// Add attributes and styles to both elements
ifrm.src = url;
ifrm.style.display = "block";
ifrm.style.width = "98%";
ifrm.style.minHeight = "100vh";
btn.style.display = "block";
btn.textContent = "Remove";
// Append <button> then <iframe> to <body>
document.body.append(btn, ifrm);
// Bind "click" event to <button>
btn.onclick = function(event) {
// Find the element that follows <button> (ie <iframe>) and remove it
this.nextElementSibling.remove();
// <button> removes itself
this.remove();
}
}
}
<button class="open">GO!</button>
Try
function removeone() {
const n = document.querySelectorAll('iframe').length;
const iframe = document.querySelectorAll('iframe')[n-1];
iframe.remove();
}
Explanation
From the official documentation
The Document method querySelector() returns the first Element within the document that matches the specified selector, or group of selectors. If no matches are found, null is returned.
so instead, you can use querySelectorAll.
Also from the official documentation
The Document method querySelectorAll() returns a static (not live) NodeList representing a list of the document's elements that match the specified group of selectors
so you simply grab the last element of that NodeList and remove it. You can get the last element in a NodeList just like you would in a regular array in Javascript.

Replace text with an anchor tag which can call a function on click

I have an XML string which is displayed in a span in a pre tag:
<pre className="xmlContainer">
<span id="xml-span"></span>
</pre>
The XML looks something like this:
<root>
<child-to-replace>
<child-to-replace/>
...
<root/>
With multiple (unknown) number of the child-to-replace tag.
I replace < and > to display the xml which is contained in a variable xml:
let element = document.getElementById('xml-span');
var displayXml = xml.replaceAll('<','<').replaceAll('>','>');
element.innerHTML = displayXml;
I also want to replace all instances of the opening tag of child-to-replace with an anchor tag which calls a function updateParentScope. I have tried to simply replace it:
function updateParentScope(scope) {
//updates scope for parent
}
useEffect(() => {
let element = document.getElementById('xml-new');
var displayXml = xml.replaceAll('<','<').replaceAll('>','>')
.replaceAll('child-to-replace',
'<a onclick="updateParentScope(\"sometext\")" >child-to-replace</a>')
element.innerHTML = displayXml;)
element.innerHTML = replaced;
}, [xml])
This gives Uncaught ReferenceError: updateParentScope is not defined when clicked.
Is there a way to solve this or is replacing the text the wrong approach?
The solution I found was to change the anchor tag to a button, then get the element from the DOM after render with a useEffect hook. When the element is retrieved, add an event listener.
useEffect(() => {
const cond = document.getElementById(btnId) || false;
if (cond && !hasEventListener){
var btn = document.getElementById(btnId);
btn.addEventListener('click', updateParentScope)
setHasEventListener(true);
}
setHasEventListener(false);
})
The cond check is to make sure the element is rendered in the DOM.
I needed to add hasEventListener to state in order to not add the event listener multiple times:
const [hasEventListener, setHasEventListener] = useState(false);
This is not an ideal solution as the function called by the click event does not accept parameters.

Insert Div into the DOM and add attributes in Angular using Javascript

Would there be any reason the following code would not insert a DIV into the DOM and add the ID attribute? For some reason it is not working:
createDivs() {
const target = document.querySelector(".navbar");
const createDiv = document.createElement("DIV");
createDiv.setAttribute("id", "result-div");
createDiv.innerHTML = "<p>Yes</p>";
createDiv.insertAdjacentElement("afterbegin", target);
}
ngOnInit() {
this.createDivs();
}
You called insertAdjacentElement on the new element instead of on the sibling element... check the documentation
Instead of using
createDiv.insertAdjacentElement("afterbegin", target);
you should use
target.insertAdjacentElement("afterbegin", createDiv);

javascript checkbox event listener breaks if containing innerHTML is changed

I am trying to create checkboxes and insert a blank line after each one. When the checkbox is changed, I want to execute a function.
My code:
var div = document.getElementById("test");
var cb1 = document.createElement('input');
cb1.id = "cb_test1";
cb1.type = "checkbox";
cb1.defaultChecked = true;
cb1.onchange = function(){alert("hi")};
div.appendChild(cb1);
div.innerHTML += "box1<br/>";
var cb2 = document.createElement('input');
cb2.id = "cb_test1";
cb2.type = "checkbox";
cb2.defaultChecked = true;
cb2.onchange = function(){alert("hi")};
div.appendChild(cb2);
div.innerHTML += "box2<br/>";
The problem is, setting the innerHTML of the containing DIV seems to erase the event listener, so when the onchange event is fired, nothing happens.
How can I modify the innerHTML to add text and a new line without losing the event handler?
My actual code is much more dynamic and done in a loop, but the issue of the event handler getting dropped can be duplicated with the above code.
Whenever you assign (or concatenate) an innerHTML property of a container, any listeners inside that container that were attached via Javascript will be corrupted. If you want to insert HTML strings, use insertAdjacentHTML instead:
document.querySelector('#child').onclick = () => console.log('child');
document.querySelector('#container').insertAdjacentHTML('beforeend', '<div>newchild</div>');
<div id="container">
<div id="child">
child
</div>
</div>
But it would generally be better to create and append elements explicitly rather than insert strings of HTML markup that then get parsed into elements. For example, instead of
div.innerHTML += "box1<br/>";
you could
div.appendChild(document.createTextNode('box1');
div.appendChild(document.createElement('br');
const div = document.querySelector('div');
div.appendChild(document.createTextNode('box1'));
div.appendChild(document.createElement('br'));
<div>
content
</div>

Categories

Resources