Accessing global variable and asynchronous issue - javascript

Below is some code I've just started to work on (an avatar generator experiment). I want to be able to click on a button and change the position of the canvas element, but I'm having troubles with some things.
In the click event function on the button I console.log out canvasTop ...
console.log(this.canvasTop);
... however, it gets undefined. I can access the variable anywhere else in the code except inside this click event function. Why is it like this?
The other thing is the next two lines ...
this.canvasTop += 10;
AvatarGenerator.canvas();
... on the first on these lines I want to iterate the canvasTop value, and on the second line call the function that draws the canvas. However, it seems like the second line runs before the first line (yes, JS is asynchronous I know) which means that the canvas element won't move until the next time I click the button. How can I solve this?
Thanks in advance!
The code:
AvatarGenerator = {
canvasTop: 50,
canvasLeft: 50,
canvas: $('#canvas')[0],
context: canvas.getContext('2d'),
init: function() {
AvatarGenerator.canvas();
AvatarGenerator.toolBox();
},
canvas: function() {
console.log(this.canvasTop); // <-- 50
this.context.beginPath();
this.context.moveTo(this.canvasLeft, this.canvasTop);
this.context.lineTo(300, 300);
this.context.stroke();
},
toolBox: function() {
var moveLeftBtn = $('#moveLeftBtn');
moveLeftBtn.on('click', function(){
console.log(this.canvasTop); // <-- undefined, why?
this.canvasTop += 10;
AvatarGenerator.canvas();
});
}
};

The click handler is called in a different context, so this doesn't point to your object anymore.
Try this:
var self = this;
moveLeftBtn.on('click', function(){
console.log(self.canvasTop);
self.canvasTop += 10;
AvatarGenerator.canvas();
});
Or, for modern browsers, you can bind your object to your function so you don't need self:
moveLeftBtn.on('click', function(){
console.log(this.canvasTop);
this.canvasTop += 10;
AvatarGenerator.canvas();
}.bind(this));
//^^^^^^^^^^ this determines what 'this' in the callback function is pointing to

Related

javascript event running when the mouse is over elements created by another function

