jquery validation with ajax not working [duplicate] - javascript

I read your reply regarding the jQuery validator where you outline a method to check a username against a value in a database.
Ive tried implementing this method but no matter what is returned from the PHP file I always get the message that the username is already taken.
Here is ths custom method...
$.validator.addMethod("uniqueUserName", function(value, element) {
$.ajax({
type: "POST",
url: "php/get_save_status.php",
data: "checkUsername="+value,
dataType:"html",
success: function(msg)
{
// if the user exists, it returns a string "true"
if(msg == "true")
return false; // already exists
return true; // username is free to use
}
})}, "Username is Already Taken");
And here is the validate code...
username: {
required: true,
uniqueUserName: true
},
Is there a specific way i am supposed to return the message from php.
Thanks
A

For anyone else who stumbles upon this, validate supports 'remote' method, which may not have existed in 2010:
https://jqueryvalidation.org/remote-method/
$("#myform").validate({
rules: {
email: {
required: true,
email: true,
remote: {
url: "check-email.php",
type: "post",
data: {
username: function() {
return $("#username").val();
}
}
}
}
}
});

You are doing an AJAX request, ergo: the validation is already finished working when your custom validator returns either true or false.
You will need to work with async. See also this post: How can I get jQuery to perform a synchronous, rather than asynchronous, Ajax request?
Something like:
function myValidator() {
var isSuccess = false;
$.ajax({ url: "",
data: {},
async: false,
success:
function(msg) { isSuccess = msg === "true" ? true : false }
});
return isSuccess;
}
Warning:
As of jQuery 1.8, the use of async: false with jqXHR ($.Deferred) is
deprecated; you must use the success/error/complete callback options
instead of the corresponding methods of the jqXHR object such as
jqXHR.done() or the deprecated jqXHR.success().

It took me forever to figure out how to get a jsonified string containing the value of an element in the page into the remote request- this is the result of a combination of many hours and trying many search results.
Key points:
async:false has been deprecated,
the function call right after remote: is the key for creating the data string with the element's value. Trying to access a current value from the form after data: was returning a blank value for the field with dataType set as json.
$("#EmailAddress").rules("add", {
required: true,
remote: function () { // the function allows the creation of the data string
// outside of the remote call itself, which would not
// return a current value from the form.
var emailData = "{'address':'" +
$('#SignupForm :input[id$="EmailAddress"]').val() +
"'}";
var r = {
url: "foobar.aspx/IsEmailAvailable",
type: "post",
dataType: "json",
contentType: "application/json; charset=utf-8",
cache: false,
data: emailData,
dataFilter: function(response) {
this.email_run = true; //fix for race condition with next button
return isAvailable(data); //return true or false
}
};
return r;
},
messages: {
remote: "* Email in use"
}
});
.ASPX page:
<input id="EmailAddress" required name="Email" type="email" placeholder="Email Address*" runat="server"/>
C# Code Behind:
[WebMethod]
public static object IsEmailAvailable(string address){...}
Formatting the response object:
function isAvailable(data) {
var response = JSON.parse(getJsonObject(data));
return (response.isAvailable === "True") ? true : false;
};
//transform response string to a JavaScript Object()
//http://encosia.com/never-worry-about-asp-net-ajaxs-d-again/
function getJsonObject(data) {
var msg = eval('(' + data + ')');
if (msg.hasOwnProperty('d'))
return msg.d;
else
return msg;
};

