What about variable scopes inside a callback function - javascript

If I have a callback function within a callback function I can't reach the variables declared inside the first callback.
See example (Uses some jquery):
my fiddle
Why is this?
Or
What am I doing wrong!?
I am using this in the following:
$('<div/>').html(data).find('load').each(function(id, deze) {
var method = $(deze).attr("method");
var animate = $(deze).attr("animate");
var locatie = $(deze).attr("id");
var html = $(deze).html();
if(!method){
method = 'overide';
}
if(!locatie){
error('A002','No loadloc');
}
if(!html){
html = '';
}
if(!animate){
animate = false;
}
else if(animate == 'fade'){
var effectout = 'fade';
var timeout = 100;
var optionsout = {
easing: 'swing'
};
var effectin = 'fade';
var timein = 600;
var optionsin = {
easing: 'swing'
};
}
else{
animate = false;
error('A006','Animation: "' + animate + '" does not exist');
}
//here I make callback or direct function call
if(animate){
$('#' + locatie).hide(effectout, optionsout, timeout, load());
}
else{
load();
}
function load(){
console.log('hallo');
console.log(html);
if(method == 'overide'){
$('#' + locatie).html(html);
}
else if(method == 'tablerow'){
var html = $('tr', deze).html();
$('#' + locatie).html(html);
}
else if(method == 'append'){
$('#' + locatie).append(html);
}
else if(method == 'prepend'){
$('#' + locatie).prepend(html);
}
else{
error('A007','load method: "' + method + '" does not exist');
}
if(animate){
$('#' + locatie).show(effectin, optionsin, timein);
}
}
});

Why is this?
It is not. You can simply access something, it is in the scope of the load function that you posted.
What am I doing wrong!?
You're not passing the load function itself as the callback, but it's result - you're immediately calling it (without arguments). You want
$('#' + locatie).hide(effectout, optionsout, timeout, load);
// ^^
//It seems I can't reach html and other variables here
console.log(this.html);
That's not a variable, that's a property of this (and the this keyword points to different things in the different calls). What you want is a simple variable:
console.log(html);
Same story for this.locatie and the others.

I changed your code a bit and I believe this is what you were trying to do:
http://jsfiddle.net/smerny/2M6k3/
var anothercallbackfunctiontriggerthing = function(cb) {
cb();
}
$('.something').each(function(id, value) {
var something = 'something';
var load = function(){
// can't reach something here
console.log(something);
}
anothercallbackfunctiontriggerthing(load);
});
As load() didn't return anything, I assume you were wanting to pass it as a callback (load) and then call it from within anothercallbackfunctiontriggerthing. The above code will print "something" for each .something.

The reason it doesn't work is because the keyword this changes meanings within the context of load() since you're defining a function using function load(). There are several ways of getting around this; here's one:
Before:
if(animate){
$('#' + locatie).hide(effectout, optionsout, timeout, load());
}
else{
load();
}
use
var load = (function(el) {
return function() {
console.log('hallo');
//It seems I can't reach html and other variables here
console.log(el.html);
if(method == 'overide'){
$('#' + el.locatie).html(el.html);
}
else if(method == 'tablerow'){
var html = $('tr', deze).html();
$('#' + el.locatie).html(el.html);
}
else if(method == 'append'){
$('#' + el.locatie).append(el.html);
}
else if(method == 'prepend'){
$('#' + el.locatie).prepend(el.html);
}
else{
error('A007','load method: "' + el.method + '" does not exist');
}
if(el.animate){
$('#' + el.locatie).show(el.effectin, el.optionsin, el.timein);
}
};
})(this);
What I did there was to change all references to this with el. And then I injected this as el by using the following format:
load = ( function(el) { return yourFunction() {...}; })(this);
Let me know if that makes sense and if it works for you :)

Related

Can't get each to work after ajax page load changes contents

