Custom validation rule with knockout.js - javascript

I'm having trouble getting a custom validation rule setup with knockout.js for checking if a username already exists. From my understanding, if the return is true then there are no errors, otherwise an error is set.
Here's an example of custom validation
//val is the username in question and searchType is the type of search(username or email)
function checkValue(val, searchType){
if(searchType == 'userName'){
$.post("/users/check_if_exists",{ 'type':'username', 'value': val },function(data) {
var info = JSON.parse(data);
if(info.username_availability == "available"){
return searchType;
//I know this is working because I've alerted the searchtype here and it displays properly
}
else{
return "unavailable";
}
});
}
}
ko.validation.rules['checkIfExists'] = {
validator: function (val, searchType) {
return searchType == checkValue(val, searchType); //if the username is availble, the searchType is returned back so it would return searchType == searchType which should be true meaning there are no errors
},
message: 'This username is taken, please select another.'
};
ko.validation.registerExtenders();
I've checked the network tab and the POST is returning the correct value. If the value is available, I return the searchType. That way, it compares searchType == searchType which should be true. However, that's not the case.
Is there any other way to accomplish what I'm trying to do?
update
Here's what I have as of now
function checkValue(val, searchType, callback) {
var callback = function(data) {
return true;
}
$.post("/users/check_if_exists", { 'type':'username', 'value': val }, function(data) {
info = JSON.parse(data);
if(info.username_availability == "available"){
callback(true);
} else {
callback(false);
}
});
}
ko.validation.rules['checkIfExists'] = {
async: true,
validator: function (val, searchType) {
alert(checkValue(val, searchType));//returns undefined
return checkValue(val, searchType);
},
message: 'This username is taken, please select another.'
};
ko.validation.registerExtenders();

ko.validation calls your validation function.
There are two types of validation functions with regards to the way they pass the success/failure state back to ko.validation: the straight-forward return true/false way, or the "async" way.
The async way exists only because $.ajax exists and it's basically just this: instead of returning a value (which is impossible, as you are using an $.ajax call) you have to somehow notify ko.validation after the ajax response gets back to the browser, right?
So the smart people who wrote this library call your validation function with an extra parameter, a function (callback), that you have to call when the response is available.
function checkValue(val, searchType, callback){
if(searchType == 'userName'){
$.post("/users/check_if_exists",{ 'type':'username', 'value': val },function(data) {
var info = JSON.parse(data);
if(info.username_availability == "available"){
callback(true);
//I know this is working because I've alerted the searchtype here and it displays properly
}
else{
callback(false);
}
});
}
}
ko.validation.rules['checkIfExists'] = {
async: true,
validator: checkValue,
message: 'This username is taken, please select another.'
};
ko.validation.registerExtenders();

Let me turn your code into something more readable:
function checkValue(val) {
var callback = function(data) {
return 'bar';
}
$.post('url', 'data', callback);
}
var x = checkValue('foo');
You are expecting x to be set to 'bar' when that is not the case.
I replaced your anonymous function with "callback" so you better understand that returning something from it does not mean "checkValue" will return anything.
After that your next problem is the fact that AJAX calls are asynchronous.
A validator function accepts three parameters: the value, extra parameters and a callback function. The callback function parameter is designed to aid you in exactly this situation.
Inside the success callback for the $.post you just have to call that callback function for the validator function passing true/false as a parameter.
function checkValue(val, params, callback) {
$.post('url', 'data', function(data) {
if(data === 'bar') {
callback(true);
} else {
callback(false);
}
});
}

checkValue as you have written it always returns undefined. Invoking "return" from inside of a callback function only sets the return value for that callback function (not the "outer function"). If the validator function expects a return value right away (as knockout appears to do) you won't be able get that data asynchronously.

Related

Submit form with ajax, asynchronously with a callback

