Javascript: Stop Code Execution with Conditional Statement in Nested Functions - javascript

I want to validate some values with javascript and the though process is like this:
I fire an event when user submits form
Javascript gets the values and validates them. If they are correct continue with number three, if not, return abortively.
Make ajax request
Here is my submit function:
submitLoginForm: function (event) {
event.preventDefault();
// get the values
var object = this._getFormValues();
// make ajax request
this.querySelector('#postLoginForm').body = object;
this.querySelector('#postLoginForm').generateRequest();
},
This is the function that grabs the values
_getFormValues: function () {
var email = this.querySelector('#email').value;
var password = this.querySelector('#password').value;
var remember = this.querySelector('#remember').value;
this._validate(email, password);
return { 'email': email, 'password': password, 'remember': remember };
}
And this is the function that validates the fields or returns
_validate: function (email, password) {
if (email === '' || password === '') {
var elements = this.querySelectorAll('.form-group');
for (var i = 0; i < elements.length; i++) {
elements[i].className += ' has-error';
};
this.hasError = true;
this.errorMessage = 'Email and Password are required fields.'
return;
}
},
Till now I used early return like this:
if ('email' === '') {
return;
}
makeAjaxRequest();
But since I have nested functions, I wonder how to abort the process completely, if the fields don't validate.
Something like an exit() function would be nice.

A better approach would be to just decouple some of your nesting. Prefer returning a bool to make control flow like this easy. So, just moving some of your code around, I would do this:
Your submit function:
submitLoginForm: function (event) {
event.preventDefault();
// get the values
var object = this._getFormValues();
if(this._validates(object)) {
// make ajax request
this.querySelector('#postLoginForm').body = object;
this.querySelector('#postLoginForm').generateRequest();
} else {
// moved this from validation
var elements = this.querySelectorAll('.form-group');
for (var i = 0; i < elements.length; i++) {
elements[i].className += ' has-error';
};
}
},
get form values
_getFormValues: function () {
var email = this.querySelector('#email').value;
var password = this.querySelector('#password').value;
var remember = this.querySelector('#remember').value;
return {
'email': email,
'password': password,
'remember': remember
};
}
Your validator:
_validate: function (values) {
var email = values.email
var passw = values.password
if (email === '' || password === '') {
return false
} else {
return true
}
},
I think I got everything - but it's a starting point anyway. Decouple each function from other tasks and it makes everything much easier.