on page load I run this function to hide long quoted posts into a clickable link to prevent huge comments on my site:
function hide_long_quotes()
{
$('.comments .comment_quote').each(function(i)
{
var actual_text = $(this).text();
var content = $(this).outerHTML();
if(actual_text.length > showChar)
{
var cite = $(this).find('cite span.username').first().text();
var cite_link = '';
if (cite.length > 0)
{
cite_link = 'from ' + cite;
}
var html = '<span class="morecontent">' + content + '</span>' + moretext + cite_link + '<br />';
$(this).replaceWith(html);
}
if (i+1 === quote_count) // this will be executed at the end of the loop
{
// deal with being linked to a comment, so we can put the window to the correct scroll position, since it will be different due to hidden quotes making the page smaller
if(window.location.hash)
{
var hash = window.location.hash.substring(1); //Puts hash in variable, and removes the # character
if (hash.indexOf("r") >= 0)
{
$('#'+hash)[0].scrollIntoView();
}
}
}
});
}
The problem is that when I reload ".comments" through ajax/load the above function no longer works:
function paginate_comments(page, article_id)
{
var url = "/includes/ajax/post_comment.php";
var current_url = window.location.href;
var host = window.location.host;
if(current_url.indexOf(host + '/admin.php?module=reviewqueue') != -1 || current_url.indexOf(host + '/admin.php?module=articles&view=Submitted') != -1 || current_url.indexOf(host + '/admin.php?module=articles&view=Submitted') != -1)
{
var area = 'admin';
}
else
{
var area = 'normal';
}
$('.comments').load(url, {'type':'reload', 'article_id': article_id, 'page': page, 'area': area}, function()
{
$(".lb-container").show();
$('.box.comments').get(0).scrollIntoView();
hide_long_quotes();
});
}
Not sure why it doesn't work, as the function is being called in the completed callback part of the load function?
What about giving the element collection to that function:
function hide_long_quotes(var comments = false)
{ /* for the case that you don't pass a collection to the function, maybe in the first call when the page is loaded */
comments = comments ? comments : $('.comments .comment_quote');
comments.each(function(i)
{
...
}
function paginate_comments(page, article_id)
{
...
$('.comments').load(
url,
{
'type':'reload',
'article_id': article_id,
'page': page,
'area': area
},
function()
{
$(".lb-container").show();
$('.box.comments').get(0).scrollIntoView();
hide_long_quotes($('.comments .comment_quote'));
}
);
}

Using setInterval as a counter

I'm trying to create a page that changes every 10 seconds 4 times and then begins again. To do this I made a counter and passed it along with my function. It doesn't even seem like its loading.
I tried using <body onload="start()"> as well.
<script>
var i = 1;
function start(){
i = setInterval(changeEdu, 10000, i);
}
function changeEdu(i){
if(i == 4){
i = 1;
}else{
i++;
}
document.getElementById("edu1").src = "left" + i + ".jpg";
document.getElementById("edu2").src = "right" + i + ".jpg";
return i;
}
</script>
By declaring i as a parameter of your function, your increment will only mutate the local variable and not your global state. Also the return value is ignored.
var i = 1;
function start() {
setInterval(changeEdu, 10000);
}
function changeEdu() {
// ^^
if (i == 4) {
i = 1;
} else {
i++;
}
document.getElementById("edu1").src = "left" + i + ".jpg";
document.getElementById("edu2").src = "right" + i + ".jpg";
}
I believe it is because you are reassigning variable i at the onset of the start() function. setInterval returns a number that is used to cancel the function with the clearInterval(theNumber) function.
I am also quite new to JS, but I would try to delete the reassignment in the start() function and try again.
That is not how setInterval works, it doesn't return your return value.
You need to create a closure.
Also, your startInterval() is never called.
Change it to this and it works:
<script>
(function(){
var i = 1;
function changeEdu(){
if(i == 4){
i = 1;
}else{
i++;
}
// document.getElementById("edu1").src = "left" + i + ".jpg";
// document.getElementById("edu2").src = "right" + i + ".jpg";
console.log(i);
}
setInterval(changeEdu, 10000);
})();
</script>

How to break from Jquery click callback function

I am making my own version of simon Game, but the callback function inside click is not existing after user press wrong input. As a result function handler.patternRepeatPlayer() is getting called recursively for each element of array pattern.I want a solution to break from the else after callinghandler.patternRepeatPlayer just once. The program is working fine in strict mode, but only in non-strict mode inside else , I am not able to break from else.
-- can access the project on Git.
https://github.com/santosh1357/simonGame.git
The flow is like from html -> Function simonnGame.PatternGen from PatternGen -> handler.PatternRepeatPlayer -> PatternRepPlayer -> PatternMatcher -> userInput(here if wrong user input in non-strict mode) -> patternRepeatPlayer
This case is failing as in this case else is not existing after calling the function only once.
//Problematic Function.
userInput: function(){
var userPattern = new Array();var id;
$('img').click(function(){
id = parseInt(this.id,10); userPattern.push(id);handler.effect(id);
if(userPattern.indexOf(id) !== simonGame.PATTERN.indexOf(id)){
if($('.chkStrict:checked').val() === "on"){
var audio = new Audio('sounds/wrong.mp3');
audio.play();
setTimeout(function(){window.location.reload(true)},1000);
} else {
var audio = new Audio('sounds/wrong.mp3');
audio.play();
userPattern.length = 0;
handler.repeatFlag = true;
handler.patternRepeatPlayer(); ****//this is getting called recursivelly rather than quiting after calling once****
return ;
}
}
//End Problematic Functiom
I think there is some misunderstanding on how click callback functions work.
//Fullcode
var simonGame = {
COUNT: 0,
PATTERN: [],
SOUND:[{file:'sounds/sa.mp3'},{file:'sounds/re.mp3'},{file:'sounds/ga.mp3'},{file:'sounds/ma.mp3'},{file:'sounds/pa.mp3'},{file:'sounds/dha.mp3'},{file:'sounds/nee.mp3'}],
patternGen: function(){
var randomId;
randomId = Math.floor(Math.random() * 7);
simonGame.PATTERN.push(randomId);
if(simonGame.COUNT > 20){
alert("You have won the game!!");
window.location.reload(true);
}
simonGame.COUNT += 1;
//debugger;
//console.log("increase count true calling count display " + simonGame.COUNT);
handler.countDisplay();
//console.log("count gen true calling patternPlayer with PATTERN " + simonGame.PATTERN );
handler.patternRepeatPlayer();
}, //close patternGen
patternMatcher: function(genPattern){
//console.log("inside patternMatch");
var genPattern = simonGame.patternGen;
//setTimeout(function(){
//console.log("PATEERN: " + simonGame.PATTERN + "COUNT " + simonGame.COUNT );
//calling user input
console.log("calling user Input");
handler.userInput();
setTimeout(function(){
if(handler.repeatFlag === false){ //execute count gen only if repeat flag is false inside user INPUT
genPattern();
}
},simonGame.COUNT*2000);
//console.log("pattern check true, calling pattern gen");
//},simonGame.COUNT*5000); //c`enter code here`lose setTimeout
}, //close patternMatcher
} //close simonGame
var handler = {
countRepPlayer: 0,
repeatFlag: false,
patternRepeatPlayer: function(){
var repeater = setInterval(function(){
handler.effect(simonGame.PATTERN[handler.countRepPlayer]);
handler.countRepPlayer += 1;
if(handler.countRepPlayer > simonGame.COUNT){
clearInterval(repeater);
//setTimeout(function(){
simonGame.patternMatcher();
//},1000);
handler.countRepPlayer = 0;
}
},1000);//close sestInterval
}, //close patternRepeatPlayer
effect: function(id){
var img = document.getElementById(id);
if(img !== null && id !== undefined){
$( img ).fadeIn(100).fadeOut(100).fadeIn(100);//fadeOut(200).fadeIn(200);
//debugger;
var audio = new Audio(simonGame.SOUND[id].file);
audio.play();
//console.log("id inside effect " + id)
}
},//close effect
countDisplay: function(){
document.getElementById("count").innerHTML = "Count: " + simonGame.COUNT;
}, //close countIncrease
userInput: function(){
var userPattern = new Array();var id;
$('img').click(function(){
id = parseInt(this.id,10);
userPattern.push(id);
handler.effect(id);
console.log(" user " + userPattern);
console.log(" pattern " + simonGame.PATTERN);
if(userPattern.indexOf(id) !== simonGame.PATTERN.indexOf(id)){
console.log(" WRONG USER INPUT ");
if($('.chkStrict:checked').val() === "on"){
var audio = new Audio('sounds/wrong.mp3');
audio.play();
setTimeout(function(){window.location.reload(true)},1000);
} else {
console.log("inside else " );
var audio = new Audio('sounds/wrong.mp3');
audio.play();
userPattern.length = 0;
handler.repeatFlag = true;
handler.patternRepeatPlayer(); ****//this is getting called recursivelly rather than quiting after calling once**.**
return ;
}
}
//reset the userPattern Array
if(userPattern.length === simonGame.PATTERN.length){
userPattern.length = 0;
}
}); //close click.
}
} //close handler
Yes, It will be called recursively, because you set interval for it.
Here you can modify your code:
patternRepeatPlayer: function(){
var repeater = setInterval(function(){
handler.effect(simonGame.PATTERN[handler.countRepPlayer]);
handler.countRepPlayer += 1;
if(handler.countRepPlayer > simonGame.COUNT){
clearInterval(repeater);
//setTimeout(function(){
simonGame.patternMatcher();
//},1000);
handler.countRepPlayer = 0;
}
},1000);//close sestInterval
}
To: (EDITED)
function myCallbackFunction(repeater){
handler.effect(simonGame.PATTERN[handler.countRepPlayer]);
handler.countRepPlayer += 1;
if(handler.countRepPlayer > simonGame.COUNT){
clearInterval(repeater);
simonGame.patternMatcher();
handler.countRepPlayer = 0;
}
}
patternRepeatPlayer: function(){
var repeater = setInterval(function() {myCallbackFunction(repeater);}, 1000);
}
And where you need to call it once, just call myCallbackFunction(repeater)

Why can I not select an element loaded with $.get in JQuery [duplicate]

This question already has answers here:
How do I return the response from an asynchronous call?
(41 answers)
Closed 8 years ago.
I have a series of buttons loaded from a JSON, which in turn should disappear and append other buttons on click.
Like so (the first level of buttons is already on the page and reacting properly to other events like hover):
...
$(document).on('click', "#subcategoryButtons button", function () {
getTemplate('imgMenu.html');
var entryIndex = this.id[0];
var subentryIndex;
if (this.id[1] === '0')
{
subentryIndex = this.id.slice(-1);
}
else
{
subentryIndex = this.id.slice(-2);
}
var imgs = data.category[entryIndex].subcategory[subentryIndex].imgs;
$.each(imgs, function (imgIndex)
{
var imgName = data.category[entryIndex].subcategory[subentryIndex].imgs[imgIndex].imgName;
var imgId = data.category[entryIndex].subcategory[subentryIndex].imgs[imgIndex].imgId;
$('#imgButtons span').append('<button id="' + imgId + '">' + imgName + '</button>');
});
});
}
;
...
This is the content of the template being loaded:
<div id="imgSpace">
<aside id="overlayRight">
Right Overlay space
</aside>
<div id="overlayBottom">
Bottom Overlay
</div>
</div>
<nav id="imgButtons" class="resizable">
<span></span>
</nav>
Here's the getTemplate code:
function getTemplate(templateUrl)
{
$.get('templates/' + templateUrl, function (content)
{
if (templateUrl === 'leftMenu.html')
{
$('#leftMenu').html(content);
}
else
{
$('#main').html(content);
}
});
}
Even though it should append the buttons to the #imgButtons span, it seems as if it cannot select any of the elements in the template just loaded. If I try to append the buttons to another part of the page (say like the left menu, which is not recently loaded) or instead of getting a template I simply clear out the HTML in the main, the attachment works. So it appears that the issue is how to select elements that have been loaded. Any help would be greatly appreciated.
Thanks to everyone who pointed me in the right direction. In the end I didn't use Ajax but deferred.done like so:
$(document).on('click', "#subcategoryButtons button", function () {
var entryIndex = this.id[0];
var subentryIndex;
if (this.id[1] === '0') {
subentryIndex = this.id.slice(-1);
} else {
subentryIndex = this.id.slice(-2);
}
var imgs = data.category[entryIndex].subcategory[subentryIndex].imgs;
$('main').html('');
$.get("templates/imgMenu.html", function (content)
{
$('#main').html(content);
}).done(function () {
$.each(imgs, function (imgIndex) {
var imgName = data.category[entryIndex].subcategory[subentryIndex].imgs[imgIndex].imgName;
var imgId = data.category[entryIndex].subcategory[subentryIndex].imgs[imgIndex].imgId;
console.log(entryIndex);
$('#imgButtons span').append('<button id="' + imgId + '">' + imgName + '</button>');
});
});
});
}
;
You're using the wrong selector #imgButtons button should be #imgButtons span to select the span in #imgButtons
Also your template is loaded asynchronously so you'll have to wait until it is loaded (via a callback function) to manipulate it. something like
$(document).on('click', "#subcategoryButtons button", function () {
getTemplate('imgMenu.html', callback);
function callback(){
var entryIndex = this.id[0];
var subentryIndex;
if (this.id[1] === '0')
{
subentryIndex = this.id.slice(-1);
}
else
{
subentryIndex = this.id.slice(-2);
}
var imgs = data.category[entryIndex].subcategory[subentryIndex].imgs;
$.each(imgs, function (imgIndex)
{
var imgName = data.category[entryIndex].subcategory[subentryIndex].imgs[imgIndex].imgName;
var imgId = data.category[entryIndex].subcategory[subentryIndex].imgs[imgIndex].imgId;
$('#imgButtons span').append('<button id="' + imgId + '">' + imgName + '</button>');
});
}
});
...
function getTemplate(templateUrl, callback)
{
$.get('templates/' + templateUrl, function (content)
{
if (templateUrl === 'leftMenu.html')
{
$('#leftMenu').html(content);
}
else
{
$('#main').html(content);
}
callback();
});
}
let's make a bit modification on your code, use promises - the jqxhr object returned by the ajax method implements the promise interface, therefore you could make use of it.
function getTemplate(templateUrl)
{
return $.get('templates/' + templateUrl, function (content)
{
if (templateUrl === 'leftMenu.html')
{
$('#leftMenu').html(content);
}
else
{
$('#main').html(content);
}
});
}
use it in this way
$(document).on('click', "#subcategoryButtons button", function (e) {
getTemplate('imgMenu.html').then(function () {
var entryIndex = this.id[0];
var subentryIndex;
if (this.id[1] === '0') {
subentryIndex = this.id.slice(-1);
} else {
subentryIndex = this.id.slice(-2);
}
var imgs = data.category[entryIndex].subcategory[subentryIndex].imgs;
$.each(imgs, function (imgIndex) {
var imgName = data.category[entryIndex].subcategory[subentryIndex].imgs[imgIndex].imgName;
var imgId = data.category[entryIndex].subcategory[subentryIndex].imgs[imgIndex].imgId;
$('#imgButtons span').append('<button id="' + imgId + '">' + imgName + '</button>');
});
});
});

Javascript scope of object property

Let's say I want to make a Javascript class for boxes in my page. When an object of that class is made, I want to add a click event to it that would require me accessing one or many of its unique properties. How would I do that? Here's an example:
function Box(boxClassName, originalColor, changeColor){
this.boxClassName = boxClassName;
this.originalColor = originalColor;
this.changeColor = changeColor;
this.initialize = function (){
var html = '<div class="' + this.boxClassName + '></div>';
$(document).append(html);
$(this.boxClassName).css("background-color", originalColor);
// Toggle box color on click
$(this.boxClassName).click(function (){
// this.boxClassName is undefined here, none of this works
if ($("." + this.boxClassName).css("background-color") == originalColor) {
$("." + this.boxClassName).css("background-color", this.changeColor);
} else {
$("." + this.boxClassName).css("background-color", this.originalColor);
}
});
};
this.initialize();
}
There are two ways:
1) Keep a reference to this in a variable and use that:
// Toggle box color on click
var self = this;
$(this.boxClassName).click(function () {
if ($("." + self.boxClassName).css("background-color") == originalColor) {
$("." + self.boxClassName).css("background-color", self.changeColor);
} else {
$("." + self.boxClassName).css("background-color", self.originalColor);
}
});
2) Or bind the click callback to the Box
$(this.boxClassName).click(function () {
if ($("." + this.boxClassName).css("background-color") == originalColor) {
$("." + this.boxClassName).css("background-color", this.changeColor);
} else {
$("." + this.boxClassName).css("background-color", this.originalColor);
}
}.bind(this));
You cannot to access property of Box in onclick event. If you would like to store data of html element, you have to use http://api.jquery.com/jquery.data/. By the way, below code might work for you purpose.
if ($(this).css("background-color") == originalColor) {
$(this).css("background-color", this.changeColor);
} else {
$(this).css("background-color", this.originalColor);
}
First of all, you can bind function to get proper this
$(this.boxClassName).click(function (){
// this.boxClassName is now defined here
if ($("." + this.boxClassName).css("background-color") == originalColor) {
$("." + this.boxClassName).css("background-color", this.changeColor);
} else {
$("." + this.boxClassName).css("background-color", this.originalColor);
}
}.bind(this));
Also consider using slightly different approach
function Box(boxClassName, originalColor, changeColor) {
var changedBg = false, div = $('<div />'), handler = function ( e ) {
$(this).css('background-color', changedBg ? originalColor : changeColor)
changedBg = !changedBg
}
div.addClass(boxClassName)on('click', handler).appendTo(document)
}

Categories

Resources