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.
Related
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;
}
});
I have an app created with NW?js that works fine.
However, it does a lot of processing (loading, save file, reload it and processing, ...) All of that is done by functions.
While I see that it is doing something (because when I click on the "button" that execute the function, the button remains "clicked" until it is finished), it seems impossible to get a spinner instead.
I have 2 functions that are used to respectively show and hide a spinner, but when I use them at start and end of the function, they are executed almost immediately (normal for show, but not foe hide) and I can't find the reason why
Any idea how I could have a spinner while my function are still processing and hide it when done ?
Thank you
In JavaScript, functions can't "sleep", all the code is executed without any stop, they return immediately. If you want to delay the execution of a function, you need a callback :
var button = document.getElementById("button");
var spinner = document.getElementById("spinner");
button.onclick = function () {
startSpinning();
button.setAttribute("disabled", "disabled");
doSomethingTimeConsumingThen(function () {
button.removeAttribute("disabled");
stopSpinning();
});
}
function doSomethingTimeConsumingThen (onDone) {
setTimeout(onDone, 1000);
}
function startSpinning () {
spinner.innerHTML = "Spinning...";
}
function stopSpinning () {
spinner.innerHTML = "Not spinning.";
}
<button id="button">Do something time consuming</button>
<div id="spinner">Not spinning.</div>
A callback is called when something happens, for example when a time consuming task is done. In the above snippet the callback is :
function () {
button.removeAttribute("disabled");
stopSpinning();
}
There must be something similar in your API.
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 a webpage that I'm using to print 'pages' of data as PDF files via the firefox 'Print to File' printer on my laptop. The code I'm invoking is as follows:
document.body.controls.cmdPrint.click = function () // Create a function that will be called when this object is clicked upon
{if (parseInt(document.body.controls.page.innerHTML) !== 0) // If we are not on the Front Cover
{return false;} // Function complete: Abnormal Termination
document.body.controls.style.pointerEvents = 'none'; // Lock down the controls so they cannot be interfered with
do // Do...
{window.print(); // Print this page
// document.body.sleep(); // Removed as this does not work as expected (see below...)
} while (document.body.controls.cmdNext.click()) // ...while we are able to advance.
document.body.controls.style.pointerEvents = ''; // Release the controls lockout
this.blur(); // Blur the focus
return true;}; // Function complete: Normal Termination
When executing, the pages flip as expected (as the cmdNext.click() function returns a true when successful and a false when it's on the last page and trying to advance), but it runs too quickly. Namely every odd page is caught out, as the 'printer' is unavailable....the window.print() is being released before the printer is ready for the next page.
I tried slowing down execution by adding a reference to a secondary function within the loop (now commented out), but this just locks up the CPU and keeps the printer from processing in another thread....so it's not a valid solution. This function (which I wrote but did not provide the expected cushioning to allow odd pages to print) is as follows.
document.body.sleep = function (delay) // Create a new function
{delay = delay || 1; // Default to a delay of 1 second
var timestamp = new Date(); // Get current time
timestamp = new Date(timestamp.getTime() + (delay * 1000)); // Add in the delay (in seconds)
while (new Date() < timestamp) {} // While we are waiting for the delay, do nothing
return true;}; // Function complete: Normal Termination
Basically what I need is a way to hold loop execution long enough to let the 'Print to File' to go through before the next window.print() is called. Using the sleep function above also keeps the window.print() from running even though (when I was trying to use it as a fix) I had put the call to this function immediately after the window.print() command.
So I ask, can anyone here supply a fix for this project so I don't have to manually cycle through each page (which could get annoyingly temporally expensive with a page count above 10)?
As it is currently, when the second page tries to print, I get the popup window holding an error from firefox: "Printer Error - Some printing functionality is not currently available." Tracking this down lead to PERR_NOT_AVAILABLE....probably because the printer (Print to File) is busy printing the prior page....so I just need to wait for that to resolve before going to the next printing. An error handler to catch this PERR_NOT_AVAILABLE instead of letting it bounce to the user (me) as a popup window that has to be clicked out of would be nice, though a spammy way of getting the pages to print in sequence as quickly as the Print to File system can process them.
If window.print() actually returned an error in this condition, I could just rerun the command...
In my experimentation, I found this workaround...it's quite fairly considered a kludge, but in the absence of a more elegant solution, it works.
document.body.controls.cmdPrint.click = function () // Create a function that will be called when this object is clicked upon
{if (parseInt(document.body.controls.page.innerHTML) !== 0) // If we are not on the Front Cover
{return false;} // Function complete: Abnormal Termination
document.body.controls.style.pointerEvents = 'none'; // Lock down the controls so they cannot be interfered with
window.onafterprint = function () // Set up a handler for after a print operation
{setTimeout(function () // Run a delayed operation
{if (document.body.controls.cmdNext.click()) // Move to next page and if this is successful
{window.print(); // Continue printing
return false;} // Function complete: Still Printing
do // Do nothing...
{} while (document.body.controls.cmdPrevious.click()) // ...while we cycle back to the start
document.body.controls.style.pointerEvents = ''; // Release the controls lockout
this.blur(); // Blur the focus
window.onafterprint = function () {}; // Remove this handler
return true;}, 2000);}; // Function complete: Normal Termination
window.print(); // Begin printing the page
return true;}; // Function complete: Normal Termination
If a page is too complex to print to PDF in 2 seconds, that 2000 figure in the setTimeout() will have to be increased to account for it. For my test case (the current 7 page document), 2000 seems to be the sweet spot between not-working and spitting out pages as quickly as Firefox will permit on my system.
I'm trying to do page automation with PhantomJS. My goal is to be able to go to a website, click an image, and continue with other code once the page has loaded from the click. To test this I'm trying to write a script that will go to the url of the quick start guide on the PhantomJS website and then click on the PhantomJS logo bringing the page to the PhantomJS homepage. Also to render a picture of the website before and after the click to make sure the click worked. This is my current code:
var page = require('webpage').create();
page.open('http://phantomjs.org/quick-start.html', function(status) {
console.log(status);
page.render('websiteBeforeClick.png');
console.log(page.frameUrl); //check url before click
var element = page.evaluate(function() {
return document.querySelector('img[alt="PhantomJS"]');
});
page.sendEvent('click', element.offsetLeft, element.offsetTop, 'left');
window.setTimeout(function () {
console.log(page.frameUrl); //check url after click
}, 3000);
console.log('element is ' + element); //check that querySelector() is returning an element
page.render('websiteAfterClick.png');
phantom.exit();
});
Problem is my before and after pictures are the same. This is my output when I run it.
success
element is [object Object]
Im using their sendEvent method from here "http://phantomjs.org/api/webpage/method/send-event.html" but I'm not sure if its working.
Also why doesnt the console.log(page.frameUrl) in my window.setTimeout() get executed?
I was looking at their page automation examples on the PhantomJS website. Particularly this one "https://github.com/ariya/phantomjs/blob/master/examples/imagebin.js".
I noticed their examples used
document.querySelector('input[name=disclaimer_agree]').click()
But when I tried it with my code I got an error.
document.querySelector('img[alt="PhantomJS"]').click();
TypeError: 'undefined' is not a function
EDIT#1:
I changed the end section of my code to this:
page.sendEvent('click', element.offsetLeft, element.offsetTop, 'left');
window.setTimeout(function () {
console.log(page.frameUrl);
page.render('websiteAfterClick.png');
phantom.exit();
}, 3000);
console.log('element is ' + element);
});
Now my after image is correct. But now my question is, If I want to continue on with my code i.e. click on another element on the site, will my new code have to be all nested inside of the timeout function?
There is an example function phantom.waitFor(callback) that I explain on the following post, it goes as follows:
phantom.waitFor = function(callback) {
do {
// Clear the event queue while waiting.
// This can be accomplished using page.sendEvent()
this.page.sendEvent('mousemove');
} while (!callback());
}
This can help streamline your code and avoid nested calls to window.setTimeout(), which are not very reliable anyway as you are waiting for a pre-set amount of time instead of waiting for the element to become visible. An example would be as follows:
// Step 1: Open and wait to finish loading
page.open('http://localhost/');
phantom.waitFor(function() {return !page.loading;});
// Step 2: Click on first panel and wait for it to show
page.evaluate(function() { $("#activate-panel1").click(); });
phantom.waitFor(function() {
return page.evaluate(function() {return $("#panel1").is(":visible");})
});
// Step 3: Click on second panel and wait for it to show
page.evaluate(function() { $("#activate-panel2").click(); });
phantom.waitFor(function() {
return page.evaluate(function() {return $("#panel2").is(":visible");})
});
console.log('READY!');
phantom.exit();
This will load each panel in succession (ie synchronously) while keeping your code simple and avoiding nested callbacks.
Hope it makes sense. You could also use CasperJS as an alternative, its aimed at making this stuff simpler.
Yes, your new code will be called from inside of the setTimeout callback. You can nest the code directly or write a function which capsules the code for you and call that function inside setTimeout.
function anotherClick(){
// something
}
page.sendEvent('click', element.offsetLeft, element.offsetTop, 'left');
window.setTimeout(function () {
console.log(page.frameUrl);
page.render('websiteAfterClick.png');
anotherClick();
phantom.exit();
}, 3000);
There is another way. You can also write it completely with multiple setTimeout, but then you cannot react to sudden conditions in previous calls.
page.sendEvent('click', element.offsetLeft, element.offsetTop, 'left');
window.setTimeout(function () {
console.log(page.frameUrl);
page.render('websiteAfterClick.png');
}, 3000);
window.setTimeout(function () {
// some more actions
}, 6000); // you cannot know if this delay is sufficient
window.setTimeout(function () {
phantom.exit();
}, 9000); // you cannot know if this delay is sufficient
I would suggest using CasperJS, if you want to do many actions/navigation steps.