Here's my "old school" hack...
Below a utility function that allows the use of "asynchronous" validations with "jquery.validate.js" library. This function creates a delay between user keystrokes otherwise the validation function "validFunc" will be called "all time" which is not very performative in some circumstances and especially problematic for functions that perform validations on "serverside"/"backend" (ajax calls basically). In this way the "validFunc" validation function is only called when the user stops typing for a certain period of time which also allows a "realtime" validation ("onkeyup": true on jqv settings) as it occurs while the user is typing.
IMPORTANT: Validations involving the use of the "jqvAsyncValid" function should always be the last ones to avoid conflicts with the others due to the asynchrony.
{
[...]
"rules": {
"field_name": {
"required": true,
"maxlength": 12,
"minlength": 4,
// NOTE: Validation involving the use of the "jqvAsyncValid" function. By Questor
"my_custom_ajax_validation": true
},
[...]
}
ANSWER'S CODE:
// NOTE: Adds the custom validation "my_custom_ajax_validation". By Questor
$.validator.addMethod("my_custom_ajax_validation", function (value, element) {
return jqvAsyncValid(element, "my_custom_ajax_validation", myValidationFunc, this);
}, "My error message!");
// NOTE: My validation function. By Questor
function myValidationFunc(domElement) {
if (someFuncWithAjaxCall(domElement.value) == "ALL_RIGHT!") {
return true;
} else {
return false;
}
}
// NOTE: Global "json" variable that stores the "status" ("isValid") and cycle control
// ("toCtrl") of asynchronously validated elements using the "jqvAsyncValid" function.
// By Questor
var jqvAsyncVState = {};
// NOTE: A utility function that allows the use of asynchronous validations with
// "jquery.validate.js". This function creates a delay between one user keystroke and
// another otherwise the validation function "validFunc" will be called "all time"
// which is not very performative in some circumstances and especially problematic
// for functions that perform validations on the serverside/backend (ajax calls basically).
// In this way the "validFunc" validation function is only called when the user stops
// typing for a certain period of time, which also allows a "realtime" validation
// as it occurs while the user is typing. By Questor
// [Ref .: https://jqueryvalidation.org/ ]
//. domElement - DOM element informed by jqv in the "addMethod" for the anonymous
// function;
//. asyncRuleNm - Validation name added via "addMethod";
//. validFunc - Function that will do the validation. Must have the signature
// "funcName(domElement)" returning "true" for valid and "false" for not;
//. jQValidInst - Instance of the current jqv within "addMethod". It is usually
// denoted by "this";
//. toInMsecs - "Timeout" in "milliseconds". If not informed the default will be
// 1500 milliseconds. Be careful not to use a very short timeout especially in
// "ajax" calls so as not to overload the serverside/backend.
// Eg.: `return jqvAsyncValid(element, "my_custom_ajax_validation", myValidationFunc, this);`.
function jqvAsyncValid(domElement, asyncRuleNm, validFunc, jQValidInst, toInMsecs) {
if (typeof toInMsecs === "undefined" || toInMsecs === "") {
toInMsecs = 1500;
}
var domEKey = jQValidInst.currentForm.id + domElement.name;
// NOTE: The validation messages need to be "displayed" and "hidden" manually
// as they are displayed asynchronously. By Questor
function errMsgHandler() {
if (jqvAsyncVState[domEKey]["isValid"]) {
// NOTE: If the current error message displayed on the element was that
// set in the rule added via "addMethod" then it should be removed since
// the element is valid. By Questor
// [Ref.: https://stackoverflow.com/a/11652922/3223785 ,
// https://stackoverflow.com/a/11952571/3223785 ]
if (jQValidInst.errorMap[domElement.name] == $.validator.messages[asyncRuleNm]) {
var iMsgNow = {};
iMsgNow[domElement.name] = "";
jQValidInst.showErrors(iMsgNow);
}
} else {
var iMsgNow = {};
// NOTE: If the element is invalid, get the message set by "addMethod"
// for current rule in "$.validator.messages" and show it. By Questor
iMsgNow[domElement.name] = $.validator.messages[asyncRuleNm];
jQValidInst.showErrors(iMsgNow);
}
}
if (!jqvAsyncVState.hasOwnProperty(domEKey)) {
// NOTE: Set the global json variable "jqvAsyncVState" the control attributes
// for the element to be asynchronously validated if it has not already been
// set. The key "domEKey" is formed by the "id" of the "form" that contains
// the element and the element's "name". By Questor
jqvAsyncVState[domEKey] = {
"toCtrl": null,
"isValid": undefined
};
}
var useOnKeyup = true;
// NOTE: The "onblur" event is required for "first validation" that only occurs
// in a "blur" event - this is inherent to jqv - and for situations where the
// user types very fast and triggers "tab" and the event "onkeyup" can not deal
// with it. By Questor
domElement.onblur = function (e) {
jqvAsyncVState[domEKey]["isValid"] = validFunc(domElement);
errMsgHandler();
useOnKeyup = false;
}
if (useOnKeyup) {
// NOTE: The strategy with the event "onkeyup" below was created to create
// a "delay" between a "keystroke" and another one otherwise the validation
// function "validFunc" will be called "all time" which is not very performative
// in some circumstances and especially problematic for functions that perform
// serverside/backend validations (ajax calls basically). In this way the
// "validFunc" validation function is only called when the user stops typing
// for a certain period of time ("toInMsecs"). By Questor
domElement.onkeyup = function (e) {
// NOTE: Clear the "toCtrl" if it has already been set. This will
// prevent the previous task from executing if it has been less than
// "toInMsecs". By Questor
clearTimeout(jqvAsyncVState[domEKey]["toCtrl"]);
// NOTE: Make a new "toCtrl" set to go off in "toInMsecs" ms. By Questor
jqvAsyncVState[domEKey]["toCtrl"] = setTimeout(function () {
jqvAsyncVState[domEKey]["isValid"] = validFunc(domElement);
errMsgHandler();
}, toInMsecs);
};
}
return jqvAsyncVState[domEKey]["isValid"];
}

Related

Wait for $.ajax result inside .each function

I have function that search for every element with a specific class:
$("#stepSurveyCtnId .questionCtnClass").each(function () {}
Inside each step, I check if a question is of type customer:
var type = $(this).children().data("question-type");
var isCustomerQuestion = false;
switch (type) {
case "Name":
case "Email":
isCustomerQuestion = true;
break;
}
If it's customer type, I get the next id of the customer's table from the database:
if(isCustomerQuestion) {
if (customerId == -1) {
$.ajax({
method: "POST",
url: urlCustomerCreate,
success: function (ajaxData) {
customerId = ajaxData.NumericValue;
}
});
}
}
The issue is that in the second iteration of the .each() function, customerId is still = -1, when it should be 1305 for example.
It seems that the execution don't stop in the $.ajax call, or the iterations are executed at the same time and the second iteration don't receive the customerId from the first iteration.
I'm still not 100% clear on sure on how everything is structured for you, but here is one way of handling asynchronicity in JavaScript (adapted from #ShubHam's answer)
function handleQuestion(questionElements, index, customerId) {
if (questionIndex >= questionElements.length) return;
var type = $(this).children().data("question-type");
var isCustomerQuestion = false;
switch (type) {
case "Name":
case "Email":
isCustomerQuestion = true;
break;
}
if(isCustomerQuestion) {
if (customerId == -1) {
$.ajax({
method: "POST",
url: urlCustomerCreate,
success: function (ajaxData) {
handleQuestion(questionElements, questionIndex + 1, ajaxData.NumericValue);
}
});
} else {
// Have ID now
handleQuestion(questionElements, questionIndex + 1, customerId);
}
}
}
// Go
handleQuestion($("#stepSurveyCtnId .questionCtnClass"), 0, -1);
This will only continue to the next iteration after the success callback has been triggered.
Put logic inside one function (say function 1) and ajax call inside other function.
Call ajax function from function 1. Inside success call function 1 with required params
Update (example added):
var x=['a','b','c']
var elm=document.getElementById('demo')
x.forEach(function(temp){
elm.innerHTML=elm.innerHTML+temp
})
<div id='demo'></div>
This can be converted to new logic as
var x=['a','b','c']
function sethtml(temp,length,maxlength){
//here ajax call can be placed
var elm=document.getElementById('demo')
elm.innerHTML=elm.innerHTML+temp
//inside success function of ajax
traverse(length+1,maxlength)
}
function traverse(length,maxlength){
if(length>=maxlength)
{
//all calls done next steps to perform
}else{
sethtml(x[length],length,maxlength)
}
}
traverse(0,x.length)
<div id='demo'></div>
Advice to be considered from Jamie-Day in comments: Check your logic for scope of improvement. Accessing db results in for each kind of scenario generally can be avoided(ideally it should be avoided for better user experience)
Change your ajax code. add "async: false" so that each code next to ajax will wait for ajax result
if(isCustomerQuestion) {
if (customerId == -1) {
$.ajax({
method: "POST",
async: false,
url: urlCustomerCreate,
success: function (ajaxData) {
customerId = ajaxData.NumericValue;
}
});
}
}
First, you need to think asynchronously.
Code that need to run after the ajax should be called from the success function. You also want to add error function to handle server errors.
Second, to improve speed and bandwidth I'd reduce number of AJAX calls to a single one, by joining all IDs together in a single AJAX request.
It require server-side changes and you did not provide the server-side, so I'll leave server side to you.
// Prepare ajax call
var customerData = [];
var customerCreateData = [];
$("#stepSurveyCtnId .questionCtnClass").each(function () {
var type = $(this).children().data("question-type");
var isCustomerQuestion = false;
switch (type) {
case "Name":
case "Email":
isCustomerQuestion = true;
break;
}
// Set customerId and customerCreateData
if(isCustomerQuestion) {
if (customerId == -1) {
customerCreateData.push(customerCreateData);
}
}
}); // end each
if (customerCreateData.length) {
$.ajax({
method: "POST",
url: urlCustomerCreate,
data: customerCreateData,
success: function (ajaxData) {
customerData = ajaxData.customerData;
doAmazingThingsWithCustomers(customerData);
},
error: function(jqXHR, textStatus, errorThrown) {
alert('Server error: ' + errorThrown);
}
});
}
The first A in AJAX stands for Asynchronous which means that the ajax calls would get executed and would not wait for the call to finish. This way we can let users interact with other elements on the page and provide a good user experience.
If we make the AJAX calls asynchronous by setting the async option to false, then the browser would wait for the ajax call to complete and users would not be able to interact with the elements until the call has completed. With the increase in number of calls, this blockage time would increase.
I would suggest you find a better alternative than this.

How to ensure that a function containing multiple $.ajax calls runs fully synchronously and also allows for browser repaints as it executes

I've been working on getting a function written to:
1) Process an input array using $.ajax calls to fill an output array (below this is inputList)
2) Below is what I have, but I'm having issues with it:
requestData(), when I call it, runs straight through to processing the outputList array without having fully populated/filled it - it puts one value into it then starts to process that, but the function still apparently runs on seperately to the subsequent processing asynchronously. I need it to be fully synchronous so that it does not return until the inputList array has been fully processed.
I'm not seeing the browser repainting the div that has its html updated on every call of the runajax() function - I'm attempting to do this with a setTimeout.
3) I've set the ajax request to be synchronous (async : false) - but this doesn't seem to help
I've tried to use jQuery's $.when to provide an ability to ensure that everything gets called in sequence - but clearly I'm not doing this correctly.
Would appreciate any help - I've asked previous related questions around this and had some useful help - but I've still not resolved this!
Thanks
//declare holding function requestData - expects a non-empty input data array named inputList
function requestData() {
//declare inner function runajax
function runajax() {
if(inputList.length > 0) {
//get first item from inputlist and shorten inputList
var data = $.trim(inputList.shift());
function getData() {
//send the data to server
return $.ajax({
url: 'sada_ajax_fetch_data.php',
cache: false,
async: false,
method: "post",
timeout: 2000,
data: {
requesttype: "getmydata",
email: encodeURIComponent(data)
}
});
}
function handleReturnedData (response) {
response = $.trim(decodeURIComponent(response));
//update the div inner html
if(response == "Failed") {
$('#fetchupdatestatus').html('There was an error retrieving the data you requested!');
} else {
$('#fetchupdatestatus').html('The item returned was '+response);
}
//add the response from ajax to the end of the outputList array
outputList.push(response);
//set up the next ajax call
var doNextBitOfWork = function () {
runajax();
};
//call setTimeout so that browser shows refreshed div html
setTimeout(doNextBitOfWork, 0);
//return
return $.when();
}
//do the next ajax request and response processing
return getData().done(handleReturnedData);
} else {
//did the last one so return
return $.when();
}
}
//kick off the ajax calls
runajax();
}
var inputList = new Array();
var outputList = new Array();
.....load +/- 100 values to be processed using ajax into array inputList
requestData();
.....process stuff in array outputList
.....etc
There was my answer with "you're doing it wrong" earlier, but then I just decided to show, how you can do it (almost) right: https://jsfiddle.net/h4ffz1by/
var request_maker = {
working: false,
queue: [],
output: [],
requestData: function(inputList) {
if (request_maker.working == true) {
return false;
}
request_maker.output = [];
request_maker.working = true;
while (inputList.length > 0) {
var data = $.trim(inputList.shift());
request_maker.queue.push(data);
}
console.log(request_maker.queue);
request_maker.doTheJob();
return true;
},
doTheJob: function() {
current_data_to_send = request_maker.queue.shift();
console.log(current_data_to_send);
if (typeof current_data_to_send != 'undefined' && request_maker.queue.length >= 0) {
$.ajax({
url: '/echo/json/',
cache: false,
method: "post",
timeout: 2000,
data: {
requesttype: "getmydata",
email: encodeURIComponent(current_data_to_send)
},
success: function(data, status, xhrobject) {
console.log(xhrobject);
request_maker.handleReturnedData(data);
},
});
} else {
request_maker.working = false;
console.log('all data has been sent');
}
},
handleReturnedData: function(response) {
console.log(response);
response = $.trim(decodeURIComponent(response));
//response= 'Failed';//uncomment to emulate this kind of response
if (response == "Failed") {
$('#fetchupdatestatus').append('There was an error retrieving the data you requested!<br/>');
} else {
$('#fetchupdatestatus').append('The item returned was ' + response + '<br/>');
request_maker.output.push(response);
}
request_maker.doTheJob();
if (request_maker.working == false) {
console.log('all requests have been completed');
console.log(request_maker.output);
}
}
}
inputList = [1, 2, 3, 4, 5, 6];
if (request_maker.requestData(inputList)) {
console.log('started working');
}
if (!request_maker.requestData(inputList)) {
console.log('work in progress, try again later');
}
Note that I've changed request path to jsfiddle's ajax simulation link and replaced html() with append() calls to print text in div. The calls are made and get handled in the same order as it is in inputList, still they don't lock user's browser. request_maker.output's elements order is also the same as in inputList.
Have in mind, that you will need to add error handling too (probably just a function that pushes 'error' string into output instead of result), otherwise any ajax error (403/404/502, etc.) will get it "stuck" in working state. Or you can use complete instead of success and check request status right there.
UPD: Answer to the question: you cannot get both. You either use callbacks and let browser repaint inbetween asynchroneous requests or you make requests synchroneous and block browser untill your code finished working.
UPD2: There actually is some information on forcing redraw, however I don't know if it will work for you: Force DOM redraw/refresh on Chrome/Mac

