I'm looking for a good approach to sometimes pause an action (function/method call) until the user confirms that he wants to do a specific part of that action. I need to do this in an environment that doesn't allow code execution to stop (ActionScript in my case, but an approach for JavaScript should be identical).
To illustrate, this is a mock-up of the action before introducing the user prompt:
<preliminary-phase> // this contains data needed by all the following phases //
<mandatory-phase> // this will be always be executed //
<optional-phase> // this will always execute too, if in this form, but in some cases we need to ask the user if he wants to do it //
<ending-phase> // also mandatory //
What I need is to insert a conditional user prompt, a "Do you want to do this part?", and do <optional-phase> only if the user wants to.
<preliminary-phase>
<mandatory-phase>
if(<user-confirmation-is-needed> and not <user-response-is-positive>){
<do-nothing>
}
else{
<optional-phase>
}
<ending-phase>
When trying to do this in ActionScript/JavaScript I got something like this:
<preliminary-phase>
<mandatory-phase>
if(<user-confirmation-is-needed>){
askForConfirmation(callback = function(){
if(<user-response-is-positive>)
<optional-phase>
<ending-phase>
});
return;
}
<optional-phase>
<ending-phase>
Now both <optional-phase> and <ending-phase> are duplicated. Also because they use objects created in <preliminary-phase> I can't move them to external functions without passing all the data to those functions.
My current solution is that I enclosed each of <optional-phase> and <ending-phase> in some local functions (so that they have access to data in <preliminary-phase>) declared before I ask for confirmation and I call those functions instead of duplicating the code, but it doesn't seem right that the code is no longer in the order it's executed.
What would you guys recommend?
Notes:
1. askForConfirmation is a non-blocking function. This means that the code that follows its call is executed immediately (this is why I have a return; in my approach).
Note: I'm not 100% sure I get your exact circumstances.
The Command Pattern might be suitable here. It's similar to what people are suggesting.
You have an array of commands that get executed in order.
[<preliminary-phase>, <mandatory-phase>, <optional-phase>, <ending-phase>]
Just shift the commands off the array one at a time and call the execute method.
In the optional-phase, check to see if the user confirmation is required, if not then execute an optional code method which dispatches a command complete event, if it is required then show the alert, wait for an event, check the result and either dispatch a command complete event or call the optional method (which will run and then dispatch a command complete).
You can also create a tree of commands so can clearly state the flow of execution without having to mess with the array.
This is how programs like installation wizards work.
It's good in that the order of execution is nice and visible and your code is nicely broken down in to chunks, and the complexity of each step is encapsulated. For example, the optional-phase doesn't know anything about the ending-phase. The optional-phase only knows that the user might need prompted before executing and it handles all of that internally.
http://en.wikipedia.org/wiki/Command_pattern
"Using command objects makes it easier to construct general components that need to delegate, sequence or execute method calls at a time of their choosing..."
"the code is no longer in the order it's executed" seems fine to me actually. It's fine to have code that isn't written in the order it's executed just as long as it's clear. In fact, since your code executes in variable orders I think it's impossible for you to write it in the order it will execute without duplicating code, which is a far greater evil. Pick good function names and your approach would pass my code review.
<preliminary-phase>
<mandatory-phase>
var optional_phase = function() {
<optional-phase>
}
var ending_phase = function() {
<ending-phase>
}
if(<user-confirmation-is-needed>){
askForConfirmation(function(){
if(<user-response-is-positive>)
optional_phase();
ending_phase();
});
return;
}
optional_phase();
ending_phase();
Does this do what you're asking for?
<preliminary-phase>
<mandatory-phase>
if(<user-confirmation-is-needed>){
askForConfirmation(function(){
if(<user-response-is-positive>)
<optional-phase-as-local-function>
<ending-phase-as-local-function>
});
} else {
<optional-phase-as-local-function>
<ending-phase-as-local-function>
}
Not a huge change , but provided this flow works, optional phase is not repeated
<preliminary-phase>
<mandatory-phase>
if(<user-confirmation-is-needed>){
askForConfirmation(function(){
if(<user-response-is-negative>)
{
<ending-phase>
return;
}
});
}
<optional-phase>
<ending-phase>
Related
Some of the scripts that I run take a long time and users might get concerned that a script stopped working if they can't see the status/step. I have added a spinner to the Sidebar to at least indicate that the script started running, but I would like to do more than that.
Ideally, I would be able to directly update the Sidebar contents from the GAS, but I gather than is not possible because of sandboxing. I have seen other questions and answers that discuss using success handlers in a daisy chain like this:
function uploadActivities(){
google.script.run.withSuccessHandler(onSuccess).activities_upload();
}
function onSuccess(lastStatus){
$('#codestatus').text(lastStatus);
google.script.run.step_two();
}
It is a hack and it would require me to split the code into smaller steps and pass values to the UI, which don't belong in the UI, and back to the code. I really don't like that approach and maintenance could be a bear.
I have tried creating a var in GAS and updating that value as the code progresses. However, I can't find a way to get the UI to periodically check until the code execution is complete AND to successfully update the UI after each step.
Here is the code I have created:
function uploadActivities(){
google.script.run.activities_upload();
getStatus();
}
function getStatus(){
var isActive = true;
while(isActive){
var lastStatus = google.script.run.getStatus();
$('#codestatus').text(lastStatus);
if(lastStatus === 'Complete'){ isActive = false; }
}
}
In GAS I use this code:
var codeStatus = 'start';
function getStatus(){
return codeStatus;
}
function activities_upload(){
codeStatus = 'Started Execution';
...
codeStatus = 'Extracting Values';
...
codeStatus = 'Uploading Activities';
...
codeStatus = 'Complete';
}
It runs the required code, and even updates the #codestatus div with the first value, but it doesn't get any values beyond the first value. Additionally, it creates a continuous loop if there is an error in the code execution, so that isn't good either.
Is there a good, efficient, and safe way to complete this approach? Or, is there a better way to notify the user of the code execution status so they don't get worried if it takes a while, and can tell if there has been an issue?
I have struggled with this for some time. Unfortunately, I don't have a good fix for your approach, but I can show what I finally did and it seems to be working.
First, create an easy way to send a toast to your users.
function updateStatus_(alert,title){
var ui = SpreadsheetApp.getActiveSpreadsheet();
var title_ = title!=""?title:"";
ui.toast(alert,title_);
}
Second, as required, use the toast to update the user.
function activities_upload(){
updateStatus_('Started Execution');
...
updateStatus_('Extracting Values');
...
updateStatus_('Uploading Activities');
...
updateStatus_('Complete');
}
This will alert the user with a temporary message as the code progresses and not require the user to clear an alert.
Please note that if the steps progress rapidly the user will see the toast flash on the screen only to be quickly replaced by the next toast. So, make sure you don't have too many throughout your execution.
i'm trying to get my script to wait for user input (click of a button) before continuing, this is v feasible in other languages, but seems impossible in js. basically, i want the user to select an option within a given time frame, if the user selects the wrong option, they're told..script then conts...otherwise, if after a certain amount of time theres no response...script just continues again sowing them the correct ans, but there seems to be nothing in js to make the script wait for that user input! ive tried a while loop, but that is just a big no no in js, ive used settimeout but has no real effect because the script just continues like normal then performs an action after x amount of time, ive tried setting variables and letting the script cont only if it is of a particular value, which is set only if the user clicks...eg var proceed=false, this is only set to true if the user clicks a button, but it still doesn't work... ive tried sooo many other solutions but nothing actually seems to be working. i like the idea of a while loop, because it doeas exactly what i want it to so, but if completly freezes my browser, is there a more effecient type of loop that will will peroform in the same manner with crashing my browser?
heres my code below that compltely freezes my computer. this method is called within a for loop which calls another method after it.
function getUserResp(){
$("#countdown").countdown({seconds: 15});
setTimeout("proceed=true", 16000);
$("#ans1").click(function(){
ansStr=$(this).text();
checkAns(ansStr);
});
$("#ans2").click(function(){
ansStr=$(this).text();
checkAns(ansStr);
});
$("#ans3").click(function(){
ansStr=$(this).text();
checkAns(ansStr);
});
would like something like this.....or just some sort of loop to make the script wait before going ahead so at least it gives the user some time to respond rather than running straight though!
do{
$(".ans").mouseover(function(){
$(this).addClass("hilite").fadeIn(800);
});
$(".ans").mouseout(function(){
$(this).removeClass("hilite");
});
}while(proceed==false);
}
You're doing it wrong.
JavaScript in the browser uses an event-driven model. There's no main function, just callbacks that are called when an event happens (such as document ready or anchor clicked). If you want something to happen after a user clicks something, then put a listener on that thing.
What you've done just keeps adding an event listener every time round the loop.
If you want to wait for user input then just don't do anything - the browser waits for user input (it's got an internal event loop). The worst thing you can do is try to reimplement your own event loop on top of the browser's.
You need to learn JavaScript. Trying to write JavaScript like you would another language only leads to pain and suffering. Seriously.
Douglas Crockford said it best:
JavaScript is a language that most people don’t bother to learn before they use. You can’t do that with any other language, and you shouldn’t want to, and you shouldn’t do that with this language either. Programming is a serious business, and you should have good knowledge about what you’re doing, but most people feel that they ought to be able to program in this language without any knowledge at all, and it still works. It’s because the language has enormous expressive power, and that’s not by accident.
You can't block the Javascript from running in the same way that you can in some other imperative languages. There's only one thread for Javascript in the browser, so if you hang it in a loop, nothing else can happen.
You must use asynchronous, event-driven programming. Setting a click handler (or whatever) combined with a timeout is the right way to start. Start a 15 second setTimeout. Inside the click handler for the answers, cancel the timeout. This way the timeout's handler only happens if the user doesn't click an answer.
For example:
var mytimeout = setTimeout(15000, function() {
// This is an anonymous function that will be called when the timer goes off.
alert("You didn't answer in time.");
// Remove the answer so the user can't click it anymore, etc...
$('#ans').hide();
});
$('#ans').click(function() {
// Clear the timeout, so it will never fire the function above.
clearTimeout(mytimeout);
alert("You picked an answer!");
});
See how the code must be structured such that it's event-driven. There's no way to structure it to say "do this thing, and wait here for an answer."
You're looking at client-side javascript as if it wasn't already in an event-driven loop. All you need to do is wait for the appropriate event to happen, and if it hasn't happened yet, continue to wait, or else perform some default action.
You don't need to:
create main loop: // All
wait for user input // Of
timer = start_timer() // This
// Is done for you
if [user has input data]:
process_data()
else if [timer > allowed_time]:
process_no_data()
else:
wait() // By the Browser
You only need the middle part. All you need to do is (Actual javascript follows, not pseudo-code):
// First, store all of the answer sections,
// so you're not grabbing them every time
// you need to check them.
var answers = {};
answers.ans1 = $("#ans1");
answers.ans2 = $("#ans2");
answers.ans3 = $("#ans3");
// This is a flag. We'll use it to check whether we:
// A. Have waited for 16 seconds
// B. Have correct user input
var clear_to_proceed = false;
var timer_id;
// Now we need to set up a function to check the answers.
function check_answers() {
if ( ! clear_to_proceed ) {
clear_to_proceed = checkAns(answers.ans1.text());
clear_to_proceed = checkAns(answers.ans2.text());
clear_to_proceed = checkAns(answers.ans3.text());
// I assume checkAns returns
// true if the answer is correct
// and false if it is wrong
}
if ( clear_to_proceed ) {
clearTimeout(timer_id);
return true; // Or do whatever needs be done,
// as the client has answered correctly
} else {
// If we haven't set a timer yet, set one
if ( typeof timer_id === 'undefined' ) {
timer_id = setTimeout(function(){
// After 16 seconds have passed we'll check their
// answers one more time and then force the default.
check_answers();
clear_to_proceed = true;
check_answers();
}, 16000);
}
return false; // We're just waiting for now.
}
}
// Finally, we check the answers any time the user interact
// with the answer elements.
$("#ans1,#ans2,#ans3").bind("focus blur", function() {
check_answers();
});
I have a function called save(), this function gathers up all the inputs on the page, and performs an AJAX call to the server to save the state of the user's work.
save() is currently called when a user clicks the save button, or performs some other action which requires us to have the most current state on the server (generate a document from the page for example).
I am adding in the ability to auto save the user's work every so often. First I would like to prevent an AutoSave and a User generated save from running at the same time. So we have the following code (I am cutting most of the code and this is not a 1:1 but should be enough to get the idea across):
var isSaving=false;
var timeoutId;
var timeoutInterval=300000;
function save(showMsg)
{
//Don't save if we are already saving.
if (isSaving)
{
return;
}
isSaving=true;
//disables the autoSave timer so if we are saving via some other method
//we won't kick off the timer.
disableAutoSave();
if (showMsg) { //show a saving popup}
params=CollectParams();
PerformCallBack(params,endSave,endSaveError);
}
function endSave()
{
isSaving=false;
//hides popup if it's visible
//Turns auto saving back on so we save x milliseconds after the last save.
enableAutoSave();
}
function endSaveError()
{
alert("Ooops");
endSave();
}
function enableAutoSave()
{
timeoutId=setTimeOut(function(){save(false);},timeoutInterval);
}
function disableAutoSave()
{
cancelTimeOut(timeoutId);
}
My question is if this code is safe? Do the major browsers allow only a single thread to execute at a time?
One thought I had is it would be worse for the user to click save and get no response because we are autosaving (And I know how to modify the code to handle this). Anyone see any other issues here?
JavaScript in browsers is single threaded. You will only ever be in one function at any point in time. Functions will complete before the next one is entered. You can count on this behavior, so if you are in your save() function, you will never enter it again until the current one has finished.
Where this sometimes gets confusing (and yet remains true) is when you have asynchronous server requests (or setTimeouts or setIntervals), because then it feels like your functions are being interleaved. They're not.
In your case, while two save() calls will not overlap each other, your auto-save and user save could occur back-to-back.
If you just want a save to happen at least every x seconds, you can do a setInterval on your save function and forget about it. I don't see a need for the isSaving flag.
I think your code could be simplified a lot:
var intervalTime = 300000;
var intervalId = setInterval("save('my message')", intervalTime);
function save(showMsg)
{
if (showMsg) { //show a saving popup}
params=CollectParams();
PerformCallBack(params, endSave, endSaveError);
// You could even reset your interval now that you know we just saved.
// Of course, you'll need to know it was a successful save.
// Doing this will prevent the user clicking save only to have another
// save bump them in the face right away because an interval comes up.
clearInterval(intervalId);
intervalId = setInterval("save('my message')", intervalTime);
}
function endSave()
{
// no need for this method
alert("I'm done saving!");
}
function endSaveError()
{
alert("Ooops");
endSave();
}
All major browsers only support one javascript thread (unless you use web workers) on a page.
XHR requests can be asynchronous, though. But as long as you disable the ability to save until the current request to save returns, everything should work out just fine.
My only suggestion, is to make sure you indicate to the user somehow when an autosave occurs (disable the save button, etc).
All the major browsers currently single-thread javascript execution (just don't use web workers since a few browsers support this technique!), so this approach is safe.
For a bunch of references, see Is JavaScript Multithreaded?
Looks safe to me. Javascript is single threaded (unless you are using webworkers)
Its not quite on topic but this post by John Resig covers javascript threading and timers:
http://ejohn.org/blog/how-javascript-timers-work/
I think the way you're handling it is best for your situation. By using the flag you're guaranteeing that the asynchronous calls aren't overlapping. I've had to deal with asynchronous calls to the server as well and also used some sort of flag to prevent overlap.
As others have already pointed out JavaScript is single threaded, but asynchronous calls can be tricky if you're expecting things to say the same or not happen during the round trip to the server.
One thing, though, is that I don't think you actually need to disable the auto-save. If the auto-save tries to happen when a user is saving then the save method will simply return and nothing will happen. On the other hand you're needlessly disabling and reenabling the autosave every time autosave is activated. I'd recommend changing to setInterval and then forgetting about it.
Also, I'm a stickler for minimizing global variables. I'd probably refactor your code like this:
var saveWork = (function() {
var isSaving=false;
var timeoutId;
var timeoutInterval=300000;
function endSave() {
isSaving=false;
//hides popup if it's visible
}
function endSaveError() {
alert("Ooops");
endSave();
}
function _save(showMsg) {
//Don't save if we are already saving.
if (isSaving)
{
return;
}
isSaving=true;
if (showMsg) { //show a saving popup}
params=CollectParams();
PerformCallBack(params,endSave,endSaveError);
}
return {
save: function(showMsg) { _save(showMsg); },
enableAutoSave: function() {
timeoutId=setInterval(function(){_save(false);},timeoutInterval);
},
disableAutoSave: function() {
cancelTimeOut(timeoutId);
}
};
})();
You don't have to refactor it like that, of course, but like I said, I like to minimize globals. The important thing is that the whole thing should work without disabling and reenabling autosave every time you save.
Edit: Forgot had to create a private save function to be able to reference from enableAutoSave
Are you able to halt JavaScript execution without locking up the browser? The way you would normally halt execution is to do an infinite while()-loop, but in the case of FireFox, it locks up the browser until the loop has ended.
What's your take on this?
I am trying to override window.confirm() to implement my own dialog using HTML. I am doing this so I don't have to change existing code (it's a pretty big code-base).
I need to be able to halt execution to allow user-input; to in turn return a boolean like the standard confirm function does:
if (confirm("..."))
{
// user pressed "OK"
}
else
{
// user pressed "Cancel"
}
Update
To my knowledge; this cannot be done using setTimeout() or setInterval() since these functions execute the code thats given to them asynchronously.
confirm() prompt() and alert() are special functions--they call out of the JavaScript sandbox into the browser, and the browser suspends JavaScript execution. You can't do the same thing, since you need to build your functionality into JavaScript.
I don't think there's a great way to drop in a replacement without doing some restructuring along the lines of:
myconfirmfunction(function() {
/* OK callback */
}, function() {
/* cancel callback */
});
Either use callbacks or make your code Firefox-only. In Firefox with support for JavaScript 1.7 and higher, you can use the yield statement to simulate your desired effect. I have created a library for this purpose called async.js. The standard library for async.js includes a confirm method, which can be used as such:
if (yield to.confirm("...")) {
// user pressed OK
} else {
// user pressed Cancel
}
You cannot stop the event thread in JavaScript, so instead you have to work around the problem, usually by using callback functions. These are functions that are run at a later time, but can be passed around like any other object in JavaScript. You might be familiar with them from AJAX programming. So, for example:
doSomeThing();
var result = confirm("some importart question");
doSomeThingElse(result);
Would be converted into:
doSomeThing();
customConfirm("some importart question", function(result){
doSomeThingElse(result);
});
where customConfirm now takes a question and passes the result to the function it takes as an argument. If you implement a DOM dialog with a button, then connect an event listener to the OK and CANCEL buttons, and call the callback function when the user clicks on one of them.
There is an extension to the JavaScript language called StratifiedJS. It runs in every browser, and it allows you to do just that: halting one line of JavaScript code without freezing the browser.
You can enable Stratified JavaScript e.g. by including Oni Apollo ( http://onilabs.com/docs ) in your webpage like:
<script src="http://code.onilabs.com/latest/oni-apollo.js"></script>
<script type="text/sjs"> your StratifiedJS code here </script>
Your code would look like this:
var dom = require("dom");
displayYourHtmlDialog();
waitfor {
dom.waitforEvent("okbutton", "click");
// do something when the user pressed OK
}
or {
dom.waitforEvent("cancelbutton", "click");
}
hideYourHtmlDialog();
// go on with your application
the way you normally halt execution should hardly ever be an infinite while loop.
break up your work into parts, that you call with SetTimeout
change this:
DoSomeWork();
Wait(1000);
var a = DoSomeMoreWork();
Wait(1000);
DoEvenMoreWork(a);
to this:
DoSomeWork();
setTimeout(function() {
var a = DoSomeMoreWork();
setTimeout(function() {
DoEvenMoreWork(a);
}, 1000);
}, 1000);
I don't think there's any way to reasonably re-create the functionality of confirm() or prompt() in your own JavaScript. They're "special" in the sense of being implemented as calls into the native browser library. You can't really do a modal dialog of that sort in JavaScript.
I have seen various UI libraries that simulate the effect by putting an element on top of the page, that looks & acts like a modal dialog, but those are implemented using async callbacks.
You will have to modify the existing library, rather than replacing window.confirm.
I tried using tight looping for this. I needed to slow down a native event (which AFAIK is the only use case for a synchronous wait that can't be re-architected asynchronously). There are lots of example loops out there that claim not to lock up the browser; but none of them worked for me (the browser didn't lock up, but they prevented it from doing the thing I was waiting for in the first place), so I abandoned the idea.
Next I tried this - storing and replaying the event, which seems to be impossible cross-browser too. However depending on the event and how flexible you need to be, you can get close.
In the end I gave up, and feel much better for it; I found a way to make my code work without having to slow down the native event at all.
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.