I'm creating my own autocomplete input in Blazor. (something like below)
function FocusOut()
{
document.getElementById("list-item-one").innerHTML = "Focus out";
}
function Click()
{
document.getElementById("list-item-one").innerHTML = "Click";
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<input type="search" onfocusout="FocusOut()" />
<ul class="dropdown">
<li id="list-item-one" onclick="Click()">List Item One</li>
</ul>
When I click on the list item, the onfocusout event fires instead of the onclick event. Is there a way to "push" the onclick event?
This isn't a parent child relation, so "stopPropagation" has no effect. Also I know there is a datalist tag, but i'm not using it because of the way it look, feels and behaves in the different browsers.
The problem is that the order of events is OnMouseDown, OnFocusOut, OnClick.
Because of this, your dropdown closes before the OnClick event, so the click is not registered.
A working solution is to replace OnClick with OnMouseDown.
Based on this answer by #DuncanLuk.
I had a similar issue and I added a await Task.Delay(250) in my #onfocusout handler and it worked. You can find the live demo by clicking three vertical dots at top right in ilovedotnet site.
<section id="social">
<div class="dropdown is-right #(MenuCollapsed ? null : "is-active")" #onclick="ToggleMenu" #onfocusout="FocusOutHandler">
<div class="dropdown-trigger">
<button class="button" aria-haspopup="true" aria-controls="social-menu">
Social
</button>
</div>
<div class="dropdown-menu" id="social-menu" role="menu">
<div class="dropdown-content">
<a href="https://ilovedotnet.org/" target="_blank" class="dropdown-item">
I Love DotNet
</a>
</div>
</div>
</div>
</section>
#code {
internal bool MenuCollapsed { get; private set; } = true;
internal void ToggleMenu()
{
MenuCollapsed = !MenuCollapsed;
}
internal async Task FocusOutHandler()
{
// to avoid race between mouse click of anchor tag Navigation. without this delay Navigation
// is not getting executed when item is clicked from mouse
await Task.Delay(250);
MenuCollapsed = true;
}
}
Related
With the help of SO member the.marolie I learned how to implement a JS filter/selector [the 'demo JS filter']. I have this working perfectly on a test page: it shows/hides divs according to "data-types" assigned to them in the body html. However, the selection is made by sliding down a <select> dropdown list and letting go at the preferred option. That's not quite what I want.
I want to utilise my existing nav-bar dropdown ['my dropdown'] as the filter/selector. I especially want to retain the existing interactivity of my dropdown, whereby one click/tap reveals the whole of the dropdown content block, and one click outside the content block closes it.
I want the elements within my dropdown to represent various show/hide <div> 'options' for the html page, and enable the user to choose from these via an additional click/tap (essentially what the demo JS filter does, but at the instigation of a click/tap). Once revealed via a nav-bar click/tap, the whole dropdown content block has to stay on-screen -as it currently does- for this to be practically possible.
After making my dropdown identifiable via id="media-selector-demo" and name="filter" I was hoping that I could assign the demo JS filter's <option> elements to the <a> elements in it, and the whole thing would function like the <select> dropdown of the demo JS filter. I had a vague idea that using <a> elements might obviate the need for another onClick in the JS. I've tried various combinations of <a> and <option> elements, but nothing has worked yet.
Do I need another onClick to invoke the JS filter via my dropdown? or
Can I invoke the JS filter via 'active' <a> status?
I'm struggling by trial and error.
Here are what I think are the relevant sections of code pertaining to all discussed above:
My dropdown is based on the following code.
JS in the page head:
/* When the user clicks on the button,
toggle between hiding and showing the dropdown content */
function myDropdownJS() {
document.getElementsByClassName("navbarDROPDOWN-JS")[0].classList.toggle("show");
}
// Close the dropdown if the user clicks outside of it
window.onclick = function(e) {
if (!e.target.matches('.dropbtn')) {
var myDropdown = document.getElementsByClassName("navbarDROPDOWN-JS")[0];
if (myDropdown.classList.contains('show')) {
myDropdown.classList.remove('show');
}
}
}
My dropdown html in nav bar (most of the css is just design styling):
<span class="dropdown" onclick="myFunction()">
<a class="dropbtn navbarDROP-ICON" style="opacity:0.5; padding-top:0px;">menu</a>
<a class="dropbtn navbarDROP-TXT" style="opacity:0.5">menu </a>
<a class="dropbtn navbarDROP-TXT">Career Works by Date </a>
<div class="dropdown-content navbarDROPDOWN-JS" >
<a class="tag-bgd-INSTLLN" href="#">Installations (all media)</a>
<a class="tag-bgd-MOVIMG" href="#">Works with moving image (inc. vid/film releases)</a>
<a class="tag-bgd-SNDMUS" href="#">...with sound and music (inc. sound/music releases)</a>
<a class="tag-bgd-PHOTO" href="#">...with photographs</a>
<a class="tag-bgd-DRAW" href="#">...with drawing (inc. 2D works)</a>
<a class="tag-bgd-TXT" href="#">...with text</a>
<a class="tag-bgd-PERF" href="#">...with performative action</a>
<a class="tag-bgd-COLPUB" href="#">Collaborative and public works</a>
<a class="tag-bgd-OBJDEV" href="#">>Objects, garments, devices</a>
<a class="tag-bgd-EDPUB" href="#">Editions, publications</a>
<a class="tag-bgd-CAREER" href="#">Career Works by Date</a>
</div>
Above: the <a href> elements were going to contain URLs for alternatively styled pages. There is no need for these if I can enable the user to selectively show/hide parts of just this one page, via this dropdown.
The demo JS filter is based on the following code (via SO user the.marolie).
JS at page end:
var select = document.getElementById('media-selector-demo');
var filter;
select.addEventListener("change", function() {
filter = select.value;
var elements = document.querySelectorAll('.wk-date_ITEM');
elements.forEach((el) => {
var type = el.dataset.type.split(', ');
if (type.includes(filter)) {
el.classList.remove('hide-by-media');
} else {
el.classList.add('hide-by-media');
}
})
});
Demo JS filter CSS:
.hide-by-media {
display: none;
}
Demo JS filter html in page body:
<select id="media-selector-demo" name="filter">
<option value="INSTLLN"> Installations (all media)</option>
<option value="MOVIMG"> Works with moving image (inc. vid/film releases)</option>
<option value="SNDMUS" >...with sound and music (inc. sound/music releases)</option>
</select>
Example div in page body (there are 80-100 of these):
<!-- ++++++++++ START FULL-WIDTH LIST ENTRY '2017 STATE OF DREAD' ++++++++++ -->
<div id="state-of-dread" class="w3-container wk-date_ITEM" data-type="INSTLLN, SNDMUS">
<div class="w3-container wk-date_TXT-IMG">
<div class="wk-date_GRID">
<div class= "h3 wk-date_DATE"> 2017 </div>
<div class="wk-date_TTL"><h1>State of Dread</h1></div>
<div class="h2 wk-date_KIND-1" >Installation</div>
<div class="p wk-date_KIND-2" ><span class="sound">Sound</span>, for x2 interconnected rooms.<br>AB, CD, EF, Solo exhibition (as trio), Ohrenhoch sound gallery, Berlin.</div>
<div class="wk-date_IMG">
<div class="w3-container w3-right wk-date_IMG-BOX-LSCP">
<img src="../../imgs/INSTALLATION-EVENT/2017_dread_thmb.jpg"
alt="'xx' by Andrew Stones, installation view, xx"></div>
</div>
</div>
</div>
</div>
<!-- ++++++++++ END FULL-WIDTH LIST ENTRY '2017 STATE OF DREAD' ++++++++++ -->
Demo JS filter: JS at end of page:
<script type="text/javascript">
var select = document.getElementById('media-selector-demo');
var filter;
select.addEventListener("change", function() {
filter = select.value;
var elements = document.querySelectorAll('.wk-date_ITEM');
elements.forEach((el) => {
var type = el.dataset.type.split(', ');
if (type.includes(filter)) {
el.classList.remove('hide-by-media');
} else {
el.classList.add('hide-by-media');
}
})
});
</script>
What you would need to do is change the event listener from select, change to drop down element, click. you would also need to add the values of the options from the select as data-value attributes on the drop down elements.
1 - add a data-value attribute to the elements to represent what to hide
<a class="tag-bgd-INSTLLN" href="#" data-value="INSTLLN">
2 - target the drop down elements you want to attach the event listener to.
const dropDownElements = document.querySelectorAll('.dropdown-content a')
3 - attach event listeners to the selected targets (PS. the e in the function stands for event, click event listener produces an event object)
dropDownElements.forEach((dropDownElement) => {
dropDownElement.addEventListener('click',(e)=>{
const filter = e.target.dataset.value;
})
})
4 - the rest is just adding the rest of the filter used in the demo js filter
dropDownElements.forEach((dropDownElement) => {
dropDownElement.addEventListener("click", (e) => {
const filter = e.target.dataset.value
var elements = document.querySelectorAll(".wk-date_ITEM")
elements.forEach((el) => {
var type = el.dataset.type.split(", ")
if (type.includes(filter)) {
el.classList.remove("hide-by-media")
} else {
el.classList.add("hide-by-media")
}
})
})
})
I am using Bootstrap 5.2 and I have two buttons that can hide content, using the Bootstrap collapse plugin.
<div class="col-12 col-sm-auto">
<span class="pe-2">
<button id="show_movements_button" type="btn" class="btn btn-outline-primary" data-bs-toggle="collapse" data-bs-target="#movements_id">
Show Movements
</button>
</span>
<span class="pe-2">
<button id="show_credits_button" type="btn" class="btn btn-outline-secondary" data-bs-toggle="collapse" data-bs-target="#credits_id">
Show All Credits
</button>
</span>
</div>
such as
<tr class="song_id collapse" id="movements_id">
<td class="col-1">
1
</td>
<td class="col">
</td>
<td class="col">
<div>
<label class="h6">
Piano Concerto no. 1 in E minor, op. 11: I. Allegro maestoso
</label>
</div>
<div class="collapse" id="credits_id">
<div class="lh-1">
<div>
<a href="/container.start?cid=0$=Instrument$708&title=Instruments+%2F+piano" class="small text-secondary">
piano
</a>
<label class="small">
by
</label>
<a href="/container.start?cid=0$=Performer_name$5540&title=Performers+%2F+Evgeny+Kissin" class="small text-secondary pe-1">
Evgeny Kissin
</a>
</div>
</div>
</div>
</td>
</tr>
This work correctly, but I want the name of the button to change to indicate if showing or hiding content so I also have this code
<script>
function listenForButtonCollapse(buttonId, collapseId, buttonShowText, buttonHideText)
{
let button = document.getElementById(buttonId);
let section = document.getElementById(collapseId);
if(section!=null)
{
section.addEventListener('show.bs.collapse',
function()
{
button.innerText=buttonHideText;
}
);
section.addEventListener('hide.bs.collapse',
function()
{
button.innerText=buttonShowText;
}
);
}
}
</script>
<script>
listenForButtonCollapse('show_credits_button','credits_id','Show All Credits','Hide Some Credits');
</script>
<script>
listenForButtonCollapse('show_movements_button','movements_id','Show Movements','Hide Movements');
</script>
Now toggling the Show/Hide Movements button works fine, but when I click on the Show/Hide Credits button for some reason it is also triggering the listenForButtonCollapse() call on the movements button as well as the credits button, so the Movement button is updated with the same (Hide/Show) value as the credits button even though it isn't actually been invoked (so it doesn't hide/show the movements div)
The credits div is within the movements div, so Im assuming that why one button works without problem and the other doesn't but I cant see what I am actually doing wrong.
In order to avoid the current behaviour you need to stop further event propagation. You can do this by using stopPropagation(). This method of the Event interface prevents further propagation of the current event in the capturing and bubbling phases.
function listenForButtonCollapse(buttonId, collapseId, buttonShowText, buttonHideText)
{
let button = document.getElementById(buttonId);
let section = document.getElementById(collapseId);
if (section != null)
{
section.addEventListener('show.bs.collapse',
function(event)
{
event.stopPropagation();
button.innerText=buttonHideText;
}
);
section.addEventListener('hide.bs.collapse',
function(event)
{
event.stopPropagation();
button.innerText=buttonShowText;
}
);
}
}
I've prepared a Code playground to illustrate that this solves your issue:
https://codesandbox.io/embed/bootstrap-5-playground-forked-b28j0g?fontsize=14&hidenavigation=1&theme=dark
Solution Explanation
The events show.bs.collapse and hide.bs.collapse are being triggered for both buttons when clicked. So when you add more than one event listener for those events, when the event occurs, all event listeners are being executed. This is why for example when you click "Hide Movements" and bootstrap triggers hide.bs.collapse
event then this executes all registered event listeners for it - in this case two - one for the show_credits_button and another for show_movements_button button - leading to changing both button texts. To prevent this you need to stop further event propagation with event.stopPropagation() method - this will stop notifying all other event listeners for this event other than the event target one.
Running this piece of Bootstrap HTML and JS code below, I am struggling to not initiate the modal when clicking on the dropdown navi-link (e.g. Mark as Read).
<ul id="notification_items" class="navi navi-hover">
<!--begin::Item-->
<li id="1f7cbbe4-2345-486a-ab66-b05601f033a9" data-notification-id="1f7cbbe4-2345-486a-ab66-b05601f033a9" data-notification-subject="Some Subject" data-notification-message="And some message" class="notification_item cursor-pointer navi-item">
<div class="pr-10 pl-2 navi-link">
<div class="navi-label mx-5"><span class="label label-dot label-lg label-primary mt-n6"></span></div>
<div class="navi-text">
<span class="font-weight-bolder font-size-lg">Some Subject</span><span class="text-muted"> - And some message</span>
<div class="text-muted font-size-sm">3 hours ago</div>
</div>
<div class="notification_actions dropdown dropdown-inline d-none">
<i class="far fa-ellipsis-h"></i>
<div class="dropdown-menu dropdown-menu-sm dropdown-menu-right" style="">
<ul class="navi flex-column navi-hover py-2">
<li class="navi-item"><span data-notification-id="1f7cbbe4-2345-486a-ab66-b05601f033a9" class="notification_mark_as_read navi-link"><span class="navi-icon"><i class="fad fa-comment-check"></i></span><span class="navi-text">Mark as Read</span></span></li>
<div class="dropdown-divider"></div>
<li class="navi-item"><span class="navi-icon"><i class="far fa-trash-alt text-danger"></i></span><span class="navi-text text-danger">Delete</span></li>
</ul>
</div>
</div>
</div>
</li>
<!--end::Item-->
</ul>
And the simple JS code piece to show the modal when clicking on the <li>:
$(document).on("click", ".notification_item", function () {
$('#notification').modal('show');
});
I tried to e.stopPropagation(); when clicking on the .notification_actions Which works initially, but then obviously every other event won't work anymore.
You have to play with the arguments from the event parameter of the click event: target and currentTarget
target will return your DOM element on which the click occurred
initially
currentTarget will always be the DOM element on which your handlers
is attached -> in your case the li.notification_item
The solution is to identify the element the click occured on - target. There are a lot of ways - in your case you can detect if your click occured in the dropdown navi-link (.dropdown-menu) by traversing the DOM up until the root menu (#notification_items):
$(document).on("click", ".notification_item", function (e) {
// you traverse up to the root ul, and looking if you are in a dropdown menu
if ($(e.target).closest('.dropdown-menu', '#notification_items').length > 0) {
// You are in a drop down menu -> do nothing
} else {
// you are somewhere else -> trigger the modal
$('#notification').modal('show');
}
});
JSFiddle
P.S. this codes checks if you are in a dropdown-menu, you can use specific selector if you want to check for a specific dropdown.
I cannot get a nested variable value to populate outside a nested function within a button's click event function even though I believe I am using a global variable.
What am I doing wrong to pull value into a console log outside the nested function?
I am creating a shopping cart utilizing jquery pop-up with ajax and php. I am able to add items to the cart as well as add a name & email input field.
When I go to console log in Chrome for the focusout event for the fields they show the values but when trying to use a Checkout button, I am not able to pass the data within the Checkout click outside of a nested function even with a global variable.
--JS--
var formname;
$(document).ready(function() {
...
$(document).on('click', '#check_out_cart', function(){
$('#cart-popover').popover({
html : true,
container: 'body',
content:function(){
return $('#popover_content_wrapper').html();
}
});
$(document).on('click', '#cart-popover', function(){
$('input#namef').focus();
});
$('input#namef').on('focusout', function(){
formname= $('input#namef').val();
console.log(formname);
});
var scart_add = $("input[name='scart_add']").val();
console.log("Scart value is "+scart_add);
console.log("Name is "+formname);
...
});
});
--HTML--
<div class="container">
<nav class="navbar navbar-default" role="navigation">
<div class="container-fluid">
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target=".navbar-collapse">
<span class="sr-only">Menu</span>
<span class="glyphicon glyphicon-menu-hamburger"></span>
</button>
</div>
<div id="navbar-cart" class="navbar-collapse collapse">
<ul class="nav navbar-nav">
<li>
<a id="cart-popover" class="btn" data-placement="bottom" title="Shopping Cart">
<span class="glyphicon glyphicon-shopping-cart-2x"></span>
<span class="badge"></span>
<span class="total_price">$ 0.00</span>
</a>
</li>
</ul>
</div>
</div>
</nav>
<div id="popover_content_wrapper" style="display: none">
<span id="cart_details"></span>
<div>
<form method="POST">
Name: <input type="text" id="namef" >
Email: <input type="text" id="emailf" >
<input type="hidden" name="scart_add" value="1" /><br><br>
</div>
<div align="right">
<button type="button" class="btn btn-primary" id="check_out_cart">
<span class="glyphicon glyphicon-shopping-cart"></span> Check out
</button>
<button type="button" class="btn btn-default" id="clear_cart">
<span class="glyphicon glyphicon-trash"></span> Clear
</button>
</div>
</form>
</div>
</div>
<div id="display_item">
</div>
...
</div>
I am expecting the value from the input#namef text to appear in the console.log ...formname variable but it just shows as "".
The event for focusout isn't added to the input until you click the button:
Also, I don't know if it is how you copied and pasted your HTML and Javascript, but it was throwing errors when I put it into a fiddle.
Move this outside of the button click handler as Rory pointed out:
var formname;
$(document).ready(function() {
//Now focusout handler is added on DOM Ready instead of when you click the button
$('input#namef').on('focusout', function(){
formname= $('input#namef').val();
console.log(formname);
});
$(document).on('click', '#check_out_cart', function(){
var scart_add = $("input[name='scart_add']").val();
console.log("Scart value is "+scart_add);
console.log("Name is "+formname);
});
});
Here is a working fiddle: https://jsfiddle.net/hqgv7zsa/
This is a race condition, of sorts.
On document.ready event, you are setting a click event handler
Inside the click handler, you are setting the focusout event handler
Since the click handler is still executing,
immediately, the code runs to show value of scart and formname.
At that time, formname is still empty because the current function
(click event handler) is still executing, and the focusout event, even
if it fires, will fire after that code is executed.
You should move the focusout handler declaration code outside of the click handler code, which will then set both handlers on document.ready() event.
Here's a breakdown of what's happening and when:
var formname;
$(document).ready(function() {
This fires when DOM is loaded (document.ready event)
$(document).on('click', '#check_out_cart', function(){
This fires when user clicks on some element with ID check_out_cart
$('input#namef').on('focusout', function(){
This event handler is only being set once user clicks on cart, but it is not yet executed!
formname= $('input#namef').val();
console.log(formname);
});
This code fires once the event handler has been set for focusout, but still has not executed. Even if a focusout event is fired, it will only execute when the current function exits.
var scart_add = $("input[name='scart_add']").val();
console.log("Scart value is "+scart_add);
Correctly, you will see that formname is empty at this point.
console.log("Name is "+formname);
...
});
});
Hope this explains the flow you're seeing;
Quick fix as in Ryan Wilson's answer, move the focusout handler declaration to the scope of the document.ready handler, outside the click handler.
Using the solution from Unable to fetch values from an input element in Bootstrap's popover, I was able to adjust my JS code to fetch the values with a .find within the .popover-content class that was added by the popover JS.
var formname;
var formemail;
$(document).ready(function() {
//shopping cart
/*load functions*/
load_product();
load_cart_data();
$('#cart-popover').popover({
html : true,
container: 'body',
content:function(){
return $('#popover_content_wrapper').html();
}
});
$(document).on('click', '#cart-popover', function(){
$('input#namef').focus();
});
$(document).on('focusout', '#namef', function(){
formname = $('.popover-content').find('#namef').val();
});
$(document).on('focusout', '#emailf', function(){
formemail = $('.popover-content').find('#emailf').val();
});
$(document).on('click', '#check_out_cart', function(){
var scart_add = $("input[name='scart_add']").val();
var nameval = $("input#namef").val();
alert("Scart value is "+scart_add);
alert("Name is "+formname);
alert("Email is "+formemail);
});
});
I'm having a problem with jQuery .not() not working.
I've got a pricing table with 3 products, when you mouse over each of the products a description appears and then disappears on mouse out so far everything works fine.
The problem however is that on page load one of the three products (each held in an <aside>) will have a .selected class applied to it and I need the mouseenter/leave function to not run on this <aside>.
The only caveat is that clicking any of the <button>'s in each <aside> will remove the .selected class from it's current <aside> and apply to the <button>'s <aside> parent (I've not included the JQ for this as it's just a dead simple .addClass).
Code example of what I currently have is below, thanks in advanced.
jQuery
$(".pricing3 aside").not('.selected').mouseenter(function() {
$(".desc", this).stop().slideDown('slow');
})
.mouseleave(function() {
$(".desc", this).stop().slideUp('slow');
});
HTML
<div class="pricing3">
<aside>
<i class="fa fa-clock-o"></i>
<h3>1 Hour Pass</h3>
<p class="desc">Description</p>
<p class="pricing">£XX</p>
<button type="submit">BUY NOW</button>
</aside>
<aside class="selected">
<i class="fa fa-clock-o"></i>
<h3>8 Hour Pass</h3>
<p class="desc">Description</p>
<p class="pricing">£XX</p>
<button type="submit">BUY NOW</button>
</aside>
<aside>
<i class="fa fa-clock-o"></i>
<h3>24-7-365 Access</h3>
<p class="desc">Description</p>
<p class="pricing">£XX</p>
<button type="submit">BUY NOW</button>
</aside>
</div>
Need to use event delegation as the target element selection is based on a dynamic state
$(".pricing3").on('mouseenter', 'aside:not(.selected)', function () {
$(".desc", this).stop().slideDown('slow');
}).on('mouseleave', 'aside:not(.selected)', function () {
$(".desc", this).stop().slideUp('slow');
});
It can be written slightly differently as
$(".pricing3").on({
mouseenter: function () {
$(".desc", this).stop().slideDown('slow');
},
mouseleave: function () {
$(".desc", this).stop().slideUp('slow');
}
}, 'aside:not(.selected)')
As the class is shifting you'd be better off checking for the class inside the event handler
$(".pricing3 aside").on('mouseenter mouseleave', function() {
if ( !$(this).hasClass('selected') ) {
$(".desc", this).stop().slideToggle('slow');
}
});
If I understand you correctly, the problem is that you want the .selected to be removed, and then the mouseenter() to run on it as if it hadn't had the .selected on it in the first place?
If this is the case, you should attach your event handler higher up the DOM tree, and use on() to filter down to the <aside>s This way you can flip/flop the class all you want, and the event handler will work as expected. As it's coded, you are only applying the filter at page load, so the state change of the class has no effect.