Value of variable is not being changed (Ajax validation)

I have been dealing with these issue for a few days, and I can't seem to be able to fix it on my own. I've already asked a question here on Stack regarding this, however I've been told this is resolved by using callback functions. I've done exactly that but the variable inside the callback still isn't changed.
I am using abide validation that is built into the Foundation framework, and I am trying to have my own custom validator for checking if an email already exists in our database. Everything works, even the console.log returns the correct results, but the variable value is not changed from false to true. Only check the code from emailVerification downwards.
Thank you for your help.
$(document).foundation({
abide : {
live_validate : false, // validate the form as you go
validate_on_blur : false,
patterns: {
positive_price: /^\+?[0-9]*\,?[1-9]+$/,
},
validators: {
positiveNumber: function(el, required, parent) {
var value = el.value.replace(/,/, '.'),
valid = (value > 0);
return valid;
},
emailVerification: function (el, required, parent) {
var email = el.value,
ajax = 1,
valid = false;
function checkServer(callback) {
$.ajax({
type: "GET",
url: "/check.do?action=userEmailAvailable",
data: "userEmail1=" + email + "&ajax=" + ajax,
success: callback
});
}
checkServer(function(data) {
if (data.result == 1) {
console.log(data.result + "does not exist");
valid = true;
} else {
console.log(data.result + "already exist");
valid = false;
}
});
return valid;
}
}
}
});
the valid that you declare in positiveNumber function is locally scoped to that function only. the next time you attempt to access it outside the function you actually create a global var called valid
As many answered, you need async validation and Abide does not support this.
I would suggest you use the excellent Parsley.js library which supports async validators through its Remote plugin
$.ajax runs asynchronously. You function returns "valid" before callback function is called. You can try running your ajax request synchronously or you need to add logic in your callback function that updates "valid" when it's called
How to create sync ajax
Add validation function to you callback
checkServer(function(data) {
if (data.result == 1) {
console.log(data.result + "does not exist");
valid = true;
} else {
console.log(data.result + "already exist");
valid = false;
}
DoCoolValidation(valid)
});
If you are sure that your server validation is fast enough you can do
function Validate(dataToSend) {
return JSON.parse($.ajax({
url: '/check.do?action=userEmailAvailable',
type: "GET",
dataType: "json",
data: dataToSend,
async: false
}).responseText);}
And use it:
valid = Validate("userEmail1=" + email + "&ajax=" + ajax);
Have a look at this post. It can help

