Selecting jQuery First Child without a specific class - c#

I have a page with nested divs being used as Js/Jq tabs. When you click on a parent tab, it fires the click event of the first child underneath the parent.
$('#topRow .pricing').click(function () {
$('#secondRow .pricing').css('display', 'block');
$('#secondRow .pricing div.tab:first-child').trigger('click');
});
This functionality works great as is. However, we're adding functionality to effectively "lock" tabs by adding a "locked" class dynamically via c#/linq and then on window.ready we unbind all the tabs with the "locked" class and color them grey to indicate that they are disabled.
I'm trying to modify the last line of the jQuery code above to click the first child that DOESN'T have the "locked" class.
$('#secondRow .pricing div.tab:first-child').trigger('click');
Translating that into plainspeak, it's saying "In the Second Row, fire the click event of the first child tab in the 'pricing' group". I'd like it to say "In the Second Row, fire the click event of the first child tab in the 'pricing' group that DOES NOT have the class 'locked'".
I've tried using:
$('#secondRow .pricing div.tab:not(.locked):first-child').trigger('click');
Which seems the correct way to do this, but it still looks at the first child and just doesn't fire the trigger('click'). Am I missing something?
Here is the html:
<div id="topRow">
<div class="pricing tab" runat="server" id="pricingTop">
<div class="tabLeft">
<div class="tabMain">
Pricing
</div>
</div>
</div>
</div>
<div id="secondRow">
<div id="pricingTabGroup" runat="server" class="pricing tabGroup" style="display:none">
<div id="pricingProductA" runat="server" class="tab locked pricingProductA">
<div class="tabLeft">
<div class="tabMain">
ProductA
</div>
</div>
</div>
<div id="pricingProductB" runat="server" class="tab pricingProductB">
<div class="tabLeft">
<div class="tabMain">
ProductB
</div>
</div>
</div>
</div>
</div>
The "locked" class is added through C#:
protected void Page_Load(object sender, EventArgs e)
{
bool ProductA = //some linq query
if (!ProductA)
{
LockTab(pricingProductA);
}
protected void LockTab(HtmlGenericControl tab)
{
//Adds the "locked" class
tab.Attributes.Add("class", "locked");
}
}

Try
$('#secondRow .pricing div.tab').not('.locked').children().first().trigger('click');
Your selector looks fine though. Usually a good idea to minimize your selector complexity.
Also remember that every time you do a selector jQuery traverses the dom, so its best to save returned elements for a given selector as a variable(for performance) eg:
var $tab_containers = $('#secondRow .pricing div.tab');
And then use it multiple times like
$tab_containers.not('.locked').children().first().trigger('click');

Related

CSS Popover Expanding Container Borders Even Though Container Has Lower Z-Index

I am trying to create a pop-over which is inside a Bootstrap Card-Body. I am giving the Card and the Card-Body a Z-Index of 1, and actually a d-flex div which contains the pop-over a Z-Index of 2. But the Card-Body borders are expanding, and I don't want them to expand.
Here are 2 images. The first image is without the pop-over.
The following image shows he pop-over.
The pop-over should be covering the Submit button.
Here is my code:
<div class="card-body" style="position:relative;z-index:1;">
<div class="d-flex flex-column">
// Extra code removed
#if(matchingTags != null)
{
<div class="d-flex" style="position:relative;z-index:2;">
#foreach(Tag tag in matchingTags)
{
<div class="ms-1"><TagInfoComponent tag="#tag" TagCallback="TagSelected" /></div>
}
</div>
}
</div>
</div>
<div class="card-footer">
// Extra code removed
The TagInfoComponent is the Popover.
What am I doing wrong? Thanks.
Edit: Solved
I figured it out. See below:
#if(matchingTags != null)
{
<div class="d-flex">
<div style="position:absolute;z-index:2;">
#foreach(Tag tag in matchingTags)
{
<TagInfoComponent tag="#tag" TagCallback="TagSelected" />
}
</div>
</div>
}
Changing position to absolute was the key to not making the container borders expand. Then I had an issue where no matter how I set the margin, the popovers were overlapping. For that, I had to surround the foreach loop with a div, and set the position:absolute and Z-Index on that div.
Now I have this, which is what I want:

Blazor Dropdown with Search field - Handle Onfocusout

I have a dropdown with a search field to filter the list. Here is the basic structure:
<div class="dropdown">
<button class="dropdown-button dropdown-toggle" #onclick="e => this.show = !this.show"></button>
<div class="dropdown-menu #(show ? "show" : "")">
<input class="form-control form-control-sm" placeholder="type filter..."/>
<div class="scrollable-menu">
<table>
...
</table>
</div>
</div>
</div>
How do I hide the dropdown when the user clicks somewhere else?
If I use the onblur event of the button the dropdown gets hidden when the user clicks inside the filter input --> doesnt work.
The dropdown-menu is outside the dropdown div so I can't use that.
It would be ideal if I could group the button and the dropdown list together somehow so that the focusout event only gets triggered when the user clicks outside this "group" of elements.
EDIT
I updated the code snipped to show where how I toggle the dropdown.
The show variable is also inverted when the user selects an element in the list.
The simplest way is to use CSS - hide the scrollable-menu by default, then display it when anything in the dropdown has focus.
.scrollable-menu {
display: none;
}
.dropdown:focus-within .scrollable-menu {
display: block;
}
Edit: Add more complicated Blazor event based version
This problem (which I had not understood fully before) is usually solved in javascript detecting whether the target element of a focus change is within the container, but that then means interop calls to set/update your show field.
A purely Blazor solution could be handled by delaying the hide and cancelling if focus remains inside.
<div class="dropdown">
<button class="dropdown-button dropdown-toggle" #onclick=HandleClick #onfocus=HandleFocus #onblur=HandleBlur ></button>
<div class="dropdown-menu #(show ? "show" : "")" #onfocusin=HandleFocus #onfocusout=HandleBlur tabindex="-1">
<input class="form-control form-control-sm" placeholder="type filter..."/>
<div class="scrollable-menu">
<table>
...
</table>
</div>
</div>
</div>
#code{
bool show;
CancellationTokenSource tokenSource;
void HandleClick() => show = !show;
async Task HandleBlur(FocusEventArgs a)
{
tokenSource = new CancellationTokenSource();
await Task.Factory.StartNew(async ()=> {
await Task.Delay(100);
show = false;
await InvokeAsync(StateHasChanged);
},tokenSource.Token);
}
void HandleFocus(FocusEventArgs a)
{
if (tokenSource is CancellationTokenSource)
tokenSource.Cancel();
}
}
Try it out here: https://blazorrepl.com/repl/wFESlpaa33iocZJR52
use InvokeVoidAsync. It will not work if you make it async.
#onclick="#(e => {
JsRuntime.InvokeVoidAsync("eval",$"bootstrap.Dropdown(yourID).toggle()");
})"

Selenium clicking button hidden by span

