I have two select menus (#id1 and #id2) that, when validated as containing a user error, should instigate some DOM changes (remove error notice) when either one of them gets interacted with.
Again:
var Heat_Check = jQuery('#id1' or '#id2').change(function() { ... });
PS. I know there's no return value from that chain.
May be you wanted to check if change is triggered from either of those select. Try below,
var Heat_Check = false;
jQuery('#id1, #id2').change(function() { Heat_Check = true; });
function heatCheck () {
if(Heat_Check) {
//Do your stuff
console.log('It is hot');
}
}
The comment from #Vega is right, but for completeness you can also do this:
heatChangeHandler = function() {
// ....
};
$('#id1').change(heatChangeHandler);
$('#id2').change(heatChangeHandler);
In general it is better to put multiple selectors in one $(), but it's worth knowing that functions can be addressed as variables, and thus referenced many times.
Related
Consider this code running on page ready:
$("input.extraOption[checked]").each(function() {
console.log($(this));
$(this).closest('.questionRow').find('.date').attr("disabled", true);
$(this).closest('.questionRow').find('.dateSpan').hide();
$(this).closest('.questionRow').find('.date').val("");
$(this).closest('.questionRow').find('.textareaResize').attr("disabled", true);
$(this).closest('.questionRow').find('.textareaResize').val("");
$(this).closest('.questionRow').find('.text').attr("disabled", true);
$(this).closest('.questionRow').find('.text').val("");
$(this).closest('.questionRow').find('.checkbox').attr("disabled", true);
});
I want to refactor these calls as they are used elsewhere as well, so I created the following function:
jQuery.fn.extend({
toggleAnswers: function (disable) {
var group = $(this);
group.find('.date').attr("disabled", disable);
group.find('.date').val("");
group.find('.textareaResize').attr("disabled", disable);
group.find('.textareaResize').val("");
group.find('.text').attr("disabled", disable);
group.find('.text').val("");
group.find('.checkbox').attr("disabled", disable);
if(checkedStatus === true){
group.find('.dateSpan').hide();
}else{
group.find('.dateSpan').show();
}
return group;
}
});
I then proceed to changing the 8 $(this).closest(...) calls with:
$(this).closest('.questionRow').toggleAnswers(true);
Here's the problem: on a page with 5 elements that match the selector, only the first one suffers the changes (in other words I only get one console.log)! Before the refactor I get the expected change in all 5 elements.
What is being done wrong in this refactor?
checkStatus isn't defined anywhere, causing an exception. You seem to want to use disable instead.
On a side note, this already refers to the jQuery collection that this method is called on, so wrapping this in a jQuery object ($(this)) is redundant/unnecessary. Note that this is specifically inside of a $.fn method, not normal jQuery methods. For example, inside event handlers, this refers to the DOM element, so you need to wrap it in $(this) in order to call jQuery methods on it.
Also, disabling an element should be done with .prop("disabled", true/false): .prop() vs .attr()
You can also combine any selectors that you call the same jQuery method on. For example, group.find('.date').val(""); and group.find('.text').val(""); can be combined into: group.find(".date, .text").val("");
Putting all of those suggestions together, as well as iterating over this (for consistency and scalable sake), here's what I'd use:
jQuery.fn.extend({
toggleAnswers: function (disable) {
return this.each(function (idx, el) {
var $group = $(el);
$group.find(".date, .text, .textareaResize, .checkbox").prop("disabled", disable);
$group.find(".date, .textareaResize, .text").val("");
$group.find(".dateSpan").toggle(!disable);
});
}
});
And depending on how you use it, I'd set it up like:
var targets = $("input.extraOption[checked]"),
toggler = function () {
$(this).closest(".questionRow").toggleAnswers(this.checked);
};
targets.each(toggler).on("click", toggler);
DEMO: http://jsfiddle.net/XdNDA/
I'm using JS and jQuery for the first time after a lot of experience with Java and C++. I'm loving jQuery's idea of $(document).on('click', 'btn-selector', react), but for more complex widgets I'm finding myself in the same rut over and over: in each react handler, I have to look up the widget as a whole and reconstruct all my knowledge about it.
For example, I'm making a simple widget out of <input>s with which the user can make a grading scale: 90 maps to an A, 80 maps to a B, etc. When one of the inputs changes, I want to check to make sure that the inputs are still in order (your scale can't go 90, 70, 80, for example).
So, I have something like
Actual
$(document).on('click', '.scale-input', function() {
var widget = $(this).closest('.scale-widget-container');
ensureLevelsAreInOrder(widget);
});
Almost every single handler has to have this first line to find its context. I'd much rather have code that looks like this:
Preferred
$(document).on('click', '.scale-input', ensureLevelsAreInOrder);
The problem is that in this form, ensureLevelsAreInOrder only has a reference to the input that changed, not the larger context.
In Java or C++, I would have called a constructor on the widget, and each input would have a handler with the context baked in via member variables. I could do something similar with
$(function() {
$('.scale-widget-container').scaleWidget();
});
with scaleWidget() setting up the contextualized handlers, but the page I'm working with loads a lot of its html with ajax and I don't have a reliable time to run that initialization.
Is this a common problem that we just have to deal with if we don't want JS in our HTML, or is there a solution I haven't come across yet?
Not sure what it is you're after exactly, but you don't seem to touch on two quite important concepts when it comes to JS: the event object, and closures. Both of these are open to you to get what you need:
event object:
The callback function is passed an argument, that describes the event itself, and references the elements affected by that event, This isn't exclusive to jQ (just google addEventListener), but it's quite handy:
$(document).on('click', '.scale-input', function(e)//<-- e is our event
{
console.log(e);//check console
});
Which, in vanilla JS would look like this:
document.addEventListener('click', function(e)
{
if (!e.className.test(/\bscale\-input\b/))
{
return e;
}
console.log(e);
}, false);
Another thing you might want to consider is enclosing references to whatever it is you need in an IIFE's scope:
(function()
{
var containers = $('.scale-widget-container'),
localBool = false,
asMany = 'varsAs you need',
previousScales = [],
inputs = $('.scale-input');//references to all DOM nodes you mention
$(document).on('click','.scale-input',function(e)
{
console.log($(this));
console.log(containers);
previousScales.push(this.value);//or something
console.log(previousScales);
//and so on.
});
}());
Hope this helped
Update:
If IE isn't a browser you don't care about that much, you could use one of the DOM-modified events, specifically DOMTreeModified:
(function()
{
var nodes = [];//<-- store current nodes here, if applicable
nodes.containsNode = function(node)
{
var i;
for (i=0;i<this.length;i++)
{
if (this[i] && this[i] === node)
{//node is set, return its index
return i;
}
}
//node not found, return -1
return -1;
};
document.body.addEventListener('DOMSubtreeModified',function(e)
{
var all = document.getElementsByClassName('scale-input'),
i;
for (i=0;i<all.length;i++)
{
if (nodes.containsNode(all[i]) === -1)
{
nodes.push(all[i]);//add new
}
}
},false);
}());
More on the mutation events, and their issues, on the DOM events wiki
I know this subject has been already discussed in similar topics, but none of the solutions I could find can help me understand the issue I have.
Here is my simplified class and its the usual was I define them.
BottomNav = function() {
this.init();
}
$.extend(BottomNav.prototype, {
init: function(){
this.insue = false;
$(".up").click($.proxy(function () {
var thisinuse = this.inuse;
if(this.inuse===false) {
this.inuse = true;
this.moveSlider('up');
}
},this));
},
moveSlider: function(d){
//some instructions
alert('move slider');
}
});
$(document).ready(function() {
new BottomNav();
});
In FireBug on the breakpoint inside the click event this.inuse is undefined! (this is my problem), my scope looks good on the watch (right panel of firebug), this.insue is false as it should be - sorry I cannot post images yet!
I would be grateful of someone might help identifying this very strange behavior.
I tried some staff like putting the click event definition inside another function but it does not work either. I tried different ways of bindings and it does not work too.
However the below example is working on a number of other classes I made. I could access class scope from events, effects.
It's just a typo:
this.insue = false;
Change insue to inuse and the property will be there :-)
Apart from that, the variable thisinuse is quite superfluous in here. And change the condition to if(! this.inuse) instead of comparing to booleans…
this.inuse can be assigned to a variable out side your click event handler and use the variable inside the handler.
In the end, I have decided that this isn't a problem that I particularly need to fix, however it bothers me that I don't understand why it is happening.
Basically, I have some checkboxes, and I only want the users to be able to select a certain number of them. I'm using the code below to achieve that effect.
$j( function () {
$j('input[type=checkbox].vote_item').click( function() {
var numLeft = (+$j('#vote_num').text());
console.log(numLeft);
if ( numLeft == 0 && this.checked ) {
alert('I\'m sorry, you have already voted for the number of items that you are allowed to vote for.');
return false;
} else {
if ( this.checked == true ) {
$j('#vote_num').html(numLeft-1);
} else {
$j('#vote_num').html(numLeft+1);
}
}
});
});
And when I was testing it, I noticed that if I used:
$j('input[type=checkbox]').each( function () {
this.click()
});
The JavaScript reacted as I would expect, however when used with:
$j('input[type=checkbox]').each( function () {
$j(this).click()
});
It would actually make the counter count UP.
I do realize that it isn't the most secure way to keep count using the counter, however I do have server side error-checking that prevents more than the requisite amount from being entered in the database, that being the reason that I have decided that it doesn't actually need fixing.
Edit: The $j is due to the fact that I have to use jQuery in noConflict mode...
$(this) contains a jQuery wrapper (with lots of functions) whereas this is solely the DOM object.
The fact that counter is going up gave me the clue that there is a link between checked attribute, which you are using, and firing the click event manually.
I searched Google for 'jquery checkbox click event raise' and found this link, where author faces the exact same problem and the workaround he used.
http://www.bennadel.com/blog/1525-jQuery-s-Event-Triggering-Order-Of-Default-Behavior-And-triggerHandler-.htm
On a side note, I think you can simplify your code further:
$j('input[type=checkbox].vote_item').click(
function()
{
var maxNumberOfChoices = 5;
//get number of checked checkboxes.
var currentCheckedCount = $j('input[type=checkbox].vote_item :checked');
if(currentCheckedCount > maxNumberOfChoices)
{
//It's useful if you show how many choices user can make. :)
alert('You can only select maximum ' + maxNumberOfChoices + ' checkboxes.');
return false;
}
return true;
});
this.click() calls the browser DOM method click().
$(this).click() calls the jQuery method click(), which does more than just call the browser method: see the implementation of the function trigger for details.
I'm using jquery and what I'm doing is binding the toggle method to a number of buttons on a webpage. It looks something like this
$('.button').toggle(function(){
// first function
}, function(){
// second function
});
However, there are animation in both of those functions. So a user can click the button while the first or second function is executing. And this messes up the order of the HTML elements and may make them move to the end of the page. Because essentially what these functions do is move one element to the end on the first click, and on the other click move it back where it originally was.
Of course, it is difficult to click the button once it is moving around the page. But it's possible.
You could use a flag. Set a flag 'isAnimating' to true when an animation begins, and false when it ends. Any subsequent animation can only proceed if this value is false.
You could also possibly check to see if the :animated selector applies to the owner of the event. And base your decisions off of that.
You could use a bool as a semiphore.. Obviously, this is in no way secure, but javascript doesn't really support locking, so you could easily have deadlocks and / or race conditions with this approach, but it will work 99,9% of the times :)
Seems like you'll be happier implementing your own toggle. Toggle really only works for cases with 0 additional logic.
$('.button').click(
function () {
if( $(self).is(":animated") {
return false;
}
if($(self).is(".rolledup")) {
self.apply(roll_window_down);
} else {
self.apply(roll_window_up);
}
});
function roll_window_up() {
$(self).addClass( 'rolledup' );
// first function
}
function roll_window_down() {
$(self).removeClass( 'rolledup' );
// first function
}
You need to place the two functions you pass to toggle in a context in which you can hold a flag to control function entrance:-
(function() {
var toggling = false;
$('.button').toggle(function(){
if (!toggling) {
toggling = true;
// first function
toggling = false;
} else {
// whatever you want to happen if re-entrance attempted
}
}, function(){
if (!toggling) {
toggling = true;
// second function
toggling = false;
} else {
// whatever you want to happen if re-entrance attempted
}
})
)();
N.B. This serialises all toggles of elements that have the .button class. IOW there is only one toggling flag for all buttons. If you want each button to have its own toggling flag:-
$('.button').each(function() {
var toggling = false;
$(this).toggle(function(){
if (!toggling) {
toggling = true;
// first function
toggling = false;
} else {
// whatever you want to happen if re-entrance attempted
}
}, function(){
if (!toggling) {
toggling = true;
// second function
toggling = false;
} else {
// whatever you want to happen if re-entrance attempted
}
});
);
You need a queue. You can build one with a semaphore variable, but jQuery already provides one, so maybe you want to use it:
$('.button').toggle(function() {
$(document).queue("foo", function() {
...
});
}, function() {
$(document).queue("foo", function() {
...
});
});
jQuery normally uses the "fx" queue to serialize animations, but you can use this "foo" queue for whatever you want.
The queue can be put on any object, so maybe you want to put it on the container that has all the .button objects in it. You cannot put it on the button (this) themselves, or you'll be back to where you're at now.
Once you've done that, all you really need to do is abort an animation. This can be done by expressly emptying the "fx" queue, or you can use $('.button').stop(); to stop all the old animations.