Blur issues on drop-down search box in jQuery - javascript

I've implemented a search box that acts as a filter. When a user clicks in the search area, a drop-down box is displayed showing all of the possible options. Typing in the search box filters the results. Clicking outside of the box hides the results.
It uses the following HTML/CSS heiarchy
&ltdiv class="search">
&ltinput type="text" name="search" value="search" placeholder="search"/>
&ltdiv class="results">
&ltdiv class="result">
Result 1
&lt/div>
&ltdiv class="result">
Result 2
&lt/div>
...
&lt/div>
&lt/div>
I use jQuery to show/hide the dropdown on focus/blur events
var searchBar = {
init : function(){
$('input[name=search]').focus(searchBar.openSearch);
$('input[name=search]').blur(searchBar.closeSearch);
$('div.result').each(function(e){
$(this).click(draftboardDynamic.selectResult);
});
},
openSearch : function(e){
$('div.results').show();
},
closeSearch : function(e){
$('div.results').hide();
},
selectResult : function(e){
console.log($(this));
},
}
$(document).ready(searchBar.init);
This works quite well and I can open, close, and search (JS removed for clarity) without issue. The only bit I'm having trouble with is selecting results. The blur event seems to trigger before result.click events and the handler is never called. I can correct this issue by removing the blur binding, however, I can't figure out how to close my drop-down box when the input loses focus.
Any ideas?

This is a tough one because the .blur event will always fire before the .click. There are two possible solutions, neither of which is particularly desirable:
Unbind the .blur event when hovering over div.result. Rebind it on mouseout.
Instead of doing this with .blur, bind a click event to the document and check that the target is not one of the search components.

