Christian asked an interesting question yesterday. The solution ended up being rather simple, but I wasted (well, wasted may be a strong term) time trying to solve it in an overly complicated manner. One of the things I've learned when it comes to ColdFusion development is that it almost always makes sense to go for simplicity. While not always possible, it doesn't hurt to stop and ask myself if the route I'm going down to solve some problem could be simplified in some way. Let me share the problem, and then I'll share the complex solutions I tried until I got to a much simpler fix.
Christian asked:
So - ensuring two fields are numeric and add up to 100. The first part of that equation is simple enough. You can use a number validator for that. You can also get a bit fancy and use a min/max validator as well. The numbers should not be less than 0 nor higher than 100. However, I was really confused about how to check the sum of two fields.I was looking through your jQuery form validation posts (very helpful, thanks!) and then through the jQuery validation docs and I can't seem to find an example of something that is probably very simple.
I have two text input fields that I need to make sure are integers AND that they total 100.
My first attempt to solve this was by using a custom rule. I talked about this during my form validation series. The validation plugin allows you to define any custom rule and apply it to a field. But the problem is that the rule applies to one field. The plugin will only pass in the field's value. You do have the option to pass any number of params to the rule. That's what I tried at first. So imagine this form:
<form id="commentForm" method="get" action="">
<fieldset>
<p>
<label for="phappy">Percent Happy</label>
<em>*</em><input id="phappy" name="phappy" size="2" />
</p>
<p>
<label for="puhappy">Percent Unhappy</label>
<em>*</em><input id="puhappy" name="puhappy" size="2" />
</p>
<p>
<input class="submit" type="submit" value="Submit"/>
</p>
</fieldset>
<div class="error"></div>
</form>
I've got two form fields, phappy, and puhappy. They represent how happy and unhappy you are, percentage wise, and should total 100. I mentioned that custom rules only get data for themselves, but can be passed additional parameters. I designed my rule then to let me specify which form IDs to check and what total value to require:
rules: {
phappy: {
required: true,
number: true,
min: 0,
max: 100,
checksum : [["phappy","puhappy"],100]
},
puhappy: {
required: true,
number: true,
min: 0,
max: 100,
checksum : [["phappy","puhappy"],100]
}
},
Here we see the rules applied to the form fields. I made them required, numeric, and use a range, and the last rule was my custom one, checksum. Notice checksum is passed 2 values: A list of form ids and a total. My rule was defined as:
$.validator.addMethod("checksum", function(value,element,params) {
//get all the IDs passed in parsm[0] and add their value
var total = 0;
for (var i = 0; i < params[0].length; i++) {
total += parseInt($("#"+params[0][i]).val())
}
return total == params[1]
}, jQuery.format("The percentage fields must equal {1} "));
The logic simply goes through the first param, which is a list, and for each, grabs the value for each (note you have to use parseInt to turn them into numbers) and creates a sum. Lastly, we check to see if the total value matches the desired total. If not, we output an error. The {1} token gets replaced with the total so we could modify the designed sum and not worry about updating the error message.
So this worked.... mostly. Because I applied the rule to two form fields, I ran into issues where the error wouldn't disappear from one form field when I fixed the second. I tried to hack around a bit... and got close... but then stopped. I took a deep breath and tried to see if there may be a simpler way to go about this.
I read over the jQuery Validation plugin docs again and noticed they had support for handling the submission of a form. By that I mean, you can tell the plugin: "Hey, if you validate everything is good, let me handle the actual form submission." The docs mention this is a good way to do an Ajax submission with the form instead of a normal POST, but instead, I used it do to my final checking. Here is the setup I used for my final example:
<script>
$(document).ready(function(){
$("#commentForm").validate({
rules: {
phappy: {
required: true,
number: true,
min: 0,
max: 100,
},
puhappy: {
required: true,
number: true,
min: 0,
max: 100,
}
},
submitHandler: function(form){
var total = parseInt($("#phappy").val()) + parseInt($("#puhappy").val());
if (total != 100) {
$("#commentForm div.error").html("Your percantage fields must sum to 100.")
return false;
} else form.submit();
}
});
});
</script>
Notice I've removed the custom rule, and just stuck with the simple numeric/range style checking. I've added submitHandler as an argument to my validate constructor. In that function I simply grab the values I want to check. If they don't equal 100, I add a new error message. If they do, I tell the form to submit as normal.
Much simpler, isn't it? It may not be as dynamic as the earlier version, but it works and just reads a lot better to me. You can view the first version here and the final, I think nicer, version here.
Archived Comments
I dunno, Ray. I really like the first version better. I think the problem of which field should the checksum rule appear on might be better handled by having an additional space for the form itself (rather than only form fields).
While your second solution certainly has the virtue of simplicity of writing (less code), it is has less internal consistency and is (according to the Schleirmacher Simplicity Theorem that I just made up), actually more complex.
BTW, I like how you're doing your validation. The one thing I've done is to put my validation into CFCs that are called via Ajax by jQuery. The benefit is that I have a single validation "engine" that can be used for both client and server-side validation.
For the first demo, I did use an additional space for the error message (but didn't bother adding line breaks). My issue though was the I ended up with 2 errors. The plugin _does_ support grouping of fields, so that may have worked as well. I'll see if I can get that working.
Can you talk about why my second solution has less internal consistency? I'm not seeing what you mean here. The first one does make it a bit more obvious that the sum can be dynamic, but practically, I don't see needing to change it often, and since the validation is repeated twice, there is a higher chance of a typo slipping in and messing things up.
In regards to your last point - imagine a single CFC that supports validation at an abstract level ("Name must be a string, 2 char long", "Age must be numeric, 0 and higher."). Now imagine a service on top that allows you to say: "Please express the validation in jQuery validation rules." or "Please epxress the validation in a custom method." In other words, you can define the logic one, and use it both for jQuery or server side validation. You could, in theory, write other interfaces to support some random Ext library, or Spry, etc. Just thinking out loud here.
By internal consistency, I mean that in your latter example, there are two places where you express validation rules. What if you had something like...
rules: {
phappy: {blah : blah},
puhappy: {foo : bar},
form: {sum :
{fields : 'phappy', 'puhappy', min: 100, max: 100}
}
}
Something like that, anyway. So you could set rules for the *form* instead of for individual fields only. Now, we have only one place where all rules are expressed, hence greater internal consistency, which (according to Carollian Coagulation Correspondence), makes for more robustified code.
As for the CFC idea, yes -- exactly. If the validation CFC returns a struct of errors, the calling page is free to deal with it as it wishes to. And moving the validation out of JavaScript into a CFC makes it more reusable both across applications and on both client and server.
Woah - does that syntax even work? I mean form: { }? (I would test, but about to get into a meeting.)
@Hal
I'm not sure how I feel about the whole Ajax validation thing. I use JS validation as a way of catching errors on the client side so I can reduce the number of unnecessary http requests. If validation happens on submit, it seems a little redundant to make two requests to validate, one after the other. If validation happens on blur for each field, then you could be making quite a few http requests just for one page.
As there are only 2 fields, why not do something like:
$("#puhappy").attr( "disabled", "disabled" );
$("#puhappy").val( 100 - $("#phappy").val() );
Of course this would not work for more than 2 fields, but for simplicity, this seems like an easy solution (as you are adding limitations to the phappy input).
Gareth is correct IMHO. We know the end result must always equal 100. We know that a user can enter a number. Why not take care of the 2nd field with jQuery and keep it controlled?
[user entered] + [jquery controlled] = 100
That's pretty slick Gareth. I'd probably not disable one form field though. I'd simply make the other one update with the right value when you change it. I may do another blog entry showing that. Simple math of course, but shows how to 'listen' to form fields in jQuery.
Hello!!! I want to know how can I use the methods "max" and "min" if I use the metadata archive... I mean: <input name="xxx" class="required">... If anybody can help me....
sera de gran ayuda!!!
Did you try max and min in the input tag?
Thanks man!!! That's work.... like I said: "eres un coco".... (it means: you know a lot of!!)...
You sure you didn't mean "loco", not "coco" ;)
It looks like you could also use the validator to show the error when the fields do not add up: this would be more useful with 3 or more fields (my current issue), where you can't force one to the appropriate value based on the other. Just replace your $().html() call with
validator.showErrors({"puhappy": "Sum of fields must be 100"});
or in my case, it would be "total" instead. (And I have a class assigned to the fields I sum, so I use .find(".allocation").each() to get the total.)
Always nice when someone discovers one of my old posts. I don't even remember this one. :)
Hi Raymond,
I am using your example but I want the validation to work for any number of fields. In your examples you have to enter the percentage for both fields but I need to have it working so that if you just enter one field (with 100%) it will validate. So you can have any number of fields instead of it relying on a set number of fields.
Wow, an old post here. :) Ronan, that sounds like an interesting idea. Let me work up a demo that supports N fields. I've not used jQuery Validate in a long time (not because I don't like it anymore, just because I haven't played with it), so it will be a refresher. I've got a presentation today and a flight tomorrow so I can't promise quick turnarond. I'll also look into some of the suggestions from others in this thread.
Here is an updated version:
https://gist.github.com/468...
I moved to using a class value to mark the individual items as required/numeric and added the min/max to each. I also switched the type to number which in modern browsers provided a basic step control.
The validation logic then gets simpler as we can just do a jQuery selector and get a total.
To be clear, there are multiple other ways of doing this. I wrote this one in about 10 minutes.
Hi Raymond,
I appreciate you coming back to me on this but unfortunately this still does not allow me to validate the entire form when you just have ONE field filled in. Does this make sense? Your new example still relies on entering the data into ALL fields. I require it to work that you may have these multiple fields but it will validate if you fill in ONE field with the 100% value. Again, I would greatly appreciate any help with this.
Kind Regards,
Ronan
Oh, so to be clear, you want N fields where you do not need to enter something in them all, but if you do, it must be numeric. And at the end, the sum is 100, right?
If so, take my code, and try removing required from the classes.
Yes that's exactly it. I tried removing the required class from the input fields and in the js code. still not working.
About to board a plane so pardon terseness.
https://gist.github.com/469...
Changed it to use a new selector to find the fields.
Check for empty string, and don't add if empty.
Seems to work for me.
Raymond, I just wanted to say thank you very much for this. It worked great. Thank you for the massive help. Greatly appreciated.
Kind regards,
Ronan
Glad to help.
Hi All,
Is there an updated version of the code based on all of the comments?
I jsut found this and using the original post (bottom code) have it working quite nicely but as mentioned the errors from the fields never clear when you update them like they normally do within the jquery validator.
Here is the basic code I am using
<div class="familypacks">
<p>Please select a combination of Adult and Youth brushes upto a maximum of {{limit}}</p>
<label for="adult">Number of Adult Brushes</label><input type="number" id="adult" name="properties[Adult]" />
<label for="youth">Number of Youth Brushes</label><input type="number" id="youth" name="properties[Youth]" />
<div class="familyerror"></div>
</div>
{{'jquery.validate.js' | asset_url |script_tag}}
{{'additional-methods.js' | asset_url |script_tag}}
{%if limit == '4'%}
<script>
$("#product-form-{{ product.id }}").validate({
rules: {
"properties[Adult]": {
required: true,
number: true,
min: 0,
max: 4,
},
"properties[Youth]": {
required: true,
number: true,
min: 0,
max: 4,
}
},
submitHandler: function(form){
var total = parseInt($("#adult").val()) + parseInt($("#youth").val());
if (total != 4) {
$("#product-form-{{ product.id }} div.familyerror").html("Your total must equal 4.")
return false;
} else form.submit();
}
});
The demos no longer work (because the paths to the jQuery scripts are now 404s). :(
You can view source and save locally. :)
fair enough :)