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!
Related
Javascript newbie here. I'm trying to understand this and bind within the context of jquery event handlers. I'm reviewing a piece of code from the todoMVC code here, and have a question.
Let's look at line 56:
$('#new-todo').on('keyup', this.create.bind(this));
Code excerpt for context:
var App = {
init: function () {
this.todos = util.store('todos-jquery');
this.todoTemplate = Handlebars.compile($('#todo-template').html());
this.footerTemplate = Handlebars.compile($('#footer-template').html());
this.bindEvents();
bindEvents: function () {
$('#new-todo').on('keyup', this.create.bind(this));
$('#toggle-all').on('change', this.toggleAll.bind(this));
$('#footer').on('click', '#clear-completed', this.destroyCompleted.bind(this));
$('#todo-list')
.on('change', '.toggle', this.toggle.bind(this))
.on('dblclick', 'label', this.edit.bind(this))
.on('keyup', '.edit', this.editKeyup.bind(this))
.on('focusout', '.edit', this.update.bind(this))
.on('click', '.destroy', this.destroy.bind(this));
},
create: function (e) {
var $input = $(e.target);
var val = $input.val().trim();
if (e.which !== ENTER_KEY || !val) {
return;
}
this.todos.push({
id: util.uuid(),
title: val,
completed: false
});
$input.val('');
this.render();
},
My question
I understand that when using jquery, this refers by default to “the element we called the method on” (#new-todo in this case), so in this code, we want to explicitly bind this to the object App instead.
In the example, both thiss appear to follow the “left of the dot rule” and refer to App. So far, so good.
From this behavior, I expect that this, if not inside the callback function must refer to the parent app, (and this inside the callback function must default to the element with ID #new-todo unless bound to some other value).
Therefore, if I call this.create without binding it to anything, this should still refer to App, right? WRONG.
As you can see, the first this now refers to the element with ID #new-todo. (And the this in other event listeners below also refer to the jquery wrapped object!)
Can someone help me understand why?
I discovered the problem (with my question), so I thought I'd post the resolution in case it helps anyone in the future.
I realized that my initial understanding of this and bind() was correct, so I must be wrong about something else.
It turns out that
(1) I made an incorrect assumption about how the debugger works
(2) I needed to review when each part of the code ran
(1) Debugger
In the debugger, when I pause and linger my mouse over a variable, the debugger shows me a preview of that variable's value. I erroneously assumed that when the debugger pauses code execution, I'd see various values of this displayed, depending on the context (so a this in one method would differ from the this in another.) I see now that's not how it works. I believe this will show up as the same value everywhere and that value will be the value of this at the point where the script was paused. See attached gif for example.
(2) Code execution timing
When code execution paused for my breakpoint, I was already inside the create method, so the this in this.create and .bind(this) was reflecting the value of this within the create method.
As a desktop developer I am very new to Javascript, so I often run into things that puzzle me about the language. I was working with click events on RaphaelJS shapes, and initially I was setting the state and animation of the object in a private method:
innershape.node.onclick = function () {
if (scope.state === 0) {
_setState(1);
} else {
_setState(0);
}
};
function _setState(state) {
scope.state = state;
if (scope.state === 0) {
innershape.animate({ fill: "#00FF19" }, 500);
} else {
innershape.animate({ fill: "#C05219" }, 500);
}
}
This was functioning as expected. I then decided to add an outside function that would loop through all the objects and de-select (and therefore reverse-animate) all the other shapes. The result may be seen in this jsfiddle: http://jsfiddle.net/txj4zasn/4/
The function is called properly, and the animate() function is apparently executed, but the visible animation never appears, and the color never changes. I suspect that this is something very basic to Javascript that I just don't understand. Can someone explain to me why this is happening?
Its not really very clear what you want to achieve (beyond getting the animation to work), so my initial solution I think isn't good, but I will expand on that.
The problem looks a bit like you are trying to combine two different elements, functional scope and object variables.
A quick solution would be to include...
this.id = 1;
var id = this.id; // so id now a closure to the later function
as updateSelected(id); the id here, is inside another function, so we can't use 'this.id'. But then later you are checking against z[i].id so you need that to be defined also.
jsfiddle
This all feels a bit clunky though, prone to error, and is quite hard to read. So the first question is do you need objects ? You could store information in the "data" part of a Raph element, which already is an object.
Here is an example of how I would write it, I appreciate this may not be suitable as it may be part of a bigger project which needs other elements in an object, but it may give some idea.
function updateSelected( el ) {
if( el.data('innerstate') == 1 ) {
el.animate({ fill: "#00FF19" }, 500);
el.data('innerstate',0)
} else {
el.animate({ fill: "#C05219" }, 500);
el.data('innerstate',1);
}
}
function addElement() {
var innershape = paper.rect(100,100,100, 100);
innershape.attr({fill: "#00FF19" });
innershape.data('innerstate', 0);
innershape.click( function () {
updateSelected( innershape )
} );
};
addElement();
This code I can pretty much read instantly and know how and if it will work.
jsfiddle
jsfiddle showing it combined with more than one element, or jsfiddle thats a bit more compact
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.
I'm displaying a series of images in a loop, and I'm trying to implement some sort of nudity filter so I'm using nude.js, a library that can somewhat detect nudity. Here's the code:
// we're inside a loop
$(".images").prepend($("<img>").attr({src: whatever, id: uniqueid}).load(function(e) {
nude.load(e.target.id);
nude.scan(function(result) { if (!result) $(e.target).detach(); });
});
However, it detaches all of the wrong images because nude.js is slow and it completes after the loop has gone on to the later iterations, detaching those images instead of the one it was working on.
I've tried using a function factory:
function generateCallback(arg) {
return function(result) { if (!result) $(arg).detach(); };
}
and
nude.scan( generateCallback(e.target) )
but the same thing happens.
What I want is a load event that will remove the image if it seems to contain nudity. How can I do this properly?
EDIT: nude.js works like this:
nude.load(imageid);
nude.scan(callback); // it'll pass true or false into the callback
another edit: accidentally omitted the id setting from the code I posted, but it was there in my real code, so I added it here.
I suspect the case here is that this kind of sequential processing won't work with nude.js.
Looking at the nude.js code, I think your problem is occurring in the call to nude.scan. nude.js has a variable that stores the function to invoke after the scan has completed. When calling nude.scan(callback), this variable is set to be callback.
From your PasteBin, it seems as though the callback gets assigned as expected on the first call, but on the second and subsequent calls, it gets replaced, hence why the second image is detached and not the first.
What happends to your script, is that the e var is global to the function and so after each loop it gets replaced with the new one. So when the first image is scanned, e already became the event of the second image, which get detached.
To solve your problem, use closures. If you want to know more about closures, have a look here.
Otherway, here's the solution to your problem :
$(".images").prepend($("<img>").attr({src: whatever, id: uniqueid}).load(function(e) {
(function(e) {
nude.load(e.target.id);
nude.scan(function(result) { if (!result) $(e.target).detach(); });
}) (e);
});
EDIT: AS nick_w said, there is var that contains the callback and probably gets replaced each time so this is why it isn't the right picture getting detached. You will probably have to modify the script yourself
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/