Use "mousedown" event instead of "click":
$(".container")
.on("blur focus", "input", function({type}) {
$(this).next().toggle(type === "focusin");
})
.on("mousedown", "ul", function({target: {innerText}}) {
$(this).prev().val(innerText);
});
$(".container")
.on("blur focus", "input", function({type}) {
$(this).next().toggle(type === "focusin");
})
.on("mousedown", "ul", function({target: {innerText}}) {
$(this).prev().val(innerText);
});
.container {
position: relative;
display: inline-block;
}
input, ul {
border: solid 1px black;
}
ul {
list-style: none;
position: absolute;
left: 0;
right: 0;
z-index: -1;
margin: -1px 0 0;
padding: 0;
}
label, li {
cursor: pointer;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<label for="_input"><b>Select value:</b></label>
<div class="container">
<input id="_input">
<ul style="display:none">
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
</div>
Picked a wrong value?

Binding and unbinding the blur event on mouse over / mouse out works. For those interested i've created a fiddle to demonstrate this: https://jsfiddle.net/AdamKMorris/uoqvfy2L/
My example uses basic jquery and .bind/.unbind to toggle the search box:
$("#searchButton").click(function()
{
$("#searchBar").slideToggle();
$("#searchText").focus();
}).mouseover(function()
{
$("#searchText").unbind('blur');
}).mouseout(function()
{
$("#searchText").bind('blur', function()
{
$("#searchBar").slideToggle("fast");
});
});

Related

On element click Trigger event fire twice for input checkbox

when i click on the div element should trigger click on checkbox only once but for some reason i get event fired twice , i saw other topics with similar problem but noone helped me
$('div').click(function(e) {
$('input').trigger('click');
check();
});
function check() {
if ($('input').is(':checked')) {
console.log('input cheked')
} else {
console.log('unchecked')
}
}
.test {
height: 150px;
width: 150px;
background: red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="test">
<input type="checkbox" name="">
</div>
The issue is because the click occurs on the div, which triggers a click on the child checkbox which in turn propagates up the DOM and runs the click handler on the div again.
If you are trying to create a bigger hit-area for the checkbox, just use a label element instead. Then you get this behaviour for free without needing any JS.
If you want to know the state of a checkbox when it's changed, hook a change event handler to it. Try this:
$(':checkbox').on('change', function() {
if (this.checked) {
console.log('input checked')
} else {
console.log('unchecked')
}
});
.test {
height: 150px;
width: 150px;
background: red;
display: block;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<label class="test">
<input type="checkbox" name="">
</label>
That is because, when you are trigerring click on the input the event is being bubbles to all its parents. To stop that use e.stopPropagation on the click event handler of input.
$('input').click(function(e) {
e.stopPropagation();
});
Read more about bubbling and capturing here.
Check the working code below:
$('div').click(function(e) {
$('input').trigger('click');
check();
});
$('input').click(function(e) {
e.stopPropagation();
});
function check() {
if ($('input').is(':checked')) {
console.log('input cheked')
} else {
console.log('unchecked')
}
}
.test {
height: 150px;
width: 150px;
background: red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="test">
<input type="checkbox" name="">
</div>
Your checkbox is inside div and you are binding click evet to div and from that you are triggering checkbox click event which again triggers click of div. That's why it's triggering 2 times.
You can directly go for checkbox change event:
$(':checkbox').on('change', function() {
if (this.checked) {
console.log('input checked')
} else {
console.log('unchecked')
}
});
.test{
height: 150px;
width: 150px;
background: red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="test">
<input type="checkbox" name="">
</div>
JQuery uses event bubbling when setting up events. This means that when you click the input the event is fired once for the input and then it 'bubbles' up the DOM tree to the parent DIV. This then notices the click event on the DIV and fires again. Therefore the event fires twice, once for the input and again for the DIV.
To stop this you will need to use the 'capture' technique instead of event bubbling. This would mean that you would use addEventListener and pass in the option as the third argument as true.
See here: https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener
Also see here to understand bubbling vs capturing: https://javascript.info/bubbling-and-capturing

How to make sure clickable objects don't propagate to the wrong element?

Languages involved: HTML, CSS, JS
Context: I'm relatively new to web development. I have two elements overlapping each other. One is a slider, one is a div. The slider is on top of the div.
Code snippets:
<div id="myDiv">
<input id="mySlider" type="range" min=1 max=100 step=1>
</div>
and
initListeners() {
document.getElementById("myDiv").addEventListener("click", divFunction);
document.getElementById("mySlider").addEventListener("input", sliderFunction);
}
I need to make it that when you click the slider, it doesn't click the div. How would I go about doing that? I've tried z-index, but that doesn't seem to change anything.
Thanks in advance!
As I'm sure you've figured out by now, events in JavaScript by default bubble up from a child to a parent. You need to stop that from happening at the child level, also known as preventing propagation.
Using the stopPropagation function, you can handle this as follows:
function sliderFunction(e) {
e.stopPropagation();
}
Simple. That event will no longer reach the parent.
EDIT
While stop propagation is the correct method to use, event listeners must also match in type. Therefore, both the slider and the parent DIV must have click event listeners (instead of input and click). stopPropagation stops propagation of a specific type of event.
function divFunction() {
console.log('DIV clicked!');
}
function sliderFunction(event) {
event.stopPropagation();
console.log('Slider clicked!');
}
function initListeners() {
document.getElementById('myDiv').addEventListener('click', divFunction);
document.getElementById('mySlider').addEventListener('click', sliderFunction);
}
initListeners();
/* unnecessary visual aides */
body *:not(label) {
padding: 2rem;
outline: 1px solid red;
position: relative;
}
label {
display: inline-block;
position: absolute;
background: #222;
color: #fff;
top: 0; left: 0;
}
<div id="myDiv">
<label>#myDiv</label>
<div id="tools">
<label>#tools</label>
<input type="range" id="mySlider">
</div>
</div>
You can also check the target once you fire that click event. I've used this approach before:
JSFiddle: http://jsfiddle.net/L4ck7ygo/1/
function divFunction(e) {
if (e.target !== this) {
return;
} else {
console.log('hit');
}
}
When the fiddle first loads, click the slider and you'll see the console log out some text. To see it work, remove the line that is being pointed to and rerun the fiddle. Now when you click the slider, you won't see anything logged in the console, but if you click on the div and not the slider, it will log to the console.
function initListeners() {
document.getElementById("myDiv").addEventListener("click", divFunction);
document.getElementById("mySlider").addEventListener("input", sliderFunction);
}
initListeners();
function divFunction(e) {
console.log('Firing...') // <-- This will log on any click
if (e.target !== this) {
return;
} else {
console.log('hit'); // <-- This will NOT log except for div click
}
}
function sliderFunction() {
console.log('Doing stuffs...');
}
<div id="myDiv">
<input id="mySlider" type="range" min=1 max=100 step=1>
</div>
UPDATE: Stupidity on my part. I had the ordering wrong for the elements which caused propagation to not act as intended.

How can I let people drag and drop LI's to a specific place in a list? (console.log issues)

I'm facing two issues, one of which is a goal and one of which is failure to obtain information from console.log() calls.
Let's look at the goal first:
I have a JSfiddle at https://jsfiddle.net/L7sbhzzj/ for a project I'm working on. The code I'm working on is:
jQuery('.collection').droppable(
{
'accept': '.collection > li',
'drop': function(e, ui)
{
console.log('Reached here!');
console.log(this);
jQuery('#selection').append(ui.draggable);
}
});
Essentially, I want people to be able to drag and drop elements to a specific position on a list. So, in the JSfiddle, you can drag "Fiction" to the right, but if you do that and then drag over "Nonfiction", "Nonfiction" is only added to the end; you cannot subsequently drag "Nonfiction" to be above and before "Fiction".
I'd like to avoid reinventing the wheel on this; I imagine there is some standard pattern like "Put the LI being dropped immediately before the target UL's LI that has the lowest distance to the top of the page but is still equal or greater than the dropped LI's distance to the top of the page."
Now to return to the other issue I'm facing: either jQuery('#selection').append(ui.draggable); is working or something else is doing equivalent work, but none of my console.log()s seem to be reported.
I have some ideas for how to implement something, but it would be nice to introspect and obtain console.log()s' diagnostic output while I'm working on things.
Is the 'drop' taking effect at all? Is it working some other way? How can I get things like what element the object was dropped to?
--EDIT--
I realized that I'd asked for one side of what I want to. I'd like people to be able to take item XYZ from one container to another and then back if they change their mind. Snowmonkey's solution worked beautifully for the right list being sortable. However, the following adaptation failed, perhaps because I called two incompatible enhancements:
jQuery(function()
{
jQuery('#selection').sortable(
{
});
jQuery('li', jQuery('#source')).draggable(
{
'connectToSortable': '#selection',
'cancel': 'a.ui-icon',
'revert': 'invalid',
'containment': 'document',
'cursor': 'move'
});
jQuery('#source').sortable(
{
});
jQuery('li', jQuery('#selection')).draggable(
{
'connectToSortable': '#selection',
'cancel': 'a.ui-icon',
'revert': 'invalid',
'containment': 'document',
'cursor': 'move'
});
How can I get a fully two-sided variation of Snowmonkey's answer?
Thanks,
So you want to connect a draggable to a sortable? Here's a simple way. Get rid of the droppable altogether, and simply add the 'connectToSortable' rule to your draggable. Thus, #selection remains a sortable and #source remains a draggable, that can drop into any position on #source.
jQuery(function() {
$(".collection").sortable({
});
jQuery('li', jQuery('.collection')).draggable({
'connectToSortable': '.collection',
'cancel': 'a.ui-icon',
'revert': 'invalid',
'containment': 'document',
'cursor': 'move'
});
});
body {
background-color: #ccc;
font-family: Verdana, Georgia;
}
div.main {
margin-left: auto;
margin-right: auto;
width: 440px;
}
div.table {
display: table;
width: 440px;
}
div.td {
display: table-cell;
width: 50%;
}
div.tr {
display: table-row;
}
ul.collection {
background-color: white;
list-style: none;
padding-left: 0;
min-height: 100px;
padding-left: 0;
width: 200px;
}
ul.collection > li {}
ul#selection {
float: right;
}
ul#source {
float: left;
}
<link href="https://code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css" rel="stylesheet"/>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>
<div class="main">
<div class="table">
<div class="tr">
<div class="td">
<ul id="source" class="collection">
<li>Everything</li>
<li>Nonfiction</li>
<li>Fiction</li>
<li>Poetry</li>
</ul>
</div>
<div class="td">
<ul id="selection" class="collection">
<li>Empty</li>
</ul>
</div>
</div>
</div>
</div>
So i updated the above post to make it work in both directions. Rather than using the ID for the two elements, I used the class that they have in common. Both .collection elements are sortable, and both connectToSortable .collection elements. Hope it helps!
You can achieve what you want just using jquery ui sortable. If you want to be notified whenever an item added to selection, you can listen for receive event.
jQuery(function()
{
jQuery( "#source" ).sortable({
connectWith: "#selection",
}).disableSelection();
jQuery("#selection").sortable({
receive: function() {
console.log("changed");
}
})
});

Click event not emitted when mousedown/mouseup do add/removeClass on element

I'm attaching mousedown, mouseup and click handlers to an element. On mousedown I add a class to the element, on mouseup I remove the class, and on click I do some work. (This is a simplification of the context. In my project the click event is handled by a 3rd party component.)
The problem I'm having is that the click event is never emitted in Safari and Firefox, but it works just fine in Chrome. (I don't know what IE does. I don't have access to it, and don't care about it.)
The code is as follows:
HTML:
<div id="clickme">
<div class="normal"></div>
<div class="highlight"></div>
</div>
<input type="text" id="textinput"/>
CSS:
#clickme:not(.active) > .highlight {
display: none;
}
#clickme.active > .normal {
display: none;
}
.normal, .highlight {
width: 100px;
height: 100px;
}
.normal {
background: blue;
}
.highlight {
background: red;
}
JS:
var clickme = $('#clickme');
var textinput = $('#textinput');
clickme.on('mousedown', function(e) {
clickme.addClass('active');
// ^-- comment this out and the click event starts working
});
clickme.on('mouseup', function(e) {
clickme.removeClass('active');
// ^-- comment this out and the click event starts working after the second click
});
clickme.on('click', function(e) {
e.preventDefault();
textinput.val(Date.now());
});
JSFiddle: https://jsfiddle.net/xLskk3po/14/
JSFiddle without JQuery: https://jsfiddle.net/xLskk3po/15/ It shows that it's not a JQuery problem.
I stumbled upon this SO question: When a mousedown and mouseup event don't equal a click and it looks like my issue is similar to that. So I did something silly: I put a transparent, absolutely positioned element on top.
HTML:
<div id="clickme">
<div class="normal"></div>
<div class="highlight"></div>
<div class="abs"></div> <!-- this is the absolute element, covering #clickme -->
</div>
<input type="text" id="textinput"/>
That fixed it.

Chrome: Recover document focus after jquery fadeOut

I'm trying to have an overlay that I can toggle with keyboard keys.
However, once I hide the menu using fade out, the document won't receive my keydown events until I click in the window. How can I make the document receive focus so that it will listen directly after fade out has finished?
<div id="overlay" class="overlay">
<input type="text" value="test"/>
</div>
$('#overlay').on('keydown', function() {
$('#overlay').fadeOut(1000);
return false;
});
$(document).on('keydown', function() {
$('#overlay').fadeIn(1000);
return false;
});
.overlay {
position: fixed;
background: black;
color: white;
width: 50%;
height: 100px;
}
See jsFiddle also
Put cursor in input field, press one key.
It should fade out over a second. After that document is not receiving any keydown until I click in it with the mouse. How can I make document receive focus so that I could toggle the modes with just one keyboard key?
Edit: Tested the fiddle in different browsers. This problem seems to be specific for chrome.
Remove focus from your text field after fading out your overlay
Working Fiddle
$(document).ready(function () {
$('#overlay').on('keyup', function () {
$('#overlay').fadeOut(1000);
$("input[type='text']").blur();
return false;
});
$('body').on('keyup', function () {
$('#overlay').fadeIn(1000);
return false;
});
});

Categories