I am using SurveyJS (https://surveyjs.io/) to make a simple site with few questions. I am able to do all my logic with the options provided by SurveyJS.
However, what I'm looking to do is:
Make an API call after one of the question (working as expected)
Wait for the API call before moving to the next question (working as expected)
If the API responds false, do NOT move to the next question. (not working)
No matter what I do, the survey continues to move to next question and I want to avoid that in this case.
Three callbacks that are available:
// triggers before the current page goes away
survey.onCurrentPageChanging.add(function (sender, options) {
if(survey.data.year === "1991") {
// let's say I want to stop user from going forward at this point.
// how can I do that?
}
});
// triggers after the current page is gone and new page is about to appear
survey.onCurrentPageChanged.add(function (sender) {
});
// triggers right before the survey is about to finish - the last page
survey.onCompleting.add(function (sender, options) {
});
Thank you for your time.
survey.onCurrentPageChanging.add(function (sender, options) {
if(survey.data.year === "1991") {
// This prevents survey go to the next page
options.allowChanging = false;
}
});
Related
I design a new survey and in one of my multiple choice questions, the alternatives are supposed to be hidden for 1 sec and so that the user is inclined to spend more attention to the question before answering.
So far my code is only able to hide the choices but waiting and showing is still missing. (code below)
Thanks for any help or suggestions on how to solve this issue.
Qualtrics.SurveyEngine.addOnload(function () {
this.hideNextButton();
this.hideChoices();
//HERE I WOULD LIKE THE CODE TO WAIT FOR 1 sec
//HERE I WOULD LIKE THE CHOICES TO REAPPEAR ON THE SCREEN
this.questionclick = function (event, element) {
if (this.getChoiceValue(4) == true) {
this.clickNextButton();
} else if (this.getChoiceValue(5) == true) {
this.clickNextButton();
}
}
});
There are two parts of the problem here.
How to wait one second? That's done with setTimeout() and a callback function.
How to make sure the callback function works on the right object? - That's done by storing the object we want to work on in a variable.
So, without knowing anything about Qualtrics SurveyEngine, it is clear from your code that this inside the onLoad callback refers to your survey object. We will need that object later in the setTimeout callback, so let's store it. The rest is easy:
Qualtrics.SurveyEngine.addOnload(function () {
var survey = this;
// attach click event handler
self.questionclick = function (event, element) {
// using `survey` instead of `this` would work here, too
if (this.getChoiceValue(4) || this.getChoiceValue(5)) {
this.clickNextButton();
}
};
// hide buttons
survey.hideNextButton();
survey.hideChoices();
// after 1000ms, show buttons again
setTimeout(function () {
survey.showNextButton();
survey.showChoices();
}, 1000);
});
I have simple sammy routing declared like:
$.sammy("#main", function () {
this.get(/\#\/(.*)/, function (context) {
context.load(url, { json: true }).then(function (result) {
// some result custom processing
return result.Html;
}).swap();
}).run('#/');
});
Everything is working very well, except one small issue. If I'm clicking on my links very fast, all requests are started. However they sometimes finishing in different order than started - which is normal because some are longer then others.
But because they are finishing in different order, the final result on screen in content from swap is different than current selected page.
Is there any way to cancel/ignore previous request (which is still asynchronously processing) if newer one is already processed?
I'm getting some odd behavior in a part of my js code.
I have some notifications which appear in a bar on top of the page and then disappear after a certain amount of time. I have used a simple setTimeout() to acheive this.
Sometimes, a notification will appear as a result of a particular url query string when the page loads but then a new one would need to be displayed when the user clicks on a button. I want the old one to disappear and the new one to appear. I'm using a variable to keep a reference to the setTimeout() in order to cancel it. However, when I try to do this I manage to create a loop that eventually crashes my chrome tab.
I have put together a jsfiddle illustrating my problem - http://jsfiddle.net/5Nm4c/
Clicking on show notification while another is visible will crash the browser tab. If you click on it when nothing is shown, it is fine.
Here is my js:
var Notification = {
// close main notification bar
close: function (callback) {
$('#notification-bar').fadeOut(250, function () {
// reset its position and fade it back in so it is ready to go again
$(this).css('top', -100).fadeIn(1);
// check if a callback function has been passed in
if (typeof callback === 'function') {
callback();
}
});
},
// open notification bar with the appropriate css class and message
open: function (message) {
// if the notification bar is already visisble
if (verge.inViewport($('#notification-bar'))) {
// hide and then show it with the new message
window.clearTimeout(Notification.timeout);
Notification.close(Notification.open(message));
return false;
}
$('#notification-bar').html(message);
$('#notification-bar').animate({
'top': 0
}, 250, function () {
Notification.timeout = window.setTimeout(function () { Notification.close() }, 1500);
});
},
timeout: null
}
Notification.open('hello');
$('#button').click(function(e){
e.preventDefault();
Notification.open('link clicked');
});
I'm using https://github.com/ryanve/verge/ as it has some nice methods to check if elements are visible in the viewport.
Could someone please tell me where my error is?
I think the error Uncaught RangeError: Maximum call stack size exceededcomes from jsfiddle itself, so I am not able to test it.
I see what you did there:
var Notification = {
open: function (message) {
Notification.close(Notification.open(message)); //Here you create the loop!!
}
}
Another problem I see in your code is, that when Notification.open is called while a animation is running Notification.timeout is not actuell. Try a $('#notification-bar').stop(true, true); to stop the actuell animation befor you call window.clearTimeout(Notification.timeout);. Maybe it would be even better to use $('#notification-bar').stop(true, false);, so the "old" setTimeout will not even be called.
We're creating a click tracking app, that builds heatmaps. I'm writing a script which users are suppose to insert into their pages for tracking to work.
It works fine on elements, which doesn't require a redirect or form submit. For example, if I click on h1 or p or whatever, it works perfectly correct. But, if I click on a a, request to our server never happens before the normal redirect.
In the last couple of days I tried a lot of ways to do that. First of, I tried a normal AJAX call, since it was a cross-domain request I had to use JSONP, but again, that AJAX call did not have time to execute before the redirect. Adding async: false would have solved the problem, but it doesn't work with JSONP requests. So I decided to add a flag variable which indicates that it is safe to move on with redirect and used an empty while loop to wait until it becomes try in the ajax callback. But the while loop was blocking the execution flow, so callback never got a chance to set that variable to true. Here is some simplified code:
$(document).on('click', function (e) {
//part of the code is omitted
$.ajax({
url: baseUrl,
data: data,
type: "get",
dataType: "jsonp",
crossDomain: true,
complete: function (xhr, status,) {
itsSafeToMoveOn = true;
}
});
while(!itsSafeToMoveOn){}
return true;
});
The next thing I tried is to use unload page event to wait until total ajax calls in progress would become zero (I had a counter implemented) and then to move on with redirect. It worked in Firefox and IE, but in WebKit there was this error:
Error: Too much time spent in unload handler
After that I realized that I don't care about the server response and using img.src for the request would be an ideal fit for this case. So at this point code looks like this:
$(document).click(function (e) {
//part of the code is ommited
(new Image).src = baseUrl + '?' + data;
if (tag === "a" || clickedElement.parents().has("a")) {
sleep(100);
}
return true;
});
That way I increased the overall script performance slightly, but problem with links remains unchanged. The sleep function appears to be also blocking the execution flow and request never happens.
The only idea left is to return false from the event handler and than redirect manually to the clicked element's href or to call submit() on the form, but it will complicate things to much and believe me it's already a huge pain in the ass to debug this script in different browsers.
Does anyone have any other ideas?
var globalStopper = true;
$(document).on('click', function (e) {
if (globalStopper === false)
return true; //proceed with click if stopper is NOT set
else {
globalStopper = false; //release the breaks
$.ajax({
//blahblah
complete: function (xhr, status,) {
$(elem).click(); //when ajax request done - "rerun" the click
}
});
return false; //DO NOT let browser process the click
}
});
Also, instead of adding image, try adding script. And then add the script to the HEAD section. This way the browser will "wait" until it's loaded.
$(document).on('click', function (e) {
var scriptTag = document.createElement("script");
scriptTag.setAttribute("type", "text/javascript");
scriptTag.setAttribute("src", url);
document.getElementsByTagName("head")[0].appendChild(scriptTag);
return true;
}
I would take a look at the navigator sendBeacon API mentioned in this stack overflow answer or directly linked to here.
From the description on the site
navigator.sendBeacon(url, data) - This method addresses the needs of analytics and diagnostics code that typically attempts to send data to a web server prior to the unloading of the document.
You can save information to ajax request in cookies or localStorage and make any worker that will send information. Saving to cookies or localStorage is faster then ajax-request. You can do next:
$(document).click(function (e) {
var queue = localStorage.getItem('requestQueue');
queue.push(data);
localStorage.setItem('requestQueue',queue);
});
$(function(){
setInterval(function(){
var queue = localStorage.getItem('requestQueue');
while (queue.length > 0) {
var data = queue.pop();
$.ajax({
...
success: function(){
localStorage.setItem('requestQueue', queue);
}
});
}
},intervalToSendData);
});
So, when user click on link or send a form, data will be saved to storage and after user go to next page, this worker starts and send data to your server.
The JavaScript is basically executed in single thread. It is not possible to have your callback function executed and at the same time have an infinite loop waiting for a flag variable from it. The infinite loop will occupy the single execution thread and the callback will never be called.
Best approach is to cancel the default handler of your event and bubbling for it (basically return false if you are really building your tracking code with jQuery), and do the necessary actions (redirect page to the necessary address if a link was clicked or trigger other default actions), but this would take a lot of careful work to recreate all the possible combinations of actiona and callbacks.
Another approach is to:
1) Look for something specific to your code in the event data
2) If it is not present - make an AJAX call and in its callback re-trigger the same even on the same element, but this time with your specific bit added to the even data; after the AJAX call return false
3) If your specific bits are present in the data - simply do nothing, allowing the default event processing to take place.
The either approach may bite, however.
So if I understand right, you want your ajax logs completed before the page unloads and follows a link href. This sounds like a perfect case where you could consider using Deferreds in jQuery.
When your user clicks on anything that's supposed to take him away from the page, just check your promise status. If it's not resolved, you could throw a modal window over the page, and ask the user to wait til the progress is complete. Then, add a new pipe to your deferred, telling it to change the location href once everything is complete.
Let me know if this is the scenario. If it is, I'll explain in more detail. No use continuing if I didn't understand your requirement properly
I am porting an old game from C to Javascript. I have run into an issue with display code where I would like to have the main game code call display methods without having to worry about how those status messages are displayed.
In the original code, if the message is too long, the program just waits for the player to toggle through the messages with the spacebar and then continues. This doesn't work in javascript, because while I wait for an event, all of the other program code continues. I had thought to use a callback so that further code can execute when the player hits the designated key, but I can't see how that will be viable with a lot of calls to display.update(msg) scattered throughout the code.
Can I architect things differently so the event-based, asynchronous model works, or is there some other solution that would allow me to implement a more traditional event loop?
Am I making sense?
Example:
// this is what the original code does, but obviously doesn't work in Javascript
display = {
update : function(msg) {
// if msg is too long
// wait for user input
// ok, we've got input, continue
}
};
// this is more javascript-y...
display = {
update : function(msg, when_finished) {
// show part of the message
$(document).addEvent('keydown', function(e) {
// display the rest of the message
when_finished();
});
}
};
// but makes for amazingly nasty game code
do_something(param, function() {
// in case do_something calls display I have to
// provide a callback for everything afterwards
// this happens next, but what if do_the_next_thing needs to call display?
// I have to wait again
do_the_next_thing(param, function() {
// now I have to do this again, ad infinitum
}
}
The short answer is "no."
The longer answer is that, with "web workers" (part of HTML5), you may be able to do it, because it allows you to put the game logic on a separate thread, and use messaging to push keys from the user input into the game thread. However, you'd then need to use messaging the other way, too, to be able to actually display the output, which probably won't perform all that well.
Have a flag that you are waiting for user input.
var isWaiting = false;
and then check the value of that flag in do_something (obviously set it where necessary as well :) ).
if (isWaiting) return;
You might want to implement this higher up the call stack (what calls do_something()?), but this is the approach you need.