How to toggle checkbox selection rather than only selecting? - javascript

I currently am using this JavaScript code snippet to select 3 checkboxes at a time
$(document).ready(function() {
var $cbs = $('input:checkbox[name="select[]"]'),
$links = $('a[name="check"]');
$links.click(function() {
var start = $links.index(this) * 3,
end = start + 3;
$cbs.slice(start,end).prop("checked",true);
});
});
Currently this code only selects the checkboxes, however I was wondering if anyone knew how to modify it so that it toggles the checkbox selection on and off?
Here's an example of my current code: "jsfiddle" - click the 1-3, 4-6 links etc to check the checkboxes.

Make the second argument to the prop("checked", ...) call depend on the "checked" status of the first (or other) checkbox in the slice:
// ...
$cbs.slice(start,end).prop("checked", !$cbs.slice(start).prop("checked"));
Here's an updated jsFiddle.
[Edit] Or to update each checkbox in the slice individually:
// ...
$cbs.slice(start,end).each(function() {
var $this = $(this);
$this.prop("checked", !$this.prop("checked"));
});
http://jsfiddle.net/ShZNF/3/

$cbs.slice(start,end).each(function() {
if ($(this).is(":checked")) {
$(this).removeProp("checked");
} else {
$(this).prop("checked",true);
}
});
http://jsfiddle.net/ShZNF/1/
Edit: Maeric's solution is better. I wasn't aware removeProp had this gotcha:
Note: Do not use this method to remove native properties such as
checked, disabled, or selected. This will remove the property
completely and, once removed, cannot be added again to element. Use
.prop() to set these properties to false instead.

Related

Jquery checkbox affect one item not all items

I was wondering if someone can help me please, I have a series of checkboxes that when clicked change the div background, activate 2 inputs and add a tick icon. My issue is that when one check box is checked the class .TickIco shows for all and so does the .disableToggle
How can i get it so that this only affects one .checkBG at a time and not all of them?
Hopefully this JSFiddle will help explain what I mean.
https://jsfiddle.net/jayjay89/xfg96we5/
thanks
$(".checkBG").click(function () {
var checked = $(this).is(':checked');
var location = $(this).parent().parent().parent();
if (checked) {
$(this).parent().parent().parent().addClass("activeformBlock");
$(".tickIco").show();
$(".disabletoggle").removeAttr("disabled");
} else {
$(this).parent().parent().parent().removeClass("activeformBlock");
$(".tickIco").hide();
$(".disabletoggle").attr('disabled', 'disabled');
}
});
thanks
you can use the context in which the selector will be looked.
You already have the location variable which is the parent context for one of your row
$(".checkBG").click(function () {
var checked = $(this).is(':checked');
var location = $(this).parent().parent().parent();
if (checked) {
$(this,location).parent().parent().parent().addClass("activeformBlock");
$(".tickIco",location).show();
$(".disabletoggle",location).removeAttr("disabled");
} else {
$(this,location).parent().parent().parent().removeClass("activeformBlock");
$(".tickIco",location).hide();
$(".disabletoggle",location).attr('disabled', 'disabled');
}
});
Your issue lies in the way you are selecting the .tickIco and .disabletoggle elements:
$(".tickIco").show();
$(".disabletoggle").removeAttr("disabled");
These jquery calls use selectors that match all classes of .tickIco and .disabletoggle.
Dirty solution (finds elements of the parent with matching classes using .find()):
$(this).parent().parent().parent().find(".tickIco").show();
$(this).parent().parent().parent().find('.disabletoggle').removeAttr("disabled")
Better solution:
jQuery selecter takes the context of your selection as a second argument so you can:
var context = $(this).parent().parent().parent();
$(".tickIco", context).show();
$('.disabletoggle', context).removeAttr("disabled")

Bootstrap form group checkboxes doesn't get checked in between nav pills

