I have a web application that performs a Jquery .post request for database queries. I also have three different web socket connections that are used to push status updates from the server to the client (CPU and Memory stats in a live chart, database status, and query queue). While a query is not running, everything works smoothly, but once a query is started (post request), then the three web socket connections seem to hang/block while waiting for the query to return. I was reading about this and have not found any relevant answers...I suspect that it is probably something really dumb on my part...but this has had me scratching my head for the better part of a day now. I thought I might try moving the web socket connections to web workers...but in theory, the POST should not be blocking to begin with...So, here are the relevant snippets of code...The full source is a couple of thousand lines of code...so I didn't want to inundate anyone with it...but could show it if it is useful. So, the big question is what am I doing wrong here? Or perhaps, am I misunderstanding how AJAX calls work as far as blocking goes?
// query execution button that grabs the query for the most recently focused query source (SPARQL editor, history, or canned)
$("#querySubmitButton").on("click", function(e) {
// Disable the query button
$("#querySubmitButton").attr('disabled',true);
// Let's make sure we are clearing out the work area and the popup contents
$("#viz").empty();
// Get YASQE to tell us what type of query we are running
var queryType = editor.getQueryType();
// refactored so that we can clean up the on-click function and also make other query types in a more modular way
switch(queryType) {
case 'SELECT':
sparqlSelect();
break;
case 'CONSTRUCT':
sparqlConstruct();
break;
case 'ASK':
sparqlAsk();
break;
case 'DESCRIBE':
sparqlDescribe();
break;
case 'INSERT':
sparqlInsert();
break;
default:
popup.show("Unrecognized query type.","error");
break;
}
});
// Functions to do each of the query types (SELECT, CONSTRUCT, ASK, DESCRIBE, INSERT)
// SELECT
function sparqlSelect() {
$.post("sparqlSelect", { database: $("#DB_label").html(),'query': editor.getValue() }).done(function(data, textStatus, xhr) {
// Enable the query button
$("#querySubmitButton").removeAttr('disabled');
// If the query worked, store it
storeQueryHistory(query);
// if the previous query was a CONSTRUCT, then lets hide the graph metrics button
$("#nav-trigger-graphStatistics").fadeOut(800);
// Need to slide the query menu back
sliders("in",$("#nav-trigger-query").attr("id"));
var columns = [];
var fields = [];
var comboboxFields = [];
// Hide the graph search panel
$("#graphSearch").fadeOut(1400);
// Show the results and visualization button/tab
$("#nav-trigger-results").fadeIn(1400);
$("#nav-trigger-visualization").fadeIn(1400);
$.each(data.results.head.vars, function(index, value) {
columns.push({'field': value, 'title': value});
var to = {};
to[value] = {type: "string"};
fields.push(to);
// Let's also populate the two Comboboxes for the Visualization while we are at it
comboboxFields.push({'text': value, 'value': value});
});
// Now, set the two combobox datasources for visualizations
var categoriesDS = new kendo.data.DataSource({
data: comboboxFields
});
vizCategoryAxis.setDataSource(categoriesDS);
var valuesDS = new kendo.data.DataSource({
data: comboboxFields
});
vizValueAxis.setDataSource(valuesDS);
var dataBindings = [];
$.each(data.results.results.bindings, function(index1, value) {
var tempobj = {};
$.each(value, function(k1,v1) {
tempobj[k1] = v1.value;
});
tempobj.id=index1;
dataBindings.push(tempobj);
});
var configuration = {
dataSource: {
data: dataBindings,
pageSize: 25
},
height: 400,
scrollable: true,
sortable: true,
filterable: true,
reorderable: true,
resizable: true,
toolbar: ["excel"],
excel: {
allPages: true,
filterable: true,
proxyURL: "/saveExcel"
},
pageable: {
input: true,
numeric: false,
pageSizes: true
},
'columns': columns,
dataBound: function(e) {
$(e.sender.element).find('td').each(function() {
var temp = $(this).html();
if (isUrl(temp)) {
$(this).html('' + temp + '');
}
});
}
};
// Create the popup window
var gridWindow = $("#resultsPopup").kendoWindow({
width: "70%",
title: "Query Results",
actions: [
"Minimize",
"Maximize",
"Close"
]
}).data('kendoWindow');
// Center and show the popup window
gridWindow.center().open();
// Create/update/refresh the grid
resultsGrid.setOptions(configuration);
resultsGrid.dataSource.page(1);
$("#nav-trigger-results").on('click',function() {
// Center and show the popup window
gridWindow.center().open();
});
}).fail(function(xhr) {
// If we are timed-out
if (xhr.status === 401) {
// First, clear the host, database, and status text
$("#host_label").html('');
$("#DB_label").html('');
$("#status_label").html('');
// Next, disable the query button
$("#querySubmitButton").attr('disabled',true);
// Change "login" tab text color to red so we know we are no longer logged in
var styles = { 'color': "#FFCCD2" };
$("#nav-trigger-login").css(styles);
popup.show("Session for " + host + " has timed out, please log back in.","error");
}
else {
// Enable the query button
$("#querySubmitButton").removeAttr('disabled');
popup.show("Error, no results (" + xhr.status + " " + xhr.statusText + ")","error");
}
});
}
// Function to connect to the query queue websocket
function queueWebsocketConnect() {
var qws = new WebSocket('wss://endeavour:3000/queue');
// Let's disconnect our Websocket connections when we leave the app
$(window).on('unload', function() {
console.log('Websocket connection closed');
qws.close();
});
// Status websocket onopen
qws.onopen = function () {
console.log('Websocket connection opened');
popup.show("Websocket connection opened","success");
};
qws.onclose = function (event) {
console.log('Websocket connection closed');
popup.show("Websocket connection closed","info");
};
qws.onmessage = function (msg) {
var res = JSON.parse(msg.data);
var tableRows = '<thead><tr><td>Query Position</td><td>Query ID</td><td>Kill/Cancel Query</td></tr></thead><tbody>';
if (res.executing != null && res.entry.length > 0) {
$("#queryQueue").empty();
tableRows += '<tr><td>1</td><td>' + res.executing.id + '</td><td><input type="button" class="k-button" value="Kill"></td></tr>';
$.each(res.entry, function(index,object) {
tableRows += '<tr><td>' + (object.pos + 1) + '</td><td>' + object.query.id + '</td><td><input type="button" class="k-button" value="Cancel"></td></tr>';
});
tableRows += '</tbody>';
$("#queryQueue").html(tableRows);
}
else if (res.executing != null) {
$("#queryQueue").empty();
tableRows += '<tr><td>1</td><td>' + res.executing.id + '</td><td><input type="button" class="k-button" value="Kill"></td></tr>';
tableRows += '</tbody>';
$("#queryQueue").html(tableRows);
}
else {
console.log(res);
$("#queryQueue").empty();
}
};
}
// Function to connect to the stats websocket
function websocketConnect () {
// Set up websocket connection for system stats
var ws = new WebSocket('wss://endeavour:3000/stats');
// Let's disconnect our Websocket connections when we leave the app
$(window).on('unload', function() {
console.log('Websocket connection closed');
ws.close();
});
// Status websocket onopen
ws.onopen = function () {
console.log('Websocket connection opened');
popup.show("Websocket connection opened","success");
};
// Status websocket onclose
ws.onclose = function (event) {
// Disable the query button
$("#querySubmitButton").attr('disabled',true);
// Change "login" tab text color to red so we know we are no longer logged in
var styles = { 'color': "#FFCCD2" };
$("#nav-trigger-login").css(styles);
// Clear the host, database, and status text
$("#host_label").html('');
$("#DB_label").html('');
$("#status_label").html('');
console.log('Websocket connection closed');
popup.show("Websocket connection closed","error");
$("#websocketReconnectButtonYes").on('click', function() {
websocketConnect();
queueWebsocketConnect();
websocketReconnect.close();
});
$("#websocketReconnectButtonNo").on('click', function() {
websocketReconnect.close();
});
websocketReconnect.center().open();
};
// When updates are received, push them out to update the details
var logoutCount = 0;
ws.onmessage = function (msg) {
if (msg.data === 'loggedOut') {
// Ensure we only emit this one time instead of a stream of them
if (logoutCount == 0) {
// Disable the query button
$("#querySubmitButton").attr('disabled',true);
// Change "login" tab text color to red so we know we are no longer logged in
var styles = { 'color': "#FFCCD2" };
$("#nav-trigger-login").css(styles);
// Clear the host, database, and status text
$("#host_label").html('');
$("#DB_label").html('');
$("#status_label").html('');
console.log("Session for " + $("#host_label").html() + " has timed out, please log back in.");
popup.show("Session for " + $("#host_label").html() + " has timed out, please log back in.","error");
}
logoutCount = 1;
}
else {
logoutCount = 0;
var res = JSON.parse(msg.data);
var host = $("#host_label").html();
var pdatabase = $("#DB_label").html();
var pstatus = $("#status_label").html();
// Disable the query button unless the database is "CONNECTED"
if ($("#status_label").html() !== res.current.databaseStatus) {
if (res.current.databaseStatus !== "CONNECTED") {
$("#querySubmitButton").attr('disabled',true);
}
else {
$("#querySubmitButton").removeAttr('disabled');
}
if (res.current.databaseStatus == 'CONNECTED' || res.current.databaseStatus == 'STOPPED') {
$("#startDB").removeAttr('disabled');
}
else {
$("#startDB").attr('disabled',true);
}
}
// Maybe a more intelligent way to do this, but need to make sure that if the cookie is still valid, then populate the database login stuff
if ($("#dbConfigHost").val() == "" && $("#dbConfigUser").val() == "") {
$("#dbConfigHost").val(res.host);
$("#dbConfigUser").val(res.user);
// Change "login" tab text color to green so we know we are logged in
var styles = { 'color': "#C5E6CC" };
$("#nav-trigger-login").css(styles);
var databasesDS = new kendo.data.DataSource({
data: res.databases.database
});
databasePicker.setDataSource(databasesDS);
}
// Update the labels when values change
if (res.host != $("#host_label").html()) {
$("#host_label").html(res.host);
popup.show("Host changed to " + res.host,"info");
}
if (pdatabase != res.current.name) {
$("#DB_label").html(res.current.name);
popup.show("Database changed to " + res.current.name ,"info");
}
if (pstatus != res.current.databaseStatus) {
$("#status_label").html(res.current.databaseStatus);
}
// Update the sparklines
cpulog.options.series[0].data = res.system.cpu;
cpulog.refresh();
memlog.options.series[0].data = res.system.mem;
memlog.refresh();
}
};
// Open the websocket connection to listen for changes to the query list
var queryWS = new WebSocket('wss://endeavour:3000/queryList');
queryWS.onmessage = function(msg) {
var res = JSON.parse(msg.data);
var queriesDS = new kendo.data.DataSource({
data: res
});
cannedQuery.setDataSource(queriesDS);
};
}
Well, I guess when one has been heading down one road for a while, one assumes that it is in the right direction. After further head-scratching, I found the issue and it was related to the blocking/non-blocking nature of my backend web-framework. As it turns out, in Mojolicious (Perl web-framework), http calls can be either synchronous or asynchronous depending on how one writes the call.
my $tx = $ua->get('http://foo.bar?query=getSomeFoo');
if($tx->success) {
$self->render($tx->res->content);
}
else {
$self->rendered($tx->res->code);
}
This is a blocking/synchronous request. Nothing happens until after the GET finishes. On the other hand, if one writes the request like so, it is an asynchronous request:
$ua->get('http://foo.bar?query=getSomeFoo' => sub {
my ($ua,$tx) = #_;
if($tx->success) {
$self->render($tx->res->content);
}
else {
$self->rendered($tx->res->code);
}
});
So, if anyone else has encountered this issue...here is the answer. If I am the only idiot on the planet that has committed this blunder...then I guess I have put my shame out there for all to have a good chuckle at.
Bottom line was that this was well documented in the Mojolicious docs...but I had been doing it one way for so long that I completely forgot about it.
Cheers!
Related
I've looked at this How-do-i-return-the-response-from-an-asynchronous-call and at why-is-my-variable-unaltered-after-i-modify-it-inside-of-a-function-asynchron , but what I'm trying to do doesn't work.
Since some of our users use IE, it seems that we would have to rely on callbacks.
The background to this question comes from a previous post. Legacy code used VBscript's MsgBox, but now the same functionality must be ported to Javascript.
To provide context, there is a bunch of buttons on a toolbar (hence toolbar.asp) such as "New", "Update", "Delete". The user can navigate to certain parts of the system and for instance create a "New" record for a particular resource/person. The details and functionality are shown on the main part of the screen (hence main.asp).
The system was originally written about 15 years ago (or more).
When the user clicks "Update" on the toolbar.asp, it depends which screen the main.asp was showing. Parts of dovalidation() in main.asp can be swopped (as in a script is fetched from the database) and inserted into dovalidation() depending on what & where the user is.
So some parts are still in Visual Basic 6, but it seems that they are trying to replace the VBscript with Javascript.
So the user is on a specific screen and clicks Update (the toolbar.asp's doupdate() is called).
This doupdate() does a number of checks on the date and other variables and the calls the main.asp's dovalidation().
Depending on where the user finds himself, dovalidation looks different.
In quite a few cases as in the specific example that I am talking about, there used to be a MsgBox in the VBscript code which gave the user a choice depending on the validation done up to that point. However, that VBscript should now be replaced by Javascript. It seems my boss doesn't want to use the normal window.confirm, because she wants to be able to customise the buttons.
VBscript's MsgBox blocked further execution, but now using a jquery confirm doesn't have the same results, because it is non-blocking.
If the validation occurs in such a way that the user is provided with the confirm dialog, and the user clicks on 'Cancel' then the next page should not be shown, but at present, whether the user clicks on 'Cancel' or not, the next page is shown after a couple of seconds.
At the end of the doupdate() function there is:
parentMain.screenform.submit();
Could that be part of why my callbacks don't work?
In a toolbar.asp file, this is called in the doupdate() funtion:
//... Other code that determines variables, fringe scenarios etc.
//...
// Then dovalidation() (in which the blocking MsgBox used to be) is called:
var sErr = parentMain.dovalidation();
if (sErr != ""){
return;
}
//Then the rest of the code which is executed irrespective of the jquery confirm.
//do update
try {
parentMain.document.all("Updating").value = "YES"
parentMain.document.body.scrollTop = 0
parentMain.document.body.scroll = 'no'
parentMain.ShowBusy();
document.getElementById("sysBusy").value = "true";
//parentMain.document.all("clockFrame").style.display = "block";
} catch(e) {
return (e)
}
//do check for resource tag
if (sScreenType.toUpperCase() == "RESOURCE TABLE") {
if (lResource == "0") {
parentMain.document.all("sysresource").value = lDetailResource
}
//alert("looping for resource tag");
var sField = ""
var sCheck = "Resource Tag"
if (!(sScreen == "Resource")) {
/*********************************************************************/
/** loop through the fields and update resouce tag if empty - submit**/
/*********************************************************************/
var elements = parentMain.document.getElementById("screenform").elements;
for (var i = 0, element; element = elements[i++];) {
if ((element.name).indexOf(sCheck) > 0) {
var sValue = element.value
if (sValue.length == 0) {
element.value = lDetailResource
}
}
if ((element.tagName.toUpperCase()) == "SELECT") {
if (element.disabled == true) {
element.disabled = false;
}
}
}
}
}
//submit screen
parentMain.screenform.submit(); //<-- Could this be part of the problem?
}
In the main.asp file the dovalidation function resides. A part of the dovalidation function is swapped out depending on the situation. That is marked between the //################
function dovalidation() {
msErr = "";
//#################
if (msErr.length == 0) {
var vEffectiveDate="";
var vLastRunDate="";
var sStatus="";
var vMsg="";
var sResponse="";
vEffectiveDate = document.getElementById("Smoke.Effective Date").value;
vLastRunDate = document.getElementById("Smoke.Last Run Date").value;
sStatus = document.getElementById("Smoke.Calc Status").value;
vMsg = "";
if ((sStatus).length == 0 ){
sStatus = "SUCCESFUL";
//document.getElementById("Smoke.Calc Status").value= sStatus;
}
if ((vEffectiveDate).length > 0 ){
if (!isDate(vEffectiveDate) ){
vMsg = vMsg+"[Effective Date] Is not a date." + ";\r\n";
} else if ( moment( toDate(vEffectiveDate)).isBefore(toDate(vLastRunDate)) ){
vMsg = vMsg+"[Effective Date] cannot be on/before "+vLastRunDate+"." + ";\r\n";
}
}
if (sStatus.toUpperCase() != "SUCCESFUL") {
$.confirm({
title: "Confirmation",
columnClass: 'col-md-6 col-md-offset-3',
content:"Forecast calculation still busy. Results might not be accurate. Continue?",
buttons: {
confirm: function() {
sResponse= "1";
vMsg = "Response 1";
processMessage(); // <--- added this
},
cancel: function() {
sResponse= "2";
vMsg = "Response 2";
// Moved code here, as it needs to execute when Cancel is clicked
$.alert({
title: "INFORMATION",
columnClass: 'col-md-6 col-md-offset-3',
content: "Screen will refresh. Please click on Update to try again.",
// Code that should execute when alert is closed:
onAction: function () {
document.getElementById("Smoke.Calc Status").value= "REFRESH";
msErr = "ABORT";
processMessage(); // <--- added this
}
});
},
}
});
} else { // <-- added
processMessage();
}
function processMessage() {
// Moved code in a function, as it should only execute after confirm/alert is closed
if (vMsg != "") {
$.alert({
title: 'Validation Message',
columnClass: 'col-md-6 col-md-offset-3',
content: vMsg,
});
msErr = "ERROR";
}
}
}
//#################
return msErr;
}
So I think my problem lies with msErr being returned long before the user has had chance to decide which button on the confirm dialog to choose. If I don't set breakpoints and click on the confirm's cancel then I do see that the alerts are shown, but the page is not refreshed (document.getElementById("Smoke.Calc Status").value= "REFRESH";) and the next page is shown. I think this comes from the sErr == "" in the toolbar.asp file and then the program flow just continues.
Anycase, I tried using callbacks, but the situation hasn't changed.
Here is what I tried to do:
parentMain.dovalidation(function(result){
if (result != ""){
return;
}
});
In main.asp the dovalidation function:
function dovalidation(callback) {
msErr = "";
//#################
if (msErr.length == 0) {
var vEffectiveDate="";
var vLastRunDate="";
var sStatus="";
var vMsg="";
var sResponse="";
vEffectiveDate = document.getElementById("Smoke.Effective Date").value;
vLastRunDate = document.getElementById("Smoke.Last Run Date").value;
sStatus = document.getElementById("Smoke.Calc Status").value;
vMsg = "";
if ((sStatus).length == 0 ){
sStatus = "SUCCESFUL";
document.getElementById("Smoke.Calc Status").value= sStatus;
}
if ((vEffectiveDate).length > 0 ){
if (!isDate(vEffectiveDate) ){
vMsg = vMsg+"[Effective Date] Is not a date." + ";\r\n";
} else if ( moment( toDate(vEffectiveDate)).isBefore(toDate(vLastRunDate)) ){
vMsg = vMsg+"[Effective Date] cannot be on/before "+vLastRunDate+"." + ";\r\n";
}
}
if (sStatus.toUpperCase() != "SUCCESFUL") {
$.confirm({
title: "Confirmation",
columnClass: 'col-md-6 col-md-offset-3',
content:"Forecast calculation still busy. Results might not be accurate. Continue?",
buttons: {
confirm: function() {
sResponse= 1;
vMsg = "Response 1";
processMessage(); // <--- added this
},
cancel: function() {
sResponse= 2;
vMsg = "Response 2";
// Moved code here, as it needs to execute when Cancel is clicked
$.alert({
title: "INFORMATION",
columnClass: 'col-md-6 col-md-offset-3',
content: "Screen will refresh. Please click on Update to try again.",
// Code that should execute when alert is closed:
onAction: function () {
document.getElementById("Smoke.Calc Status").value= "REFRESH";
msErr = "ABORT";
processMessage(); // <--- added this
}
});
},
}
});
} else { // <-- added
processMessage();
}
function processMessage() {
// Moved code in a function, as it should only execute after confirm/alert is closed
if (vMsg != "") {
$.alert({
title: 'Validation Message',
columnClass: 'col-md-6 col-md-offset-3',
content: vMsg,
});
msErr = "ERROR";
}
}
}
//#################
callback(msErr);
}
So, it isn't working as it should, and I don't know what I've done wrong, but I suppose I haven't used the callbacks correctly.
Does it make a difference that it is in two different files?
This has to work given that the parts between the //######### are swopped.
I would appreciate any feedback and guidance.
Well, ok. I started to write a comment, but it is too small for all I need to say, so I will continue in an answer.
I see you added some more code, but... you know, your code are blowing my mind =) what you are trying to do is to get spaghetti-code and make it async.
Throw this away and let's go to the upper level. We call it "program on an interface level".
Example: replace all your big code blocks with functions (with correct name). Like code between //################# will be converted to
function dovalidation(callback) {
msErr = getValidationResult();
callback(msErr);
}
What do I do here is just throw away all your low-level code, because your problem is in the order of execution.
But this is just the first step of converting your code to something other. Next step is realizing that our "virtual" function getValidationResult has some $.confirm and $.alert inside, so it is async. That's why we need to use getValidationResult as async. Two possible ways - convert to Promise or use callbacks. Let's use callbacks. Then our simplified code will convert to:
function dovalidation(callback) {
getValidationResult(callback);
}
That is what I was trying to show on my previous answer.
And now you've added some more code with such a comment: //Then the rest of the code which is executed irrespective of the jquery confirm.. Ok, good, we will name all this code as theRestOfTheCodeIrrespectiveToConfirm(). So your original function call will be converted from this:
//... Other code that determines variables, fringe scenarios etc.
//...
// Then dovalidation() (in which the blocking MsgBox used to be) is called:
var sErr = parentMain.dovalidation();
if (sErr != ""){
return;
}
theRestOfTheCodeIrrespectiveToConfirm();
to this:
//... Other code that determines variables, fringe scenarios etc.
//...
// Then dovalidation() (in which the blocking MsgBox used to be) is called:
parentMain.dovalidation(sErr => {
if (sErr != ""){
return;
}
theRestOfTheCodeIrrespectiveToConfirm(); // <- this will execute only if sErr is empty
});
Am I going to the right direction?
P.S. One thing to ask you - why do you have } in the end of you example code, but no correcsponding {?
Maybe you show us not all code?
There is no need to involve jQuery.
JavaScript has the functions alert, confirm and prompt which are synchronous (i.e. blocking execution until they return) just like MsgBox was.
So if this is easier to you, you can keep your original code structure this way. Because as it was explained in the "how do I return a response from an asynchronous call" article, you cannot make the dovalidation function return anything that depends on the result of an asynchronous operation like the $.confirm that you currently use, since that would require time travel ;) - but you can make it dependent on synchronous operations like JS' built-in confirm.
You are mixing sync code with async... Because $.confirm is async, but you call dovalidation as sync.
Note: your code is very strange, maybe because of small JS experience, so I will try to guess what you need. You can ask if something in my code is incorrect.
Your second example with callback is more close to working solution, but you need to call the callback only if you know the user's answer.
So let's change your code a bit. Extract processMessage function (now it is async) and call processMessage with correct parameters:
function processMessage(vMsg, msErr, callback) {
// Moved code in a function, as it should only execute after confirm/alert is closed
if (vMsg) {
$.alert({
title: 'Validation Message',
columnClass: 'col-md-6 col-md-offset-3',
content: vMsg,
});
msErr = "ERROR";
}
callback(msErr); // <-- return result
}
function dovalidation(callback) {
var vMsg = "", msErr = "";
//#################
if (msErr.length == 0) {
var vEffectiveDate = "";
var vLastRunDate = "";
var sStatus = "";
var sResponse = "";
vEffectiveDate = document.getElementById("Smoke.Effective Date").value;
vLastRunDate = document.getElementById("Smoke.Last Run Date").value;
sStatus = document.getElementById("Smoke.Calc Status").value;
vMsg = "";
if ((sStatus).length == 0) {
sStatus = "SUCCESFUL";
document.getElementById("Smoke.Calc Status").value = sStatus;
}
if ((vEffectiveDate).length > 0) {
if (!isDate(vEffectiveDate)) {
vMsg = vMsg + "[Effective Date] Is not a date." + ";\r\n";
} else if (moment(toDate(vEffectiveDate)).isBefore(toDate(vLastRunDate))) {
vMsg = vMsg + "[Effective Date] cannot be on/before " + vLastRunDate + "." + ";\r\n";
}
}
if (sStatus.toUpperCase() != "SUCCESFUL") {
$.confirm({
title: "Confirmation",
columnClass: 'col-md-6 col-md-offset-3',
content: "Forecast calculation still busy. Results might not be accurate. Continue?",
buttons: {
confirm: function() {
sResponse = 1;
vMsg = "Response 1";
processMessage(vMsg, msErr, callback); // <--- added this
},
cancel: function() {
sResponse = 2;
vMsg = "Response 2";
// Moved code here, as it needs to execute when Cancel is clicked
$.alert({
title: "INFORMATION",
columnClass: 'col-md-6 col-md-offset-3',
content: "Screen will refresh. Please click on Update to try again.",
// Code that should execute when alert is closed:
onAction: function() {
document.getElementById("Smoke.Calc Status").value = "REFRESH";
msErr = "ABORT";
processMessage(vMsg, msErr, callback); // <--- added this
}
});
},
}
});
} else { // <-- added
processMessage(vMsg, msErr, callback); // <--- added this
}
}
//#################
}
Note: this code is not "clean". If this function dovalidation you showed us has full code, then you can clean the code. This code is runnable (better use it in fullscreen), but I still do not understand what you are trying to do...
const isDate = (x) => true; // Mock
const toDate = (x) => x; // Mock
function processMessage(mType, vMsg, msErr, callback, sResponse) {
if (vMsg) {
$.alert({
title: 'Validation Message',
columnClass: 'col-md-6 col-md-offset-3',
content: vMsg,
});
msErr = "ERROR"; // ???
}
callback(mType, msErr, vMsg, sResponse); // <-- return result
}
function dovalidation(callback) {
var sResponse = "";
let vMsg = '';
let msErr = '';
let vEffectiveDate = document.getElementById("Smoke.Effective Date").value;
let vLastRunDate = document.getElementById("Smoke.Last Run Date").value;
let sStatus = document.getElementById("Smoke.Calc Status").value;
if (!sStatus) {
sStatus = "SUCCESFUL";
document.getElementById("Smoke.Calc Status").value = sStatus;
}
if (vEffectiveDate) {
if (!isDate(vEffectiveDate)) {
vMsg = vMsg + "[Effective Date] Is not a date.;\r\n";
} else if (moment(toDate(vEffectiveDate)).isBefore(toDate(vLastRunDate))) {
vMsg = vMsg + "[Effective Date] cannot be on/before " + vLastRunDate + ".;\r\n";
}
}
if (sStatus.toUpperCase() != "SUCCESFUL") {
$.confirm({
title: "Confirmation",
columnClass: 'col-md-6 col-md-offset-3',
content: "Forecast calculation still busy. Results might not be accurate. Continue?",
buttons: {
confirm: () => {
sResponse = 1;
vMsg = "Response 1";
processMessage('Confirm', vMsg, msErr, callback, sResponse);
},
cancel: function() {
sResponse = 2;
vMsg = "Response 2";
$.alert({
title: "INFORMATION",
columnClass: 'col-md-6 col-md-offset-3',
content: "Screen will refresh. Please click on Update to try again.",
// Code that should execute when alert is closed:
onAction: () => {
document.getElementById("Smoke.Calc Status").value = "REFRESH";
msErr = "ABORT";
processMessage('Abort', vMsg, msErr, callback, sResponse);
}
});
},
}
});
} else {
processMessage('Success', vMsg, msErr, callback, sResponse);
}
}
function test() {
dovalidation(function(mType, msErr, vMsg, sResponse) {
console.log('[TYPE]', mType, '[RESULT]', msErr || '?', '[MESSAGE]', vMsg || '?');
//if (result != "") {
// return;
//}
});
}
<script src="https://momentjs.com/downloads/moment.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/jquery-confirm/3.3.2/jquery-confirm.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-confirm/3.3.2/jquery-confirm.min.js"></script>
<input type="text" id="Smoke.Effective Date" value="2020-01-02"/>
<input type="text" id="Smoke.Last Run Date" value="2020-01-01"/>
<input type="text" id="Smoke.Calc Status" value="WTF?"/>
<button onclick="test()">TEST</button>
The idea is to allow me to press a button on the HTML page to execute a command to copy and delete all photos on cameras with feedback showing at the beginning and ending of the execution.
At the moment, after clicking the "Get Images From Camera", the textarea is showing this text:
Executed command: \copyImages
Result is as below: Copying images from
both cameras...\n
And it goes on to copy and delete all images like I want. But at the end of this process, nothing is returned back to the screen, so the user has no idea what happens. The nature of callback in Node js makes it too confusing for me to figure out how to do this.
P.S. I've tried all I know before I come here to get your help. So know that any suggestions are very appreciated!
So, my question is how do I change the codes below so that I could
display a message to show the user that the copying is completed successfully like:
Please wait for the copying to complete...
Completed!
Below are the HTML markups
<button id="copyImages" type="button" class="button">Get Images From Camera</button>
<textarea id="output" readonly></textarea>
Here is the Javascript event handling:
copyImages.onclick = function() {
dest = '/copyImages';
writeToOutput(dest);
}
function writeToOutput(dest) {
$.get(dest, null, function(data) {
resultText += "Executed command: "+dest+"\n"
+"Result is as below: \n"+data;
$("#output").val(resultText);
}, "text");
return true;
}
These functions below are for setting up a Node App server using express module to listen to anything the HTML page passes to it. They are run on a different device.
expressServer.listen( expressPort, function() {
console.log('expressServer listening at *:%d', expressPort );
});
// allow CORS on the express server
expressServer.use(function(req, res, next) {
// enable cross original resource sharing to allow html page to access commands
res.header("Access-Control-Allow-Origin", "*");
// return to the console the URL that is being accesssed, leaving for clarity
console.log("\n"+req.url);
next();
});
expressServer.get('/copyImages', function (req, res) {
// user accesses /copyImages and the copyImages function is called
copyImages(function(result) {
res.end(result + "\n");
});
});
Copy images from Theta S Camera to Raspberry Pi and delete those from the cameras
var resultCopyImages = "";
copyImages = function (callback) {
resultCopyImages = "Copying images from both cameras...\n";
for (var i = 0; i < camArray.length; i++) {
copyOneCamImages(i, callback);
}
return (callback(resultCopyImages));
//how to return multiple messages?
}
copyOneCamImages = function (camID, callback) {
d.on('error', function(err){
console.log('There was an error copying the images');
return(callback('There was an error running a function, please make sure all cameras are connected and restart the server'));
})
d.run(function(){
var imageFolder = baseImageFolder + camID;
// if the directory does not exist, make it
if (!fs.existsSync(imageFolder)) {
fs.mkdirSync(imageFolder);
console.log("no 'images' folder found, so a new one has been created!");
}
// initialise total images, approximate time
var totalImages = 0;
var approxTime = 0;
// get the first image and do not include thumbnail
var entryCount = 1;
var includeThumb = false;
var filename;
var fileuri;
// get the total amount of images
camArray[camID].oscClient.listImages(entryCount, includeThumb)
.then(function (res) {
totalImages = res.results.totalEntries;
approxTime = totalImages * 5;
resultCopyImages = '';
resultCopyImages = 'Camera ' + (camID + 1) + ': Copying a total of: ' + totalImages + ' images'
+ '\nTo folder: ' + imageFolder
+ '\nThis process will take approximately: ' + approxTime + ' seconds \n';
console.log(resultCopyImages);
callback(resultCopyImages);
});
// copy a single image, with the same name and put it in images folder
camArray[camID].oscClient.listImages(entryCount, includeThumb)
.then(function (res) {
filename = imageFolder + '/' + res.results.entries[0].name;
fileuri = res.results.entries[0].uri;
imagesLeft = res.results.totalEntries;
// gets the image data
camArray[camID].oscClient.getImage(res.results.entries[0].uri)
.then(function (res) {
var imgData = res;
fs.writeFile(filename, imgData);
camArray[camID].oscClient.delete(fileuri).then(function () {
if (imagesLeft != 0) {
// callback to itself to continue copying if images are left
callback(copyOneCamImages(camID, callback));
//????????????????????????????????????????????????????????????????????????????
//if(imagesLeft==1) return(callback("Finished copying"));
}/* else {
resultCopyImages = "Finshed copying image.\n";
console.log(resultCopyImages);
}
else if
return(callback(resultCopyImages));
}*/
});
});
});
})
}
So far there is no real answer to the question I asked so we have concluded the project and skipped the feature. However, it's just the matter of mastering the REST API and the asynchronous functions in NodeJs. The project is expected to continue for a next version sometime next year.
I am trying to explicitly get the system properties from my table but it is not working. I can see that the URL is returning all the data including these fields if I use https://myservice.azure-mobile.net/tables/todoitem?__systemProperties=* but on the code I cannot get it as item.__version or item.version. I have tried adding todoitemtable = WindowsAzure.MobileServiceTable.SystemProperties.All; but no success! I have also looked at http://azure.microsoft.com/en-us/documentation/articles/mobile-services-html-validate-modify-data-server-scripts/ but this is adding a new column instead of using the existing system columns.
$(function() {
var client = new WindowsAzure.MobileServiceClient('https://ib-svc-01.azure-mobile.net/', 'key');
var todoItemTable = client.getTable('todoitem');
// = WindowsAzure.MobileServiceTable.SystemProperties.All;
// Read current data and rebuild UI.
// If you plan to generate complex UIs like this, consider using a JavaScript templating library.
function refreshTodoItems() {
var query = todoItemTable.where({ complete: false });
query.read().then(function(todoItems) {
var listItems = $.map(todoItems, function(item) {
return $('<li>')
.attr('data-todoitem-id', item.id)
.append($('<button class="item-delete">Delete</button>'))
.append($('<input type="checkbox" class="item-complete">').prop('checked', item.complete))
.append($('<div>').append($('<input class="item-text">').val(item.id))
.append($('<span class="timestamp">'
+ (item.createdAt && item.createdAt.toDateString() + ' '
+ item.createdAt.toLocaleTimeString() || '')
+ '</span>')));
});
$('#todo-items').empty().append(listItems).toggle(listItems.length > 0);
$('#summary').html('<strong>' + todoItems.length + '</strong> item(s)');
}, handleError);
}
function handleError(error) {
var text = error + (error.request ? ' - ' + error.request.status : '');
$('#errorlog').append($('<li>').text(text));
}
function getTodoItemId(formElement) {
return $(formElement).closest('li').attr('data-todoitem-id');
}
// Handle insert
$('#add-item').submit(function(evt) {
var textbox = $('#new-item-text'),
itemText = textbox.val();
if (itemText !== '') {
todoItemTable.insert({ text: itemText, complete: false }).then(refreshTodoItems, handleError);
}
textbox.val('').focus();
evt.preventDefault();
});
// Handle update
$(document.body).on('change', '.item-text', function() {
var newText = $(this).val();
todoItemTable.update({ id: getTodoItemId(this), text: newText }).then(null, handleError);
});
$(document.body).on('change', '.item-complete', function() {
var isComplete = $(this).prop('checked');
todoItemTable.update({ id: getTodoItemId(this), complete: isComplete }).then(refreshTodoItems, handleError);
});
// Handle delete
$(document.body).on('click', '.item-delete', function () {
todoItemTable.del({ id: getTodoItemId(this) }).then(refreshTodoItems, handleError);
});
// On initial load, start by fetching the current data
refreshTodoItems();
});
I was trying to access the system properties from within the API scripts and found this and thought it was useful and relevant: http://www.brandonmartinez.com/2014/10/22/retrieve-system-properties-in-azure-mobile-services-javascript-backend/
Basically you can do this (example from the post):
myTable.read({
systemProperties: ['__createdAt', '__updatedAt'],
success: function(tableEntries) {
// So on and so forth
}
}
I am stuck with Social login (Facebook, Google and twitter) through Phonegap.
I have googled and found so many solutions, but they don't work on either platform (i.e: android or iOS).
Does any one have implemented social login in his/her app using phonegap?
If any one could provide me the running code, that would be appreciated.
Thanks,
Sabir
I know it's probably late to answer your particular question but I have had the same issue - all of the current (September 2016) scripts, snippets and libraries for social login in PhoneGap/Cordova that I have tried did not work so I made some simple functions from scratch which may still be useful to people ending up here. You can use them to log the user in with LinkedIn, Facebook and Google(+). I have also made some simple functions that retrieve some basic user information from the access token that is returned by logging the user in with the given network. You can examine the functions but they usually save the token or/and the user data to localStorage for later usage. They have been tested in September 2016 and work perfectly. I hope that this would help other people who also land on failing snippets around the web.
You can just insert the code and use the functions whenever you want. It requires jQuery and PhoneGap's InAppBrowser (besides having made apps/clients in the social media in order to fill the app id and app secret).
As a side note, it is not the best move to store the client secret directly in the PhoneGap application as the source can be viewed by malevolent people.
The code can be refactored at many places, so feel free to do that, but it does the trick. You may also have to handle cases where the user cancels the login process.
var facebookLogin = function(appId, appSecret, successCb,errCb) {
/*$.get("https://graph.facebook.com/oauth/access_token?client_id=" + appId + "&client_secret=" +appSecret + "&grant_type=client_credentials", function(res) {
if (res.indexOf("access_token=") !== -1) {
successCb(res.replace("access_token=", "").trim());
}
else {
errCb(res);
}
})
*/
var ref = window.open("https://www.facebook.com/dialog/oauth?display=popup&response_type=token&client_id="+appId+"&redirect_uri="+"http://anyurlhere.com", "_blank", "location=no");
ref.addEventListener("loadstop", function(evt) {
if (evt.url.indexOf("anyurlhere.com") !== -1) {
if (evt.url.indexOf("#access_token") !== -1) {
localStorage.fbToken = evt.url.split("#access_token=")[1];
ref.close();
ref.addEventListener("exit", function() {
successCb(localStorage.fbToken);
})
}
}
})
}
var linkedinLogin = function(appId,appSecret,successCb,errCb) {
var ref = window.open("https://www.linkedin.com/oauth/v2/authorization?response_type=code&client_id="+appId+"&redirect_uri="+(encodeURI("http://anyurlhere.com"))+"&state=987654321&scope=r_basicprofile", "_blank", "location=no");
ref.addEventListener("loadstop", function(evt) {
if (evt.url.indexOf("anyurlhere.com") !== -1) {
if (evt.url.indexOf("code=") !== -1) {
var code = evt.url.split("code=")[1];
code = code.split("&")[0];
//TODO: get actual token to access user profile
$.post("https://www.linkedin.com/oauth/v2/accessToken", {"grant_type": "authorization_code", "code": code, "redirect_uri":encodeURI("http://anyurlhere.com"), "client_id":appId,"client_secret":appSecret}, function(data) {
for (key in data) {
if (key == 'access_token') {
localStorage.linkedinToken = data[key];
ref.close();
ref.addEventListener("exit", function() {
successCb(localStorage.linkedinToken);
})
}
}
})
}
}
})
}
var googleLogin = function(appId, appSecret, successCb, errCb) {
var ref = window.open("https://accounts.google.com/o/oauth2/v2/auth?response_type=token&client_id=" + appId + "&redirect_uri="+encodeURI("http://anyurlhere.com")+"&scope="+encodeURIComponent("email profile")+"&state=profile", "_blank", "location=no");
ref.addEventListener("loadstop", function(evt) {
if (evt.url.indexOf("anyurlhere.com") !== -1) {
if (evt.url.indexOf("access_token=") !== -1) {
var accessToken = evt.url.split("access_token=")[1];
accessToken = accessToken.split("&")[0];
localStorage.gToken = accessToken;
ref.close();
ref.addEventListener("exit", function() {
successCb(localStorage.gToken);
})
}
}
})
}
var getGoogleInfo = function(successCb, errCb) {
//get basic user profile
$.get("https://www.googleapis.com/oauth2/v1/userinfo?alt=json&access_token=" + localStorage.gToken, function(userInfo) {
successCb(userInfo);
})
}
var getFacebookInfo = function(successCb, errCb) {
//get basic user profile-name
$.get("https://graph.facebook.com/me?fields=email,name,picture&access_token=" + localStorage.fbToken, function(userInfo) {
var myInfo = {};
if (userInfo.name) {
myInfo.name = userInfo.name;
}
if (userInfo.email) {
myInfo.email = userinfo.email;
}
if (userInfo.picture) {
myInfo.picture = userInfo.picture.data.url;
}
localStorage.myInfo = JSON.stringify(myInfo);
successCb(myInfo);
// localStorage.myInfo = myInfo;
})
}
//get basic data for linked in
var getLinkedinInfo = function(successCb, errCb) {
$.ajax({
url: "https://api.linkedin.com/v1/people/~?format=json",
headers: {
"Authorization": "Bearer " + localStorage.linkedinToken
},
success: function(userInfo) {
var myInfo = {};
if (userInfo.firstName && userInfo.lastName) {
myInfo.name = userInfo.firstName + " " + userInfo.lastName;
}
if (userInfo.headline) {
myInfo.linkedinHeadline = userInfo.headline;
}
localStorage.myInfo = JSON.stringify(myInfo);
successCb(myInfo);
},
fail: function(err) {
alert(err);
for (key in err) {
alert(key);
alert(err[key]);
}
}
})
}
//example of logging in the user with Google + and getting his/her data
googleLogin("93-54932-423-fkfew.apps.googleusercontent.com", "", function(accessToken) {
getGoogleInfo(function(userInfo) {
var myInfo = {};
alert(userInfo.name);
if (userInfo.email) {
myInfo.email = userInfo.email;
}
if (userInfo.name) {
myInfo.name = userInfo.name;
}
if (userInfo.given_name) {
myInfo.firstName = userInfo.given_name;
}
if (userInfo.familyName) {
myInfo.familyName = userInfo.family_name;
}
if (userInfo.picture) {
myInfo.picture = userInfo.picture;
}
I have a enhancegrid which fields are editable. Some fields will be changed manually while others will change dynamically. The thing is that I would not want to store values onApplyEdit, I want to force save the values but I cant find the solution for it. Here is how it´s right now which actually working great.
dojo.connect(grid, "onApplyEdit", grid, function(evt, rowIndx, fieldIndx){
var selected_item = grid.getItem(evt);
//Not sure if this is the most efficient way but it worked for me
var row_id = grid.store.getValue(selected_item, "row_id");
var from_item_no = grid.store.getValue(selected_item, "from_item_no");
var from_inventory = grid.store.getValue(selected_item, "from_inventory");
var comment = grid.store.getValue(selected_item, "comment");
edit_quantity = grid.store.getValue(selected_item, "quantity");
var to_item_no = grid.store.getValue(selected_item, "to_item_no");
var to_inventory = grid.store.getValue(selected_item, "to_inventory");
var foundation = grid.store.getValue(selected_item, "foundation");
// Instantiate some write implementing store.
var store = grid.store;
// Set our load completed handler up...
var onCompleteFetch = function(items, request){
// Define the save callbacks to use
var onSave = function(){
dojo.xhrPost({
url: url,
content: {
row_id: row_id,
from_item_no: from_item_no,
from_inventory: from_inventory,
comment: comment,
quantity: edit_quantity,
to_item_no: to_item_no,
to_inventory: to_inventory,
foundation: foundation
},
handleAs: "text",
load: function(data){
console.log("Returned value: " + data);
global_saved_check = false;
},
error: function(error){
alert(error);
}
});// end xhrPost
} // end onSave
var onSaveError = function(error){
alert("Error occurred: " + error);
}
// If the store has modified items (it should), call save with the handlers above.
if(store.isDirty()){
store.save({onComplete: onSave, onError: onSaveError});
}
} // end onCompleteFetch
// Define a fetch error handler, just in case.
var onFetchError = function(error, request){
alert("Fetch failed. " + error);
}
// Fetch some data... All items with a foo attribute, any value.
store.fetch({query: {foo:"*"}, onComplete: onCompleteFetch});
});//end connect onApplyEdit