Javascript: Variable scope and usage order - javascript

I came across a peculiar issue when trying to make an ajax call and isolate the actions of the function to itself. Here is the code snippet
$(document).on('click', 'input.action', function(event) {
var self = this;
$.ajax({
url:'http://date.jsontest.com/',
method:'GET',
cache:false,
dataType:'json',
success:self.process,
error:function(){self.process(false);}
});
self.process = function(data) {
if (data) {
alert(data.time);
}
else {
alert("Operation Failed!");
}
}
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script>
<div class="container">
<input type="button" value="Get Time" class="action"/>
</div>
Let me briefly explain what I am trying to do, on click of the button, I wish to receive some data from the server and display the data. I am using a process function to process the received data. In case of an error I reuse the process function in a different way to display the error message. I simply use the self variable to contain all the elements within the parent function. I fully understand the following:
What I know
I do not have to use the self to contain the process function because another method will not have access to it
Because the process method in the snippet above is declared after the ajax call for the program as far as it is concerned the process function is undefined.
I clearly know how to fix it.
Experiment:
Click on the Get Time button
Wait for as long as you want but see no result, which is expected because of the process function is declared after the ajax call
Click on the Get Time button again
It works now! Some time (which is probably not your time :P) is displayed now!!
What I wish to know:
What just happened? why does it work the second time and everytime after? Remember this only works for ajax calls, if it were the case that the assignment is retained in the function after calling it once, then this should work in every situation but it does not. Here is an experiment to show that it does not work the same way when ajax calls are not used: Fiddle - Experiment
The Solution:
I am adding a sample solution based on #Felix Kling's answer below. In the Sample Solution, there are two buttons Get Time and Get Date. I've attached the parameter to retrieve time in case of Get Time and date in the case of Get Date to the object self and it is interesting that once I click on Get Time nothing happens just like before but if I click on either Get Time or Get Date the second time only time is displayed.

What just happened?
In a simplified way, this:
var process;
// First click
ajaxCall(process); // process is still undefined
process = function() { ... };
// second click
ajaxCall(process); // process is defined
process = function() { ... };
The assignment to self.process "persists" between events because self refers to the same element.
Here is an experiment to show that it does not work the same way when ajax calls are not used: ...
It doesn't work in your fiddle because of one big difference: you are trying to execute process immediately. But it doesn't exist, so an error is thrown. At this point the script terminates and won't execute the rest of the function, where the function definition takes place.
In your example here, the execution of process is delayed. The script "doesn't know" that there is no function to call until the response was received.

There is nothing "special" going on here. Yes, accepted that the first time it returns undefined as it is yet to be defined.
But, you're attaching the function to the same element input.action. So, the next time, when you click the button, this already has the process method attached to it, so gets called when clicked again.
Try adding one more button with same class and click each once. Now though you've clicked the first button, clicking the second button will still not create an alert as it has not yet had the process function attached to it.

Related

Cannot copy data to the clipboard vanilla js/angular

i am working in an angular project. In a controller, we have a button that is supposed to generate & copy an embed code (similar to youtube) to the clipboard. However, depending on the type of the item, the embed code can only be generated/returned by an ajax call. Have a look at this code:
function copyEmbed(e) {
var embedCode = '';
if (type === "typeA"){
api.items.compile.get({'id': item.selected.id},
function (response) {
embedCode = response.html; //<-- takes time to populate obviously
copyToClipboard();
});
} else {
embedCode = generateEmbedCodeTemplate(); //no ajax here. populates immediately
copyToClipboard();
}
function copyToClipboard() {
clipboard.copyText(); // all seems good but copying will fail as this function is not invoked with a click handler!
}
}
The problem is that because of the ajax call, the code to copy the resulting embed code cannot be in the copyEmbed function scope, as this means the ajax call will not have the time to get the data before copying. If i was able to make everything synchronous, i would be able to get the data and then call the copy command from within the scope of the copyEmbed function, so it would not fail, as the copyEmbed function is bound to a click event. However, in the example, i am handling the ajax call right, but the copyToClipboard function is not invoked with a click handler so the copy command fails. Any ideas, without resulting in hacky setIntervals to check for embedCode contents?
As always, handling async stuff can only happen by actually handling them in an async fashion. So instead of grabbing the embed code at the time of the button click, i am doing so way before, so it is available as a variable or a data-attribute of the element to be clicked. Only adding the answer for people who might come in here with the same brainfart i had when i opened it.

Ajax .load() won't work when triggered initially

So I have a simple tab system which I handle with the .load function to load the desired content. The problem is that the page itself which contains this tab system is a ajax loaded content. And for some reason the initial call of the tab function to display the initial tab content won't work. But after manually choosing a tab, the load function loads the content properly.
her some code to look at:
The tab handler:
function loadTab(tab) {
$(".tab_a:eq("+otab+")").removeClass("tab_slc");
$('#tab_content').hide();
$('#tab_content').load("include/tab_downloadVersions.html .tab:eq("+tab+")");
$(".tab_a:eq("+tab+")").addClass("tab_slc");
$('#tab_content').fadeIn(function() {});
otab = tab;
}
at the end I call loadTab(tab); and the thing should be initialized. but for some reason the content remains empty. As soon as you manually click on a tab (I have an on click function which calls loadTab(tab) everything starts working)
Because the code by itself works, I think the problem is caused by the other script which handles the page itself. It is also a .load function which loads the page, which loads this tab system.
So do multiple .loads don't like each other? and if so, what can I change?
Thanks in advance ;)
EDIT: I could't post the entire code for some reason, but if you go here you can see the site in action with all the scripts:
n.ethz.ch/student/lukal/paint.net
The tab system is on the download page.
EDIT:-----------------------------------------------------------------------------------------------------------------------------
Big Update
So this is still the same issue but with a slight twist: I did what was recommended in the comments and put my secondary .load() call inside the success call of the first one.
$("#content").load("pages/contact #contentInside", function() {
$("#OtherContent").load("include/info #OtherContentInside");
});
So this works.
But now I had the great idea to make a giant load function. It is a slightly better function than just the plain load, cause it does some fading and stuff. But now I have the same problem, but even more complicated. I created the load function as a "plugin" so the function itself is in a different script file and therefore I can't access the inside of the success function. I solved this problem with a return $.ajax(); and a .done() call. The problem here is that there is some rare case where it just skips the secondary load function. So I am searching for a guaranteed way of controlling the order of the .load calls. Any idea?
The mock-up website is up to date with the new scripts if you wish to take a look. And people were complaining about potential virus spread from my link. For some reason I can't post long code snippets so the site is the best source I got to show everything. If you know a more trustworthy way to share my code please let me know.
We cannot see the rest of your code to tell where the initial call is being invoked from. A set up like the following should work:
$(function() {
var tab = 0;
loadTab( tab );
});
function loadTab(tab) {
//WHAT IS otab???
$(".tab_a:eq("+otab+")").removeClass("tab_slc"); //<<<==== otab
$('#tab_content').hide();
$('#tab_content').load("include/tab_downloadVersions.html .tab:eq("+tab+")");
$(".tab_a:eq("+tab+")").addClass("tab_slc");
$('#tab_content').fadeIn(function() {});
otab = tab;
}
Update
The reason it does not work initial is because otab is not defined the first time the function is called. You have initialized otab at the end of the function but you are using it at the beginning of the function.
UPDATE 2
I have had a chance to look at your code and I just found out what the issues are:
You do not have DOM ready
You are not calling the function on page load.
The following version of your code should work -- try not to use global variable as you're doing with otab. Since you're loading this script at the end of the page (an you are using event delegation) you may get away with DOM ready. Adding .trigger('click') or click() as indicated below should resolve the issue.
//Tab-loader
//Haeri Studios
var tab = 0;
var otab = tab;
var counter = 0;
//click detect
$(document).on('click', '.tab_a', function() {
tab = counter == 0 ? tab : ($(this).attr('id'));
loadTab(tab);
counter++;
return false;
})
.trigger('click'); //<<<<<===== This will call the function when the page loads
//Tab setup
function loadTab(tab) {
//Content Setup
$(".tab_a:eq("+otab+")").removeClass("tab_slc");
$('#tab_content').hide();
$('#tab_content').load("include/tab_downloadVersions.html .tab:eq("+tab+")");
$(".tab_a:eq("+tab+")").addClass("tab_slc");
$('#tab_content').fadeIn(function() {});
otab = tab;
}
//Initialize << WHAT ARE YOUR INTENTIONS HERE .. DO YOU REALLY NEED THIS PIECE?
$.ajax({success: function() {
loadTab(tab);
}});
A partial answer to this problem was to call the loadTab function inside the success call of the page load function, like charlietfl pointed out. But the problem is that there is no need to call the tabloader every time a new page gets called. So I would rather not have a rare call in every page setup function.
I am a bit disappointed by the system on stackoverflow. It seems like if you have not a high reputation level, no one gives a "S" about your questions. Well but at least some input was give, for which I am very thankful.
So by digging deeper into google I found out that the callback can be manually placed in the function where ever you like.
so if we have a function:
foo(lol, function() {
//This after
});
this does stuff after foo() is done. But what if we have another function inside foo() which we also need to wait for:
function foo(lol) {
bar(troll, function() {
//This first
});
}
The bar function is not relevant to the success call of foo. This causes the unpredictable outcome of calls.
The trick is to control when the success function of foo gets called.
If we add a parameter(callback) inside foo and call this "parameter" (callback();) inside the success call of bar, we can make sure the order is guaranteed.
And that's it:
function foo(lol, callback) {
bar(troll, function() {
//This first
callback(); //<-This callback placement defines when it should be triggered
});
}
foo(lol, function() {
//This after
});
We get:
//this first
//this after

"Function factory" doesn't seem to work, JavaScript

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

jQuery recursion

I have a problem with my code, some code does not work when I call recursive call of same function inside it. It has to be something with myGalleria = Galleria.get(0);, but I have no idea how to make it all work.
Document Ready (just to show when I call function for the first time, everything works fine for first time)
$(document).ready(function(){
$.getJSON('getImages.php', {
cat: "123"
}, function(imgData){
createGallery(imgData);
});
});
Now function itself, note that when I click on .galleria-menuButtons span that calls same function nothing is working, well galleria itself is creating, but nothing else.
function createGallery(imgData){
$("#gallery").galleria({
image_margin: 30,
clicknext: true,
transition: "fade",
dataSource: imgData
});
myGalleria = Galleria.get(0); // I don't think this works after recursive call
// Adding menu and menu buttons
myGalleria.addElement("menu").appendChild("container", "menu");
myGalleria.addElement("menuButtons").appendChild("menu", "menuButtons");
$.ajax({
url: "menuButtons.php",
success: function(data){
myGalleria.$("menuButtons").html(data);
}
});
// Menu button click events
$('.galleria-menuButtons span').live('click', function(){
alert(this.id);
// Getting jSon data
$.getJSON('getImages.php', {
cat: this.id
}, function(imgData) {
alert(imgData);
createGallery(imgData); // <- Recursive call
});
});
}
I have similar function on window.resize and it also does not work after recursive call.
$(window).resize(function(){
$(".galleria-container").css("width", $(window).width());
$(".galleria-container").css("height", $(window).height());
galleriaRescale = Galleria.get(0);
galleriaRescale.rescale(); // <- this is not working either
//sizeBG();
});
FYI - this isn't actually recursion in the traditional sense because you're calling createGallery from a click handler which launches a JSON request which then calls createGallery when that succeeds, both of which will occur after the previous call to createGallery finishes.
But you do have surviving function closures which could be confusing things or causing problems. One guess is that you may want to make sure that things you expect to be local variables (like myGalleria have a var in front of them so they really are local variables and not variables that might be scoped to a higher level and be influenced by a previous incarnation of this call or be influencing an earlier call that hasn't yet completed.
var myGalleria = Galleria.get(0);
Then, assuming imgData is some sort of data structure like an array or object, you have to make sure that there's either only one global version of that data structure that never changes or that each call of createGallery has the appropriate separate copy of that data structure. If it's getting changed along the way, then subsequent calls to createGallery may not be getting the data they want. If it's a read-only data structure (you don't change it), then you're probably OK on that one.
OK, let's talk through the pseudo code for what this does.
On page ready, you get some JSON image data.
When that succeeds, you call createGallery with that image data.
The createGallery call does some sort of operation in the DOM (perhaps an animation)
It then calls: myGalleria = Galleria.get(0); Because there is no var in front of myGalleria, this is a global variable declaration (bad news for recursion and closures)
You then use the myGalleria data structure to make some changes to the DOM (adding menus and menu items).
You then add a .live click handler on a pretty generic set of CSS classes (it's possible you have added this click handler more than once here).
You then fetch some JSON image data again.
When that image data is fetched, you start the whole process over again by called createGallery.
Summary
The two potential problems I see are that myGalleria is not a local variable and probably should be and you may be adding duplicate click handlers.
If neither of these fully solve the issue, then we probably need more information about what Galleria.get(0) is doing.
FYI, the resize clickHandler looks like it may have the same issue with not using var to make your variable declaration a local variable.
Round 2
OK, here are some more observations.
When you add the menu and menu buttons with this block of code, you aren't providing any unique identifiers to either the addElement or appendChild functions (you're providing "menu" and "menuButtons" to both). As such, I don't know how you can uniquely hook up to them in the subsequent click event. As far as your code looks, all the menu items look identical and none have unique state. I don't know the Galleria code, but I assume somebody has to make unique identifiers for these new items so that you can uniquely identify them in your subsequent click handler.
// Adding menu and menu buttons
myGalleria.addElement("menu").appendChild("container", "menu");
myGalleria.addElement("menuButtons").appendChild("menu", "menuButtons");
When you set up a click handler to presumably handle the clicks for just these menu items, you are using the exact same CSS selector every time so there's no way that this click handler is going to be uniquely assigned to just the newly create menu items (which is what I assume you want). I don't know the Galleria code, but I assume that you should create some sort of unique ID that you pass into addElement and appendChild for the newly created menu items and then reference that unique identifier when you install the click handler. Likewise, this function needs to uniquely target just the menu buttons you created by using unique identifiers myGalleria.$("menuButtons").html(data);
Lastly, I'd suggest you change the name of one of your variables just to avoid confusion. In your click handler, change the three occurrences of imgData to just data so there can be no confusion about closures and the value of imgData.
Round 3
Ultimately one of the fixes was this (embedded in the comments):
I think it might work if you just only install the .live click handler once outside the createGallery function rather than call it each time. Since it's .live it will automatically work for all future buttons you create so you should only call it once. I'd suggest putting it in the $(document).ready function block.

JScript Wait function

I have a function written in JScript (Not javascript) I need to suspend until a certain global variable becomes true.
The global variable is changed to true when another function is called after an ajax response:
function(req, event, data) {
globalVariable = true;
}
When I try to loop until the variable is true:
while (globalVariable!= true) {
}
I go into a busy waiting and the callback function is never called.
Some suggested the use of WScript.wait() but my app doesn't seam to know WScript.
SetTimeout() also won't help because it's asynchronic call and won't suspend my original function.
Any other suggestion?
Some more information regarding my question:
I want my script to call 2 functions:
waitWhileAjaxIsNotCompleted();
doSomthingElse();
I want the waitWhileAjaxIsNotCompleted() to click a button that submits an ajax request (implemented by A4J) and terminate upon the ajax completion.
In order for me to know when does tha ajax completed, I registered a function as a listener that will be awaken when the ajax completes. This function changes a globalVariable value.
My waitWhileAjaxIsNotComplete() goes into an infinite loop, waiting for the glovalVariable value to change. When it does change (After the listener has awaken), I can end the function ad continue with the doSomthingElse() function.
You can see more on the implementation on: QTP Web extensibilty toolkit and ajax
I can't remember what the heck I used
a few months ago since I don't use
Jscript anymore (not enough time)...
But I am currently looking in my
program to see if I still have the
script saved. I did the exact same
thing a few months back.
I'll post the code once I've found
it...
Sorry about that. I can't seem to find the code snippet. I must have deleted it... Typical of me though.
So, the only thing that I can think of until a better solution is available it to enter your code into an infinite loop, and simply break; out of it once the GlobalVariable returns true.
I hope this helps. I'm going to keep at it until I can either find the snippet or come up with a much better answer.

Categories

Resources