I realize SO is loaded with material on this subject, I've gone through it, but just can't seem to get this to work.
My function takes a JSON response, parses it and returns a false on success and true on error (This is backwards on purpose).
console.log shows I'm getting the correct response, but my function won't return (submitme remains false although it is changed to true inside the callback). Thus, the function always returns false.
I'm guessing my callback operation is faulty but I'm not sure what I'm doing wrong.
I might be off here, but I think the callback is OK, it is just being executed after the script is executed (hence asynchronous).
But my question remains, how in this case can I have the form submit true if the boolean value is only being changed via ajax?
Here's my snippet:
$(".form").submit(function(){
error = $("#error");
var submitme = false;
function ajaxCall(url, type, data, callback) {
$.ajax({
url: url,
type: type,
data: data,
success: function(response){
try {
obj = JSON.parse(response);
text = obj.Error;
error.html('');
error.append(text);
//if the json was parsed, return false
submitform = false;
}
catch (error) {
//if an error was thrown, return true
submitform = true;
}
callback(submitform);
},
});
}
function ajaxCallSuccess(submitform) {
console.log(submitform); //This shows correct output!
if (submitform == 'true'){ //Here I want to manipulate the global submitme variable so I can submit the form on "true"
submitme = true; //<-- this (I think) changes the global variable asynchronousy (so after the function is read), hence the outer function always returns false
}
};
ajaxCall(url, type, data, ajaxCallSuccess);
if (submitme == true) {
return true;
}
else {
return false;
}
});
EDIT:
I edited the post to perhaps clarify the question a little bit
You need to always return false and then manually submit the form in case the AJAX call succeeds:
$('.form').submit(function() {
function ajaxCall(url, type, data, callback) {
$.ajax({
url: url,
type: type,
data: data,
dataType: 'json',
success: function(response) {
callback(true);
},
error: function() {
callback(false);
}
});
}
var form = $(this);
function ajaxCallSuccess(submitform) {
if (submitform) {
// submit the form here:
form[0].submit();
}
};
ajaxCall(url, type, data, ajaxCallSuccess);
return false;
});
I realize SO is loaded with material on this subject, I've gone through it, but just can't seem to get this to work.
Why? Have you not understood what was explained there …?
But my question remains, how in this case can I have the form submit true if the boolean value is only being changed via ajax?
You can not return a value out of an asynchronous handler, and expect that value to be available in the code that follows it, because of the asynchronous execution. The following code will not “wait” for the asynchronous action to be finished, that’s the whole point of asynchronism.
So cancel the submitting of the form right away – and then submit the form from within your callback function if applicable.

jquery ajax deferred object not returning value

I have this code..
if (!checkIfCustomerIsValid(event)) {
event.preventDefault();
return false;
}
else {
AddCustomer();
}
function checkIfCustomerIsValid(event) {
if ($('#txtCName').val() == '') {
alert('Please enter a valid value for customer name!');
return false;
}
if ($('#txtCAddress').val() == '') {
alert('Please enter a valid value for customer address!');
return false;
}
}
It returned fine, until then, but I added a new check and its not returning anything.
function checkIfCustomerIsValid(event) {
// code that was already there, the name and address check
var _mobNo;
if ($('#txtMobile').val() == '') return false;
var _unq = $.ajax({
url: '../Autocomplete.asmx/IsMobileUnique',
type: 'GET',
contentType: 'application/json; charset=utf8',
dataType: 'JSON',
data: "mobileNo='" + $('#txtMobile').val() + "'",
async: false,
timeout: 2000,
success: function (res) { if (res.d) return false; else return true; },
error: function (res) { alert('some error occurred when checking mobile no'); }
}),chained = _unq.then(function (data) { if (data.d == false) { alert('mobile no already exists!'); $('#txtMobile').focus(); return false; } return true; });
}
If mobile no is not unique the alert shows fine that mobile no is not unique, but when it is unique the code doesn't go into AddCustomer (in the else part)??? Is it not returning true? Why is it not going into AddCustomer???
With your new test, checkIfCustomerIsValid is asynchronous. There is no way for it, even with deferred, to directly return the result of a distant call.
The simplest here would be to pass a callback to your checkIfCustomerIsValid function or to return the promise from the function. As you mix synchronous and asynchronous tests, the best would be to pass a callback to checkIfCustomerIsValid.
You're correct, there is not a scenario where checkIfCustomerIsValid would return true. This is because you're attempting to return true from an anonymous function (i.e. a callback after the ajax request). When you return true from
chained = _unq.then(function (data) { if (data.d == false) { alert('mobile no already exists!'); $('#txtMobile').focus(); return false; } return true; });
You're only returning from that anonymous function, not from checkIfCustomerIsValid. Solving this problem is not completely straight forward and is an issue created from the nature of asynchronous calls. The most common solution to this is to pass a callback to your asynchronous call. Here is a fiddle which implements this.
http://jsfiddle.net/p3PAs/
Ajax is asynchronous and won't block, so the return value is most likely undefined. You can modify the code to something like the following:
success: function (res) { if (res.d) callRoutineToAddCustomer(); },