Dojo Forms. How to show errors about incorrect filled form elements like dijit method validate() does?

I am using dojo forms and submitting using AJAX. I use 2 methods of validate: on client side and on server side:
dojo.connect(form, "onsubmit", function(event){
dojo.stopEvent(event);
var digit_form = dijit.byId("user_profile_form");
if (!digit_form.validate()) {
return false;
}
// client-side validation is ok, so we submit form using AJAX
var xhrArgs = {
form: form,
handleAs: "json",
load: function(responseText){
// here I get response from server
// and if there are errors on server
// responseText object contains array with errors, so I
// need to show this errors to user
},
error: function(error) {
}
}
var deferred = dojo.xhrPost(xhrArgs);
}
The problem is that validate() method shows nice error messages to user, but when I get errors from server I can't show errors like method validate() does, so I use native javascript alert() method that is not so nice. I would like equal displaying of errors that validates on server and client side.
For each server side error, set a new widget.SSError property.
error: function(error) {
widget.set('SSError','The value is invalid because server thought so...'
form.validate();
}
Override the widget.isValid() function and make it check the new widget.SSError property.
Based on this message,
var myCustomFS = dojo.declare(dijit.form.FIlteringSelect, {
postMixInProperties: function() {
this.inherited(arguments);
// Point _isValidOld at the function isValid currently points at
this._isValidOld = this.isValid;
// Point isValid at a new function
this.isValid = function() {
if (this.SSError) {
return false;
}
return this._isValidOld(); // Calls the original isValid function
}
}
};
Put a watch on the value and reset widget.SSError when it changes.
widget.watch('value', function(){
widget.set('SSError', false)
})

Return value for function containing jQuery $.post() function

I understand that AJAX is asynchronous, and all that. But I have the following code:
function doesUsernameExist(element)
{
// Check via AJAX (POST) if username already exists in the database
var funcReturned = null;
$.post(
'_ajax_checkexist.php',
{
var: 'username',
value: element.value
},
function(data) {
funcReturned = (data.toLowerCase() == 'exists');
},
'data'
);
return funcReturned;
}
I understand why the function isn't returning a value properly, but what can I use as a workaround? This is for the Validity plugin, which requires a boolean returned by the doesUsernameExist function. I can't do the usual workaround of having the $.post function alter some HTML element. It needs to return true/false to doesUsernameExist.
What kind of workaround can I use? I'm stumped by this.
You could return a value if the call is synchronous. Here you have an example:
Solution for synchronous mode:
function doesUsernameExist(element)
{
// Check via AJAX (POST) if username already exists in the database
var funcReturned = null;
$.ajaxSetup({async: false});
$.post(
'_ajax_checkexist.php',
{
var: 'username',
value: element.value
},
function(data) {
funcReturned = (data.toLowerCase() == 'exists');
},
'data'
);
$.ajaxSetup({async: true});
return funcReturned;
}
The problem with your code, is that while the $.post is executing, the function continues it's execution (that's asynchronous), so will return null before your $.post ended.
Solution for asynchronous mode:
Using an asyncrhonous call, you cannot return a value, but can call callbacks or execute code just there. This could be an async solution:
function doesUsernameExist(element)
{
var funcReturned = null;
$.post( //This post is asynchronous
'_ajax_checkexist.php',
{
var: 'username',
value: element.value
},
function(data) {
funcReturned = (data.toLowerCase() == 'exists');
userExistanceCallback(funcReturned);
},
'data'
);
//We don't return anything
}
function userExistanceCallback(funcReturned){
if(funcReturned == true)
alert("User exists!!");
else
alert("User does not exist!!");
}
Hope this helps. Cheers
If this is for validation purposes you will find that the jquery.validate plugin and its remote rule are perfectly adapted for what you are trying to achieve.
So, instead of writing doesUsernameExist functions and worrying about asynchronousness of AJAX and how to return values you will write validation rules for your form (which in fact is the final goal of the exercise => focus on the business and leave the plumbing to plugins):
$('#id_of_some_form').validate({
rules: {
password: {
required: true
},
username: {
remote: '_ajax_checkexist.php'
}
}
});

Categories