jQuery 'on' click not checking for updated selector? - javascript

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>

Related

How do i toggle between multiple classes for multiple elements in jQuery?

So I'm trying to implement a set of functions on my website with multiple div objects in it, so that when I click on Div A, it sets the text color of the page to red through Class A, and when i click on Div B, it sets the text to green through Class B, and so on and so forth.
My issue is that the other classes don't unset when clicking multiple objects and one class overrides the others, so the color of the text won't switch anymore.
I've been looking for solutions and trying to use addClass() and removeClass(), but it doesn't work for some reason. Here is a snippet of my code here
$(function() {
$('.one').click(function() {
$("h1").addClass('onetxt');
$("h1").removeClass('twotxt, threetxt');
});
});
$(function() {
$('.two').click(function() {
$("h1").addClass('twotxt');
$("h1").removeClass('onetxt, threetxt');
});
});
$(function() {
$('.three').click(function() {
$("h1").addClass('threetxt');
$("h1").removeClass('onetxt, twotxt');
});
});
h1 {
text-align: center;
}
div {
height: 65px;
width: 65px;
margin: 20px auto;
border-style: solid;
}
/*style info, ignore above here*/
.one {
background-color: red;
}
.onetxt {
color: red;
}
.two {
background-color: green;
}
.twotxt {
color: green;
}
.three {
background-color: blue;
}
.threetxt {
color: blue;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<h1>Sample Text</h1>
<div class="one"></div>
<div class="two"></div>
<div class="three"></div>
Any help on this would be greatly appreciated, and if you need more info, ask me in the replies, thank you!
EDIT: Here's a JSFiddle link demonstrating the code that i currently have, my intention is for all three of the DIV elements to change the top text's color when selected in any order, with using the classes if possible.
It seems like the main issue is that removeClass doesn't support multiple class selectors (like 'onetxt, twotxt'). Also you aren't removing all possible classes depending on the order of clicks.
Here's a solution that might work. I've written some helper functions which hopefully clarify what's going on.
const targets = 'h1, p'
const classmap = {
one: 'onetxt',
two: 'twotxt',
three: 'threetxt'
}
const allclasses = Object.values(classmap);
function clearSelection() {
allclasses.forEach(function(clz) { $(targets).removeClass(clz) });
}
function addSelection(sel) {
$(targets).addClass(classmap[sel]);
}
$(function() {
$('.one').click(function(){
clearSelection();
addSelection('one')
});
});
$(function() {
$('.two').click(function(){
clearSelection();
addSelection('two')
});
});
$(function() {
$('.three').click(function(){
clearSelection();
addSelection('three')
});
});
Here's a vanilla DOM API solution based on classList.toggle(className: string, force: boolean). The second parameter controls whether toggle works as remove or add.
const classes = ['one', 'two', 'three'];
classes.forEach(clazz => {
document.querySelector(`.${clazz}`).addEventListener('click', () => {
classes.forEach(cl => document.querySelector('h1').classList.toggle(`${cl}txt`, cl === clazz));
})
})
h1 {
text-align: center;
}
div {
height: 35px;
width: 35px;
margin: 5px auto;
border-style: solid;
}
/*style info, ignore above here*/
.one {
background-color: red;
}
.onetxt {
color: red;
}
.two {
background-color: green;
}
.twotxt {
color: green;
}
.three {
background-color: blue;
}
.threetxt {
color: blue;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<h1>Sample Text</h1>
<div class="one"></div>
<div class="two"></div>
<div class="three"></div>
There is no .removeClass() overload supports multiple argument, it only remove the first class. .removeClass() with no argument remove all the classes. This maybe the one you need.
I tweak your code a little bit, including:
Rename some class name. .color-palette is the container of all color selection. .color is individual color choice box.
Add data-color-name attribute to .color. The attribute will be used for sample-text css class assignment.
Simplify the click event with a single, event-delegate handler. I try to decouple the add/remove class logic with the actual color name. This way if you have more color boxes to add, you do not need to copy a new set of function.
Define custom css property. E.g. (--color-1, --color-2). The same property is used for color box background and sample text font color. You don’t have to maintain colors in two different place.
$('.color-palette').on('click', '.color', function(e) {
$("#sample-text").removeClass().addClass($(e.currentTarget).data('color-name'));
});
/* maintain the color choices here */
:root {
--color-1: red;
--color-2: green;
--color-3: blue;
}
h1 {
text-align: center;
}
.color {
height: 65px;
width: 65px;
margin: 20px auto;
border-style: solid;
}
/* style info, ignore above here */
/* color palette style */
.color.color-1 {
background-color: var(--color-1);
}
.color.color-2 {
background-color: var(--color-2);
}
.color.color-3 {
background-color: var(--color-3);
}
/* sample text style */
#sample-text.color-1 {
color: var(--color-1);
}
#sample-text.color-2 {
color: var(--color-2);
}
#sample-text.color-3 {
color: var(--color-3);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<h1 id="sample-text">Sample Text</h1>
<div class="color-palette">
<div class="color color-1" data-color-name="color-1"></div>
<div class="color color-2" data-color-name="color-2"></div>
<div class="color color-3" data-color-name="color-3"></div>
</div>

Change class value javascript in event onclick?