This function create_canvas_card() creates a box with smaller boxes in it. How do I get it to call the function card_mouseover() whenever the mouse is over one of the boxes?
function create_canvas_card(card_data, each_card){//where card_data is an element/object and each_card is an int
click_canvas_card_x = 10, click_canvas_card_y = 10;//these are set elsewhere
image_id = $(card_data.node).data('card')
click_canvas_cards[each_card] = click_canvas.rect(click_canvas_card_x, click_canvas_card_y, 40, 40).attr('fill', 'url(/images/thumbnails/image'+ image_id +'.jpg)');
//my attempt
$(card_data.node).bind('mouseover', function(e){
var card = cards[$(this).data('card')];
card_mouseover(card);
});
//another attempt
//click_canvas_cards[each_card].mouseover(click_canvas_card_mouseover(card_data.node));
}
which is called from this loop
for(each_card in cards_to_create_for_click){
var card_data = cards_to_create_for_click[each_card];
create_canvas_card(card_data, each_card);
}
So far neither's worked.
Use Raphael's built in Element.hover() method.
Element.hover
I've made a quick little fiddle to show you it in action here
For your example, I imagine you'd want to change your function to something like this:
function create_canvas_card(card_data, each_card){
click_canvas_card_x = 10, click_canvas_card_y = 10;//these are set elsewhere
image_id = $(card_data.node).data('card')
click_canvas_cards[each_card] = click_canvas.rect(click_canvas_card_x, click_canvas_card_y, 40, 40)
.attr('fill', 'url(/images/thumbnails/image'+ image_id +'.jpg)')
.hover(function(){
//Do something on hover
});

setInterval() example from the Javascript Mozilla docs not working in JSFiddle

I copied over Example #2 given in the Mozilla docs about setInterval() (https://developer.mozilla.org/en-US/docs/Web/API/window.setInterval?redirectlocale=en-US&redirectslug=DOM%2Fwindow.setInterval#Example_1.3A_Generic) but the color of the text does not alternate between red and blue in my JSFiddle:
DEMO: http://jsfiddle.net/lpsternotes/JHpt9/
It can't be because I need jQuery or because of the markup, right? I copied it over exactly.
I also wanted to ask for clarification purposes: the reason var nIntervId; is defined as a global variable at the top is so that it can be used in both the changeColor() and stopTextColor() functions, right?
var nIntervId; //global variable
function changeColor() {
nIntervId = setInterval(flashText, 500);
}
function flashText() {
var oElem = document.getElementById("my_box");
oElem.style.color = oElem.style.color == "red" ? "blue" : "red";
}
function stopTextColor() {
clearInterval(nIntervId);
}
In other words, if the code looked like this:
function changeColor() {
var nIntervId = setInterval(flashText, 500);
}
function flashText() {
var oElem = document.getElementById("my_box");
oElem.style.color = oElem.style.color == "red" ? "blue" : "red";
}
function stopTextColor() {
clearInterval(nIntervId); //undefined??
}
Your functions are defined inside another function (which is executed onLoad) (as per the second drop down menu in the JSFiddle options on the left).
This means they are scoped to that function and are not globals.
They are therefore out of scope for your onload="stopTextColor();" attribute.
Just call stopTextColor() in your existing onload handler and not in another one you create with the onload attribute.
Your fiddle works fine. Just change the second drop down menu on the left which says onLoad to No Wrap - in Head option.
You should also have a look at This link to know WHY.

Set onCLick callback inside object?

I have a carousel object, with images and a pager. The problem is I can't set the onClick to the pager. I'm obviously missing something here but I don't know what.
The error when I click in a pager item is:
Uncaught TypeError: Object #<HTMLDivElement> has no method 'scrollCarouselTo'
I set my onclick
carouselDots.on('click',function(){
this.scrollCarouselTo(1,5); // <-- problem lies here, how can i call this method?
});
and the scrollTo method
this.scrollCarouselTo=function(dotIndex, numDots)
{
//H_SCROLL.scrollToElement("#carouselItem"+dotIndex, 300);
H_SCROLL.scrollToPage(dotIndex, 0 , 300);
this.highlightCarouselDot(dotIndex, numDots);
}
Last, on my HTML file this is how I set it:
var tempCarousel = new Carousel();
tempCarousel.initialize(params,cont,scrollContainer);
My Carousel class: (parts of it that i think are relevant)
function Carousel() {
var container;
var pager;
var opt;
var thisCarousel;
//render the correct number of dots and highlight the indexed one
this.highlightCarouselDot=function(dotIndex, numDots)
{
var ui_str="";
console.log("numDots" + numDots);
for (var i=0;i<numDots;i++)
{
if (i==dotIndex)
ui_str+='<div class="carouselDot selected" id="carouselDot'+i+'"></div>';
else
ui_str+='<div class="carouselDot" id="carouselDot'+i+'"></div>';
}
console.log(ui_str);
console.log(pager);
pager.html(ui_str);
var carouselDots = $(pager.selector + " .carouselDot");
var dotSelected = $(pager.selector + " .selected");
carouselDots.css('background',opt.pagerImage);
carouselDots.width(opt.pagerSize);
carouselDots.height(opt.pagerSize);
carouselDots.on('click',function(){ //replace with touch
this.scrollCarouselTo(0,5);
});
dotSelected.css('background',opt.pagerSelectedImage);
}
this.scrollCarouselTo=function(dotIndex, numDots)
{
//H_SCROLL.scrollToElement("#carouselItem"+dotIndex, 300);
H_SCROLL.scrollToPage(dotIndex, 0 , 300);
this.highlightCarouselDot(dotIndex, numDots);
}
}
Thank you!
You are having trouble understanding where the scope is changing in your code. Yes this refers to the object you are in, but when you assign an event handler such as onclick, that function is run in the scope of the UI element that was clicked. This means that in your onclick code, this refers to the html object that was clicked, and not the highlightCarouselDot object.
One common solution to this problem is to use an extra variable to store the value of this.
var self = this;
at the start of your object, that way you can refer to self within your event handlers when you want to refer to the outside object.

Same function repeatedly called resets the setTimeout inner function

All,
I have a 'credit module' (similar to credit system in games), which when a user performs an action, creates an inner div with the cost to be added or substracted so user can see what the cost of the last action was.
Problem: Everything works fine as long as the function is called once, if the user performs multiple actions quickly, the setTimeout functions (which are suppose to animate & then delete the cost div) donot get executed. It seems the second instance of the function resets the setTimeout function of the first.
(function()
{
$("#press").on("click", function(){creditCost(50)});
function creditCost(x)
{
var eParent = document.getElementById("creditModule");
// following code creates the div with the cost
eParent.innerHTML += '<div class="cCCost"><p class="cCostNo"></p></div>';
var aCostNo = document.getElementsByClassName("cCostNo");
var eLatestCost = aCostNo[aCostNo.length - 1];
// following line assigns variable to above created div '.cCCost'
var eCCost = eLatestCost.parentNode;
// cost being assigned
eLatestCost.innerHTML = x;
$(eCCost).animate ({"left":"-=50px", "opacity":"1"}, 250, "swing");
// following code needs review... not executing if action is performed multiple times quickly
setTimeout(function()
{
$(eCCost).animate ({"left":"+=50px", "opacity":"0"}, 250, "swing", function ()
{
$(eCCost).remove();
})
}, 1000);
}
})();
jsfiddle, excuse the CSS
eParent.innerHTML += '<div class="cCCost"><p class="cCostNo"></p></div>';
is the bad line. This resets the innerHTML of your element, recreating the whole DOM and destroying the elements which were referenced in the previous invocations - letting their timeouts fail. See "innerHTML += ..." vs "appendChild(txtNode)" for details. Why don't you use jQuery when you have it available?
function creditCost(x) {
var eParent = $("#creditModule");
// Create a DOM node on the fly - without any innerHTML
var eCCost = $('<div class="cCCost"><p class="cCostNo"></p></div>');
eCCost.find("p").text(x); // don't set the HTML if you only want text
eParent.append(eCCost); // don't throw over all the other children
eCCost.animate ({"left":"-=50px", "opacity":"1"}, 250, "swing")
.delay(1000) // of course the setTimeout would have worked as well
.animate ({"left":"+=50px", "opacity":"0"}, 250, "swing", function() {
eCCost.remove();
});
}
You are starting an animation and scheduling a timeout to work on DOM elements that will get modified in the middle of that operation if the user clicks quickly. You have two options for fixing this:
Make the adding of new items upon a second click to be safe so that it doesn't mess up the previous animations.
Stop the previous animations and clean them up before starting a new one.
You can implement either behavior with the following rewrite and simplification of your code. You control whether you get behavior #1 or #2 by whether you include the first line of code or not.
function creditCost(x) {
// This first line of code is optional depending upon what you want to happen when the
// user clicks rapid fire. With this line in place, any previous animations will
// be stopped and their objects will be removed immediately
// Without this line of code, previous objects will continue to animate and will then
// clean remove themselves when the animation is done
$("#creditModule .cCCost").stop(true, false).remove();
// create HTML objects for cCCost
var cCCost = $('<div class="cCCost"><p class="cCostNo">' + x + '</p></div>');
// add these objects onto end of creditModule
$("#creditModule").append(cCCost);
cCCost
.animate ({"left":"-=50px", "opacity":"1"}, 250, "swing")
.delay(750)
.animate({"left":"+=50px", "opacity":"0"}, 250, "swing", function () {
cCCost.remove();
});
}
})();
Note, I changed from setTimeout() to .delay() to make it easier to stop all future actions. If you stayed with setTimeout(), then you would need to save the timerID returned from that so that you could call clearTimeout(). Using .delay(), jQuery does this for us.
Updated code for anyone who might want to do with mostly javascript. Jsfiddle, excuse the CSS.
function creditCost(x)
{
var eParent = document.getElementById("creditModule");
var eCCost = document.createElement("div");
var eCostNo = document.createElement("p");
var sCostNoTxt = document.createTextNode(x);
eCCost.setAttribute("class","cCCost");
eCostNo.setAttribute("class","cCostNo");
eCostNo.appendChild(sCostNoTxt);
eCCost.appendChild(eCostNo);
eParent.insertBefore(eCCost, document.getElementById("creditSystem").nextSibling);
$(eCCost).animate ({"left":"-=50px", "opacity":"1"}, 250, "swing");
setTimeout(function()
{
$(eCCost).animate ({"left":"+=50px", "opacity":"0"}, 250, "swing", function ()
{
$(eCCost).remove();
})
}, 1000);
}

