How to click on a child element - javascript

There many items and I get object of the desired item. But I don't know how can I click on the child element in this object.
html:
<div class="item">
<div role="button" tabindex="-1">
<strong>ItemName2</strong>
</div>
<div class="d">
<div class="item-icon" role="button" tabindex="-1" style="display: none">
<i aria-label="icon: add" class="add"></i> <!-- I need to click on this Item -->
</div>
<div class="item-icon" role="button" tabindex="-1" style="display: none">
<i aria-label="icon: del" class="del"></i>
</div>
</div>
</div>
<div class="item"> ... </div>
<div class="item"> ... </div>
<div class="item"> ... </div>
js:
let fBtns = await driver.findElements(By.tagName('strong')); // Find all objects
let buttons = fBtns.map(elem => elem.getText());
const allButtons = await Promise.all(buttons);
console.log(allButtons); // All object names
let current = fBtns[fBtns.length - 1];
console.log(current); // This is desired object
await current.click(); // This is click on the object and operates as expected
// But I need to click on the <i aria-label="icon: add" class="add"> element
// How can I click on the desired element?

To click the element <i aria-label="icon: del" class="del"></i>, you can just use an XPath to query directly on the element:
await driver.findElement(By.xpath("//div[div/strong[text()='ItemName2']]/div/div/i[#class='del']")).click()
You can probably shorten this a bit to:
await driver.findElement(By.xpath("//div[div/strong[text()='ItemName2']]//i[#class='del']")).click()

Try invoking click by trigger method:
$('.item-icon .add').trigger("click");

In the below example, I scan the document for a dynamic xpath that finds the strong with string ItemName2 and then traverse back up one level (/../) before moving back down to the child element. This will act like a waitForElement that you can hopefully repurpose to trigger a click.
var MyDefaultTimeout = 1500;
var loaded = false;
do {
var icon = document.getElementsByClassName('//*[contains(#strong,\'ItemName2\')]/../div/div/i');
if(!icon.length == 0)
{
setTimeout(function() { loaded = false }, MyDefaultTimeout);
}
else
{
if(!document.readyState === 'complete')
{
setTimeout(function() { loaded = false }, MyDefaultTimeout);
}
else
{
loaded = true;
return document.readyState;
}
}
}
while(loaded === false);

Related

Using jQuery to find the next div and show/hide it

I'm working on an application that lists products/sub products. I'm trying to show the sub products when I click on the chevron. For some reason, I can't get this to work. I've been able to get the flipping of the chevron to work.
Here is my code:
<div class="item">
Product 1
<div style="float: right;"><i class="fas fa-fw fa-chevron-down" onclick="expand(this,event)"></i></div>
<div class="sub-item-list" style="display: none">
<div class="sub-item">
Sub Product 1
</div>
</div>
</div>
function expand(event) {
if ($(event).hasClass("fa-chevron-down")){
setTimeout(function () {///workaround
$(event).removeClass("fa-chevron-down");
}, 10);
$(event).addClass("fa-chevron-up");
$(event).closest('div').next().find("sub-item-list").css('display', 'inherit');
} else {
setTimeout(function () {///workaround
$(event).removeClass("fa-chevron-up");
}, 10);
$(event).addClass("fa-chevron-down");
$(event).closest('div').next().find("sub-item-list").css('display', 'none');
}
};
Can someone tell me that the issue is?
You can use .closest('div.item') to get the closest div and then use .find(".sub-item-list") to find the div which you need to display .
Demo Code :
function expand(event) {
if ($(event).hasClass("fa-chevron-down")) {
setTimeout(function() { ///workaround
$(event).removeClass("fa-chevron-down");
}, 10);
$(event).addClass("fa-chevron-up");
//get closest div with class item -> find class
$(event).closest('div.item').find(".sub-item-list").css('display', 'inherit');
} else {
setTimeout(function() { ///workaround
$(event).removeClass("fa-chevron-up");
}, 10);
$(event).addClass("fa-chevron-down");
//get closest div with class item -> find class
$(event).closest('div.item').find(".sub-item-list").css('display', 'none');
}
};
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="item">
Product 1
<div style="float: right;"><i class="fas fa-fw fa-chevron-down" onclick="expand(this,event)"> >> </i></div>
<div class="sub-item-list" style="display: none">
<div class="sub-item">
Sub Product 1
</div>
</div>
</div>
As you are setting onclick event on <i> tag you can also call parent().next() method instead of closest() to get subitem/product.
Try this example:
function expand(event)
{
if ($(event).hasClass("fa-chevron-down"))
{
setTimeout(function()
{///workaround
$(event).removeClass("fa-chevron-down");
}, 10);
$(event).addClass("fa-chevron-up");
$(event).parent().next().css("display", "block");
}
else
{
setTimeout(function ()
{///workaround
$(event).removeClass("fa-chevron-up");
}, 10);
$(event).addClass("fa-chevron-down");
$(event).parent().next().css('display', 'none');
}
};
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="item">
Product 1
<div style="float: right;">
<i class="fas fa-fw fa-chevron-down" onclick="expand(this, event)">Toggle Chevron</i>
</div>
<div class="sub-item-list" style="display:none">
<div class="sub-item">
Sub Product 1
</div>
</div>
</div>