Javascript array returns undefined

I am currently linking a javascript file to an html page, and upon using a function in that javascript file the return value essentially gets erased and shows up as undefined, even though in the function itself the value is defined (that was probably very confusing, i'll just show the code and it should make sense):
functions.js
function addActivity(contactNameSelected, username) {
var returnArray = [];
//post to .php
if(data.added)
{
var newEvent = [];
newEvent['id'] = data.id;
newEvent['date'] = formattedDate;
returnArray.push(true);
returnArray.push(newEvent);
return returnArray; //when i debug, this has a value and is a valid array at this point
}
else
{
returnArray.push(false);
returnArray.push(data.message); //when i debug, this has a value and is a valid array at this point
return returnArray;
}
}
home.html
var response = [];
response = addActivity(contactNameSelected, username); //although valid above, undefined here
if(response[0]) //error b/c response is undefined
{
//do stuff if successful
}
else{
//do other stuff if unsuccessful
}
If i just return a string it works fine, but for some reason if i attempt to return an array it is simply undefined. Why is this?
Thanks!
I'm guessing that the omitted '//post to .php' looks something like
$.post('...php', { ... }, function(data) {
if (data.added) ...
The AJAX response is handled by a callback function, which executes asynchronously. In other words, returnArray is populated well after addActivity has returned.
The return returnArray; statements are useless because you are returning a value from the callback, not from addActivity. The callback is invoked not by your code, but by XHR (in a different execution context) and its return value is discarded.
To properly pass your data back in asynchronous style, we need to tweak your code.
function addActivity(contactNameSelected, username, callback) {
$.post('...', { ... }, function(data) {
var returnArray=[];
if(data.added)
{
...
}
else
{
...
}
callback(returnArray);
});
}
addActivity(contactNameSelected, username, function(response) {
if(response[0])
{
...
}
else
{
...
}
});

jquery - return value from callback function (in post request) into the function its inside of?

I have a javascript function that posts data to a validation script and grabs a value from there. The callback function on the post request returns a boolean value, and I'm trying to get the entire function to return that boolean value. Right now, the callback function returns the correct value, but the function itself doesn't return anything. Here's the code:
function validate(request_type, request_text) {
$.post("http://www.example.com/ajax/validate.php",{
type: request_type,
text: request_text
}, function(data) {
return (data == "valid");
});
}
I realise that this is sort of a "synchronous" call, and that's not what AJAX is about, but I already have numerous functions in validate.php (database calls, etc.) that I can't implement in Javascript, and I saw threads like this one that talk about using some form of handler.
How would I write a simple handler that will make either the variable data or the result of the boolean comparison data == "valid" available when I use it in an if/else statement (which is where this function is supposed to be used)?
EDIT: For example, one of the if statements that will be using the boolean result:
if (!validate('password',pass_new)) {
$('#pass_new').addClass('error');
$('#pass_confirm_new').addClass('error');
$(error_string.format('Please enter a valid password.')).insertAfter('#pass_confirm_new');
$('#pass_text_short').hide();
$('#pass_text_long').show();
EDIT: The function called with the onsubmit event in my HTML form:
function valid_pass_sett() {
//code to remove errors left over from previous submissions - snipped
pass_old = $('input[name=pass_old]').val();
pass_new = $('input[name=pass_new]').val();
pass_confirm_new = $('input[name=pass_confirm_new]').val();
//some if statements that don't involve AJAX requests - snipped
if (!validate('password',pass_new)) {
$('#pass_new').addClass('error');
$('#pass_confirm_new').addClass('error');
$(error_string.format('Please enter a valid password.')).insertAfter('#pass_confirm_new');
$('#pass_text_short').hide();
$('#pass_text_long').show();
return false;
}
return true;
}
I haven't edited this code to include the updated code that's been posted, but my question is how I return false from it to stop form submission?
function validate(request_type, request_text, callback) {
$.post("http://www.example.com/ajax/validate.php",{
type: request_type,
text: request_text
}, function(data) {
callback(data == "valid");
});
}
And usage would be:
validate(request_type, request_text, function (isValid) {
if(isValid) {
// do something
} else {
// do something if invalid
}
});
Unless you make a synchronous AJAX call (which you probably don't want to do), you simply can't.
If this function is used in several places in your code, your best bet may be to allow it to receive a function.
That way instead of relying on the result being returned from your function to be used in some code, you're actually passing your code directly in, so it is ensured to be able to use the response.
var my_form = $('#my_form');
my_form.submit( valid_pass_sett );
function valid_pass_sett() {
//code to remove errors left over from previous submissions - snipped
pass_old = $('input[name=pass_old]').val();
pass_new = $('input[name=pass_new]').val();
pass_confirm_new = $('input[name=pass_confirm_new]').val();
validate('password', pass_new, pswd_validation_callback); // async validation
return false; // cancel form submission
}
function validate(request_type, request_text, callback ) {
$.post("http://www.example.com/ajax/validate.php",{
type: request_type,
text: request_text
}, callback );
}
function pswd_validation_callback( data ) {
if ( data === 'valid' ) {
// if valid, call the native .submit() instead of the jQuery one
my_form[ 0 ].submit();
} else {
// Otherwise do your thing for invalid passwords.
// The form has already been canceled, so no concerns there.
$('#pass_new').addClass('error');
$('#pass_confirm_new').addClass('error');
$(error_string.format('Please enter a valid password.')).insertAfter('#pass_confirm_new');
$('#pass_text_short').hide();
$('#pass_text_long').show();
}
}
EDIT: Changed to use code posted in question.
EDIT: Updating to work with additional code posted. Narrowing answer down to the named function for clarity.
function validate(request_type, request_text) {
$.post("http://www.example.com/ajax/validate.php",{
type: request_type,
text: request_text
}, function(data) {
return (data == "valid");
}); }
You can't really return from 'validate' the result of the AJAX call. You could try declare a variable before the $.post call, let's call it 'x', and inside the response function assign the value to that variable (x=data=="valid"), and outside the $.post block, but inside the 'validate' function, return that value.
function validate(request_type, request_text) {
var x;
$.post("http://www.example.com/ajax/validate.php",{
type: request_type,
text: request_text
}, function(data) {
x = data == "valid";
});
return x; }
The real problem is that the function 'validate' will continue even if the post call haven't return any value, so it will always be 'false'.
The best thing you can do is call another function INSIDE the response function, so you can assure that the server call is over before getting to the next part.
Edit:
It's been a long time since I posted this answer. The world has changed and so AJAX calls.
Now we have promises ;)
You still cannot return a direct value from a function, but you can return a Promise object, which can be chanined to another Promise, and the second promise will get the data you returned from the first one.
function validate(request_type, request_text) {
var promise = $.ajax("http://www.example.com/ajax/validate.php",{
type: request_type,
text: request_text
});
function getStuff(data) {
//Do something and return the data to the next promise
return data;
}
promise.then(getStuff).then(function(data){
// Do something else with data
});
}
If I understand this question correctly you can achieve what you want by simply storing the returned value into a HTML element and then return that elements value from your custom function:
function validate(request_type, request_text){
$.post("http://www.example.com/ajax/validate.php",{
type: request_type,
text: request_text},
function(data) {
$('#someElement').text(data);
});
//return the data by getting the value of the html element
return $('#someElement').text();
}

Jquery $.post() variable scope

I'm not massively experienced with JavaScript and I'm having trouble with variable scope and jquery. I have the following structure:
function pass_variables()
{
username = "efcjoe"
response = post_variables(username)
alert(response)
}
function post_variables(username)
{
$.post(
'/path/to/url/',
{
'username': username,
},
function(data)
{
valid = (data != 0) ? true : false
// OPTION 1: If I put return here...
return valid; // ... the alert box in pass_variables says "undefined"
},
"text"
);
// OPTION 2: If I put return here...
return valid; // ... The alert box does not pop up, and Safari debug gives
// me the error: "Can't find variable: valid"
}
Am I missing something there? I think valid should be a global variable, and therefore option 2 should work fine. I'm really not sure about option 1.
Can anyone give me any advice on the best way to get this working?
Thanks a lot.
Ajax calls are asynchronous which means they get called but do wait around for execution to complete. Basically your alert is firing before the ajax request has completed and run the callback function to change your variable.
The best thing you can do is actually pass a function to run when the ajax request has completed. This also negates the need for global variables which are frowned upon since other plugins, script can alter their state and leave your script open to errors, flaws etc
E.g
function foobar(){
//call function to do post request and also pass a function to run
//when post has returned
runPostRequest( callbackFn );
}
function runPostRequest(callback){
$.post( '/foo', callback );
}
function callbackFn( data ){
console.log('post request complete');
}
In your option 1 you are returning from the callback function, and its return value is never used because this function is only called when the Ajax request ends.
In the option 2, you are returning from your main function, but that return happens before the callback function assign any value to your valid variable.
I would refactor your code in this way, without using global variables:
function post_variables(username){
$.post('/path/to/url/',{
'username': username,
},
function(data){
var valid = data != 0;
// OPTION 3: Work in the callback function
alert(username);
alert(valid);
// OPTION 4: Pass the values and work on another function
otherFunction(username, valid);
},"text");
}
function otherFunction(username, isValid){
//...
}
Yeah, your problem is that you're not grasping some order of operations issues here. The function you're passing to $.post is a callback; it runs later, considerably after post_variables() finishes. post_variables() itself does not wait for the $.post to finish, so valid doesn't exist when you're trying to use it.
Remember that AJAX is asynchronous. The return valid; gets executed immediately after the $.post() is set up, but before the post has completed (and therefore, before valid is defined). What you probably want to do is this:
function post_variables(username)
{
var username = "efcjoe";
$.post(
'/path/to/url/',
{
'username': username,
},
function(data)
{
var valid = (data != 0) ? true : false
alert(valid);
},
"text"
);
}
And note that this no longer needs global variables, but function-scope variables that are declared using var.
You could solve the problem quite easily by assigning it a function instead of an inline one, and the event function does the alert:
function pass_variables()
{
username = "efcjoe"
response = post_variables(username);
}
function post_variables(username)
{
$.post(
'/path/to/url/',
{
'username': username,
},
receivedData,
"text"
);
}
function receivedData(data)
{
valid = (data != 0) ? true : false;
alert(valid)
}

Categories