Here's the JSFiddle of my work: https://jsfiddle.net/pb23Ljd8/5/
I use Bootstrap nav-pills to show all products and categorized too like this:
And I based my checkboxes from here: http://bootsnipp.com/snippets/featured/fancy-bootstrap-checkboxes
I count the number of products checked in between the tabs like this:
jQuery(document).ready(function($) {
jQuery(".select-product").change(function() {
jQuery(".counter").text(jQuery("[type='checkbox']:checked").length);
});
});
But the glyphicon check icons doesn't appear on the second and third tabs for some reason. But when I click the products on the second and third, it increases the counter and also when I view it on the first tab, it is checked.
I just need the products to also be visibly checked on the second and third tabs and not only on the first one so it's not confusing for the user.
Ideas, anyone?
Edit: I fetch the list of products from CMS so it's dynamic. I now understand that the duplication of IDs is causing the problem.
Before we try and resolve this issues, we should break it down and see what the actual problem is.
First, let's check if we remove the content from tab 1b is the issue still present?
Nope, if we remove the checkboxes from the first tab, the checkboxes function normally on the second and third.
Fiddle #1
What if we change the id of the checkboxes (remember ids should be unique).
Notice how Book #1 now works if we change the first checkbox's id to 1a.
Fiddle #2
So now we "know" the issue is likely due to the fact that we are using checkboxes with the same id value (ref). The "issue" is now:
How do we check multiple checkboxes if one is checked
(or something like that)
Here's what I would do:
assign all "like" checkboxes the same class (ex. Book #1 checkboxes will have class b1)
use jQuery/javascript to make sure all that all "like" checkboxes, check and uncheck in unison
Working Example
EDIT
Dynamic values for the classes can be achieved by putting the IDs as classes so the similar products would match. These can be passed to JS like this assuming that $products_id_array is a PHP array that contains all the classes needed.
var productIDs = <?php echo json_encode($products_id_array) ?>;
and then creating the snippet of jQuery code on the fiddle like this
productIDs.forEach(function(val, key) {
jQuery('.' + val).on('change', function(){
jQuery('.' + val).prop('checked',this.checked);
});
})
Try this JS, This will work
jQuery(".select-product").change(function() {
var checkValue = jQuery(this).prop('checked');
$('.select-product#' + jQuery(this)[0].id).each(function() {
if (checkValue == true) {
jQuery(this).prop('checked', true)
} else {
jQuery(this).prop('checked', false);
}
});
var uniqueId = [];
jQuery("[type='checkbox']:checked").each(function() {
uniqueId.push(jQuery(this)[0].id);
});
Array.prototype.getUnique = function() {
var u = {},
a = [];
for (var i = 0, l = this.length; i < l; ++i) {
if (u.hasOwnProperty(this[i])) {
continue;
}
a.push(this[i]);
u[this[i]] = 1;
}
return a;
}
jQuery(".counter").text(uniqueId.getUnique().length);
});

Why select option value is not selected after clone it? [duplicate]

I didn't expect it but the following test fails on the cloned value check:
test("clone should retain values of select", function() {
var select = $("<select>").append($("<option>")
.val("1"))
.append($("<option>")
.val("2"));
$(select).val("2");
equals($(select).find("option:selected").val(), "2", "expect 2");
var clone = $(select).clone();
equals($(clone).find("option:selected").val(), "2", "expect 2");
});
Is this right?
After further research I found this ticket in the JQuery bug tracker system which explains the bug and provides a work around. Apparently, it is too expensive to clone the select values so they won't fix it.
https://bugs.jquery.com/ticket/1294
My use of the clone method was in a generic method where anything might be cloned so I'm not sure when or if there will be a select to set the value on. So I added the following:
var selects = $(cloneSourceId).find("select");
$(selects).each(function(i) {
var select = this;
$(clone).find("select").eq(i).val($(select).val());
});
Here's a fixed version of the clone method for jQuery:
https://github.com/spencertipping/jquery.fix.clone
// Textarea and select clone() bug workaround | Spencer Tipping
// Licensed under the terms of the MIT source code license
// Motivation.
// jQuery's clone() method works in most cases, but it fails to copy the value of textareas and select elements. This patch replaces jQuery's clone() method with a wrapper that fills in the
// values after the fact.
// An interesting error case submitted by Piotr PrzybyƂ: If two <select> options had the same value, the clone() method would select the wrong one in the cloned box. The fix, suggested by Piotr
// and implemented here, is to use the selectedIndex property on the <select> box itself rather than relying on jQuery's value-based val().
(function (original) {
jQuery.fn.clone = function () {
var result = original.apply(this, arguments),
my_textareas = this.find('textarea').add(this.filter('textarea')),
result_textareas = result.find('textarea').add(result.filter('textarea')),
my_selects = this.find('select').add(this.filter('select')),
result_selects = result.find('select').add(result.filter('select'));
for (var i = 0, l = my_textareas.length; i < l; ++i) $(result_textareas[i]).val($(my_textareas[i]).val());
for (var i = 0, l = my_selects.length; i < l; ++i) result_selects[i].selectedIndex = my_selects[i].selectedIndex;
return result;
};
}) (jQuery.fn.clone);
Made a plugin out of chief7's answer:
(function($,undefined) {
$.fn.cloneSelects = function(withDataAndEvents, deepWithDataAndEvents) {
var $clone = this.clone(withDataAndEvents, deepWithDataAndEvents);
var $origSelects = $('select', this);
var $clonedSelects = $('select', $clone);
$origSelects.each(function(i) {
$clonedSelects.eq(i).val($(this).val());
});
return $clone;
}
})(jQuery);
Only tested it briefly, but it seems to work.
My approach is a little different.
Instead of modifying selects during cloning, I'm just watching every select on page for change event, and then, if value is changed I add needed selected attribute to selected <option> so it becomes <option selected="selected">. As selection is now marked in <option>'s markup, it will be passed when you'll .clone() it.
The only code you need:
//when ANY select on page changes its value
$(document).on("change", "select", function(){
var val = $(this).val(); //get new value
//find selected option
$("option", this).removeAttr("selected").filter(function(){
return $(this).attr("value") == val;
}).first().attr("selected", "selected"); //add selected attribute to selected option
});
And now, you can copy select any way you want and it'll have it's value copied too.
$("#my-select").clone(); //will have selected value copied
I think this solution is less custom so you don't need to worry if your code will break if you'll modify something later.
If you don't want it to be applied to every select on page, you can change selector on the first line like:
$(document).on("change", "select.select-to-watch", function(){
Simplification of chief7's answer:
var cloned_form = original_form.clone()
original_form.find('select').each(function(i) {
cloned_form.find('select').eq(i).val($(this).val())
})
Again, here's the jQuery ticket: http://bugs.jquery.com/ticket/1294
Yes. This is because the 'selected' property of a 'select' DOM node differs from the 'selected' attribute of the options. jQuery does not modify the options' attributes in any way.
Try this instead:
$('option', select).get(1).setAttribute('selected', 'selected');
// starting from 0 ^
If you're really interested in how the val function works, you may want to examine
alert($.fn.val)
Cloning a <select> does not copy the value= property on <option>s. So Mark's plugin does not work in all cases.
To fix, do this before cloning the <select> values:
var $origOpts = $('option', this);
var $clonedOpts = $('option', $clone);
$origOpts.each(function(i) {
$clonedOpts.eq(i).val($(this).val());
});
A different way to clone which <select> option is selected, in jQuery 1.6.1+...
// instead of:
$clonedSelects.eq(i).val($(this).val());
// use this:
$clonedSelects.eq(i).prop('selectedIndex', $(this).prop('selectedIndex'));
The latter allows you to set the <option> values after setting the selectedIndex.
$(document).on("change", "select", function(){
original = $("#original");
clone = $(original.clone());
clone.find("select").val(original.find("select").val());
});
If you just need the value of the select, to serialize the form or something like it, this works for me:
$clonedForm.find('theselect').val($origForm.find('theselect').val());
After 1 hour of trying different solutions that didn't work, I did create this simple solution
$clonedItem.find('select option').removeAttr('selected');
$clonedItem.find('select option[value="' + $originaItem.find('select').val() + '"]').attr('selected', 'true');
#pie6k show an good idea.
It solved my problem. I change it a little small:
$(document).on("change", "select", function(){
var val = $(this).val();
$(this).find("option[value=" + val + "]").attr("selected",true);
});
just reporting back. For some godly unknown reason, and even though this was the first thing I tested, and I haven't changed my code whatsoever, now the
$("#selectTipoIntervencion1").val($("#selectTipoIntervencion0").val());
approach is working. I have no idea why or if it will stop working again as soon as I change something, but I'm gonna go with this for now. Thanks everybody for the help!

Modify function to filter by several data-attributes simultaneously

The function below allows users to filter products by data-attributes, and accommodates filtering by multiple values simultaneously. It does this by creating an array of the values selected, and when any of the values are clicked (in this case checked/unchecked) it hides all the items and then re-shows those that match the values in the updated array.
It works correctly when filtering for one data-attribute, but when combined to filter by more than one attribute it no longer shows all results matching any of the values and instead only shows results matching all the specified values.
I've posted a fiddle which demonstrates the problem here: http://jsfiddle.net/chayacooper/WZpMh/94/ All but one of the items have the values of both data-style="V-Neck" and data-color="Black" and they should therefore remain visible if either of the filters are selected, but if another value from a different data-attribute some of the items are hidden.
$(document).ready(function () {
var selected = [];
$('#attributes-Colors *').click(function () {
var attrColor = $(this).data('color');
var $this = $(this);
if ($this.parent().hasClass("active")) {
$this.parent().removeClass("active");
selected.splice(selected.indexOf(attrColor),1);
}
else {
$this.parent().addClass("active");
selected.push(attrColor);
}
$("#content").find("*").hide();
$.each(selected, function(index,item) {
$('#content').find('[data-color *="' + item + '"]').show();
});
return false;
});
$('#attributes-Silhouettes *').click(function () {
var attrStyle = $(this).data('style');
var $this = $(this);
if ($this.parent().hasClass("active")) {
$this.parent().removeClass("active");
selected.splice(selected.indexOf(attrStyle),1);
}
else {
$this.parent().addClass("active");
selected.push(attrStyle);
}
$("#content").find("*").hide();
$.each(selected, function(index,item) {
$('#content').find('[data-style *="' + item + '"]').show();
});
return false;
});
});
Both of your handlers are updating the selected array, but only one handler executes on a click. The first one if a color was (de)selected, the second if a style. Let's say you've clicked on "Black" and "Crew Neck". At that time your selected array would look like this: [ "Black", "Crew_Neck" ]. The next time you make a selection, let's say you click "Short Sleeves", the second (style) handler executes. Here's what is happening:
Short_Sleeves gets added to the selected array.
All of the items are hidden using $("#content").find("*").hide();
The selected array is iterated and items are shown again based on a dynamic selector.
Number 3 is the problem. In the above example, a style was clicked so the style handler is executing. Any items in the selected array that are colors will fail because, for example, no elements will be found with a selector such as $('#content').find('[data-style *="Black"]').show();.
I would suggest 2 things.
Keep 2 arrays of selections, one for color, one for style.
Combine your code to use only a single handler for both groups.
Here's a (mostly) working example.
Note that I added a data-type="color|style" to your .filterOptions containers to allow for combining to use a single handler and still know which group was changed.
Here's the full script:
$(document).ready(function () {
// use 2 arrays so the combined handler uses correct group
var selected = { color: [], style: [] };
// code was similar enough to combine to 1 handler for both groups
$('.filterOptions').on("click", "a", function (e) {
// figure out which group...
var type = $(e.delegateTarget).data("type");
var $this = $(this);
// ...and the value of the checkbox checked
var attrValue = $this.data(type);
// same as before but using 'type' to access the correct array
if ($this.parent().hasClass("active")) {
$this.parent().removeClass("active");
selected[type].splice(selected[type].indexOf(attrValue),1);
}
else {
$this.parent().addClass("active");
selected[type].push(attrValue);
}
// also showing all again if no more boxes are checked
if (attrValue == 'All' || $(".active", ".filterOptions").length == 0) {
$('#content').find('*').show();
}
else {
// hide 'em all
$("#content").find("*").hide();
// go through both style and color arrays
for (var key in selected) {
// and show any that have been checked
$.each(selected[key], function(index,item) {
$('#content').find('[data-' + key + ' *="' + item + '"]').show();
});
}
}
});
});
UPDATE: incorporating suggestions from comments
To make the handler work with checkboxes instead of links was a small change to the event binding code. It now uses the change method instead of click and listens for :checkbox elements instead of a:
$('.filterOptions').on("change", ":checkbox", function (e) {
// handler code
});
The "All" options "hiccup" was a little harder to fix than I thought it would be. Here's what I ended up with:
// get a jQuery object with all the options the user selected
var checked = $(":checked", ".filterOptions");
// show all of the available options if...
if (checked.length == 0 // ...no boxes are checked
|| // ...or...
checked.filter(".all").length > 0) // ...at least one "All" box is checked...
{
// remainder of code, including else block, unchanged
}
I also added an all class to the appropriate checkbox elements to simplify the above conditional.
Updated Fiddle

Can't get javascript/jquery to sort elements correctly more than one time

I have child divs that I'm trying to sort based on a jquery .data() value that I give them that is just a single number. This code works perfectly, but only once, after that I can't figure out how the heck it's sorting them. Here is a simplified version:
var myArray = $('#container div').get();
myArray.sort(function(x,y) {
return $(x).data('order') - $(y).data('order');
});
$('#container').empty().append(myArray);
I've tried so many other different methods of sorting, other plugins, etc., and I can't get anything to work right. This is as close as I can get. I just have this running on a jquery change event.
Here is the whole thing in case I'm doing something stupid elsewhere:
$('#attorneyFilter').change(function() {
//get array of links for sorting
var myArray = $('#attorneyBlocks div').get();
var selectedArea = $(this).val();
//sort alphabetically when "all" is selected
if (selectedArea == 'all') {
$('#attorneyBlocks div').show();
myArray.sort(function(a,b) {
return $(a).text() > $(b).text() ? 1 : -1;
});
//filter attorneys based on practice area and then assign its order# to the div with data, getting all values from the div's class
} else {
$('#attorneyBlocks div').hide().each(function() {
var attorneyArea = $(this).attr('class').split(', ');
for (var i=0;i<attorneyArea.length;i++) {
var practiceArea = attorneyArea[i].split('-');
if (selectedArea == practiceArea[0]) {
$(this).show().data('order',practiceArea[1]);
}
}
});
//sort based on order, the lower the number the higher it shows up
myArray.sort(function(x,y) {
return $(x).data('order') - $(y).data('order');
});
}
//append order back in
$('#attorneyBlocks').empty().append(myArray);
});
And a link to the page in question
Here's a jsFiddle with this working using .detach() instead of .empty() to keep the data.
http://jsfiddle.net/shaneblake/Tn9u8/
Thanks for the link to the site, that made it clear.
It seems to me you never clear out the data from the prior time. You hide everything but maybe something like this will solve your problem (here I set everything hidden to the bottom, you can clear it or use a different value -- as long as it is not the same as any sort key):
$('#attorneyBlocks div').hide().data('order',999999).each(function() {
var attorneyArea = $(this).attr('class').split(', ');
for (var i=0;i<attorneyArea.length;i++) {
var practiceArea = attorneyArea[i].split('-');
if (selectedArea == practiceArea[0]) {
$(this).show().data('order',practiceArea[1]);
}
}
});
Also, the code on the server is missing the 2nd line you have above:
var myArray = $('#attorneyBlocks div').get();
The problem is the change event is tied to the original items. After the sort you make all new items. They don't have any event tied to them. You will need to use .live()
Eventually figured it out, the data values from hidden divs were screwing with my sorting, so I changed my sorting code to only pay attention to :visible divs and that did the trick. Doh! Thanks for your help everyone.

Categories