Remove class if id's the correct ID

Looking to remove a class if a certain button is clicked.
<div class="slide-container">
<section class="about" id="slide-0">
<div class="menu-total">
<nav class="nav">
<button class="nav_link home" onclick="slideTo('slide-2')">HOME</button>
<button class="nav_link about" onclick="slideTo('slide-0')">ABOUT</button>
<button class="nav_link fun-stuff" onclick="slideTo('slide-1')">FUN STUFF</button>
<button class="nav_link professional" onclick="slideTo('slide-3')">PROFESSIONAL</button>
<button class="nav_link contact" onclick="slideTo('slide-4')">CONTACT</button>
</nav>
<div class="hamburger">
<span class="hamburger__patty"></span>
<span class="hamburger__patty"></span>
<span class="hamburger__patty"></span>
</div>
</div>
The one I want to remove the class on is the HOME button. So "slideTo('slide-2)". If it's clicked on the others then the class is kept. I believe someone is either wrong with my loop or not getting the ID correctly of the items/
function slideTo(slideId) {
const slide = document.getElementById(slideId);
slide.scrollIntoView({
behavior: 'smooth'
})
// above this line works fine
let nonHome = document.querySelectorAll('.slide-container section');
let nonHomeID = document.getElementById('slide-2');
var i;
setTimeout(function(){
for (i=0; i < nonHome.length; i++ ){
// i believe it's somewhere here it is wrong
if (nonHome[i].id != nonHomeID){
nonHome[i].classList.add("nav-visibility");
} else{
nonHomeID.classList.remove("nav-visibility");
}
}
}, 1000)
}
If you can use jquery library, you can write in the HTML:
<button class="nav_link" data-value="home">HOME</button>
...
and then in the JS code:
$(".nav_link").on("click", function() {
var valueClicked = $(this).data("value"); // Get the data-value clicked
$(".nav_link").each(function() { // Loop through all elements of the class 'nav-link'
var v = $(this).data("value");
if (v == valueClicked) {
$(this).removeClass("nav-visibility");
} else {
$(this).addClass("nav-visibility");
}
)
}
Not much simpler, but the HTML is cleaner.
Simpler version if it is not required to browse through all buttons at each button click:
$(".nav_link").on("click", function() {
var valueClicked = $(this).data("value"); // The value of the button clicked by the user
if (valueClicked == "home") {
$(this).removeClass("nav-visibility");
console.log('remove')
} else { $(this).addClass("nav-visibility");
console.log('add')
}
});

How do I allow only one accordion item to be open at a time on a dynamic accordion list?

I have a well functioning accordion list that allows me to add new items to the list and they will immediately have the accordion functionality. Although, I would like to have only one accordion item open at a time. All of the answers I've found regarding this have all been for static lists and don't really help with my issue. I apologize if this is a bad question, but I have spent hours on this with no luck and I am out of ideas. Thanks in advance for your help.
HTML: There is more to the HTML. I just added the relevant elements.
<body>
<div class="container">
<!-- New accordion item added here -->
</div>
<script src="main.js"></script>
</body>
Javascript:
// Get items
const addAccBtn = document.getElementById('addAccBtn');
const container = document.querySelector('.container');
addAccBtn.addEventListener('click', e => {
e.preventDefault();
let newAccItem = document.createElement('div');
newAccItem.innerHTML = `
<div class="acc-title">
<i class="fas fa-trash delete"></i>
<h3 class="header-click">${accName}</h3>
<i class="fas fa-sort-down down-arrow"></i>
</div>
<div class="acc-info hidden">
<i class="far fa-edit"></i>
<h4>Section 1</h4>
<p>${input1}</p>
<h4>Section 2</h4>
<p>${input2}</p>
</div>
`;
container.appendChild(newAccItem);
let downBtn = newAccItem.firstElementChild.childNodes[5]; // .down-arrow
let info = newAccItem.firstElementChild.nextElementSibling; // .acc-info
downBtn.addEventListener('click', () => {
if(downBtn.parentElement.nextElementSibling == info && info.classList.contains('hidden')) {
info.classList.remove('hidden');
info.classList.add('active');
}
else {
info.classList.remove('active');
info.classList.add('hidden');
}
});
});
Again, the accordion items will all open and close as they should, i would just like one item open at a time. Thanks again for your time.
I believe these lines should work
...
let downBtn = newAccItem.firstElementChild.childNodes[5]; // .down-arrow
let info = newAccItem.firstElementChild.nextElementSibling; // .acc-info
downBtn.addEventListener('click', () => {
//here you close all opened accordions ---------------
document.querySelectorAll(".acc-info.active").forEach(element => {
if (element !== info) {
element.classList.remove("active");
element.classList.add("hidden");
}
};
// --------------------------------------
if(downBtn.parentElement.nextElementSibling == info && info.classList.contains('hidden')) {
info.classList.remove('hidden');
info.classList.add('active');
}
else {
info.classList.remove('active');
info.classList.add('hidden');
}
});
});
_ edit _
added a if

Knockout JS overwriting all values with the last value in foreach binding

In my KnockoutJS app, I am looping over an observable array and displaying some stuff like this:
<div id="user-list-container" data-bind="foreach: users">
<div class="row order-line list-row">
<div class="medium-7 small-10 columns">
<i class="fi-torso tip-right"></i>
</div>
<div class="medium-3 columns">
<a href="#" class="button split tiny info radius">
<i data-bind="text:role"></i>
<span data-dropdown="leftDrop" data-options="align:left"></span>
</a>
</div>
<div class="medium-2 small-2 columns">
<i class="fi-trash" title="#Texts.Remove"></i>
</div>
<ul id="leftDrop" class="f-dropdown" data-dropdown-content>
<li>Foreman</li>
<li>Worker</li>
</ul>
</div>
</div>
Everything works fine and all the elements are shown, but when I click one item to operate on that particular item, each item then has the value of last item in the array.
In my Javascript function:
self.makeWorker = function (user) {
var project = self.selectedProject();
var account = self.accounts.findByKey(user.accountId);
var ur = user;
console.log(user);
console.log(this);
if (project.role() != "Worker") {
var data = {
role: "Worker",
organizationId: project.organizationId,
projectId: project.id,
accountId: user.accountId
}
TippNett.Project.ChangeRole(data, function (result) {
project.users.findByKey(user.id).role("Worker");
ur.role("Worker");
account.roles.findByKey(user.id).role("Worker");
});
}
}
The value passed to the function is always the last value in 'users' observable array.
Any input on why this is happening? See the image for more briefing:

how to create generic html with javascript

I have the following html:
<div id="prog" class="downloads clearfix">
<div class="item">
<div class="image_container">
<img src="/img/downloads/company.png" width="168" height="238" alt="">
</div>
<div class="title">
pricelist: <label id="pr1"></label>
</div>
<div class="type">
pdf document
</div>
<div class="link">
<a id="pdfdocument" class="button" target="_blank" href="#">start Download </a>
</div>
</div>
</div>
I want build HTML which is inside the <div id="prog"> with Javascript:
<div id="prog" class="downloads clearfix"></div>
I'm trying to use this Javascript, but without success:
var tmpDocument, tmpAnchorTagPdf, tmpAnchorTagXls, parentContainer, i;
parentContainer = document.getElementById('prog');
for (i = 0; i < documents.length; i++) {
tmpDocument = documents[i];
tmpAnchorTagPdf = document.createElement('a id="pdfdocument" ');
tmpAnchorTagPdf.href = '/role?element=' + contentElement.id + '&handle=' + ope.handle;
tmpAnchorTagPdf.innerHTML = 'start Download';
tmpAnchorTagXls = document.createElement('a');
tmpAnchorTagXls.href = '/role?element=' + contentElement.id + '&handle=' + ope.handle;
tmpAnchorTagXls.innerHTML = 'start Download';
parentContainer.appendChild(tmpAnchorTagPdf);
parentContainer.appendChild(tmpAnchorTagXls);
}
If this is a section of code that you will be using more than once, you could take the following approach.
Here is the original div without the code you want to create:
<div id="prog" class="downloads clearfix">
</div>
Create a template in a hidden div like:
<div id="itemtemplate" style="display: none;">
<div class="item">
<div class="image_container">
<img src="/img/downloads/company.png" width="168" height="238" alt="">
</div>
<div class="title">
pricelist: <label></label>
</div>
<div class="type">
pdf document
</div>
<div class="link">
<a class="button" target="_blank" href="#">start Download </a>
</div>
</div>
</div>
Then duplicate it with jquery (OP originally had a jquery tag; see below for JS), update some HTML in the duplicated div, then add it to the document
function addItem() {
var item = $("#itemtemplate div.item").clone();
//then you can search inside the item
//let's set the id of the "a" back to what it was in your example
item.find("div.link a").attr("id", "pdfdocument");
//...the id of the label
item.find("div.title label").attr("id", "pr1");
//then add the objects to the #prog div
$("#prog").append(item);
}
update
Here is the same addItem() function for this example using pure Javascript:
function JSaddItem() {
//get the template
var template = document.getElementById("itemtemplate");
//get the starting item
var tempitem = template.firstChild;
while(tempitem != null && tempitem.nodeName != "DIV") {
tempitem = tempitem.nextSibling;
}
if (tempitem == null) return;
//clone the item
var item = tempitem.cloneNode(true);
//update the id of the link
var a = item.querySelector(".link > a");
a.id = "pdfdocument";
//update the id of the label
var l = item.querySelector(".title > label");
l.id = "pr1";
//get the prog div
var prog = document.getElementById("prog");
//append the new div
prog.appendChild(item);
}
I put together a JSFiddle with both approaches here.

Categories