Why is this onClick event not being applied correctly? - javascript

I'm using late-binding to assign onClick events to checkboxes (styled as dots). My first assignment [located at https://github.com/farfromunique/vampirrePoints/blob/master/events.js#L264 ] goes perfectly, and reacts as expected - all dots have this as their onClick:
/* https://github.com/farfromunique/vampirrePoints/blob/master/main.js#L440 */
allDots[i].onclick = function() {
if (this.checked) {
decrementCounter(1);
} else {
incrementCounter(1);
};
}
However, when Step11() is triggered, and freebieDotSetup() is called, only some of the checkboxes get their onClicks updated. Specifically, sta1, sta2, sta3, sta4, sta5 (possibly others, too) keep their initial value.
I have tried putting console.log() statements in during the assignment process, and it looks like the assignment happens, but it doesn't "stick". Why doesn't this work?
Code reference (whole site): https://github.com/farfromunique/vampirrePoints
By request, a non-working JSFiddle: http://jsfiddle.net/farfromunique/mS4Lp/
Note: the menu is on the wrong side, doesn't advance properly, and none of the "dots" (checkboxes) are clickable, so functionality is not testable. I strongly suspect that my code is not cross-browser compatible, but that isn't a priority for me (yet).

The solution: In the function freebieDotSetup(), I was doing this:
for (i=0;i<attributeDots.length;i++) {
attributeDots[i] = allDots[i];
}
...
for (i=0;i<abilityDots.length;i++) {
abilityDots[i] = allDots[i+attributeDots.length];
}
...
for (i=0;i<disciplineDots.length;i++) {
disciplineDots[i] = allDots[i+abilityDots.length];
}
...
Since I was using the previous group's length, it would occasionally overwrite the previous values (due to groups being shorter). I corrected this with this:
startPos = startPos + attributeDots.length;
for (i=0;i<abilityDots.length;i++) {
abilityDots[i] = allDots[i+startPos];
}
Adding the previous group's length to startPos each time. Making this change resolved my issue.
... But the JSFiddle is totally busted, still.

Related

Separate desktop and mobile jQuery functions on a responsive site

I'm working on a responsive site with a specific set of jQuery functions for the desktop layout and mobile layout. They interfere with each other if they're both active at the same time.
By checking window.width, I'm able to deliver only the correct set of functions on page load, and I'd like to do the same on window.resize.
I've set up a stripped down Fiddle of where I'm at here: http://jsfiddle.net/b9XEj/
Two problems exist right now:
Either desktopFunctions or mobileFunctions will continuously fire on page resize, whether they have already been loaded or not.
If the window is resized beyond one breakpoint and then returned to the previous size, the incorrect set of functions will already have been loaded, interfering with the current set.
The window.resize function should behave in the following way:
Check if the correct set of functions currently active for the viewport size
If yes, return.
If no, fire correct set of functions and remove incorrect set of functions if they exist.
In the Fiddle example above, you would always see a single line, displaying either "Mobile Functions are active" or "Desktop Functions are active".
I'm a bit lost at this point, but I have tried using
if ($.isFunction(window.mobileFunctions))
to check if functions already exist, but I can't seem to get it working without breaking the overall function. Here's a fiddle for that code: http://jsfiddle.net/nA8TB/
Thinking ahead, this attempt also wouldn't take into account whether the incorrect set of functions exists already. So, I'm really hoping there's a way I can deal with this in a simpler way and solve both problems.
Any help would be much appreciated!
Following conquers 2 of the problems. The resize fires many times a second, so using a timeout will fix it firing your code constantly. It also adds a check to see if the same size is in effect, and return if it is
$(document).ready(function() {
var windowType;
var $wind = $(window);
var desktopFunctions = function() {
$('body').append('<p>Desktop functions are active</p>');
}
var mobileFunctions = function() {
$('body').append('<p>Mobile Functions are active</p>');
}
var mobileCheck = function() {
var window_w = $wind.width();
var currType = window_w < 940 ? 'mobile' :'desktop';
if (windowType == currType) {
$('body').append('<p>No Type Change, Width= '+window_w+'</p>');
return;
} else {
windowType = currType;
}
if (windowType == 'mobile') {
mobileFunctions();
} else {
desktopFunctions();
}
}
mobileCheck();
var resizeTimer;
$wind.resize(function() {
if (resizeTimer) {
clearTimeout(resizeTimer);
}
resizeTimer = setTimeout(mobileCheck, 300)
});
});
DEMO: http://jsfiddle.net/b9XEj/1/
Without seeing some real world differences between your 2 sets of functions it is hard to provide gudance on how to stop them conflicting. One possibility is checking the windowType in your functions
You can prevent the continuous firing by adding a delay mobileCheck. Use a setTimeout along with a checkPending boolean value.
var checkPending = false;
$(window).resize(function(){
if (checkPending === false) {
checkPending = true;
setTimeout(mobileCheck, 1000);
}
});
See here: http://jsfiddle.net/2Q3pT/
Edit
As far as the second requirement, you could use this pattern to create or use the existing one:
mobileFunctions = mobileFunctions || function() {
// mobile functions active
};
See: http://jsfiddle.net/2Q3pT/2/

