I have an AngularJS app that makes a call to an API and returns a bunch of data that users can then filter by tags for greater granularity in the results. Each time a tag is clicked to filter the data, the app makes a new $http.get() call, and the URL is modified with the appropriate query parameters so that the user can save the permalink and come back to any particular data set.
I'm trying to give the app proper history handling with window.history.pushState(), and passing the relevant query parameters for each history object as state data. I'm using window.onpopstate to detect when the back/forward buttons are clicked, and using that to make the new $http.get() call with the relevant state data from the history.
For some reason, the $http.get() function only fires on every second popstate, and then it makes two calls. It's almost as if there's some caching going on, but I haven't been able to find the culprit. This behaviour persists in both directions, backwards and forwards, and is consistently every second event. I've verified that window.history.length is only incremented by 1 for every tag added/removed, that the state data is being successfully sent, that new search queries are being correctly assembled, and that the request path is correct. It's just not firing. What's going on??
To illustrate, the behaviour flow looks like this:
Load page at /default
Add first tag: URL is /default&tags=a, $http.get() returns new data
Add second tag: URL is /default&tags=a,b, $http.get() returns new data
Add third tag: URL is /default&tags=a,b,c, $http.get() returns new data
Add fourth tag: URL is /default&tags=a,b,c,d, $http.get() returns new data
First back button event
window.onpopstate fires, URL is now /default&tags=a,b,c
No network changes
Second back button event
window.onpopstate fires, URL is now /default&tags=a,b
$http.get() fires, sends network request for data with /default&tags=a,b,c
$http.get() fires again, sends network request for data with /default&tags=a,b
dataset for /default&tags=a,b loads
Third back button event
window.onpopstate fires, URL is now /default&tags=a
No network changes
Fourth back button event
window.onpopstate fires, URL is now /default
$http.get() fires, sends network request for data with /default&tags=a
$http.get() fires again, sends network request for data with /default
dataset for /default loads
Relevant code snippet:
$scope.apiRequest = function(options, callback) {
// Omitted: a bunch of functions to build query
// based on user-selected tags.
// I've verified that this is working correctly.
$http.get(path)
.then(function(response) {
console.log('http request submitted');
if (callback) {
callback(response.data.response, response.data.count, response.data.facets);
console.log('data returned');
}
}, function(response) {
console.log('there has been an error');
});
}
Neither the success nor error events fire. I've tried using $http.get().then().catch() to see if there might be something else going on, but for some reason I keep getting an error in my console that says that ...catch() is not a valid function, which is in and of itself bewildering. Any ideas?
Thanks!
This sounds indicative of a function not cycling through the $digest loop. In this case you may attempt to add $scope.$apply(); as the last line in your window.onpopstate handler function to kick the $digest cycle to execute your function call.
This article, Notes On AngularJS Scope Life-Cycle helped me to better understand the $digest cycle and how you can force the $digest cycle to run with $scope.$apply(); Keep in mind you want to use $scope.$apply() sparingly but in some cases you are forced to kick off the cycle, especially with async callbacks.
Related
I am using face-api.js to detect faces in a given image. Everything is configured as mentioned on Github and detection is working.
But I want to call faceapi.detectAllFaces() asynchronously, I means I don't want to wait for its result, so I can't call await faceapi.detectAllFaces(). I tried below code:
In cam.js
async function detectFace()
{
document.getElementById('camPic').src = getCurrentImageAsBase64(); //getCurrentImageAsBase64() gets the image in base64 format from canvas
return faceapi.detectAllFaces(document.getElementById('camPic'), new faceapi.SsdMobilenetv1Options());
}
In index.jsp
$('#camPrcd').click(function(e)
{
e.preventDefault();
detectFace().then((arr) => console.log(arr));
console.log('detection called');
});
After button click, on console I can see message "detection called" and after sometime detection result gets logged from then() block.
But my observation is when detectFace() is called the html page feels like hanged (not able to click on any button) and when arr gets printed on console then I can able to click on page.
Looks like even though "detection called" message gets printed immediately before detection happens but faceapi.detectAllFaces() is not doing work asynchronously.
Note: This happens only for first call of faceapi.detectAllFaces() as per author of face-api.js during first call model for face detection get compiled and so detection time is more at first call as compare to subsequent call to function.
So is there any way that, I can call for detection and still web page is accessible and when it's finished a callback function will handle the detection result.
This question has been asked and answered many times with the answer "call another function from the success callback" Unfortunately, I can't figure out how to make that answer work in my situation, for a couple of reasons.
For starters, the ajax being called is the submit function of an ajaxForm.
In one place I call the ajax submit, the calling function ITSELF needs to return either true or false, depending on the success of the AJAX call and some data returned from it. This is based on a tab widget used in multiple places: you can bind a function to the tab, which is run when the tab clicked. The tab widget itself expects this function to return either true (show the tab) or false (don't show the tab). Since this tab widget is used throughout my site, changing this behavior would be difficult at best.
The form itself can be submitted from more than one place, so I can't simply call the rest of this tab function from the success handler (even if I re-wrote the tab handler to work asynchronously), as that may not be the appropriate response to success when the form is submitted elsewhere. In this case, I want to show the tab the user clicked. In another case I want to reload the page or reset the form (new, blank form), and in a third case I want to go to a different page. But in all cases, it is in response to submitting the same form.
I suppose I could make it so the tab ALWAYS switches when clicked, and then (by setting flags or something so this only happens after a tab click) if the submit doesn't work, switch back, but that feels quite kludgy. So, is there any way to make the calling function wait until the callbacks are complete? Or is there another way to structure things that I am not thinking of?
EDIT:
I now have a jsfiddle of the current structure available here. The general idea is that back before I knew jquery had a tab widget, I needed tabs, so I wrote my own. It works, so I've seen no reason to change :-).
On document ready, each button with the appropriate class gets a click handler assigned. The flow is as follows:
The onClick handler triggers a beforeShow event to run the function (if any) bound to the tab object
The beforeShow function does any sort of prep work (such as loading data) desired before showing the tab. If needed, the 'show' data of the tab object can be set to false here to prevent the tab from being shown - such as if an error was detected on the current tab that needs to be fixed before switching.
After the beforeShow function returns, the onClick handler checks to see if the 'show' data was set to false, and if not hides all other tabs and shows the clicked tab.
So my dilemma here is that to prevent the tab from being shown, I need to set the 'show' data of the tab object to false before the onClick handler checks for it (i.e. during the beforeShow function). But with the AJAX callbacks being asynchronous, I don't get the result of the AJAX to see if I should show the tab or not until after the onClick handler has completed - thereby showing the tab.
As I mentioned, I can do something like store the current tab, and if the ajax results indicate NOT to show the new tab, switch back, but that just feels wrong to me.
The click handler:
function changeTab(){
var jqButton=$(this);
jqButton.data('show',true); //start by assuming we will show the tab.
if(jqButton.hasClass("current")) //don't do anything if switching to the current tab
return;
//run the "before show" function, if any.
jqButton.trigger('beforeShow');
var showTab=jqButton.data('show'); //see if the show flag has been set to false
if(showTab===false) //if no before show function, then showTab is null, not false
return; //if false, then don't show the tab.
$(".tabDiv").hide();
//run any "after hide" scripts bound to the current tab
$('.tabButton.current').trigger('afterHide');
var requestedDiv=jqButton.val(); //the "value" of the tab button is the ID of the tab content div
$("#"+requestedDiv).show(); //may show nothing, if no div exists for the current tab.
$(".tabButton").removeClass("current"); //remove the current class from all tab buttons, so it will only be applied to the new current tab button.
jqButton.addClass("current");
}
This is pretty much what you want
function tab2BeforeShow(){
var $form = $('#SomeajaxFormObject'); // This is the form you want to submit
// I'd probably show some load indicator here
var result;
$.ajax({
url: $form.attr('action'),
type: 'POST',
data: $form.serialize(),
async: false,
success: function(response) {
result = true; // set the result variable declared in outer function
},
error: function() {
result = false; // bad response - dont show new tab
}
});
// Hide your load indicator now
// since we declared async: false on the ajax request,
// we can be positive that result is set to a boolean value at this point
$(this).data('show', result);
}
We make a synchronous post to your forms action url, serializing the form's data so we can make an ajax POST with it. This should drop in, with maybe some minor tweaking.
The key factors here are that we set data to $form.serialize() and we set async to false, so the browser does not continue execution while the request is working.
If there is any way to avoid using synchronous ajax, you really should. If you can't avoid it, this should work for you. Do note that if the server hangs, it is going to hang your client as well.
This is a method to do it asynchronously.
Let the beforeShow handler accept an optional promise parameter:
var eventParams = {
promise: null
};
jqButton.trigger('beforeShow', eventParams);
In the handler itself, create a promise if necessary:
function tab2BeforeShow(e, eventParams) {
eventParams.promise = $.Deferred();
$.ajax(... async code...
var showTab = [some value received from request];
eventParams.promise.resolve(showTab);
);
}
Then if a promise is created, use a promise chain instead of synchronous function calls:
// doShowTab is the logic that actually shows/hides a tab
if (eventParams.promise) {
// use promise instead
eventParams.promise.then(doShowTab);
} else {
// beforeShow handler didn't return a promise, assume showTab=true
doShowTab(true);
}
Fiddle: http://jsfiddle.net/669v1Lcd/ (I used setTimeout to simulate an async request)
I am having some trouble with a bit of code. I have a function that does some stuff to some data, calls a remote system (activating a script on that system and passing in the data), and then makes another call to the same system to activate a different script (which acts on the data saved above). The problem is that the 1st call to the remote system appears to get lost in the execution.
This is being run in Safari, uses jquery; the function is tied to a button click, which is defined in the javascript code with an onclick function (i.e. it is not defined in the html button definition).
Here's a rough breakdown of the function (cleaned out for viewing purposes - I hope I left enough to make it clear):
function compareJSON() {
// loop through the objects, testing and changing data
// ...
dataSession=({ //build object for output });
$.each( dataSession.chapters , function( indexC, value ) {
//compare objects to some others, testing and changing data
});
// ...
//Call remote script on other system
urlString="url://blah.dee.com/Blar?script=SaveJSON&$JSONobject=";
window.location= urlString + JSON.stringify(dataSession);
//Call remote script on other system
window.location="url://blah.dee.com/Blar?script=EditJSON";
}
The last three lines of code are the two calls. It uses the window.location to actually trigger the remote system, passing the data through the URL. But I need BOTH scripts to get called and run. It appears that only the LAST script in the sequence ever gets run. If I switch them around it remains whatever is in last place.
Is there something about the window.location that doesn't actually process until the end of the function?
This script actually used to be a series of separate function calls, but I figured I was running into asynchronous execution that was causing the various script calls to not register. But once I put the code into this single function, it was still happening.
Any clues would be helpful.
Thanks,
J
Modifing the value of window.location is reserved exclusively for instances in which you'd like to cause a browser redirect.
It looks like you want to trigger a page request instead. You say you already have jQuery loaded, if so, you can trigger such a request using jQuery.get or a similar function.
For example:
// Loads the myscript.php page in the background
$.get('myscript.php');
// You can also pass data (in the form of an object as the second argument)
$.get('myscript.php', { name: "John", time: "2pm" });
I have a method in which I want to stop execution (do not return), wait on an event triggered by the UI, then continue that method.
chrome.webRequest.onBeforeSendHeaders.addListener(
function(details) {
var newHeaders;
//I need to stop here, wait on some user event, update the `newHeaders` variable with
//the content set by the user on the page
return {requestHeaders:newHeaders};
},
{urls: ["<all_urls>"]},
["blocking", "requestHeaders"]
);
What I'm doing: Developing a chrome plugin that intercepts requests, modify them by the input of the user from html, then send the request. Based on the docs, I assumed I have to modify it directly in the method and return it.
I hope this points you in the right direction:
The docs at: http://developer.chrome.com/trunk/extensions/webRequest.html say:
If the optional opt_extraInfoSpec array contains the string 'blocking'
(only allowed for specific events), the callback function is handled
synchronously. That means that the request is blocked until the
callback function returns. In this case, the callback can return a
BlockingResponse that determines the further life cycle of the
request. Depending on the context, this response allows cancelling or
redirecting a request (onBeforeRequest), cancelling a request or
modifying headers (onBeforeSendHeaders, onHeadersReceived), or
providing authentication credentials (onAuthRequired).
My guess is that you can specify "blocking" and then gather information from your user.
So I have a normal link on my website, and I want to add tracking for it. I could envision a bunch of ways to do this, but I've settled on this as being really easy by writing a small jquery function, and dropping a small snippet in my tags:
click me!
javascript:
function saveClick(someparamhere){
$.ajax({
url: "somepage.php",
data: {param:someparamhere}
});
}
Now, I know my syntax might be bad, I'm just asking about the overall concept here. When you click the link, I want javascript to issue the call to saveClick which immediately makes an ajax call. There's no success handler because I don't really care if or what gets returned. I'll just have somepage.php log the event. Then, after all of that, I want the tag to go to it's href.
Is that the case? Will the ajax call be issued before the document goes to the other page? Will this work in all cases?
Has anybody ever done something like this? Any experience would be appreciated ....
If you want to make sure the AJAX call goes through you could do:
click me!
$('[data-parameters]').bind('click', function (event) {
//cache this element to use in AJAX function
var $this = $(this);
//prevent the default naviation
event.preventDefault();
$.ajax({
url: "somepage.php",
data: {param:$this.attr('data-parameters')}
success : function () {
//now navigate to the requested page
location = $this[0].href;
}
});
});
UPDATE
$.ajax() exposes a timeout function:
timeoutNumber
Set a timeout (in milliseconds) for the request. This will override
any global timeout set with $.ajaxSetup(). The timeout period starts
at the point the $.ajax call is made; if several other requests are in
progress and the browser has no connections available, it is possible
for a request to time out before it can be sent. In jQuery 1.4.x and
below, the XMLHttpRequest object will be in an invalid state if the
request times out; accessing any object members may throw an
exception. In Firefox 3.0+ only, script and JSONP requests cannot be
cancelled by a timeout; the script will run even if it arrives after
the timeout period.
So you could set a timeout and an error function that mimics the success function. The documentation does state that: it is possible for a request to time out before it can be sent but if your timeout is a very small (maybe zero) delay then it could reduce the lag between the user clicking the link and the browser loading the new page.
I simply wouldn't do that... it could bring to situation your onclick event isn't fired.
I think it would be better to call a javascript function on click that does your ajax call and then bring the user to the target page.
You can do this, for example, this way:
...
your javascript function then, shall be something like:
myfunc(paramofpageclickhere) {
//do ajax call
saveClick(someparamhere);
//go to target page
location.href = "target.htm";
}