I am trying to automate an environment selection screen where there are multiple selectable buttons individually hidden by a span, these display as tiles.
I have managed to navigate to a given tile and pull up the button but I am unable to click it.
Here is the code I have
public static void NavigateToEnvironment(IWebDriver driver, string environment)
{
IWait<IWebDriver> wait = new WebDriverWait(driver, TimeSpan.FromSeconds(5.00));
wait.Until(ExpectedConditions.ElementIsVisible(By.XPath($"//span[text()='{environment}']")));
var tile = driver.FindElement(By.XPath($"//span[text()='{environment}']"));
Actions action = new Actions(driver);
action.MoveToElement(tile).Perform();
wait.Until(ExpectedConditions.ElementIsVisible(By.XPath($"//*[#span=(text()='{environment}')][#btn=(starts-with(text(), 'Start'))]")));
driver.FindElement(By.XPath($"//*[starts-with(text(), 'Start')]")).Click();
}
The first part successfully moves to the correct tile and opens the span so on screen the button is there.
The wait.until condition is fine too so Selenium can see the element so its the final click command I have an issue with.
It seems only to look for the button hidden within tile one but I am trying tile three. All the buttons have the same HTML tags.
In the current code state I get element not visible.
I have tried to use the xpath as in the wait condition but that returns that the parameters are not elements so again fails.
I am kind of at a loss. Any ideas?
UPDATE:
Some HTML of one of the buttons. This basically repeats with a different application name
<li class="trans tile">
<div class="tileWrap noselect" aria-haspopup="true">
<div class="divNavIcon">
<span class="spnNavIcon primarycolorfont enable" data-bind="css: Code"></span>
</div>
<div class="tilePopup primarycolor">
<span data-bind="text: ApplicationNameAlias ? ApplicationNameAlias : ApplicationName">Enable QA</span>
<span data-bind="text: Description" class="tileSubText">Enable CI Environment</span>
<div class="tilePopupToggle">
<button type="button" data-bind="click: $parent.startApp, css: { disabled: IsRevoked }" class="btn">Start <i class="fa fa-fw fa-desktop"></i></button>
<button type="button" style="display:none;" data-bind="click: $parent.startAppNew, css: { disabled: IsRevoked }" class="btn">Start New <i class="fa fa-fw fa-external-link"></i></button>
<button type="button" style="display:none;" data-bind="attr: { "data-target": "#appPreview_" + ApplicationID }" class="btn" data-toggle="modal" data-target="#appPreview_3043">Preview <i class="fa fa-fw fa-play"></i></button>
</div>
</div>
</div>
Screenshot to help understanding - Each tile acts in the same way with a hidden start button. My code works fine for this first tile but if I want the second or third tiles it cannot find the start button
As per the HTML you have shared to click on the button with text as Start you can use the following code block :
wait.Until(ExpectedConditions.ElementToBeClickable(By.XPath("//div[#class='tilePopup primarycolor']//div[#class='tilePopupToggle']/button[#class='btn' and normalize-space()='Start']/i[#class='fa fa-fw fa-desktop']"))).Click();
Update
Can you try removing the <button> tag as :
wait.Until(ExpectedConditions.ElementToBeClickable(By.XPath("//div[#class='tilePopup primarycolor']//div[#class='tilePopupToggle']//i[#class='fa fa-fw fa-desktop']"))).Click();
Note : As per aurelia/binding/issues/163 disable.bind disables button but inner content is still clickable and we are targeting i[#class='fa fa-fw fa-desktop']
I have managed a pretty elegant work around to this issue. The buttons are contained in li items so i'm just finding the relevant one of those.
public void NavigateToEnvironment(IWebDriver driver, string environment)
{
var tile = driver.FindElement(By.XPath($"//span[text()='{environment}']"),5);
Actions action = new Actions(driver);
action.MoveToElement(tile).Perform();
var tile2 = driver
.FindElement(By.XPath("//*[#id='content']/div/div/div/div/ul"))
.FindElements(By.TagName("li"))
.Where(x => !string.IsNullOrWhiteSpace(x.Text))
.ToList();
var singleTile = tile2.Single(x => x.Text.Contains(environment));
driver.FindElement(By.XPath($"//*[#id='content']/div/div/div/div/ul/li[{tile2.IndexOf(singleTile) + 1}]/div[1]/div[2]/div/button[1]")).Click();
}

Is it possible to call a web page LOAD event from JQuery OnClient Click event?

I have created a dialog box that calls a user Control for creating a grid and populating it respectively. but this user control is used earlier, so when I am showing the dialog box, it is showing me the same grid. So, in order to call the user control again, i want its load event to get fired again. Is it possible to do this. Below is my code:
HTML
<%# Register TagPrefix="uc" TagName="ReviewGroupGrid" Src="UserControls/ReviewGroupGrid.ascx" %>
<%# Register TagPrefix="uc" TagName="ReviewGroupGrids" Src="UserControls/ReviewGroupGrid.ascx" %>
<%# Register Assembly="OCM.Phoenix.WebToolsFramework.Server.Modules.DataSteward.WebExtensions" Namespace="OCM.Phoenix.WebToolsFramework.Server.Modules.DataSteward.WebExtensions" TagPrefix="cc1" %>
<asp:Content ID="BodyContent" runat="server" ContentPlaceHolderID="DataStewardContent">
<cc1:Button ID="btnMerge" runat="server" Text="Merge & Edit" OnClick="btnMerge_Click"
OnClientClick="javascript:return reviewGroupForm.getSelectedRowsForMerge()" />
<div class="modal-dialog" id="updateClaimConfirmPopUp" style="display: none">
<div class="modal-content">
<div class="modal-header" id="popUpHeader">
<button type="button" class="close closeClaimPopUp">
<span>×</span></button>
</div>
<div class="modal-body" id="confirmData">
<div id="random"></div>
<div class="dataTable_wrapper">
<div class="table-responsive">
<uc:ReviewGroupGrids ID="reviewGroupCtrls" runat="Server" />
</div>
</div>
</div>
<div class="modal-footer">
<asp:LinkButton ID="claimMerge" CssClass="buttonClassClaim btn btn-primary" runat="server" Text="Accept" OnClick="btnMerge_Click"/>
<button type="button" id="btnClosePopUp" class="buttonClassClaim btn btn-primary closeClaimPopUp">
Discard</button>
</div>
</div>
</div>
</asp:Content>
jQuery
getSelectedRowsForMerge: function () {
var entityType = $("input[id*='hdnEntityType']").val();
if (entityType === "19") {
$('#popUpHeader').find('h4').remove();
$('#random').find('h4').remove();
$('#popUpHeader').append(' <h4 class="modal-title" > ' + 'Need Your' + ' Attention!</h4>');
$('#random').append('<h4><b> ' + 'The Claims that you are merging are associated with different patients, merging the claims will result in explicit merging of the associated Patients as well, Please review the patient(s) details shown below before merging the claim' + '</b></h4>');
//$('#confirmData').append('<div class="table-responsive"').append(' <uc:ReviewGroupGrid ID="reviewGroupCtrl" runat="Server" />').append('</div>');
$("#updateClaimConfirmPopUp").dialog({
autoOpen: true,
width: 1600,
resizable: false,
draggable: true,
modal: true,
show: { effect: 'blind' }
});
}
},
When my dialog box is getting created, it is creating the user control but this user control is already populated. I want to call its load event so that the user control processes again. Can i do this via click event when opening the dialog?
Do you want the btnMerge to refresh the div everytime its shown. If so then you need to get the data from server via ajax call in your getSelectedRowsForMerge which can update the data shown in your ReviewGroupGrids control. In the success handler of this promise, set the popup to be shown and not immediately. This will refresh everything in the div and not require any server side load event
Snippet as follows, please add real code
getSelectedRowsForMerge: function () {
// various cleanups as present
$.ajax(...)
.success( function() {
//show the div
$("#updateClaimConfirmPopUp").dialog({ .... } );
})

How to hide specific jqueryui tab based on some condition?

I am working on an application in which a selected tab should not visible to specific users.My code is
<div id="tabs">
<ul>
<li>Log Tickets</li>
<li>Open Tickets</li>
</ul>
<div id="divLogTickets" runat="server" style="padding: 25px;">
</div>
</div>
if (getUserRole(Convert.ToString(Session["UserId"])) == "HR")
{
//hide tab
}
How to hide a specific tab based on specific user role.
You can add id and runat="server" attributes to the elements that you want to access from the code behind and set the .Visible property in code behind.
For example if you want to hide Log Tickets tab, here's what your aspx code should look like:
<div id="tabs">
<ul>
<li id="liLogTickets" runat="server">Log Tickets</li>
<li>Open Tickets</li>
</ul>
<div id="divLogTickets" runat="server" style="padding: 25px;">
</div>
</div>
Then set the visibility of liLogTickets and divLogTickets in code behind:
if (getUserRole(Convert.ToString(Session["UserId"])) == "HR")
{
//hide Log Tickets tab
liLogTickets.Visible = false;
divLogTickets.Visible = false;
}
You can use $(selector).hide(); method hide.
For eg:
if (getUserRole(Convert.ToString(Session["UserId"])) == "HR")
{
//hide tab
$('#userId').hide();
}
After the validations , i.e. you have validated that this particular user you want to hide it from (as you have mentioned in your code) further you can use the hide() function to hide that particular element.
$('#Id_of_Element').hide();

Categories