hello I need to do it only in javascript (not in jquery)
How would you do something equivalent in javascript something like this in query?
<body>
<div class="wysiwyg">text......text...</div>
<div class="btn btn-primary verMas">Ver más</div>
<script>
$('.verMas').click(function(e){
e.preventDefault();
$('.wysiwyg').css('height','auto');
$(this).hide();
$('.layerMiddle').hide();
});
</script></body>
Your example suggests that you need to be able to
Select Items from the DOM
Add an on-click event listener
Modify the display state of elements
Looking at how you might achieve these in native JS then I would suggest the following:
document.querySelector()
This provides some similar functionality to JQuery's selectors - at least for this use case.
element.addEventListener('click', function)
This provides equivalent functionality as JQuery's $('').click(function)
element.style
This enables modification of the in-line style of the element so setting the style of display to none can achieve this
Putting these all together:
// use querySelector to get the .verMas element and add on-click event listener
document.querySelector('.verMas').addEventListener('click', e => {
// prevent default event
e.preventDefault()
// use querySelector to get the .wysiwyg element
// set the style.height parameter to auto
document.querySelector('.wysiwyg').style.height = 'auto'
// e.target is the element that this event was fired on
// to hide it, set the style.display parameter to none
e.target.style.display = 'none'
// use querySelector to get the .layerMiddle element
//hide the layerMiddle Element
document.querySelector('.layerMiddle').style.display = 'none'
})
div {
padding: 10px;
}
.btn{
display: inline-block;
padding: 6px 12px;
text-align: center;
white-space: nowrap;
vertical-align: middle;
cursor: pointer;
border: 1px solid transparent;
border-radius: 4px;
color: #333;
background-color: #fff;
border-color: #ccc;
}
<div class="wysiwyg">text......text...</div>
<div class="layerMiddle">[Layer Middle]</div>
<div class="btn btn-primary verMas">Ver más</div>

Trying to change div content, but Jquery event is not working properly

I am trying to change content in the div, when user typing in other div. Everything works fine: JSFiddle
But when I insert these divs inside another div (I insert these divs inside HTML-editor that is editable div) - it is not working at all.
How can it block this?
$(document).keyup(function() {
var add_title = $("#add_title").html();
$("#title").html(add_title);
});
* {
box-sizing: border-box;
}
.input {
display: inline-block;
min-width: 400px;
min-height: 40px;
padding: 10px;
border: 1px solid blue;
}
.title {
margin-bottom: 10px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id=title class=title>default</div>
<div id=add_title class=input contenteditable=true></div>
Try this.
$(document).on('keyup','.input',function () {
var add_title = $(this).html();
$( "#title" ).html(add_title);
});
It seems like you are adding dynamic elements to the page and for those elements it is not working. if that is the issue then you have to look into event delegation a bit more. The above code will hopefully fix your issue.

"between" CSS/jQuery selector

The problem
Given a jQuery selection of one element (.context), how can I select into it:
the elements that are not child/grandchild of a specific class (e.g. .paragraph) [the class can have deeper nested levels of itself, like .paragraph .paragraph]
the child/grandchild elements with a certain set of tags (e.g. strong | i)
Notes
.context can be descendant of another .context or another .paragraph.
the elements I want to selects can be identified by [data-hint^="I want"] selector (obviously the data attribute is not present in the real scenario).
I don't want just the direct children of .context but also the descendants (obviously filtering away the elements contained in .context .paragraph.
The battle field
$selection = $('.context').first();
$formatting_elements = $selection.find('strong, i')
.not('.paragraph *');
.paragraph {
margin: 15px;
position: relative;
border: 1px solid black;
}
.paragraph .paragraph {
border: 1px solid #444;
}
.paragraph .paragraph .paragraph {
border: 1px solid #888;
}
.context {
margin: 15px;
position: relative;
background-color: limegreen;
}
[data-hint^="I want"] {
background-color: violet;
}
.paragraph:before {
content: '-paragraph-';
position: absolute;
bottom: 100%;
left: 200px;
}
.context:before {
content: '-context-';
position: absolute;
bottom: 100%;
left: 200px;
color: green;
}
.context .paragraph:before {
font-style: italic;
color: #444;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script>
<div class="paragraph">
<i>bar</i>
<div class="paragraph">
<strong>foo</strong>
<i>bar</i>
<div class="context" data-hint="Find elements relative to this element">
<i data-hint="I want to get this">foo</i>
<div class="paragraph">
<i>bar</i>
<div class="paragraph">
<strong>foo</strong>
<div class="paragraph">
<strong>foo</strong>
<i>bar</i>
</div>
<i>bar</i>
</div>
</div>
<strong data-hint="I want to get this">foo</strong>
<i data-hint="I want to get this">bar</i>
</div>
</div>
</div>
I tried the above script but it doesn't seem to work.
The goal
is being able to select and change yellow elements to be violet.
Edit
Sorry, misunderstood your question therefore re-writing the entire answer.
What you are trying to achieve here can be done using a custom filter function.
The theory is simple, you select all the elements that meet a specific criteria (including the one's that are children/grandchildren of some specific selector), then you filter your set out given the parent/grandparent criteria
var myElements = $('strong, i').filter(
function() {
return $(this).parents('.context').length < 1;
});
See working fiddle here
UPDATE
In light of your comment, I have made the following changes to the fiddle. I hope this is what you are looking for.
var myElements = $('strong, i', '.context').filter(
function() {
return $(this).parent('.context .paragraph').length < 1;
});
See updated fiddle here

Can't Use :not() Selector

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>

Categories