I have been searching but can't find anything relevant to my issue. I need to change the cursor when a user initiates an action so they don't shotgun the request.
The statement to change the cursor is simple enough, but I can't get it to fall into sequence correctly. My cursor doesn't change until the function completes. I can see this by commenting out the statement to return the cursor to normal behavior.
I want the cursor to go to wait and stay that way until the call tot he ajax function is done, then return to normal.
// wrapper to set wait cursor during ajax calls (the cursor change doesn't work, btw -- leaving it here so it can be fixed)
function loadForm(){
setTimeout("document.getElementsByTagName('BODY')[0].style.cursor = 'wait'",1);
populateReadPage();
setTimeout("document.getElementsByTagName('BODY')[0].style.cursor = 'auto'", 1);
}
// configure calls to populate entire page: first string identifies the call, the second string identifies the custom function to implement
function populateReadPage(){
setScreenObject("noncom","grid1");
setScreenObject("cur_cert","grid2");
setScreenObject("cur_hecm","grid3");
setScreenObject("prev_cert","grid4");
setScreenObject("prev_hecm","grid5");
}
Javascript execution is single-threaded, so this is what happens when you execute the loadForm function:
setTimeout(Action1,1) : Action1 is enqueued to be executed when the current (main) execution line finishes
populateReadPage() : This method is executed
setTimeout(Action2,1) : Again, Action2 is enqueued to be executed when the current (main) execution line finishes.
Nothing else in the main line, Action1 is executed
Action2 is executed
UPDATE: After some research, it seems that the body style changes need a "pause" to be set, so I made it work with the following code:
function loadForm(){
document.body.style.cursor = 'wait'; //sets the cursor to wait
setTimeout(()=> { //enqueues the tasks
populateReadPage();
document.body.style.cursor = 'auto'; //once all is done, sets the regular pointer
});
}
Just one thing: the cursor does not change until it moves, not sure why
This is a bit of a topic switch ... I talked them into letting me use jQuery.
As I said at the top; all I want to do is set the cursor to "wait" then perform my async tasks then set it back to "auto". The code below is what I have now and it seems to work, (the paint doesn't happen until after the get) but the cursor never appears to change.
So far nothing (including the accepted answer) has worked for me.
This is my current code.
function setWait(){
if(document.body.style.cursor = 'wait'){
return this;
}
}
function setAuto(){
document.body.style.cursor = 'auto';
return this;
}
function loadForm(){
setWait();
setTimeout(function() {
$.when(setScreenObject("prev_cert"),setScreenObject("noncom"), setScreenObject("cur_cert")).done(function() {parseResults();});
setAuto();
});
}
function setScreenObject(str1){
// if "_user" exists use url 1 (counselor view), otherwise use url2 (hud/contractor view)
if(document.getElementById('_user')){
url="/clas/html/f17counselor_emp_req_db.cfc?method=getAgencyList&qCode=" + str1 + "&uid=" + document.getElementById('_user').value;
} else {
url="/clas/html/f17counselor_emp_req_db.cfc?method=getAgencyList&qCode=" + str1 + "&uid=&cid=" + document.getElementById('_identry').value;
}
$.ajax({
type : 'GET',
url : url,
async : false,
success : function (json) {
switch(str1) {
case "noncom":
noncom = json;
break;
case "cur_cert":
cur_cert = json;
break;
case "prev_cert":
prev_cert = json;
break;
default:
sysFault = true;
displayError();
}
},
error : function () {
sysFault = true;
displayError();
},
});
}
Related
My Google Scripts function keeps running even after returning the next function.
To summarise, I have a function (nightTargetSelection ) that calls another function (dayStart). And as soon as the second function gets called, I want the first one to stop running.
I have stripped down the function and removed everything that should not be relevant for this post. But please let me know if you need more information.
Firstly, I call a user-input form via the following code in GS:
function roleWakeUp (roleName, j){
var roleStrAppend = "<div id='id_role_name' style='display:none;'>" + roleName + "</div>";
var jAppend = "<div id='id_j' style='display:none;'>" + j + "</div>";
var actionInputDlg = HtmlService.createHtmlOutputFromFile('night')
.setSandboxMode(HtmlService.SandboxMode.IFRAME)
.setWidth(600)
.setHeight(425);
var wakeUpText = roleName + " wakes up."
actionInputDlg.append(roleStrAppend).append(jAppend);
SpreadsheetApp.getUi().showModalDialog(actionInputDlg, wakeUpText);
}
Then I have the form submitted by a user via HTML/JS:
<script>
selected = select.options[select.selectedIndex].value
google.script.run.withSuccessHandler(selected).nightTargetSelection(selected);
google.script.host.close();
</script>
Back in GS, I have the nightTargetSelection (the problematic function that I need helped getting fixed)
function nightTargetSelection (selected){
return dayStart();
}
Finally I have the dayStart function that is called within nightTargetSelection.
function dayStart() {
Browser.msgBox("DAY START");
//If I do not close the msgBox within 6 minutes (GS timeout period), then nightTargetSelection times out.
roleWakeUp (roleName, j);
}
I am expecting that the nightTargetSelection function stops running as soon as dayStart is called.
Unfortunately that is not happening and the nightTargetSelection function only completes when the next nightTargetSelection gets called.
As you can see, dayStart function calls roleWakeUp , making it a loop between functions. I have omitted including the loop details into this question because I dont believe it is relevant. Let me know if it is relevant and I will provide more details.
I don't know if this is your problem but in this:
<script>
var selected=select.options[select.selectedIndex].value;
google.script.run
.withSuccessHandler(selected)//select is supposed to be a function name
.nightTargetSelection(selected);
google.script.host.close();
</script>
.withSuccessHandler(functionname or anonymous function)
Yes, because nightTargetSelection() will not terminate until all of the processes it called have finished or it times out.
Looks like the end of the cycle is Browser.msgBox("DAY START");. So allow your server code to finish execution and then use .withSuccessHandler() to call roleWakeUp() via the client-side script.
(I don't really know what your code is doing. nightTargetSelection() seems to be unnecessary given the information you provided. So the example below is an abstraction to show you the flow.)
Client-Side Script
<script>
function yourOriginal() {
selected = select.options[select.selectedIndex].value;
google.script.run
.withSuccessHandler(restartCycle) // Calls restartCycle() after nightTargetSelection() finishes
.nightTargetSelection(selected);
google.script.host.close();
}
function restartCycle(values) {
google.script.run.roleWakeUp(values.roleName, values.j);
}
</script>
Server-Side Scripts
function roleWakeUp(roleName, j) {
// opens the dialog
}
function nightTargetSelection(selected) {
return dayStart();
}
function dayStart() {
Browser.msgBox("DAY START");
return {"roleName": roleName, "j": j}; // Pass these values to client-side script
}
I'm using web BLE. I have based my code according to the example of the heart rate measurement.
Everything is working fine most of the time. But sometimes, even if the connection is successfully made, when I try to bind to the notification, it doesn't work.
The link is made in this function :
_startNotifications(characteristicUuid) {
let characteristic = this._characteristics.get(characteristicUuid);
console.log(characteristic);
return characteristic.startNotifications().then(() => characteristic);
}
When everything is OK, I can see in the console that BluetoothRemoteGATTCharacteristic has a value : DataView(2) {}
Otherwise, when it's not working it has a value : null
I would like to be able to retry automatically, if I detect that the value is null. But I'm not familiar with Promise (I think this is it) and console.log(characteristic.value) doesn't work here.
How would you approach this ?
What I ended up doing is "bypass" the issue. So it's a more algorithmic resolution than a pure Javascript one.
I didn't change the connection function, so it is still called like this :
device._startNotifications(some_uuid).then(handleHeartRateMeasurement)
I check everything in the handleHeartRateMeasurement function :
var ready = false;
function handleHeartRateMeasurement(heartRateMeasurement) {
console.log("Hold on...");
heartRateMeasurement.addEventListener("characteristicvaluechanged", event => {
// Everytime the value change, this should be triggered
// If it did not, variable "ready" will stay false
ready = true;
var value = device.parseValue(event.target.value);
// Do something with value here
});
var check = function(){
// If we have received data from characteristic, we are ready to go !
if(ready === false){
console.log("Device connected but not receiving data");
// Stop the current notification subscription
device.stopNotificationsHeartRateMeasurement();
// Start a new one
device._startNotifications(some_uuid).then(handleHeartRateMeasurement);
setTimeout(check, 1000); // check again in a 1s
}
else{
console.log("Device connected and receiving data");
}
}
setTimeout(() => {
check();
}, 500);
}
First disclaimer: I am still extremely new to Typescript/javascript/front-end development
Background: I have a set of images (which represent a hand of cards). When it is the AI's turn to play a card, I am trying to show a simulation of "thinking" instead of immediately playing the card. My simulation is to iterate through the hand of cards and "select" each one (by "select", I mean move the image slightly to the left and then back to the right).
I am using Visual Studio Code and I am debugging in Chrome. My current code is below. The premise is that an API call is made (which is really where the AI logic is performed). Then the presentation iterates through the hand of cards and for each one it waits 1/4 second, shifts the card to the left, waits another 1/4 second, and shifts the card back to the right. After all cards have been "selected", then the actual card is played. I currently have everything in callback functions to keep it synchronous, but I'm not sure that I even need to do that.
// Call in to the API, which will perform the AI logic
// The API will return a slot that was played, which contains the card placed into that slot
opponentTurn(callback)
{
this.boardService.opponentTurn()
.subscribe(
cardPlayed =>
{
// Set the card played
let cardData = [];
cardData = JSON.parse(cardPlayed.toString());
// To add a slight hint of "thinking", let's "select" (slightly move the card to the left) each card down, up, and back down to the card that the API selected
this.selectCard(function(_thisagain) {
// Done
// Now simulate the move
_thisagain.dragService.simulateDragDrop(cardData);
});
callback(this);
}
);
}
// Call this to "select" a card; used by the AI "thinking" simulation
selectCard(callback)
{
for (var i = 0; i < this.players[1].cardHand.length; i++)
{
let imgObj = document.getElementById(this.players[1].name + i);
if (imgObj != null)
{
this.moveCard(imgObj, '400px', function(_thisagain) {
_thisagain.moveCard(imgObj, '350px', function() {
// Done
});
});
}
}
callback(this);
}
moveCard(imgObj, position, callback)
{
this.wait(250, function() {
imgObj.style.right = position;
});
callback(this);
}
wait(ms, callback)
{
var start = new Date().getTime();
var end = start;
while(end < start + ms)
{
end = new Date().getTime();
}
callback(this);
}
So the struggle I am having is that the code works, but only when I put a breakpoint on it. For example, if I put a breakpoint on the "_thisagain.moveCard(imgObj, '350px', function() {" line and then debug it, I can see each card shift to the left and back to the right as I would expect every time I 'Continue'. If I remove the breakpoint, the cards don't shift at all (yet I still get the wait before the card is played on the board).
Still being new to Typescript/javascript, I'm not really sure what is going on. It seems that when I have the breakpoint set, a redraw occurs to show the card shift. Without the breakpoint, it seems that no redraw is occurring. I'm not sure how to correct that, though, thus why I am here.
So after more research and a lot of trial-and-error, I have it working. The post that really got me on the right track is this:
DOM refresh on long running function
In that question, there was an answer posted which stated:
"Webpages are updated based on a single thread controller, and half the browsers don't update the DOM or styling until your JS execution halts, giving computational control back to the browser."
Turns out that was the key I was missing. I was processing far too much in a single statement and the js was not releasing back to the browser, thus the DOM/html was not being updated to show the "animation".
I then used the jsfiddle example also posted in an answer there to cobble together a "worker" thread that breaks up the process into chunks based on a procedural "status" and gives the control back to the browser with setTimeout(). Each worker thread is still set up as a callback function to ensure that the processing in each chunk finishes before moving on. For the intent of my original question, everything is working. I'm still refactoring and I'm sure there is probably a better way to achieve the results, but for now I am content.
For the sake of maybe helping someone in the future that might stumble across my question, this is what I have in my worker thread. Of course, my choice of terminology may be incorrect, but the gist of it is this. When I want to start a process flow, I can set the "status" text and call in to the worker. The worker then processes the flow as needed.
doHeavyWork() {
if (this.status == 'finish')
{
// All steps done
}
else
{
let _this = this;
switch (_this.status)
{
case 'start':
_this.status = 'working';
_this.opSelectCard = 0;
this.opponentTurn(function(response) {
_this.opCardData = response;
_this.status = 'selectCard';
});
break;
case 'selectCard':
_this.status = 'working';
this.selectCard(_this.opSelectCard, function(response) {
_this.opImgObj = response;
_this.status = 'moveCardLeft';
});
break;
case 'moveCardLeft':
_this.status = 'working';
this.moveCardLeft(_this.opImgObj, '-25px', function(root) {
_this.status = 'moveCardRight';
});
break;
case 'moveCardRight':
_this.status = 'working';
this.moveCardRight(_this.opImgObj, '1px', function(root) {
_this.opSelectCard++;
if (_this.opSelectCard < _this.players[1].cardHand.length)
{
_this.status = 'selectCard';
}
else
{
_this.status = 'simulateDragDrop';
}
});
break;
case 'simulateDragDrop':
_this.status = 'working';
this.dragService.simulateDragDrop(_this.opCardData, function(root) {
_this.status = 'refreshDisplay';
});
break;
case 'refreshDisplay':
_this.status = 'working';
this.refreshCardDisplay(function(root) {
_this.status = 'refreshDisplay';
});
break;
case 'refreshBoard':
_this.status = 'working';
this.refreshBoard(_this.boardCards, function(root) {
_this.status = 'updateScore';
});
break;
case 'updateScore':
_this.status = 'working';
this.updateScore(_this.boardCards, function(_this) {
_this.status = 'switchTurns';
});
break;
case 'switchTurns':
_this.status = 'working';
this.switchTurns(function(_this) {
if (_this.turn == 1)
{
_this.status = 'finish';
}
else
{
_this.status = 'start';
}
});
break;
}
setTimeout(function() {
_this.doHeavyWork();
}, 0);
}
}
Hoping some of you may help me with this problem.
I have a few navigation link on top of my application which have active and not-active state. In theory, I want to jump from one to another and if there exists a form which isn't complete/valid, I trigger validation errors and stay on the same page. What is happening in my case is form validation works fine but navigation links on top change state from non-active to active, whichever was clicked.
I have a ValidateForm function that validates and submits the form if its is valid, else it returns deferred.reject();
function ValidateForm(submitAnyway) {
var deferred = $.Deferred();
var form = $('form');
// if form doesn't exist on the page - quit
if (typeof form[0] === "undefined") return true;
// now check for any validation errors
if (submitAnyway) {
if (!$(form).valid()) {
deferred.reject();
} else {
$(form).submit();
deferred.resolve();
}
} else {
deferred.resolve();
}
return deferred.promise();
}
I have a click event for those top navigation links as below:
var DonutsClickEvent = function (e, arg) {
var url = $(this).attr('data');
var data = { param: arg };
if (typeof url === "undefined") return false;
$.when(window.ValidateForm(false)).then(
function () {
LoadPartialView_Main(url, data);
},
function(){
return false; // I'm trying to return false here to stop event from continuing
}
);
Console.log("This statement runs before $.when completes");
// and event continues and the clicked nav button changes
// its state from non-active to active, even though form doesn't validate
}
I recently added $.Deferred functionality to my code due to some events firing with messed up sequence... Before my validateForm method would return true or false and based on that i'd continue executing event if true, if false i'd stop and it was all good.
Not sure what am I doing wrong here. I'd appreciate any kinda help.
Thanks!
Johny
You can't asynchronously decide whether you want to block the default action or not. By the time you get your async result, your function has already returned and the default action has already occurred.
If you have to validate asynchronously, then you will have to always block the default action. If the validation succeeds, then you will have to manually carry out the desired action with Javascript.
So, assuming that LoadPartialView_Main() is what you want to have happen when validation succeeds, you can do something like this:
var DonutsClickEvent = function (e, arg) {
var url = $(this).attr('data');
var data = { param: arg };
if (typeof url === "undefined") return false;
window.ValidateForm(false).then(function () {
LoadPartialView_Main(url, data);
}, function(err){
// probably show validation error message to the user here
});
// always prevent default action
return false;
}
Also, there's no reason to use $.when() with a single promise. You can just use window.ValidateForm(...).then(...).
Has anyone found that their javascript doesnt work, but when they step through the code it works fine ?
var cookie = getCookie('lusr');
var username = cookie;
if(!cookie){
$("#UserNav").load("loginform.html");
$("#loginbtn").click( function(){
var username = $("#usernametxt").val();
var password = $("#passwordtxt").val();
login(username,password);
});
}else{
$("#UserNav").load("user.htm");
$("#WelcomeUser").text("Welcome "+ username);
}
My issue occurs on this line :
$("#WelcomeUser").text("Welcome "+ username);
That's because load() is asynchronous: it returns right away, performs its work in the background, then calls a user-provided function when its task is complete. The stepping delay gives you the illusion that the function is synchronous and performs all its work before returning.
Therefore, you should pass a callback function to load() and perform your subsequent work inside that callback:
var cookie = getCookie("lusr");
if(!cookie) {
$("#UserNav").load("loginform.html", function() {
$("#loginbtn").click(function() {
var username = $("#usernametxt").val();
var password = $("#passwordtxt").val();
login(username, password);
});
});
} else {
$("#UserNav").load("user.htm", function() {
$("#WelcomeUser").text("Welcome " + cookie);
});
}
You are using the load() function which asynchronously fetches from the server. This means your form has not loaded by the time you go searching for its fields.
The reason it works when you step through is because it gives it time to load the form while you step.
You can use another version of load which has an asynchonous callback function, allowing you to provide functionality only to be called once the load is complete.
Check the jQuery docs for more info.