Making button click once

EDIT**
In my game I want my button to disable after one click but for some reason all the ways I have looked at do not work.
Here is what I have at the moment..
$('.minibutton').click(function() {
$(this).attr('disabled','disabled');
$('.minibutton').prop('disabled', false);
$('.picstyle').show();
$('td').removeClass('spellword');
var r = rndWord;
while (r == rndWord) {
rndWord = Math.floor(Math.random() * (listOfWords.length));
}
$('td[data-word="' + listOfWords[rndWord].name + '"]').addClass('spellword');
$('td[data-word=' + word + ']').removeClass('wordglow').removeClass('wordglow4').removeClass('wordglow3').css('color', 'transparent');
var noExist = $('td[data-word=' + listOfWords[rndWord].name + ']').hasClass('wordglow2');
if (noExist) {
$('.minibutton').click();
} else {
$("#mysoundclip").attr('src', listOfWords[rndWord].audio);
audio.play();
$("#mypic").attr('src', listOfWords[rndWord].pic);
jQuery(pic).show();
}
}).trigger("click");
I have tried changing the click function to..
$('.minibutton').one("click", function() {
I have also tried to bind and unbind it but it will not work
fiddle http://jsfiddle.net/7Y7A5/9/
You spell the highlighted word, using the picture as a clue. If you get the word wrong 3 times you are given the opportunity to move on because "minibutton" appears. At this point I only want the user to be able to click it once before it disappears again.
Take a look at the jQuery .on() and .off() methods. In the documentation on jQuery's site, there should be a section about namespacing - this section covers what you're looking for.
Below, I have given you the solution that you are looking for, but review the documentation anyway so that you can better understand it.
First, add this variable declaration above your click-handler:
var timesClicked = 0;
Then, change your handler declaration to this:
$('.minibutton').on("click.oneclick", function () {...});
And add these lines in your handler:
if (timesClicked > 0) {
$(this).off("click.oneclick");
} else {
timesClicked++;
}
Basically, what's happening here is that you are giving your click-event handler a distinct name that differentiates it from all other click-event handlers on the page (that is, all other click-handlers that do not also have that namespace). You could have applied the namespace "foo" - I chose to apply the namespace "oneclick". The result is that you have the ability to remove that specific click-event handler without removing any of the other click-event handlers (that is, any others that do not have the given namespace).
Try this updated fiddle of your solution.
UPDATE:
To help you with your question of re-enabling the click event, first do the following (note: there are a lot of things in your code that need cleaning up, but I did point out one thing, which was an empty click handler being set to minibutton):
var clickOnceTest = 0;
function enableMinibutton() {
$('.minibutton').on("click.clickOnceNS", function() {
if (clickOnceTest > 0) {
$(this).off("click.clickOnceNS");
} else {
clickOnceTest++;
}
$('.minibutton').prop('disabled', false);
$('.picstyle').show();
$('td').removeClass('spellword');
var r = rndWord;
while (r == rndWord) {
rndWord = Math.floor(Math.random() * (listOfWords.length));
}
$('td[data-word="' + listOfWords[rndWord].name + '"]').addClass('spellword');
$('td[data-word=' + word + ']').removeClass('wordglow').removeClass('wordglow4').removeClass('wordglow3').css('color', 'transparent');
var noExist = $('td[data-word=' + listOfWords[rndWord].name + ']').hasClass('wordglow2');
if (noExist) {
/////
/////
/////
/////
/////
////// Is this supposed to trigger a click? because all this does is set another click handler (which does nothing) to minibutton..
/////
/////
/////
/////
/////
$('.minibutton').click();
} else {
$("#mysoundclip").attr('src', listOfWords[rndWord].audio);
audio.play();
$("#mypic").attr('src', listOfWords[rndWord].pic);
pic.show();
}
});
}
enableMinibutton();
$(".minibutton").trigger("click");
Once you've done that, when you're ready to re-enable minibutton all you have to do is call the method enableMinibutton(). If you need to call the trigger("click") method again, do so (I'm not exactly sure why it was there for in the first place). You can call this method either immediately before/after showing the button the second time (my preference - I don't like unnecessary handlers slowing my script down), or you can call it immediately after you hide the button.
As for the other things that need cleaning: throughout your code you have things, like the empty click handler that I pointed out, that are unnecessary, slow down your program, throw errors, or just plain don't work. Remove or comment these things out ASAP. Once you do, your code will run much smoother, and your tests/experiments will be more likely to succeed/fail properly, without influence of bad code.
Additionally, and I cannot stress this one enough, you need to set your element selectors to variables. You select minibutton and minibutton2 and a bunch of other items dozens of times throughout your code. Just set their selectors to variables at the top and use said variables to refer to your elements, instead of selecting them over and over again.
Every time you call a jQuery selector, you are essentially searching the DOM for the elements that satisfy your selector. If your collection is always going to have the same elements in it, set the selector to a variable and call on the variable instead. This way, you are only searching the DOM once for these elements. The performance difference is very noticeable in heavy-weight programs, especially in ones like this. In case you are unfamiliar with this syntax, here are two examples, using minibutton and minibutton2:
var $minibutton = $(".minibutton");
var $minibutton2 = $(".minibutton2");
....
$minibutton.on("click.click.......
...
$minibutton.trigger(....
...
$minibutton2.prop(...
...
As you can see, I like to prefix my selector variables with the $ so that it is easier to see and remember that the variable they hold is a selected element. This is not required, and you can set the variable name to whatever you want.
The only catch to doing this is that you must "update" the variable if you plan to add more items to the DOM that would be returned by the selector; the variable will reference all elements returned by the selector AT THE TIME THE SELECTOR WAS INITIALLY CALLED. This means that if you have a collection of elements of a particular class and set a variable to refer to that collection, before adding additional elements with said class, in order to reference additional elements that would later be returned by that selector you will have to set it again. Yes, I know that was long and confusing, but it's actually pretty simple and there are dozens of examples online about it.
<input type="submit" onclick="this.disabled = true" value="Save"/>
Use modern Js, with "disabled" or "once"!
button.disabled = true;
or
button.addEventListener("click", function() {
// Fire event
}, {once : true});
Documentation, CanIUse

Issue with execution order in javascript

I'm relatively new to javascript so please hold it against me.
I have a bit of code which should give the user a little time to reach the submenu from the base-menu.
My problem is that the code keeps executing in a weird order.
Here is the code:
function onFocusOut() {
var tester = 0;
setTimeout(function(){menuReset(tester)},1000);
}
function menuReset(tester) {
var hoverCheck = function (event) {
alert("#navBase a has focus"); //is fired, but to late...
var tester = event.data.varTester;
var tester = 1;
};
jQuery('#navBase').on('mousemove', 'a', { varTester: tester }, hoverCheck);
jQuery('#navBase').off('mousemove', 'a', { varTester: tester }, hoverCheck);
alert(tester); //This keeps firing first, before the alert in hoverCheck
if(tester == 1){
alert("tester = 1");
return;
}
else {
jQuery('#navBase ul').hide();
jQuery('#navBase').css({'width': ''});
jQuery('#navBaseAnchor').css({
'width': '', 'color': '',
'font-size': '',
'border-bottom-style': '',
'border-bottom-width': '',
'border-bottom-color': ''});
tester = 0;
}
}
Now I keep getting the alert that "tester" is 0, before the hoverCheck function is executed (which should set "tester" to 1) and fires the alert within that function.
Could someone tell me what I'm doing wrong?
I am also fairly new to JS, but should you also be watching out for variable scope errors too?
You have declared tester locally in onFocusOut() and in menuReset(tester), and then called it as a global var outside?
From answers.oreilly.com
LOCAL - Are those that are specific to a function and only work on it.
GLOBAL - Are those that are not defined within a function and may also serve to functions unless the function has not required that
variable.
Nevermind people...
I found a way around it all.
Currently i'm setting a .focus() to the anchor involved on mouseOver. (and of course blur() on mouseleave)
Then it's real easy to check the currently focussed element using document.activeElement.
So problem solved, altough in a bit different way.
alert(tester) is the first line of code that is executing something you notice as a user. The two function calls jQuery().on() and jQuery().off() are only attaching event handlers. If you want to see a "1" in the alert, you have to quickly move your mouse before hoverCheck is executed. But probably you cannot move your hand faster than JavaScript reaching the next line, which is the alert() with tester equals "0".
A little bit different approach would be to set a Javascript timeout() to make the submenu disappear after a certain amount of time if a certain condition isn't met.
Check out this JSFiddle example
Best of luck!

if/else statement in a function: using onclick as a switch

I have looked for solutions to this on google for what seems like an eternity, but I can't seem to formulate my search correctly, or nobody has posted the code I'm looking for earlier.
I am currently trying to make a function that will modify one or several margins of a div element. I want to use an if/else statement within the function, so that the onclick event will switch between the two conditions. This is what I have been working on so far;
function facebookToggle()
{
if($('#facebooktab').style.margin-left == "-250px";)
{
document.getElementById("facebooktab").style.marginLeft="0px";
}
else
{
document.getElementById("facebooktab").style.marginLeft="-250px";
}
}
I have tried twisting it around a little, like switching between "marginLeft" and "margin-left", to see if I was just using the wrong terms.. I'm starting to wonder if it might not be possible to combine jQuery and regular javascript? I don't know.. It's all just guesses on my part at this point.
Anyway, I have a div, which is now positioned (fixed) so almost all of it is hidden outside the borders of the browser. I want the margin to change onclick so that it will be fully shown on the page. And when it is shown, I want to be able to hide it again by clicking it.
I might be approaching this in the wrong way, but I really hope someone can help me out, or even tell me another way to get the same results. Thank you for any help you can give me.
You can see it in action at: http://www.torucon.no/test/
(EDIT: By the way, I am a complete javascript novice, I have no experience with javascript prior to this experiment. Please don't be too harsh, as I am aware I probably made some really stupid mistakes in this short code.)
Fixed problem:
function facebookToggle() {
var fb = $('#facebooktab'); // save reference to element
if( fb.css('margin-left') === '-250px' ) {
fb.css('margin-left', '0px');
} else {
fb.css('margin-left', '-250px');
}
}
A jQuery object doesn't have a property called style, so
if($('#facebooktab').style.margin-left == "-250px";)
// also remove this semi-colon! ^
is going to throw an error. Some options for accessing CSS properties are (1)
document.getElementById("facebooktab").style.marginLeft;
which you have correctly used, or (2)
$('#facebooktab').css('margin-left');
Consider being consistent and using the same approach for all three cases. You can assign css properties with jQuery like
$('#facebooktab').css('margin-left', '-250px');
With these things in mind, here's a suggested rewrite:
function facebookToggle() {
var fb = $('#facebooktab'); // save reference to element
if( fb.css('margin-left') === '-250px' ) {
fb.css('margin-left', '0px');
} else {
fb.css('margin-left', '-250px');
}
}
and here's another that uses a predefined CSS class:
#facebooktab {
margin-left: -250px; /** default state */
}
.no-left-margin {
margin-left: 0px;
}
function facebookToggle() {
$('#facebooktab').toggleClass('no-left-margin');
}
toggleClass
jQuery is just a JavaScript library. It is written in JavaScript and its API is in JavaScript. Your event handler could be rewritten as follows:
function facebookToggle() {
var el = document.getElementById('facebooktab');
if (el)
el.style.marginLeft = (el.style.marginLeft == '250px' ? 0 : -250) + 'px';
}
Since you are mixing jQuery with javascript, you got mixed up. Apart from what paislee's advice. you are do this too.
if($('#facebooktab')[0].style.margin-left == "-250px";){
document.getElementById("facebooktab").style.marginLeft="0px";
}
else {
var fb = document.getElementById("facebooktab");
fb.style.marginLeft="-250px";
}

Binding multiple events of the same type?

Firstly, is it possible? Been struggling with this one for hours; I think the reason my events aren't firing is because one event is unbinding/overwriting the other. I want to bind two change events to the same element. How can I do that?
As per request, here's the function I'm struggling with:
(function($) {
$.fn.cascade = function(name, trigger, url) {
var cache = {};
var queue = {};
this.each(function() {
var $input = $(this);
var $trigger = $input.closest('tr').prev('tr').find(trigger);
//$input.hide();
var addOptions = function($select, options) {
$select.append('<option value="">- Select -</option>');
for(var i in options) {
$select.append('<option value="{0}">{1}</option>'.format(options[i][0], options[i][1]));
}
$select.val($input.val()).trigger('change');
}
var $select = $('<select>')
// copy classes
.attr('class', $input.attr('class'))
// update hidden input
.bind('change', function() {
$input.val($(this).val());
})
// save data for chaining
.data('name', name)
.data('trigger', $trigger);
$input.after($select);
$trigger.bind('change', function() {
var value = $(this).val();
$select.empty();
if(value == '' || value == null) {
$select.trigger('change');
return;
}
// TODO: cache should be a jagged multi-dimensional array for nested triggers
if(value in cache) {
addOptions($select, cache[value]);
} else if(value in queue) {
$select.addClass('loading');
queue[value].push($select);
} else {
var getDict = {}
getDict[name] = value;
// TODO: use recursion to chain up more than one level of triggers
if($(this).data('trigger')) {
getDict[$(this).data('name')] = $(this).data('trigger').val();
}
$select.addClass('loading');
queue[value] = [$select];
$.getJSON(url, getDict, function(options) {
cache[value] = options;
while(queue[value].length > 0) {
var $select = queue[value].pop();
$select.removeClass('loading');
addOptions($select, options);
}
});
}
}).trigger('change');
});
return this;
}
})(jQuery);
The relevant chunk of HTML is even longer... but essentially it's a select box with a bunch of years, and then an <input> that gets (visibly) replaced with a <select> showing the vehicle makes for that year, and then another <input> that gets replaced with the models for that make/year.
Actually, it seems to be running pretty well now except for on page load. The initial values are getting wiped.
Solved the issue by pulling out that $select.bind() bit and making it live:
$('select.province').live('change', function() {
$(this).siblings('input.province').val($(this).val());
});
$('select.make').live('change', function() {
$(this).siblings('input.make').val($(this).val());
});
$('select.model').live('change', function() {
$(this).siblings('input.model').val($(this).val());
});
Sucks that it's hard-coded in there for my individual cases though. Ideally, I'd like to encapsulate all the logic in that function. So that I can just have
$('input.province').cascade('country', 'select.country', '/get-provinces.json');
$('input.make').cascade('year', 'select.year', '/get-makes.json');
$('input.model').cascade('make', 'select.make', '/get-models.json');
Yes that is possible.
$(…).change(function () { /* fn1 */ })
.change(function () { /* fn2 */ });
jQuery event binding is additive, calling .change a second time does not remove the original event handler.
Ryan is correct in jQuery being additive, although if you find there are problems because you are chaining the same event, beautiful jQuery allows another approach, and that is calling the second function within the first after completion of the first as shown below.
$('input:checkbox').change(function() {
// Do thing #1.; <-- don't forget your semi-colon here
(function() {
// Do thing #2.
});
});
I use this technique frequently with form validation, one function for checking and replacing disallowed characters input, and the second for running a regex on the results of the parent function.
Update to Post:
OK... You all are quick to beat on me with your negative scores, without understanding the difference in how we each view Mark's request. I will proceed to explain by example why my approach is the better one, as it allows for the greatest flexibility and control. I have thrown up a quick example at the link below. A picture's worth a 1000 words.
Nested Functions on One Event Trigger
This example shows how you can tie in three functions to just one change event, and also how the second and third functions can be controlled independently, even though they are still triggered by the parent change event. This also shows how programmatically the second and third functions can BOTH be tied into the same parent function trigger, yet respond either with or independently (see this by UNCHECKING the checkbox) of the parent function it is nested within.
$('#thecheckbox').change(function() {
$("#doOne").fadeIn();
if ($('#thecheckbox').attr('checked')) { doFunc2() }
else { doFunc3() };
function doFunc2() { $("#doTwo").fadeIn(); return true; }
function doFunc3() { $("#doTwo").fadeOut(); return true; }
$("#doThree").fadeIn();
});
I've included the third 'Do thing #3 in the example, to show how yet another event can follow the two nested functions as described earlier.
Forgive the earlier bad pseudocode originally posted first, as I always use ID's with my jQuery because of their ability to give everything an individual status to address with jQuery. I never use the 'input:checkbox' method in my own coding, as this relies on the 'type' attribute of an input statement, and therefore would require extra processing to isolate any desired checkbox if there is more than one checkbox in the document. Hopefully, the example will succeed at articulating what my comments here have not.
I am actually not sure exactly if you can bind two different change events. But, why not use logic to complete both events? For example...
$('input:checkbox').change(function() {
// Do thing #1.
// Do thing #2.
});
That way, you get the same benefit. Now, if there are two different things you need to do, you may need to use logic so that only one or the other thing happens, but I think you would have to do that anyway, even if you can bind two change events to the same element.

Categories

Resources