JavaScript/jQuery onmouseover problem

I want that when mouse is over an image, an event should be triggered ONCE, and it should be triggered again only after mouse is out of that image and back again, and also at least 2 seconds passed.
My current function is called continuously (refreshcash) if I leave the mouse over my image
<img src="images/reficon.png" onmouseover="refreshcash()" onmouseout="normalimg()" id="cashrefresh"/>
function refreshcash() {
$("#showname").load('./includes/do_name.inc.php');
$("#cashrefresh").attr("src","images/reficonani.gif");
}
function normalimg() {
$("#cashrefresh").attr("src","images/reficon.png");
}
code update
This code seems to have a bug,but the algorithm is kinda logical
<script type="text/javascript">
var canhover = 1;
var timeok = 1;
function redotimeok() {
timeok = 1;
}
//
function onmenter()
{
if (canhover == 1 && timeok == 1)
{
enter();
canhover = 0;
}
}
//
function onmleave()
{
leave();
canhover = 1;
setTimeout(redotimeok(), 2000);
leave();
}
//
$('#cashrefresh').hover(onmenter(),onmleave());
function enter(){
$("#showname").load('./includes/do_name.inc.php');
$("#cashrefresh").attr("src","images/reficonani.gif");
}
function leave(){
$("#cashrefresh").attr("src","images/reficon.png");
}
</script>
Try the hover:
$('#cashrefresh').hover(function(){
$("#showname").load('./includes/do_name.inc.php');
$("#cashrefresh").attr("src","images/reficonani.gif");
}, function(){
$("#cashrefresh").attr("src","images/reficon.png");
});
And your image should look like:
<img src="images/reficon.png" id="cashrefresh"/>
Update:
Modify your code like this:
var e = null;
var l = null;
$('#cashrefresh').hover(function(){
e = setTimeout(enter, 2000)
}, function(){
l = setTimeout(leave, 2000)
});
function enter(){
$("#showname").load('./includes/do_name.inc.php');
$("#cashrefresh").attr("src","images/reficonani.gif");
clearTimeout(e);
}
function leave(){
$("#cashrefresh").attr("src","images/reficon.png");
clearTimeout(l);
}
Do you have the images cached in some way? If you replace them by their src attribute without specifying width/height elsewhere (best would be CSS) or having them readily available then the hovered box (img element) will collapse into a smaller (or no) box until the image has been loaded far enough for the browser to know the correct dimensions of the image to resize the box (which may affect other elements being adjusted to the image). The exact effect depends on the browser but you may lose the hover state causing the call of your mouseout function.
I assume that both images are the same size, so if you didn't already, you could try adding the dimensions to your CSS for #cashrefresh and see if that fixes the problem.
For the delay I would recommend using the jQuery timers plugin (or a similar one) which eases handling of timers compared to doing it on your own. You would probably want to give your timers names and try to stop older ones before you add the next one.

Categories