I have two forms that get displayed alternatively in a popup, one for editing (which is loaded by ajax), one for creation.
I want to use jquery validation to display hints both on editing and on submission of fields.
Some of the validation involves time spans which must not overlap.
This works for creation, but the new rule I am creating for editing does not get triggered because the rule for creation takes precedence.
I have added class rules, because the rules I created by name or id somehow don't get active at all, maybe because I am confused how the jquery validate plugin works. These rules are commented out in the code snippet.
var classRules = {};
classRules['thresholdStartTime'] = {};
classRules['thresholdStartTime'][globalThresholdCheckName] = $form
classRules['thresholdEndTime'] = {};
classRules['thresholdEndTime'][globalThresholdCheckName] = $form
/*
*
for(var q = 0; q<20; ++q) {
validationrules['thresholdStartTime' + q] = {};
validationrules['thresholdStartTime' + q]['required'] = true;
validationrules['thresholdStartTime' + q][globalThresholdCheckName] = $form
validationrules['thresholdEndTime' + q] = {};
validationrules['thresholdEndTime' + q]['required'] = true;
validationrules['thresholdEndTime' + q][globalThresholdCheckName] = $form
}
*/
for(var cls in classRules) {
var rule = classRules[cls];
$.validator.addClassRules(cls, rule);
}
//return validationrules as rm.rules here
//in the caller, get validationrules from result
var editForm = $('#editForm')
.validate(
{
rules : rm.rules,
classRules: rm.classRules,
messages : rm.messages,
success : $.noop
});
Make the form's default action be prevent e.preventDefault(), then submit using jQuery.ajax(), choosing what data to send, then bind that ajax request to the form's submit button. Do this for each form:
jQuery('.bidvalue').click(function(e) {
e.preventDefault();
jQuery.ajax({
type: 'POST',
url: ajaxurl,
data: 'action=newbid&id='+e.target.name,
success: function(msg){
jQuery('#vehicle-value-box'+e.target.name).html(msg+',00€');
}
});
EDIT, as I completely missed the actual question...
For each form, you can specify validation as below:
$('#myform1').validate({
rules: {
myfield1: {
required: true,
minlength: 3
},
myfield2: {
required: true,
email: true
}
}
});
but you'll have to be more specific and show the HTML of both forms, and maybe what validation rules you'd like, for me to really answer your question helpfully.
EDIT #2: To add some stuff form the comments (kudos to Vicente Olivert Riera for input).
If you wish to toggle which form to apply validation upon, do this:
var activeForm;
$("form").focus(e => {
e.validate({
rules: {
myfield1: {
required: true,
minlength: 3
},
myfield2: {
required: true,
email: true
}
}
});
});
If you have many forms and just want specific forms matching certain criteria only:
$("form").focus(e => {
// Some criteria to check against for the form.
// Obviously you could make a crafty selector and avoid `if (e.hasClass())`,
// but this is to demonstrate using if to test the form if you wanted to check the form element in any way before applying `.validate()`.
if (e.hasClass("someClass") {
e.validate(...);
}
});
Related
I have a form which in fact consists of two forms. Each form is a reservation form. There are two dropdowns in both forms - destination from and destination to. There is an even handler, which calls AJAX to get possible destinations to when destination from is being selected/changed.
Another event handler (round trip checkbox) fills second form dropdowns by switching destinations from first form.
So if the first form has:
destination one: France
destination two: Austria
Then, if round trip is checked, the second form is immediately filled:
destination one: Austria
destination two: France
The problem is that this two events don't cooperate correctly.
When this code is executed:
id_form_1_destination_from.val(destination_to_0.val());
id_form_1_destination_to.val(destination_from_0.val());
id_form_1_destination_from.change();
id_form_1_destination_to.change();
The first line calls another handler which fills second form (this is the only case when it's not needed). Since it's AJAX, the second line overtakes this AJAX, so now, the second form is correctly filled (switched destinations from first form), but when AJAX is done, it changes the selection of the destination two field.
Is there a way how to avoid this? For example to turn off the event handler or better make JQuery wait until the AJAX is done and then continues. I can't just do .off() on destination to field because I use select2 plugin.
Here is my JQuery:
$(document).ready(function () {
var destination_from_0 = $("#id_form-0-destination_from");
var destination_to_0 = $('#id_form-0-destination_to');
var ride_two = $('#ride_two');
$('.class-destination-from').on('change', function () {
destination_from_changed.call(this);
});
$("#id_round_trip").on('change', function () {
if (($('#id_round_trip').is(':checked')) ) {
var id_form_1_destination_from =$('#id_form-1-destination_from');
var id_form_1_destination_to = $('#id_form-1-destination_to');
ride_two.show('fast');
//id_form_1_destination_from.off();
id_form_1_destination_from.val(destination_to_0.val()).change();
//id_form_1_destination_from.on();
//id_form_1_destination_from.change();
id_form_1_destination_to.val(destination_from_0.val()).change();
}else{
ride_two.hide('fast');
ride_two.find(':input').not(':button, :submit, :reset, :checkbox, :radio').val('').change();
ride_two.find(':checkbox, :radio').prop('checked', false).change();
}
});
$('.class-destination-to').on('change', destination_to_changed);
});
function destination_to_changed() {
var destination_id = $(this).val();
var arrival_container = $(this).siblings('.arrival-container');
var departure_container = $(this).siblings('.departure-container');
if (destination_id == '') {
return;
}
$.ajax({
url: '/ajax/is-airport/' + destination_id + '/',
success: function (data) {
if (data.status == true) {
arrival_container.hide("slow");
departure_container.show("slow");
}
if (data.status == false) {
departure_container.hide("slow");
arrival_container.show("slow");
}
arrival_container.change();
departure_container.change();
}
})
}
function destination_from_changed() {
var destination_id = $(this).val();
if (destination_id == '') {
return;
}
var ajax_loading_image = $('#ajax-loading-image');
var destination_to = $(this).siblings('.class-destination-to');
destination_to.empty();
ajax_loading_image.show();
$.ajax({
url: '/ajax/get-destination-to-options/' + destination_id + '/',
async:false, // ADDED NOW - THIS HELPED BUT IT'S NOT NECESSARY EVERYTIME
success: function (data) {
ajax_loading_image.hide();
destination_to.append('<option value="" selected="selected">' + "---------" + '</option>');
$.each(data, function (key, value) {
destination_to.append('<option value="' + key + '">' + value + '</option>');
});
destination_to.change();
}
})
}
If i'm understanding correctly, you have a concurrency issue. You basically want your first ajax call to be terminated before calling the second right?
I don't see any ajax request in your code but I think the paramter async: false, might be what you need.
Check the documentation: http://api.jquery.com/jquery.ajax/
Hope it helps
You definitely have a classic "race condition" going on here.
Since the AJAX calls seem fairly unrelated to one another, you might need to add some code on the JavaScript side so that potentially "racing" situations cannot occur. For example, to recognize that a combo is "being populated" if you've issued an AJAX request to populate it but haven't gotten the response back yet. You might disable certain buttons.
Incidentally, in situations like this, where two (or more ...) forms are involved, I like to try to centralize the logic. For example, there might be "a singleton object" whose job it is to know the present status of everything that's being done on or with the host. A finite state machine (FSM) (mumble, mumble ...) works very well here. This object might broadcast events to inform "listeners" when they need to change their buttons and such.
You need to cancel the first AJAX request before you start the second. From this SO question:
Abort Ajax requests using jQuery
var xhr;
function getData() {
if (xhr) { xhr.abort(); }
xhr = $.ajax(...);
}
General info
I'm working on a intranet based administration system for a distribution centre.
Situation
Basicly I have a contenteditable table with all user data. Except for the passwords of course. You could compare it with a webbased Excel sheet. Using a jQuery UI dialog, I'm popping op a form that allows the admin (company manager) of the system to change the employees passwords if clicked on a button.To make sure the password change will be applied to the correct user, I'm passing along the used id to my function that pops up the dialog. Using .append() I'm adding this id to the form. Up to this point everything works perfectly fine.
Problem
If the password change is cancelled, the id must be removed from the form again. Otherwise you end up appending more and more ids to the form on each user clicked. The same goes for when the password change is succeeded. I've tried doing this with jQuery .remove(), but it doesn't seem to work, even though I can't find any issue with the code.
Code
function changePass(id){
var addID = $("<input>")
.attr("type", "text")
.attr("id", "userid")
.attr("name", "userid").val(id);
$('#passChangeForm').append($(addID));
$("#changePass").dialog({
modal: true,
resizable: false,
title: "Change password",
buttons: [
{
text: "Cancel",
click: function() {
$("#passChangeForm").remove("#userid");
$(this).dialog("close");
}
},
{
text: "Ok",
click: function() {
$("#passChangeForm").submit();
}
}
]
});
}
$("#passChangeForm").on("submit", function(e){
e.preventDefault();
var password = document.getElementById("chPass1").value;
var password2 = document.getElementById("chPass2").value;
var userid = document.getElementById("userid").value;
$.ajax({
url: "system/changepass.php",
type: "POST",
data:'pass1='+password+'&pass2='+password2+'&id='+userid,
success: function(text){
alert(text);
$("#passChangeForm").remove("#userid");
$("#changePass").dialog("close");
}
});
});
Simply use this :
$( "#userid" ).remove();
Change the line
$("#passChangeForm").remove("#userid");
to
$("#userid").remove();
I want to prevent from adding a category to the Select2 element if it fails creating the row first in my db. The action is not prevented when i call ev.preventDefault(); Nothing happens.. what is wrong?
$('#sel2').select2({
placeholder: 'Enter categories',
minimumInputLength: 3,
multiple: true,
ajax: {
url: 'async/get_categories.php',
dataType: 'json',
quietMillis: 250,
data: function (term, page) {
return {
q: term,
};
},
results: function (data, page) {
return {
results: data.items
};
},
cache: true
},
formatResult: format,
formatSelection: format
}).on('select2-selecting', function(e) {
console.log(e);
if (e.val == 4) {
// if category id equals 4
// do not add this category to select 2
// e.preventDefault();
// the above works just fine and its just for testing
}
// Is something wrong here?
var ev = e;
$.ajax({
type: 'POST',
url: 'async/create_profile_category.php',
data: {
profile_id: '1',
category_id: ev.val
},
success: function(response) {
console.log(response);
if (response.error === false) {
// category assigned successfully
} else {
// failed to assign category
// so i want now to prevent from adding to select2
console.log('should not add this category');
ev.preventDefault();
// the above is not working
}
},
error: function() {
alert('Failed to assign category!');
}
});
});
The AJAX request is made asynchronusly, so by the time it has finished the element has already been added. Even though you are calling ev.preventDefault(), it is too late for it to make a difference. So this leaves you with two options:
Make the request synchronusly, which will allow preventDefault to make the difference.
Make the request asynchronusly, and manually remove the element if it fails.
Both options have their pros and cons, and it's up to you to decide which option you go with.
Making the request synchronusly
Pros
The value will never be added if the request fails.
Works well in cases where the element cannot be added quite often.
Cons
Blocks the UI - So the user is potentially left with an unresponsive page while the request is made.
Making the request asynchronusly
Pros
Does not block the UI.
Works well in cases where elements typically can be added.
Cons
The value will always show up for the user, even if it fails later.
You must manually unset the new option.
What's important to consider here is the user experience of both options. When making synchronus requests, it's not uncommon for the browser to stop relaying events - which gives the illusion that the UI has locked up and the page has gone unresponsive. This has the benefit of ensuring that the value never shows up if it isn't allowed. But if users typically can add the elements, it also has the downside of complicating the most common use case.
If users can usually add elements, then it is a better experience to add the element while the request is being made, and then notifying the user later (while removing the element) if there was an issue. This is very common is web applications, and you can see it being used in many places, such as the Twitter and Facebook like buttons (where requests usually work), as well as places on Stack Overflow.
There is a way to get around this with version4 of the select2 library.
on select2:selecting we cancel the preTrigger event. Which will stop the select2:select event. We do our ajax call. On success we then get out Select2 instance then call the trigger of the Observer that way it by passes overwritten trigger method on your select2 instance.
The call method needs your select2 instance as the context so that the existing listeners are available to call.
var sel = $('#sel');
sel.select2(config);
sel.on('select2:selecting', onSelecting);
function onSelecting(event)
{
$.ajax({
type: 'POST',
url: 'async/create_profile_category.php',
data: {
profile_id: '1',
category_id: event.params.args.data.id
},
success: function(event, response) {
console.log(response);
if (response.error === false) {
// category assigned successfully
// get select2 instance
var Select2 = $users.data('select2');
// remove prevented flag
delete event.params.args.prevented;
// Call trigger on the observer with select2 instance as context
Select2.constructor.__super__.trigger.call(Select2, 'select', event.params.args);
} else {
// failed to assign category
// so i want now to prevent from adding to select2
console.log('should not add this category');
}
}.bind(null, event),
error: function() {
alert('Failed to assign category!');
}
});
event.preventDefault();
return false;
}
here how I did it for yii2 Select2 integrated into Gridview:
'pluginEvents' => [
'select2:selecting' => "
function(event)
{
var select2 = $('#types-" . $model->id . "');
select2.select2('close');
$.post('update',{id: " . $model->id . ", type_id: event.params.args.data.id})
.done (function(response)
{
select2.val(event.params.args.data.id);
select2.trigger('change');
})
.fail(function(response)
{
krajeeDialog.alert('Error on update:'+response.responseText);
});
event.preventDefault();
return false;
}",
],
it allows to asynchoronous update data in the grid using select2 and ajax and return it to previous value if there was an error on updating.
I am working on KnockOut validation and so far so good. I do have a question though. I have some code like the following:
shippingMethodModel.Description.extend({ required: true });
And that shows a validation message below BUT does it set a flag or something which I can read to disable my save button?
I had this same need recently, so I'll try to translate what I did based on the line of code you provided above...
Try adding a ko.computed observable similar to the following:
shippingMethodModel.formIsNotValid = ko.computed(function () {
// original line
// var errors = ko.utils.unwrapObservable(ko.validation.group(self));
// ** oops, not "self" in this case
// UPDATED line
var errors = ko.utils.unwrapObservable(ko.validation.group(shippingMethodModel));
return (errors.length > 0);
});
UPDATE
I made a correction in the code above after noticing my error.
For those declaring such a model/class as a function all at once, this code may look similar to the following:
var ShippingMethodModel = function () {
var self = this;
self.shippingMethodId = ko.observable().extend({ required: true });
self.description = ko.observable().extend({ required: true });
self.formIsNotValid = ko.computed(function () {
var errors = ko.utils.unwrapObservable(ko.validation.group(self));
return (errors.length > 0);
});
};
/UPDATE
UPDATE2
Based on input from #ericb in the comments below, I made a change to the way I'm implementing my own solution, which I'll demonstrate by adapting the example code in my update above:
var ShippingMethodModel = function () {
var self = this;
self.shippingMethodId = ko.observable().extend({ required: true });
self.description = ko.observable().extend({ required: true });
self.formIsNotValid = ko.observable(false);
self.validateObservableFormField = function (nameOfObservableToValidate,
data, event) {
for (var prop in data) {
if (prop === nameOfObservableToValidate) {
var theObservable = data[prop];
theObservable.valueHasMutated();
ko.validation.validateObservable(theObservable);
if (theObservable.error) {
self.formIsNotValid(true);
}
else {
if (self.formIsNotValid()) {
var errors =
ko.utils.unwrapObservable(ko.validation.group(self));
self.formIsNotValid(errors.length > 0);
}
}
return;
}
}
};
};
Notice that I've now defined formIsNotValid as an observable, but I'm using the validateObservableFormField function to help me with pre-submit form validity determination. This change ensures that the ko.validation.group function is called only as needed, and that call should only be needed when the observable being validated is valid, but formIsNotValid is true (to handle the case where that current observable was the one that had set formIsNotValid to true).
Here's an example of how I'm doing this:
<input data-bind="value: description,
event: { blur: function(data, event) {
validateObservableFormField('facilityName',
data,
event)
}
}" />
goofy formatting to eliminate horizontal scroll
NOTE: I was already using this technique, but I've adapted it to improve the performance of checking whether or not the form is valid. #Californicated, I realized after my last comment that calling this function from the blur event of validated form fields is why I was seeing my save/submit button toggle between enabled and disabled states.
Thanks again to #ericb for the performance tip.
Further tips, from anyone, are always welcome!
/UPDATE2
Once you've got that in place, disabling the button is a matter of binding to that formIsNotValid computed observable in whatever way makes sense for how you intend to disable the button, e.g. css: { 'ui-state-disabled': formIsNotValid } and/or disable: formIsNotValid and/or some other method(s).
Hope this helps, and let me know if you run into trouble.
I would setup the following:
saveEnabled = ko.computed(function(){
// possible other logic
return shippingMethodModel.Description.isValid();
});
and then in your HTML:
<button data-bind="enable: saveEnabled"> Save </button>
Or, if you have multiple properties on your model, you could do something like:
ko.validation.group(shippingMethodModel);
and then in your HTML:
<button data-bind="enable: isValid"> Save </button>
the group function adds an isValid property to whatever it groups.
I currently use Jquery Validation and Qtip together to deal with the actual validation and displaying of information to the screen using the nice tooltip style notifications upon validation errors using the errorPlacement component of the validation options.
Currently each viewModel has its own custom method for setting up and kicking off the validation and callbacks, however I was trying to look at a nicer way of doing this, be it adding a custom binding to setup my validation rules via the data-bindings or an alternative way, but still yielding the same results (i.e the errorPlacement is triggered when a validation error occurs and tells Qtip to display the error for the given element).
Now before I started making one myself I just checked online and found Knockout Validation, which I initially thought was a great idea, I could apply my validation logic directly to the data within my viewModel and then just find some sort of callback to get Qtip to kick in, however it seems there is no callback that I can find documented. The library seems to do everything I want for the validation side of things, just not for the displaying side of things. I looked through the source code and examples but couldn't see anything other than ko.validation.group(viewModel) which would give me an observable containing the errors, but I am not sure if I could use this the same way as I was expecting.
Here is an example of how my current validation happens:
/*globals $ ko */
function SomeViewModel() {
this.SetupValidation = function () {
var formValidationOptions = {
submitHandler: self.DoSomethingWhenValid,
success: $.noop,
errorPlacement: function (error, element) {
if (!error.is(':empty'))
{ qtip.DoSomethingToDisplayValidationErrorForElement(element, error); }
else
{ qtip.DoSomethingToHideValidationErrorForElement(element); }
}
};
$(someForm).validate(formValidationOptions);
this.SetupValidationRules();
};
this.SetupValidationRules = function() {
$(someFormElement1).rules("add", { required: true, minlength: 6, maxlength: 20, alphaNumeric: true });
$(someFormElement2).rules("add", { required: true, minlength: 6, maxlength: 20 });
$(someFormElement3).rules("add", { required: true, email: true, });
};
}
I currently am sure I can remove the need for the validation rules method by adding a custom binding so I can set the validation in the data-bind, however if possible I would like to use the same sort of callback approach with the existing Knockout-Validation binding.
I haven't used Knockout-Validation specifically but I have written something similar in the past. A quick glance at the source shows that each extended observable gets a sub-observable isValid. This could be used to hide show messages in your markup using conventional knockout visible bindings.
To get QTip to work a custom binding could subscribe to this isValid property and perform the necessary initialization to show/hide QTip when triggered.
EDIT
Here is an example to get you started
http://jsfiddle.net/madcapnmckay/hfcj7/
HTML:
<!-- Note that you have to reference the "qtipValMessage" binding -->
<!-- using the "value" binding alone is not enough -->
<input data-bind="value: emailAddress, qtipValMessage : emailAddress" />
JS:
ko.bindingHandlers.qtipValMessage = {
init: function (element, valueAccessor, allBindingsAccessor, viewModel) {
var observable = valueAccessor(), $element = $(element);
if (observable.isValid) {
observable.isValid.subscribe(function(valid) {
if (!valid) {
$element.qtip({
overwrite: true,
content: {
text: observable.error
}
});
} else {
$element.qtip("destroy");
}
});
}
}
};
I had been editing madcapnmckay's post, but the differences have become significant enough that I think a new answer is needed.
It is heavily based off of madcapnmckay's post, but it fixes a bug pointed out by MorganTiley. The original only works if the user has modified the observable. If they haven't then the code never gets fired. So, I've modified it so that it fires the tooltip code when it gets created, in addition to when it changes.
ko.bindingHandlers.qtipValMessage = {
init: function (element, valueAccessor, allBindingsAccessor, viewModel) {
var observable = valueAccessor(), $element = $(element);
if (observable.isValid) {
var updateTooltip = function (valid) {
if (!valid) {
$element.qtip({
overwrite: true,
content: {
text: observable.error
}
});
} else {
$element.qtip("destroy");
}
}
updateTooltip();
observable.isValid.subscribe(updateTooltip);
}
}
};
The one downside is that the tooltip will display on hover before knockout validation has been run (example, you have a "required" validation on a field, before you press submit a tooltip will display saying the field is required, but the field will not highlight in pink). Once you change the field however, the tooltip will disappear if the field is valid.
My app was not using qtip, but rather Twitter Bootstrap Tooltip, so here is the code for that as well.
ko.bindingHandlers.invalidTooltip = {
init: function (element, valueAccessor, allBindingsAccessor, viewModel) {
var observable = valueAccessor(), $element = $(element);
if (observable.isValid) {
var updateTooltip = function (valid) {
if (!valid) {
$element.attr("data-original-title", observable.error);
$element.tooltip();
} else {
$element.tooltip("destroy");
}
}
updateTooltip();
observable.isValid.subscribe(updateTooltip);
}
}
};