Stop event from continuing on deferred.reject - javascript

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(...).

Related

EnableRule in ribbon cannot get boolean value from async function

I want to return true or false from the function showHideAddNewButton. I have an EnableRule in a ribbon button, which calls a custom rule that calls this function showHideAddNewButton. On passing either true, which will show the button, or false, which will hide the button.
I have to access statuscode (Status Reason) and statecode (Status) fields on the entity. I have created a query using the Xrm.WebApi.retrieveMultipleRecords, but cannot get it to return a flag. I want the retrieveMultipleRecords method to only execute on and never be called again but 'return true' below gets executed setting my button to true always.
function showHideAddNewCsrsRecalculation(primaryControl){
var fileNumber = primaryControl.getAttribute("ssg_filenumber").getValue();
Xrm.WebApi.retrieveMultipleRecords("rrg_csrsfile", "?$select=statuscode,statecode,rr_filenumber&$filter=rr_filenumber eq '" + fileNumber + "'").then(
function success(result) {
for (var i = 0; i < result.entities.length; i++) {
var statusCode = result.entities[i].statecode;
var statusReasonCode = result.entities[i].statuscode;
//if draft make button invisible
if (statusReasonCode == 8676725)
return false;
//if submitted make button invisible
if (statusReasonCode == 8676726)
return false;
//if inactive make button invisible
if (statusCode == 1)
return false;
}
},
function (error) {
console.log(error.message);
// handle error conditions
}
);
//if draft make button invisible
//if (primaryControl.getAttribute("statuscode").getValue() == 867670025)
// return false;
//if submitted make button invisible
//if (primaryControl.getAttribute("statuscode").getValue() == 867670026)
// return false;
//if inactive make button invisible
//if (primaryControl.getAttribute("statecode") != 'undefined' && primaryControl.getAttribute("statecode").getValue() == 1)
// return false;
//other options make button visible
return true; --> This keeps getting called as a result my button is always visible
}
Function Xrm.WebApi.retrieveMultipleRecords returns a promise, not an actual boolean value. The function is executed asynchronously, so immediately after the call to this function the next line is executed and that line always returns true.
In fact it is not possible to make an asynchronous call synchronous. Instead we can follow another approach by following these steps:
Do the query in the form's onload function and store the result in a variable.
Refresh the ribbon.
Create a ribbon button handler returning the variable's value.
let isRecalculationButtonVisible = false;
function onLoad(context) {
const formContext = context.getFormContext();
const filter = "$filter=rr_filenumber eq '"
+ formContext.getAttribute("ssg_filenumber").getValue()
+ "' and (statecode eq 1 or statuscode eq 8676725 or statuscode eq 8676726)";
Xrm.WebApi.retrieveMultipleRecords("rrg_csrsfile", "?$select=rrg_csrsfileid&$top=1&" + filter)
.then(function (result) {
isRecalculationButtonVisible = result.entities.length === 0;
})
.catch(function (error) {
console.log(error.message);
})
.finally(() => {
formContext.ui.refreshRibbon(false);
});
}
function showHideAddNewCsrsRecalculation() {
return isRecalculationButtonVisible;
}
As you probably already noticed I added a few improvements.
All your button needs to know is if there are any records meeting specific conditions, so there is no need to actually retrieve them. Therefore these conditions can simply be placed in the query's filter. I also added a $top=1, because the number of records meeting the conditions is not relevant here. As a consequence the only check that needs to be done is whether a record is returned or not.
As explained, retrieveMultipleRecords returns a promise. The recommended error handling for promises is adding a catch function at the end of the chain.

How to control the visibility to Cesium/CZML DataSources