I think an improved design here would be a better approach. Why does your _getFormValues do any validation? If you decouple the two functions your code is both more clear and you can easily "early return". I would 1. Save what is returned from _getFromValues into a variable 2. Query that Object for empty values ('email === '')3. Then if appropriate fields are filled in validate then do ajax call. If not, notify the user and DO NOT validate or ajax.

I would have a look at some Promises implementations.
jQuery $.Deferred() object or Q implementations. It also works great if your nested functions must run asynchronously.
//quick example
submitLoginForm: function (event) {
var data = _getFormValues();
_submit(data).then(
function () {
//SUCCESS
//update UI accordantly
},
function (error) {
//FAIL
console.log(error);
}
);
}
_submit: function (data) {
var $q = new $.Deferred();
//AT ANY POINT, run $q.resolve() if you've got a success;
//OTHERWISE, run $q.reject() to trigger a failure.
if (_validate() == false) {
$q.reject("validation failed");
}
makeAjax("/api/foo", function () {
//ajax success
$q.resolve(jsonFromServer);
})
return deferredObj.promise();
}
Your function _submit() will return a promise object that will then run a success or fail function depending on if you run a resolve or reject call. This is great for sync or async with several nested functions.

Related

AngularJS - API Call in a Loop - getting the max value of an array and setting a response

This is my first time here, and I am really lost on what to do.
So I have a loop where I am making a post request to an API. I need to send some questions and get their rates match back. After that, I need to calculate the highest match and set an answer in some $scope variable to display on the screen.
The problem I have is that, since the call is asynchronous, sometimes it will display the wrong answer (because it's the last response returned).
I know it's asynchronous and I should have some callback, but I tried many ways and still can't figure out how to. All I want is to be able to "sort" the result so I can get the max number and display the associated answer AFTER all the calls are done. What I have so far is that:
for (var i = 0; i < $scope.possibleQuestions.length; i++) {
var compare = compareAPI($scope.results, $scope.possibleQuestions[i].question,
$scope.possibleQuestions[i].answer,
$scope.possibleQuestions[i].keywords,
$scope.possibleQuestions[i].similar,
function (score, question, answer, keywords, similar) {
$scope.compareAPI = score.average;
if ($scope.compareAPI >= 0.6) {
realAnswer = answer;
questionsAskedCount++;
$scope.answer = realAnswer;
} else {
var isMatch = matchKeywordAPI(question, keywords);
if (isMatch == 0) {
$scope.answer = "Please try asking another question!";
}
else {
//have to return a string just to know, bcause realAnswer is undefined in here, have to return callback function hahahahaha again, or set the answer in the match function
$scope.answer = realAnswer;
}
}
});
}
And the other function:
function compareAPI (possibleQuestion, possibleAnswer, fn) {
console.log("compare API");
var apiMatch = semanticService.getSemantic($scope.studentQuestion, possibleQuestion)
apiMatch.then(function (result) {
fn(result.data, possibleQuestion, possibleAnswer);
console.log(result.data);
}, function(error){
$scope.status = 'Unable to load question data: ' + error.message;
});
}
My biggest problem is that this part
if ($scope.compareAPI >= 0.6) {
realAnswer = answer;
questionsAskedCount++;
$scope.answer = realAnswer;
} else {
var isMatch = matchKeywordAPI(question, keywords);
if (isMatch == 0) {
$scope.answer = "Please try asking another question!";
}
else {
$scope.answer = realAnswer;
}
}
is random because of the async, so if the wrong answer is the last response, it will go to the 'else' and the answer gets wrong.
Any help will be very appreciated!!! Thanks!
Promises can done sequentially by chaining them:
var promise = $q.when(null);
for (let i=0; i<promiseArray.length; i++) {
promise = promise.then(function() {
//return promise to chain
return promiseArray[i];
});
};
promise.then(function() {
console.log("ALL Promises Done");
});
For more information, see AngularJS $q Service API Reference - Chaining Promises.

Javascript var not changing

I want to modify this vtiger function that does a few checks before submission.
I added globalvarwhich i want to use to check if the form can be submitted. What can I change here to get the required results?
globalvar should be 2 or 3 by the end of the function but it stays 1, i also added alerts where its being set to make sure that it gets to those points.
registerRecordPreSaveEvent : function(form) {
var thisInstance = this;
if(typeof form == 'undefined') {
form = this.getForm();
}
form.on(Vtiger_Edit_Js.recordPreSave, function(e, data) {
var accountName = thisInstance.getAccountName(form);
var recordId = thisInstance.getRecordId(form);
globalvar = 1;
var params = {};
if(!(accountName in thisInstance.duplicateCheckCache)) {
Vtiger_Helper_Js.checkDuplicateName({
'accountName' : accountName,
'recordId' : recordId,
'moduleName' : 'Accounts'
}).then(
function(data){
thisInstance.duplicateCheckCache[accountName+'_accounts'] = data['success'];
globalvar =2;
},
function(data, err){
thisInstance.duplicateCheckCache[accountName+'_accounts'] = data['success'];
thisInstance.duplicateCheckCache['message'] = data['message'];
var message = 'Company already exists';
Vtiger_Helper_Js.showMessage({'text' : message,'type': 'error'})
}
);
}
else {
if(thisInstance.duplicateCheckCache[accountName+'_accounts'] == true){
var message = 'Company already exists';
Vtiger_Helper_Js.showMessage({'text' : message,'type': 'error'})
} else {
delete thisInstance.duplicateCheckCache[accountName+'_accounts'];
return true;
}
}
if(!(accountName in thisInstance.duplicateCheckCache)) {
Vtiger_Helper_Js.checkDuplicateName({
'accountName' : accountName,
'recordId' : recordId,
'moduleName' : 'Leads'
}).then(
function(data){
globalvar =3;
console.log(globalvar);
thisInstance.duplicateCheckCache[accountName+'_leads'] = data['success'];
},
function(data, err){
thisInstance.duplicateCheckCache[accountName+'_leads'] = data['success'];
thisInstance.duplicateCheckCache['message'] = data['message'];
var message = 'Lead already exists';
Vtiger_Helper_Js.showMessage({'text' : message,'type': 'error'})
}
);
}
else {
if(thisInstance.duplicateCheckCache[accountName+'_leads'] == true){
var message = 'Lead already exists';
Vtiger_Helper_Js.showMessage({'text' : message,'type': 'error'})
} else {
delete thisInstance.duplicateCheckCache[accountName+'_leads'];
return true;
}
}
console.log(globalvar);
e.preventDefault();
})
}
This code has numerous asynchronous operations in it. An asynchronous operation completes sometime later, after the containing function has actually returned. Thus, you cannot use the results of an asynchronous operation immediately after the containing function has finished because the asynchronous operation itself is still going and has not completed.
So, setting globalVar in an async operation (which you are doing) will not show it's value when the containing function has completed.
The only way to use the results of an async operation is to use it IN the callback that signifies the async operation is complete or to call a function from that callback and pass it the data you want. Those are your choices. You simply can't do it the way you're trying to do it.
As with all questions here, if you describe the actual problem you were trying to solve rather than explain the problem with your attempted solution, we could probably be helpful at a much higher level.
On StackOverflow, the failure to describe what you're really trying to accomplish is known as the XY problem.
You cannot run e.preventDefault() in an asynchronous response because by the time that async response finishes, the form has already been submitted.
You will likely have to flip your logic so that the form is NOT submitted until all async operations have completed with no errors. Then, you will manually submit the form.

Function return doesn't wait for angularjs promises

I have a problem about the angularjs promises, maybe someone can point me to right direction. I have a validation function in my controller which should return true if everything is good, returns false, if there is a problem. Single page app works on this assumption, so I can't easily change this assumption...
window.customerValidation = function ($scope, $rootScope, $alert, $q) {
var isValidated = false;
if (!$scope.scopeData.customer.nationalId && !$scope.scopeData.customer.passportNumber && !$scope.scopeData.customer.driverLicenseNumber) {
$alert.error("Please enter at least one identification info.");
return false;
}
$scope.scopeData.customer.checkForDuplicateIdentity().then(function () {
var customer = $scope.scopeData.customer;
if (!customer.idValidated) {
$alert.error(customer.idValidationMessage);
}
if (!customer.idValidated)
{
return false;
}
if ($scope.customerDataForm && $scope.customerDataForm.$error && $scope.customerDataForm.$error.required) {
var missingFields = [];
angular.forEach($scope.customerDataForm.$error.required, function (value, key) {
missingFields.push("-" + value.$name);
});
$alert.error("Please fill in the all required fields.\r\n" + missingFields.join("\r\n"));
}
else if ($scope.customerDataForm && $scope.customerDataForm.$error && $scope.customerDataForm.$error.email) {
var invalidEmailFields = [];
angular.forEach($scope.customerDataForm.$error.email, function (value, key) {
invalidEmailFields.push("-" + value.$name);
});
$alert.error("Please enter valid e-mail addresses.\r\n" + invalidEmailFields.join("\r\n"));
}
else if (!Date.parse($scope.scopeData.customer.dateOfBirth)) {
$alert.error("Please enter a valid date for Date of Birth.");
}
else {
$scope.scopeData.customer.updateIndividualCustomerInfoRequest();
$scope.scopeData.customer.updateOrder();
$rootScope.customerName = $scope.scopeData.customer.firstName + " " + $scope.scopeData.customer.lastName;
isValidated = true;
}
});
return isValidated;
};
Everything was working fine until requirements changed lately, I am trying to check server to see if id values have been used in the system before. Therefore I have to make an async call to server and check it, you can see that I used
$scope.scopeData.customer.checkForDuplicateIdentity() method and promises to get the async call work.
Call works fine I tested it, only problem I have is my customerValidation method finishes before async call completes and returns false all the time. Which is to say
return isValidated line always executes before isValidated becomes true.
I can't make the outer return statement wait for the async call's completion. If I return isValidated from inner function, it won't return the value to customerValidation function. Can somebody tell me how to achieve this?
A function that calls an async function must be an async function itself. Change window.customerValidation so it returns a promise.
remove var isValidated = false;
replace return false by return $q.reject() on line 7
add return in front of $scope.scopeData.customer.checkForDuplicateIdentity()
inside the callback you give to then, return when the validation passes, and throw an error when it fails
Then in the caller of window.customerValidation replace
if (window.customerValidation()) {
// validation passed
} else {
// validation failed
}
by
window.customerValidation()
.then(function () {
// validation passed
})
.catch(function (error) {
// validation failed
})
You can always use the timeout functions provided by javascript, like
setTimeout(function {DO_SOMETHING}, 50);
This will wait 50ms before calling the function. This way you can wait for the async call to complete.
Hope this helps.

Dojo using deferred functions to get data in ajax callback function

I have a function with a return however in the function there is an async request which holds the value that is suppose to be returned by the function. I understand with the nature of async request the function will complete and not return a value while waiting on the async function to complete.
I attempted to use dojo deferred functions to have my function PostInformation() to return a value within the ajax request callback. I am having some issues and i am not sure where my issue is. Under is my code:
Dojo Deferred Function
function PostInformation(){
var hasErrors = false;
var containers = [dijit.byId("container1"), dijit.byId("container2")];
var Employee = {
//data
};
var def = new dojo.Deferred();
def = dojo.xhrPost({
url: 'hello',
content: Employee,
load: function (data) {
formErrors = {
"errors": true,
"fName": "123",
"surname": "456",
"oNames": "789",
"bSurname": "784585"
};
//formErrors = (JSON.parse(data)).formErrors;
$.each(formErrors, function (key, value) {
if (key == 'errors') {
hasErrors = value;
//console.log('hasErrors set to '+value);
}
});
if (hasErrors == true) {
for (var i = 0; i < containers.length; i++) {
var processingContainer = containers[i];
dojo.forEach(processingContainer.getChildren(), function (wid) {
var widgetName = wid.attr('id');
$.each(formErrors, function (key, value) {
if (key == widgetName && value.length > 0) {
var myWidget = dijit.byId(widgetName);
//var wdgName = dijit.byId(widgetName).attr("id");
var myWidgetValue = value;
myWidget.validator = function () {
//console.log('Attribute Name is :' + wdgName + ' Error Value is : ' + myWidgetValue);
//console.log(wdgName + " : "+myWidgetValue);
this.set("invalidMessage", myWidgetValue);
};
myWidget._hasBeenBlurred = true;
myWidget.validate();
}
});
});
}
}
console.log(hasErrors);
def.resolve(hasErrors);
},
error: function(err){
console.log(err);
def.reject(err);
}
});
def.then(function(data){
console.log('In the then function');
//alert('In the def.then and the results is : ' + data);
if(data == true){
return false;
}else{return true;}
},function(err){
return false;
alert('In the def.error and there has been an error ' + err);
});
//return the value of hasErrors here
};
Devdar, you are making heavy wether out of something quite simple. In particular, you don't need to loop through an object to access one of its properties, and the variable hasErrors is not really necessary.
Your code should simplify to something like this :
function PostInformation() {
var $containers = $("#container1, #container2");
var Employee = {
//data
};
return dojo.xhrPost({
url: 'hello',
content: Employee
}).then(function(data) {
data = JSON.parse(data);
var formErrors = data.formErrors;
if(formErrors.errors) {
$containers.each(function(i, c) {
$(c).children().each(function(wid) {
var val = formErrors[wid.id],
myWidget;
if(val) {
myWidget = dijit.byId(wid.id);
myWidget.validator = function() {
this.set("invalidMessage", val);
};
myWidget._hasBeenBlurred = true;
myWidget.validate();
}
});
});
//Send an enhanced error object down the "error" route
throw $.extend(formErrors, {
'message': 'PostInformation(): validation failure'
});
}
//Send the data object down the "success" route
return data;
});
};
PostInformation().then(function(data) {
console.log('PostInformation(): everything went OK');
//access/process `data` here if necessary
//and/or just display a nice "success" message to the user
}, function(err) {
console.error(err.message);
});
Barring mistakes on my part, this code should do everything you want and more. As with your own code, it processes the server's JSON response and returns a Promise, but that's where the similarity stops.
In your code, you seek to return a Promise which is eventually resolved with a boolean to indicate whether or not errors were detected. Whilst this will (if correctly written) meet your immediate needs, it is not the best Promise logic.
In my code, the Promise is resolved only if validation succeeds and rejected if validation fails for whatever reason. Not only is this logically correct behaviour for a Promise (success goes down the success route, and errors go down the error route) but as a bonus should (see note below) also allow you to pass more information to whetever function(s) eventually handle errors. I choose to pass the whole formErrors object enhanced with an error message, thus providing a great deal of freedom in the error handler to display/log/etc as much or as little as is appropriate, and with virtually no assumption inside PostInformation() as to what will happen subsequently. You currently believe that you will only read and act on the boolean formErrors.errors but it could be beneficial to pass as much error data as possible thus allowing yourself the freedom to change your mind at a later date without needing to change anything in PostInformation().
In this regard you can think of PostInformation() as an agent of the server-side service; and like that service, it can be written with incomplete knowledge (or maybe no knowledge at all) of how the (promise of) data/errors it delivers will be used by "consumer code".
NOTE: I have to admit that I'm not 100% familiar with Dojo's Promises, so I'm not sure that a JS plain object can be thrown in the way I indicate. I have found evidence but not proof that it can. For that reason, I am cautious above in saying "your code should simplify to something like this" Anyway, that issue aside, the principle of sending success down the success route and errors down the error route should still apply.
I'd suggest this where you create your own Deferred() object, return it from your PostInformation() function and then register .then() handlers on it so you can pick up the resolve or reject on your own Deferred object that happens inside the PostInformation() function.
The way you had it you were creating your own Deferred() object, but then immediately overwriting it with the xhrPost return result which meant def is now something else and you weren't returning your Deferred from PostInformation() so it can be used outside that function to track the progress.
function PostInformation() {
var hasErrors = false;
var containers = [dijit.byId("container1"), dijit.byId("container2")];
var Employee = {
//data
};
var def = new dojo.Deferred();
dojo.xhrPost({
url: 'hello',
content: Employee,
load: function (data) {
formErrors = {
"errors": true,
"fName": "123",
"surname": "456",
"oNames": "789",
"bSurname": "784585"
};
//formErrors = (JSON.parse(data)).formErrors;
$.each(formErrors, function (key, value) {
if (key == 'errors') {
hasErrors = value;
//console.log('hasErrors set to '+value);
}
});
if (hasErrors == true) {
for (var i = 0; i < containers.length; i++) {
var processingContainer = containers[i];
dojo.forEach(processingContainer.getChildren(), function (wid) {
var widgetName = wid.attr('id');
$.each(formErrors, function (key, value) {
if (key == widgetName && value.length > 0) {
var myWidget = dijit.byId(widgetName);
//var wdgName = dijit.byId(widgetName).attr("id");
var myWidgetValue = value;
myWidget.validator = function () {
//console.log('Attribute Name is :' + wdgName + ' Error Value is : ' + myWidgetValue);
//console.log(wdgName + " : "+myWidgetValue);
this.set("invalidMessage", myWidgetValue);
};
myWidget._hasBeenBlurred = true;
myWidget.validate();
}
});
});
}
}
console.log(hasErrors);
def.resolve(hasErrors);
},
error: function (err) {
console.log(err);
def.reject(err);
}
});
return def.promise;
};
PostInformation().then(function (data) {
console.log('In the then function');
// process data value here which will contain the value you resolved with
}, function(err)
// process an error in the ajax result here
});
I think this is more of an issue with design of the function then.
Since the xHR call is asynchronous, the postInformation shouldn't really return anything unless it's the Deferred object itself. An alternative option is to have postInformation do some sort of event publishing (dojo/topic), that other functions will subscribe to and know how to handle said events.

Returning a value from 'success' block in JS (Azure Mobile Service)

I have a rather simple getUser method that I'm having some trouble with. I am not deeply familiar with scopes and such in JS so this is giving me a head ache. Basically I want to fetch an object from the database and return it to the calling method:
function getUser(uid)
{
var result = null;
var userTable = tables.getTable('Users');
userTable.where({
userId: uid
}).read({
success: function (results) {
if (results.length > 0) {
result = results[0];
console.log('userid'+result.id);
}
}
});
console.log('userid-'+result.id); // undefined!!
return result;
}
Also, returning from inside the success doesn't return from getUser, but just the function defined inside. I tried "result = function(results)" as well but it stores the defined function and not the return value.
How am I supposed to do this?
I found a solution to this elsewhere. In practice (to the best of my understanding), it is not possible to do this within a JavaScript with asynchronous functions. What you need to do is create a recursion instead from inside the success handler.
Because the call to the database is asynchronous, your last two lines are executed (and hence result is undefined) before the call the database actually finishes. So you need to handle everything inside your success callback. Or, if your getUser() func is a helper, you could structure your code (without recursion) like this with a callback:
function insertOrWhateverCallingMethod()
{
var uid = 'blah';
getUser(uid,function(user) {
// Do something with the user object
});
}
function getUser(uid,callback)
{
var result = null;
var userTable = tables.getTable('Users');
userTable.where({
userId: uid
}).read({
success: function (results) {
if (results.length > 0) {
result = results[0];
console.log('userid'+result.id);
callback(result);
}
}
});
callback(null);
}
The code above assumes you're in a table script, where the tables object is available - if it's not you can pass it as a parameter to getUser().

Categories

Resources