I find myself stuck for some days on a callback issue, and I can't find any convenient solution. Here is the problem:
I have some jQuery that looks like that
$(document).ready(function(){
masterFunction_A();
masterFunction_B();
});
function masterFunction_A() {
littleFunction_1();
littleFunction_2();
littleFunction_3();
littleFunction_4();
// etc...
}
function masterFunction_B() {
// do stuff
}
I would like to start executing masterFunction_B() when all littleFunctions() are done. When I try to set masterFunction_B() as a callback for masterFunction_A(), it seems that it is launched when masterFunction_A() has launched all the littleFunctions(), but not when littleFunctions() are over...
I tried to:
Set a timer, and launch masterFunction_B() at the end of that timer but... that's not a proper way to to it
Set callbacks in all the littleFunctions and a counter that counts until the right amount of callbacks are called, but I think there is a better and cleaner way to get what I want.
Can you help ?
Thanks a lot!
—— EDIT ——
This is the content of masterFunction_A(), actually called loadContents() :
function loadContents () {
$.getJSON('data/data.json', function(data) {
for (var i = 0 ; i < data.length ; i++) {
$(".projects_ordered_list").append("<!-- PROJECT " + i + " -->");
$(".projects_ordered_list").append("<li id='projects_ordered_list_item_" + i + "'></li>");
}
for (var i = 0 ; i < data.length ; i++) {
$("#projects_ordered_list_item_" + i).load('projects.html', function() {
var this_html_id = $(this).attr("id");
var this_id = this_html_id.substr(this_html_id.length - 1);
this__display_id = this_id;
this__display_id++;
$(this).find(".project_id").css("background", data[this_id].color);
$(this).find(".project_id_title").html(this__display_id + ".");
$(this).find(".project_name").html(data[this_id].name.en);
$(this).find(".project_date").html(" — " + data[this_id].date.en);
for (var j = 0 ; j < data[this_id].imgs.length ; j++) {
$(this).find(".bxslider").append("<li><img src='" + data[this_id].imgs[j] + "'></li>");
}
});
}
});
}
Using jQuery deferreds which are built into jQuery ajax functions, you can make it all work like this.
First, make each of your littleFunction_x() code look like this:
function littleFunction_1() {
var d = $.getJSON(...)
// other code
return d;
}
Then, you can do this:
$(document).ready(function(){
$.when(masterFunction_A()).done(masterFunction_B);
});
function masterFunction_A() {
var d = $.Deferred();
$.when(littleFunction_1(),
littleFunction_2(),
littleFunction_3(),
littleFunction_4()
).done(function() {
d.resolve();
});
return d;
}
After thinking about it a bit more, I think you can also do a slightly cleaner version like this:
$(document).ready(function(){
masterFunction_A().done(masterFunction_B);
});
function masterFunction_A() {
return $.when(littleFunction_1(),
littleFunction_2(),
littleFunction_3(),
littleFunction_4()
);
}
Take a look at the jQuery Deferred feature. I think it's exactly what you need.
first, create a Deferred for each littleFunction:
dfd1 = $.Deferred();
dfd2 = $.Deferred();
dfd3 = $.Deferred();
dfd4 = $.Deferred();
in each littleFunction, resolve the Deferred at the end:
function littleFunction1() {
...
dfd1.resolve();
}
...
when all Deferreds are resolved, start executing master_Function_B:
$.when(dfd1, dfd2, dfd3, dfd4).then(masterFunction_B);
Related
I have the following code and I dont know why it is returning trackingIds[i] as undefined in the View and Click function... I want to fill an array and the code should go through each array index and checks if element is hovered or clicked. el_id and trackingIds[i] in the first function return the correct values. I would appreciate any help because I cant seem to figure this out.
jQuery(document).ready(function(){
var trackingIds = ["elementid"];
for(i=0; i<trackingIds.length; i++){
var el_id = jQuery('#'+trackingIds[i]);
console.log(el_id);
console.log(trackingIds[i]);
el_id.click(function() { Click(trackingIds[i]);});
el_id.mouseover(function() { View(trackingIds[i]);});
}
});
function Click(a) {
//do stuff...
console.log("Click was called from:"+a);
}
function View(b){
// do stuff..
console.log("View was called from:"+b)
}
You need to use let in your for-loop.
jQuery(document).ready(function() {
var trackingIds = ["elementid"];
for (let i = 0; i < trackingIds.length; i++) {
var el_id = jQuery('#' + trackingIds[i]);
//console.log(el_id);
//console.log(trackingIds[i]);
el_id.click(function() {
Click(trackingIds[i]);
});
el_id.mouseover(function() {
View(trackingIds[i]);
});
}
});
function Click(a) {
//do stuff...
console.log("Click was called from: " + a);
}
function View(b) {
// do stuff..
console.log("View was called from: " + b)
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<h1 id='elementid'>Click me!</h1>
Resource
let
The let statement declares a block scope local variable, optionally initializing it to a value.
Alright, here's a puzzler. I've got a jQuery function to display a PHP generated list of announcements for a website via .fadeIn/.fadeOut; the very first thing loaded on the page is jQuery 1.11.xx from a CDN. I'm running Bootstrap, fullCalendar, SmartMenus, etc., and jQuery is most definitely loading.
Except within the setInterval() to update the announcement. This is rough-code, some functionality isn't present, but to my mind it should be doing an animation.
var announcementArray = [];
var announcementSource = "../announcements.php";
var totalAnnc;
$.getJSON(announcementSource, function(data) {
announcementArray = data.concat();
totalAnnc = announcementArray.length;
});
var count = 0;
var fadeAnnc = function() {
$('#announcementArea').text(announcementArray[count].announceText);
$('#announcementArea').fadeIn('slow',function() {
$('#announcementArea').css('display','block');
}).delay(2000).fadeOut('slow',function() {
count = (count + 1) % totalAnnc;
});
};
setInterval(function() {
fadeAnnc();
}, 3000);
Instead, when I run the page, I get a "function not defined" error for any jQuery function that's called within the setInterval(). If I call using document.getElementById('announcementArea').innerHTML = etc., it works, but doing the fade in/out via DOM manipulation seems to be more work than is needed when jQuery is available and working everywhere else on the page.
I've tried a few scope adjustments and have been working on what should be simple code for the last 5 hours. So, where's my glaring error? ;)
Not sure what kind of scope issue you are having (looks like it's the result of unposted code, as everything in your question looks OK), but if you want a fairly foolproof way of passing along the jQuery object, you could always pass it as a parameter:
var fadeAnnc = function($) {
$('#announcementArea').text(announcementArray[count].announceText);
$('#announcementArea').fadeIn('slow',function() {
$('#announcementArea').css('display','block');
}).delay(2000).fadeOut('slow',function() {
count = (count + 1) % totalAnnc;
});
};
setInterval(function() {
fadeAnnc($);
}, 3000);
Based on your updated answer, here's another possible solution:
(function($){
var announcementArray = [];
var announcementSource = "../announcements.php";
var announcementSpace = "#announcementArea";
$.getJSON(announcementSource, function(data) {
announcementArray = data.concat();
if (announcementArray.length === 0) {
$('#anncRow').css('display','none');
}
});
var count = 0;
var masterCount = 0;
var totalAnnc = announcementArray.length;
var timer;
var fadeAnnc = function() {
if (announcementArray.length > 0) {
$(announcementSpace).html(announcementArray[count].announceText);
$(announcementSpace).fadeIn(750, function() {
$(announcementSpace).css('display','block');
}).delay(4500).fadeOut(750, function() {
$(announcementSpace).css('display','hidden');
});
}
count += 1;
if ((count % announcementArray.length) == 0) {count = 0}
};
setInterval(fadeAnnc, 6000);
}(jQuery));
$ is defined as a function parameter and thus overrides the globally scoped $ within the function body, protecting it's definition for your code. This is actually exactly what jQuery recommends when creating an extension.
My previous answer - scratch that:
The issue was more interesting - somewhere between the SmartMenu plugin and the LibraryThing book display widget there is a jQuery conflict created. This explains why - depending on the load order - different parts would break, but always the setInterval(), which always loaded after SmartMenu and LibraryThing.
So, my somewhat messy solution is to release the $ at the beginning of the script and reclaim it at the end so on other pages jQuery has access to it, like so:
jq = jQuery.noConflict();
var announcementArray = [];
var announcementSource = "../announcements.php";
var announcementSpace = "#announcementArea";
jq.getJSON(announcementSource, function(data) {
announcementArray = data.concat();
if (announcementArray.length === 0) {
jq('#anncRow').css('display','none');
}
});
var count = 0;
var masterCount = 0;
var totalAnnc = announcementArray.length;
var timer;
var fadeAnnc = function() {
if (announcementArray.length > 0) {
jq(announcementSpace).html(announcementArray[count].announceText);
jq(announcementSpace).fadeIn(750, function() {
jq(announcementSpace).css('display','block');
}).delay(4500).fadeOut(750, function() {
jq(announcementSpace).css('display','hidden');
});
}
count += 1;
if ((count % announcementArray.length) == 0) {count = 0}
};
setInterval(fadeAnnc, 6000);
$ = jQuery.noConflict();
Use closures (which is considered good practice anyways):
(function($) {
var your_function = function() {
$(...);
};
setTimeout(function() {
your_function();
});
}(jQuery));
Using closures creates a sort of 'sandbox' for your code, so you don't have to worry about overwriting any variables declared in a parent scope (such as the dollar-sign $ used by jQuery).
Last week a made a function for ellipsing the text inside some selector.
I was calling the function like this:
ellipsiText('.class',50) passing the selector and the max length of the text that i wanted to. This works fine, but im trying to make it a plugin, to call like this: $('.class').ellipsiText(50).
So, i was reading the tutorial in jquery website, and i understood how to do it. But i think i'm having an issue with the "this" seletor. Here is my original function:
function ellipsiText(selector,maxLength){
var array = $(selector).map(function(){
return $(this).text();
}).get();
var i;
var teste = [];
for (i=0;i<array.length;i++){
if (array[i].length > maxLength){
teste.push(array[i].substr(0,maxLength) + "...");
} else {
teste.push(array[i]);
}
}
for (var i=0;i<teste.length;i++){
$(selector).each(function(i){
$(this).text(teste[i]);
});
}
}
and here is my tentative of making a jquery plugin:
(function ($) {
$.fn.ellipsiText = function(length){
var array = $(this).map(function(){
return $(this).text();
}).get();
var i;
var teste = [];
for (i = 0; i < array.length; i++){
if (array[i] > length){
teste.push(array[i].substr(0,length) + "...");
} else {
teste.push(array[i]);
}
}
$(this).each(function(i){
$(this).text(teste[i]);
});
};
}(jQuery));
What am i doing wrong?
Well first thing is not a problem, but instead of $(this) in the first function scope, you can use this.map/this.each.
The problem is, in the second code you do
if (array[i] > length)
instead of
if (array[i].length > length)
Nothing to do with the jQuery plugin!
http://jsfiddle.net/UY88r/
This is untested, but the basic structure is something like this. Also you have so much looping in your code when one loop is needed.
$.fn.ellipsiText= function(options) {
var settings = $.extend({ //nice way to give to give defaults
length : 50,
ellipsi : "..."
}, options );
return this.each(function() { //the return is needed for chaining
var elem = $(this);
var txt = elem.text();
if (txt.length>settings.length) {
elem.text(txt.substr(0,settings.length) + settings.ellipsi );
}
});
};
and call it
$( "div.defaults" ).ellipsiText();
$( "div.foo" ).ellipsiText({
length : 10
});
$( "div.more" ).ellipsiText({
length : 10,
ellipsi : "<more>"
});
You already have a working function, just use it.
$.ellipsiText = ellipsiText;
$.fn.ellipsiText = function (count) {
ellipsiText(this, count);
}
now you can use it like any of the following:
ellipsiText('.class',50);
$.ellipsiText('.class',50);
$('.class').ellipsiText(50);
There's no sense in rewriting the function you already have working when you can just use it.
The variable my_sound is declared in the first, outer function. So, I should be able to use it in the nested function. However the mouseout event produces no result. What am I doing wrong? Thanks for any help.
$(document).ready(function () {
var starting_pics = ["CN.gif", "EN.gif", "GN.gif"];
var starting_sounds = ["CN.mp3", "EN.mp3", "GN.mp3"];
var i = 0;
for (i = 0; i < starting_pics.length; i++) {
$("<img/>").attr("src", "images/" + starting_pics[i]).appendTo("#main").addClass("pics");
}
$("#main").on("click", ".pics", function () {
var i = $(this).index();
var my_sound =($("<audio/>").attr("src", "audio/" + starting_sounds[i])).load().get(0).play();
$("#main").on("mouseout", ".pics", function () {
$("my_sound").animate({ volume: 0 }, 1000);
});
});
});
The problem is probably that .play() doesn't return a jQuery object (or anything, for that matter, hence undefined).
Additionally, as the other comments have said, you don't want $('my_sound').whatever but rather just my_sound.whatever if it were a jQuery object, which it is not. So maybe you could try
var $my_sound = $("<audio />").attr("suchandsuch","etc");
$my_sound.load().get(0).play();
$my_sound.whatever();
I am trying to learn url persistence, and a friend told me to study this block of code:
$(document).ready(function(){
var objects = {};
var DEFAULT_LOCATION = "diameter";
$("#animateObjects").hide();
var urlDimension = location.hash.replace("#","");
if (urlDimension.length == 0) {
// location.hash = "#" + DEFAULT_LOCATION;
urlDimension = DEFAULT_LOCATION;
$("#"+DEFAULT_LOCATION).addClass("active");
clog("started with no dimension, defaulted to " + DEFAULT_LOCATION);
}
else {
$("#"+urlDimension).toggleClass("active");
clog("started with dimension: " + urlDimension);
}
What does the clog() method accomplish?
Full code is here.
I'd bet its a wrapper around console.log to cater for IE not being able to handle it.
something like this:
function clog(message) {
try {
console.log('message');
}
catch (ex) {}
}
Reference: http://benwong.me/javascript-console-log-and-internet-explorer/