I have a CZML datasource declared:
public geometryDataPromise: Cesium.CzmlDataSource;
I am loading it with an endpoint above when a component is loaded:
if(!this.store.geometryDataPromise) {
this.store.geometryDataPromise = Cesium.CzmlDataSource.load(environment.apiBaseURL + `/protectedareas/geometry/all`);
}
all the rendered objects are displayed to terrain but trying to follow the instructions by doing:
this.store.geometryDataPromise.show = false;
the objects are not being hidden
The problem here is that Cesium.CzmlDataSource.load does not return a Cesium.CzmlDataSource. It returns a Promise to asynchronously go get a CzmlDataSource, and that's not the same thing at all. Your code is trying to show or hide the promise, that's not a thing that gets displayed.
var dataSourcePromise = Cesium.CzmlDataSource.load( ... );
var dataSource = null;
dataSourcePromise.then(function(d) { dataSource = d; });
Note that after the above code runs, dataSource will be null for some time while the browser waits for the server's response to finish downloading. Once the callback function fires, the dataSource is ready.
function onClick() {
if (dataSource !== null) {
dataSource.show = !dataSource.show;
}
}
You can wire up a click handler for a toggle button like this. But the toggle won't do anything until after the dataSource is downloaded and ready.
First I have to take the result of the Cesium.CzmlDataSource.load promise
Cesium.when(Cesium.CzmlDataSource.load(environment.apiBaseURL + `/protectedareas/geometry/all`), result => {
this.sources = result;
this.viewer.dataSources.add(this.sources);
});
and then just change it's fiend show when the visibility changed
this.store.sourceVisibility.subscribe(visibility=>this.sources.show=visibility);

Web BLE Characteristic startNotifications sometimes doesn't bind

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);
}

Javascript cursor: sequence of events

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();
},
});
}

How to prevent/detect race condition between processing and restoring store data when waking up an Event page

I am using sendMessage and onMessage listener (both in background and content pages), and I am seeing some messages are getting lost. I have a few "global" variables that I store everytime I go to suspend state, and restore them among the first things when the script starts (I register the handlers first). However, given that the chrome.storage is asynchronous, I suspect message processing is happening before I get to load the global state (and hence the appearance of losing the messages).
Following is the relevant piece of code.
# Register some important listeners.
chrome.alarms.onAlarm.addListener(onAlarm);
chrome.runtime.onMessage.addListener(onMessage);
# Define global variables.
var tabIdList = new Array();
var keepAlives = new Array();
keepAlives["myTab1"] = -1;
keepAlives["myTab2"] = -1;
tabIdList["myTab1"] = -1;
tabIdList["myTab2"] = -1;
# Reload previously stored state of global variables...
reloadGlobalVariable();
# Handle received messages, anytime a message is received,
# set keepAlive for the tab that sends the message.
#
function onMessage(msg, sender) {
if (sender.tab) {
if (msg.message === "hello") {
recordNewTab(msg.tabName, sender.tab.id);
}
keepAlive(msg.tabName, sender.tab.id);
}
function recordNewTab(tabName, tabId) {
tabIdList[tabName] = tabId;
}
function keepAlive(tabName, tabId) {
if (tabIdList[tabName] == tabId) {
keepAlives[tabName] = 1;
}
}
chrome.runtime.onSuspend.addListener(function() {
storeGlobalState();
});
function onAlarm(alarm) {
for (var key in tabIdList) {
if (tabIdList[key] != -1) {
if (keepAlives[key] == -2) {
removeTabRecord(key);
} else {
--keepAlives[key];
sendMessage(key, "ping"); // content pages respond to this message
}
}
}
storeGlobalState(); // probably unnecessary.
}
How can I make sure that onAlarm only continues processing if the global variables have been reloaded?
I use chrome.storage.local.set/get which are asynchronous.
Original question to get debugging hints about suspended/wake states here...
How to debug background/event page suspended state
Well, you can't do anything about async nature of Event page processing and Chrome Storage API. And there's no "delaying until" in async JS.
Therefore, you'll need to make do with callbacks. This should work:
var globalsReady = false;
chrome.foo.onBar.addListener(handler);
function handler(a, b, c) {
restoreGlobals(function() {
/* Do actual handling using a, b, c */
});
// Special note for onMessage: if you are sending a reply asynchronously,
// you'll need to return true; here
}
function restoreGlobals(callback) {
if(!globalsReady) {
chrome.storage.local.get(/*...*/, function(data) {
/* restore globals here */
globalsReady = true;
if(typeof callback == "function") callback();
});
} else {
// Already done restoring
if(typeof callback == "function") callback();
}
}
restoreGlobals();

Categories