This question already has answers here:
Event binding on dynamically created elements?
(23 answers)
Closed 5 years ago.
When i add an <li> with the css below i can select the item but if i add it using .append i cannot what am i doing wrong?
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<ul id="events" class="select" name="events">
<li>Opt 1</li>
</ul>
.
$('ul.select li').click(function(e){
var item = $(e.target);
item.addClass('selected');
item.siblings().removeClass('selected');
$('#events').val(item.text());
});
$('#events').append('<li>Opt 2</li>');
.
ul.select {
list-style: none;
margin: 0;
padding: 2px;
height:50vh;
overflow-x: hidden;
border: 1px solid grey;
width: 50vw;
background-color: black;
color: white
}
ul.select li {
padding: 2px 6px;
}
ul.select li:hover {
cursor: pointer;
}
ul.select li.selected {
background-color: lightgrey;
color: black;
}
See Fiddle for example of issue.
$('ul.select li').click will add an event listener to all elements, which match this selector, at the time of the code being called. The following will work:
$('.select').on('click', 'li', function(){
// code here
});
This is called event delegation. Essentially you add an event listener to an element higher up the chain (.select). The click event is then propegated up to the element higher up the chain (.select) and if e.target matches the selector (li), the callback is called.
Related
I have a menu with a list of items created dynamically using javascript.
They have different colour and country attributes created using setAttribute.
$("#menuList a").hover(
function() {
var countryName = $(this).attr('country');
var fruitColour = $(this).attr('colour');
$('#toshow').append($("countryName \n fruitColour"));
},
function() {}
);
.toshow {
display: none;
}
#menuList a:hover div.toshow {
top: 0;
right: 0;
display: block;
position: absolute;
z-index: 99999;
background: red;
width: 200px;
height: 100px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<ul class="menubar" id="menuList">
<li>Watermelon</li>
<li>Grapes</li>
<li>Strawberry</li>
<li>Blueberry</li>
</ul>
<div class="toshow" id="toshow"></div>
Here, I want to have a separated hidden div (display at top right of the page or next to the menuList) that does not have any content until any of the <a> tag being hovered, and show its responding two attributes until no more mouse hovered.
The code does not have errors. But I don't see anything in red when the mouse hovered through the list. Is it possible to achieve what I am looking for?
You can use the mouseout event to hide the toshow div with hide as you leave a list element. And at each hover event, you can change the html of toshow to the values of the li element which the user is hovering over and use show to display it.
Also make sure you attach the event handlers after you've inserted the html of the dynamically generated list.:
function displayGeneratedList() {
$('#menuList').html(`
<li>Watermelon</li>
<li>Grapes</li>
<li>Strawberry</li>
<li>Blueberry</li>
`);
$("#menuList a").hover(function() {
var countryName = $(this).attr('country');
var fruitColour = $(this).attr('colour');
$('#toshow').html(`${countryName}<br>${fruitColour}`).show();
});
$('#menuList a').mouseout(function() {
$('#toshow').hide();
});
}
$(document).ready(function() {
displayGeneratedList();
});
#menuList {
display: inline-block;
}
.toshow {
display: none;
float: right;
background: maroon;
width: 200px;
height: 100px;
padding: 5px;
color: white
}
<ul class="menubar" id="menuList">
</ul>
<div class="toshow" id="toshow"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
I am trying to build a simple dropdown plugin for small project of mine. I do not want to use ready plugins, I want to learn by making one on my own.
html:
<div>
<span class="dropdown_triger">press</span>
<div class="content dropdown-closed">
</div>
</div>
css:
span{
display:inline-block;
background: green;
padding: 5px;
}
.content{
position: absolute;
top: 40px;
border: solid 1px black;
width: 200px;
height: 100px;
}
.dropdown-closed { display: none; }
.dropdown-open { display: block; }
and JS:
$(document).ready(function(){
$('body').on('click', '.dropdown_triger', function(e){
var $wrapper = $(this).parent();
var $content = $(this).next();
var $triger = $(this);
if($triger.hasClass('selected')){
$(document).off('mouseup.dropdownDocClick');
console.log('hasClass');
}
$triger.toggleClass('selected');
$content.toggleClass('dropdown-closed dropdown-open');
$(document).on('mouseup.dropdownDocClick',function (e){
console.log('fire');
if (!$wrapper.is(e.target) && $wrapper.has(e.target).length === 0){
if($content.hasClass('dropdown-open')){
$content.toggleClass('dropdown-closed dropdown-open');
$(document).off('mouseup.dropdownDocClick');
}
}
});
});
});
Everything works except for this place:
if($triger.hasClass('selected')){
$(document).off('mouseup.dropdownDocClick');
console.log('hasClass');
}
I expect that mouseup event would not fire anymore but it does. Here is a fiddle, just try it. If I open dropdown, mouseup event is attached to document and keeps firing until I have clicked outside container thus closed dropdown.
But if I close dropdown by clicking again on triger button(span in my example) event is not removed and I can not understand why?
Below is a simplified version of the input dropdown I am working with.
A basic summary of what it does is: if you focus on the input, a dropdown appears. If you click one of the options in the dropdown, the option populates the input and the dropdown disappears. This is achieved using onfocus and a functions I called dropdown(); and undropdown();.
I'm in a dilemma, where I'm unable to make the dropdown disappear when someone clicks elsewhere. If I use onblur, it successfully hides the dropdown, but if you click on an option it doesn't populate the input, this is because, the onblur function runs first and, therefore, the input(); function doesn't not run because the dropdown is already hidden.
If you put an onclick on the body tag, or other parent, it considers the onfocus as a click, where it run's the dropdown(); function then the undropdown(); function immediately so the dropdown never appears since the functions overlap.
I would appreciate help on figuring out how to order the functions so that they are executed in the right order without overlapping with each other.
JSFiddle available here.
function input(pos) {
var dropdown = document.getElementsByClassName('drop');
var li = dropdown[0].getElementsByTagName("li");
document.getElementsByTagName('input')[0].value = li[pos].innerHTML;
undropdown(0);
}
function dropdown(pos) {
document.getElementsByClassName('content')[pos].style.display = "block"
}
function undropdown(pos) {
document.getElementsByClassName('content')[pos].style.display = "none";
}
.drop {
position: relative;
display: inline-block;
vertical-align: top;
overflow: visible;
}
.content {
display: none;
list-style-type: none;
border: 1px solid #000;
padding: 0;
margin: 0;
position: absolute;
z-index: 2;
width: 100%;
max-height: 190px;
overflow-y: scroll;
}
.content li {
padding: 12px 16px;
display: block;
margin: 0;
}
<div class="drop">
<input type="text" name="class" placeholder="Class" onfocus="dropdown(0)"/>
<ul class="content">
<li onclick="input(0)">Option 1</li>
<li onclick="input(1)">Option 2</li>
<li onclick="input(2)">Option 3</li>
<li onclick="input(3)">Option 4</li>
</ul>
</div>
PS: In addition to the above problem, I would appreciate suggestion for edits to get a better title for this question such that someone experiencing a similar problem could find it more easily.
In this case, On onblur you could call a function which fires the undropdown(0); after a very tiny setTimeout almost instantly. Like so:
function set() {
setTimeout(function(){
undropdown(0);
}, 100);
}
HTML
<input type="text" name="class" placeholder="Class" onfocus="dropdown(0)" onblur="set()" />
No other change is required.
function input(pos) {
var dropdown = document.getElementsByClassName('drop');
var li = dropdown[0].getElementsByTagName("li");
document.getElementsByTagName('input')[0].value = li[pos].innerHTML;
undropdown(0);
}
function dropdown(pos) {
document.getElementsByClassName('content')[pos].style.display= "block"
}
function undropdown(pos) {
document.getElementsByClassName('content')[pos].style.display= "none";
}
function set() {
setTimeout(function(){
undropdown(0);
}, 100);
}
.drop {
position: relative;
display: inline-block;
vertical-align:top;
overflow: visible;
}
.content {
display: none;
list-style-type: none;
border: 1px solid #000;
padding: 0;
margin: 0;
position: absolute;
z-index: 2;
width: 100%;
max-height: 190px;
overflow-y: scroll;
}
.content li {
padding: 12px 16px;
display: block;
margin: 0;
}
<div class="drop">
<input type="text" name="class" placeholder="Class" onfocus="dropdown(0)" onblur="set()" />
<ul class="content">
<li onclick="input(0)">Option 1</li>
<li onclick="input(1)">Option 2</li>
<li onclick="input(2)">Option 3</li>
<li onclick="input(3)">Option 4</li>
</ul>
</div>
You could make the dropdown focusable with tabindex, and in the input's blur event listener only hide the dropdown if the focus didn't go to the dropdown (see When onblur occurs, how can I find out which element focus went to?)
<ul class="content" tabindex="-1"></ul>
input.addEventListener('blur', function(e) {
if(!e.relatedTarget || !e.relatedTarget.classList.contains('content')) {
undropdown(0);
}
});
function input(e) {
var dropdown = document.getElementsByClassName('drop');
var li = dropdown[0].getElementsByTagName("li");
document.getElementsByTagName('input')[0].value = e.target.textContent;
undropdown(0);
}
[].forEach.call(document.getElementsByTagName('li'), function(el) {
el.addEventListener('click', input);
});
function dropdown(pos) {
document.getElementsByClassName('content')[pos].style.display = "block"
}
function undropdown(pos) {
document.getElementsByClassName('content')[pos].style.display = "none";
}
var input = document.getElementsByTagName('input')[0];
input.addEventListener('focus', function(e) {
dropdown(0);
});
input.addEventListener('blur', function(e) {
if(!e.relatedTarget || !e.relatedTarget.classList.contains('content')) {
undropdown(0);
}
});
.drop {
position: relative;
display: inline-block;
vertical-align: top;
overflow: visible;
}
.content {
display: none;
list-style-type: none;
border: 1px solid #000;
padding: 0;
margin: 0;
position: absolute;
z-index: 2;
width: 100%;
max-height: 190px;
overflow-y: scroll;
outline: none;
}
.content li {
padding: 12px 16px;
display: block;
margin: 0;
}
<div class="drop">
<input type="text" name="class" placeholder="Class" />
<ul class="content" tabindex="-1">
<li>Option 1</li>
<li>Option 2</li>
<li>Option 3</li>
<li>Option 4</li>
</ul>
</div>
Accepted answer is a naive and unreliable approach. I had a hard-to-catch bug in a complex application because I used a setTimeout to give ~200ms delay so the browser can process dropdown click before blur event happens. While it worked great on every setup I tested it with, some users did have issues, in particular users with slower machines.
The correct way is to test relatedTarget on focusout event:
input.addEventListener('focusout', function(event) {
if(!isDropdownElement(event.relatedTarget)) {
// hide dropdown
}
});
relatedTarget for focusout contains an element reference, which is receiving focus. This reliably works in every browser I've tested so far (I didn't test IE10 and lower, only IE11 and Edge).
W3Schools has a nice example of how to create a custom dropdown:
https://www.w3schools.com/howto/howto_custom_select.asp
This is how the focus is handled in that example:
A global click-handler handles clicks outside of the dropdown list: document.addEventListener("click", closeAllSelect);. So as soon as the user clicks anywhere in the document, all dropdowns are closed.
But when the user selects an element of the dropdown list, the click-event is stopped by e.stopPropagation(); inside of the selection-handler.
This way, you donĀ“t need the timer workaround.
I'm creating a simple button (sort of) for a user to iterate through a number of selections when clicking "up" or "down".
I'm using jQuery to check after each click that there are more things up (or down) and updating the classes / styles / selections accordingly. However if I change the class of the element that is triggering the "on" function, it is still triggering (on click) even though all the classes specified in the selector are not there (in the DOM) any more.
In this simplified example if you click the "i.up.enabled" element then it's class switches ".up.disabled" and the visible field changes. Fine so far. However, if you click it again then it updates again, which it shouldn't(?) as the selector used to call the 'on' function is "i.up.enabled" and not "i.up.disabled". It's reasonably simple to work round this but I wondered why this is?
Does "on" read from the source rather than the DOM & is there a more accepted way doing this?
HTML
<div class="wrapper">
<div data-state="1">Number 1</div>
<div data-state="0">Number 2</div>
<i class="up enabled">up</i>
</div>
CSS
i {
cursor: pointer;
}
div[data-state="0"] {
display: none;
padding: 0 2rem;
border: 1px solid gray;
}
div[data-state="1"] {
padding: 0 2rem;
border: 1px solid gray;
}
.wrapper > * {
display: inline-block;
font-size: 90%;
}
i.disabled {
color: gray;
cursor: default;
}
i.enabled {
color: blue;
cursor: pointer;
}
JavaScript / jQuery
$('.wrapper i.enabled.up').on('click', function(){
var $current = $(this).siblings('div[data-state="1"]');
var $next = $(this).siblings('div[data-state="0"]');
$current.attr('data-state', 0)
$(this).addClass('disabled').removeClass('enabled');
$next.attr('data-state', 1);
});
And the fiddle is here
N.B. I appreciate that .data() is better for manipulating data-* elements, but due to restrictions I have to use attr("data-*", [value])
Currently what you are using is called a "direct" binding which will only attach to element that exist on the page at the time your code makes the event binding call.
Its does't matter even if selector is modified, Event will still be attached with these elements when using "direct" binding.
You need to use Event Delegation using .on() delegated-events approach, when generating elements dynamically or manipulation selector (like removing and adding classes).
General Syntax
$(staticParentElement).on('event','selector',callback_function)
Example
$('.wrapper').on('click', 'i.enabled.up', function(){
});
DEMO
You can remove the event inside the on function using $(this).off("click");:
$('.wrapper i.enabled.up').on('click', function(e) {
var $current = $(this).siblings('div[data-state="1"]');
var $next = $(this).siblings('div[data-state="0"]');
$current.attr('data-state', 0)
$(this).addClass('disabled').removeClass('enabled');
$next.attr('data-state', 1);
$(this).off("click");
});
i {
cursor: pointer;
}
div[data-state="0"] {
display: none;
padding: 0 2rem;
border: 1px solid gray;
}
div[data-state="1"] {
padding: 0 2rem;
border: 1px solid gray;
}
.wrapper > * {
display: inline-block;
font-size: 90%;
}
i.disabled {
color: gray;
cursor: default;
}
i.enabled {
color: blue;
cursor: pointer;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="wrapper">
<div data-state="1">Number 1</div>
<div data-state="0">Number 2</div>
<i class="up enabled">up</i>
</div>
I am trying to make a Tic-Tac-Toe game and I am currently working on the aspect of selecting the boxes themselves, but while using JQuery the :not selector doesn't seem to be working.
function main(){
//Functions
$('.cell:not(.block)').click(function(){
$(this).addClass(color);
$(this).addClass('block');
if(color=='g'){color='r';}else{color='g';}
});
//Variables
var color = 'g';
}
$().ready(main);
html {
background-color:black;
color:white;
text-align:center;
}
.cell {
border: 1px solid white;
margin:1px;
width:30%;height:30%;
}
.g {background-color:lime;}
.r {background-color:red;}
#board {height:500px;}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<header>Tic Tac Toe</header>
<div id='board'>
<div class='cell'></div>
<div class='cell'></div>
<div class='cell'></div>
</div>
That isn't how jQuery selects elements.
When you run $('selector'), the selector is evaluated immediately, against the current state of the DOM. Your three elements are found because none of them have .block, and click handlers are bound to all three elements.
There are several ways of fixing this:
If you want the selector to be dynamically evaluated, you need to use on to delegate the event to one of the containing elements. The event on the specific child element will bubble up to the containing element's handler and be tested each time against the selector. This is the most expensive option, and probably the least desirable; you shouldn't be relying on jQuery selectors for this kind of logic:
$('.board').on('click', '.cell:not(.block)', function () {
// ...
});
Alternatively, the simplest and cheapest option is to simply check for .block in the click handler:
$('.cell').click(function () {
if ($(this).hasClass('block')) return;
//...
Finally, you can unbind the click handler at the same time you add the .block class
$('.cell').click(function () {
$(this).unbind( "click" );
// ...
Since you are changing the class after already have made the selection it would count as a dynamic selector and you need to use .on() for that.
function main() {
//Functions
$('#board').on('click', '.cell:not(.block)', function() {
$(this).addClass(color).addClass('block');
color = color == 'g' ? 'r' : 'g';
});
//Variables
var color = 'g';
}
$().ready(main);
html {
background-color: black;
color: white;
text-align: center;
}
.cell {
border: 1px solid white;
margin: 1px;
width: 30%;
height: 30%;
}
.g {
background-color: lime;
}
.r {
background-color: red;
}
#board {
height: 500px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<header>Tic Tac Toe</header>
<div id='board'>
<div class='cell'></div>
<div class='cell'></div>
<div class='cell'></div>
</div>