How to pass variables to jQuery validation - javascript

Building off of this SO question, I'm trying to pass two variables to a custom validator method. Using console.log I can see that the upper & lower ranges are defined in the HTML, but are not being passed correctly to the options, instead I'm getting NaN instead.
The problem seems to be that the values of the two text boxes are not defined or set yet (when they're sent in the validation rules below), but are when they arrive to the validator (this is just a guess, and I haven't been able to come up with a method to sniff their values prior to the validation attempt). So if I log them inside the validator method, they show up fine, but if I pass them as variables, they show up as NaN inside the PCBID object (Chrome browser):
Object {PCBID: Object}
PCBID: Object
lower: NaN
upper: NaN
__proto__: Object
__proto__: Object
Here's the validator, there are other rules set to prevent anything from integers being entered, so that shouldn't be the problem:
//this validator checks to make sure the user has entered the PCBIDs in
//the correct order in the range form.
$.validator.addMethod("highLowCheck", function (value, element, options)
{
console.log("Inside highLowCheck validator");
console.log(parseInt($('#pcbid_range_lower').val(),10)); //shows expected values
console.log(parseInt($('#pcbid_range_upper').val(),10)); //shows expected values
console.log(options.PCBID.upper); //NaN
console.log(options.PCBID.lower); //NaN
//evaluates to false because NaN is falsey
console.log(options.PCBID.upper > options.PCBID.lower);
console.log("Done logging");
return options.PCBID.upper > options.PCBID.lower;
}
);
Here are the variables I'm trying to pass:
pcbid_range_upper: {
required: true,
digits: true,
rangelength: [3, 6],
highLowCheck:
{ PCBID:
{
//this doesn't work
lower: parseInt($('#pcbid_range_lower').val(),10),
upper: parseInt($('#pcbid_range_upper').val(),10)
}
},
If I pass in primitive values like this:
highLowCheck:
{ PCBID:
{
lower: 1000, //works
upper: 2000
}
},
This method works, but it's not very useful because users can enter any value they like so I have to be able to pass them in as variables. I also need this to work with variables because I need to call it from more than one validation routine, otherwise I'd just use the variables in the validator directly (as I was before the need for more than one form to use the validator).
In case it's useful, here is the HTML for the two inputs:
<div data-role="fieldcontain" data-controltype="textinput" class="pcbid_selections" tabindex="2">
<label for="pcbid_range_lower">Starting PCBID *</label>
<input name="pcbid_range_lower" id="pcbid_range_lower" class="create_group" placeholder="52759" value="" type="text" data-mini="true" />
</div>
<div data-role="fieldcontain" data-controltype="textinput" class="pcbid_selections" tabindex="3">
<label for="pcbid_range_upper">Ending PCBID *</label>
<input name="pcbid_range_upper" id="pcbid_range_upper" class="create_group" placeholder="52769" value="" type="text" data-mini="true">
</div>
The Question:
How can I pass variables inside a rule to a custom validator?
EDIT:
The Solution: with thanks to #Sparky & Mathletics
The validator method was changed to only receive the strings of the names of the two variables I wanted to pass, not their contents. Then using #Mathletic's suggestion, I simply put them into jQuery variable form inside the validator:
//this validator checks to make sure the user has entered the PCBIDs in the
//correct order in the range form.
$.validator.addMethod("highLowCheck", function (value, element, options){
return parseInt($('#' + options.PCBID.upper).val(), 10) >
parseInt($('#' + options.PCBID.lower).val(), 10);
}
);
And called them from the rules like so:
highLowCheck:
{
PCBID:
{
lower: 'pcbid_range_lower',
upper: 'pcbid_range_upper'
}
},
Just an FYI to anyone who finds this, I tried passing in the pound sign ("#") with the strings from the rules (EG: '#pcbid_range_lower'), but that didn't seem to work. This method does, and you can just prepend the "#" in the validator method instead which works well.

Quote OP:
"The Question: How can I pass variables inside a rule to a custom validator?"
Declared within .validate()...
rules: {
myField: {
customMethod: [1, "two", 3, "foo"]
}
}
customMethod defined...
$.validator.addMethod("customMethod", function (value, element, options) {
console.log(options[0]); // <- 1
console.log(options[1]); // <- "two"
console.log(options[2]); // <- 3
console.log($('[name=' + options[3] + ']').val()); // <- value of field named "foo"
console.log(value); // <- the value of "myField"
console.log(element); // <- the "myField" object
// your function
}, "Custom message with options used as {0}, {1}, {2}, {3} etc.");
DEMO: http://jsfiddle.net/pupggbu7/
Documentation: http://jqueryvalidation.org/jQuery.validator.addMethod/

Related

How can I make this function take dynamic values?

I have created a function to test a input field, the function currently accepts 6 inputs, 3 of them are text to be typed and the other 3 are error messages that we get when certain text is typed, this works fine. The function will loop depending on the number of texts entered, so in this case it will enter text and then click submit and then check error message. It will do this 3 times because I am passing in 3 text values. However I want to be able to make it dynamic so that it can take any number values and loop according to however many are passed.
Cypress.Commands.add('checkErrMsg', (fieldText1, fieldText2, fieldText3, errorText1,errorText2, errorText3) => {
var fieldValues = [fieldText1, fieldText2, fieldText3];
var errorValues = [errorText1, errorText2, errorText3];
var sum =0;
fieldValues.forEach(function(entry) {
cy.get('.textBox').clear().type(entry)
cy.get('.addBtn').click()
cy.get('#errMsg').should('be.visible').and('have.contain',(errorValues[sum++])).click();
});
})
I add the custom command to namespace as required by typescript project in cyprss.
declare namespace Cypress {
interface Chainable {
checkErrMsg(fieldText1: string, fieldText2: string, fieldText3:string, errorText1:string
,errorText2: string, errorText3:string): Chainable<string>;
}
}
The best way to make this kind of variadic function is with a rest parameter. The tricky part is that you need two - one for the fields, one for the errors - if you have each of them as a separate argument. One option is a [string, string] tuple type for each pair of field and error. The declaration would look like this:
declare namespace Cypress {
type FieldAndErrorValues = [string, string];
interface Chainable {
checkErrMsg(...fieldAndErrorValues: FieldAndErrorValues[]): Chainable<string>;
}
}
And the implementation:
Cypress.Commands.add('checkErrMsg', (...fieldAndErrorValues: Cypress.FieldAndErrorValues[]) => {
fieldAndErrorValues.forEach(function([fieldValue, errorValue]) {
cy.get('.textBox').clear().type(fieldValue);
cy.get('.addBtn').click();
cy.get('#errMsg').should('be.visible').and('have.contain',(errorValue)).click();
});
});
And a possible invocation:
cy.checkErrMsg(['field1', 'error1'], ['field2', 'error2']);

what is the equivalent of a reduce in javascript

I'm a backend dev moved recently onto js side. I was going through a tutorial and came across the below piece of code.
clickCreate: function(component, event, helper) {
var validExpense = component.find('expenseform').reduce(function (validSoFar, inputCmp) {
// Displays error messages for invalid fields
inputCmp.showHelpMessageIfInvalid();
return validSoFar && inputCmp.get('v.validity').valid;
}, true);
// If we pass error checking, do some real work
if(validExpense){
// Create the new expense
var newExpense = component.get("v.newExpense");
console.log("Create expense: " + JSON.stringify(newExpense));
helper.createExpense(component, newExpense);
}
}
Here I tried to understand a lot on what's happening, there is something called reduce and another thing named validSoFar. I'm unable to understand what's happening under the hood. :-(
I do get the regular loops stuff as done in Java.
Can someone please shower some light on what's happening here. I should be using this a lot in my regular work.
Thanks
The reduce function here is iterating through each input component of the expense form and incrementally mapping to a boolean. If you have say three inputs each with a true validity, the reduce function would return:
true && true where the first true is the initial value passed into reduce.
true && true and where the first true here is the result of the previous result.
true && true
At the end of the reduction, you're left with a single boolean representing the validity of the entire, where by that if just a single input component's validity is false, the entire reduction will amount to false. This is because validSoFar keeps track of the overall validity and is mutated by returning the compound of the whether the form is valid so far and the validity of the current input in iteration.
This is a reasonable equivalent:
var validExpense = true;
var inputCmps = component.find('expenseform')
for (var i = 0; i < inputCmps.length; i++) {
// Displays error messages for invalid fields
inputCmp.showHelpMessageIfInvalid();
if (!inputCmp.get('v.validity').valid) {
validExpense = false;
}
}
// Now we can use validExpense
This is a somewhat strange use of reduce, to be honest, because it does more than simply reducing a list to a single value. It also produces side effects (presumably) in the call to showHelpMessageIfInvalid().
The idea of reduce is simple. Given a list of values that you want to fold down one at a time into a single value (of the same or any other type), you supply a function that takes the current folded value and the next list value and returns a new folded value, and you supply an initial folded value, and reduce combines them by calling the function with each successive list value and the current folded value.
So, for instance,
var items = [
{name: 'foo', price: 7, quantity: 3},
{name: 'bar', price: 5, quantity: 5},
{name: 'baz', price: 19, quantity: 1}
]
const totalPrice = items.reduce(
(total, item) => total + item.price * item.quantity, // folding function
0 // initial value
); //=> 65
It does not make sense to use reduce there and have side effects in the reduce. Better use Array.prototype.filter to get all invalid expense items.
Then use Array.prototype.forEach to produce side effect(s) for each invalid item. You can then check the length of invalid expense items array to see it your input was valid:
function(component, event, helper) {
var invalidExpenses = component.find('expenseform').filter(
function(ex){
//return not valid (!valid)
return !ex.get('v.validity').valid
}
);
invalidExpenses.forEach(
//use forEach if you need a side effect for each thing
function(ex){
ex.showHelpMessageIfInvalid();
}
);
// If we pass error checking, do some real work
if(invalidExpenses.length===0){//no invalid expense items
// Create the new expense
var newExpense = component.get("v.newExpense");
console.log("Create expense: " + JSON.stringify(newExpense));
helper.createExpense(component, newExpense);
}
}
The mdn documentation for Array.prototype.reduce has a good description and examples on how to use it.
It should take an array of things and return one other thing (can be different type of thing). But you won't find any examples there where side effects are initiated in the reducer function.

Vuejs - Assign computed properties to data()

I have an input field that I would like to either be blank or populate depending on the condition
Condition A: new student, blank field
Condition B: existing student, populated field
This data is coming from the data() function or a computed: property. For example:
data () {
return {
studentName: '', // empty student name property (/new student route)
studentPersonality: ''
}
},
computed: {
...mapGetters({
getStudent // existing student object (/edit student route)
})
}
My input field should either be blank if we are arriving from the /new student route, or populate the field with the existing student info, if we're coming from the /edit route.
I can populate the input field by assigning getStudent.name to v-model as shown below.
<input type="text" v-model="getStudent.name">
...and of course clear the field by instead assigning studentName to v-model
<input ... v-model="studentName">
Challenge: How can I use getStudent.name IF it exists, but fall back on the blank studentName data() property if getStudent.name does NOT exist? I have tried:
<input ... v-model="getStudent.name || studentName">
...which seemed to work, but apparently invalid and caused console errors
'v-model' directives require the attribute value which is valid as LHS
What am I doing wrong?
There's really no need to have the input field register to different properties in your vue component.
If you want to have a computed property that is also settable, you can define it using a set & get method.
computed: {
student_name: {
get: function() {
return this.$store.getters.get_student.name
},
set: function(val) {
this.$store.commit('set_student_name');
}
}
}
One other way is to separate the value from the input change handler in the input element itself, in this case you would use the getter as you've set it
<input type="text" :value="getStudent.name" #input="update_name($event.target.value)">
And lastly, if you need to really use two different properties you can set them on a created/activated hook (and answering your original question):
created: function() {
this.studentName = this.getStudent
},
activated: function() {
this.studentName = this.getStudent
}
You'll always need to delegate the update to the store though so I would either go with the get/set computed property, or the value/update separation

Javascript populate form after validation

I am doing a form for my javascript class, and I am getting stuck on a certain portion of it. I have a separate validator javascript file and call the function on the html file. All the validation works if the form areas are not filled in. What I want to do is if the fields are left blank they will fail the validation and will insert a value into that field. Below are an example of the form field, javascript function in the html page, and the external validator js file.
call function in html head:
function formvalidation(thisform) {
with (thisform) {
if (textbox_validation(first_name,"Please enter your first name.")==false)
{first_name.blur(); return false;};
if (textbox_validation(business_name,"Please enter your business. Please enter N/A if
you do not have one.")==false) { business_name.focus(); return false;
business_name.value=="N/A";};
The external js validator:
function textbox_validation(entered, alertbox) {
with (entered) {
if (value==null || value=="") {
alert(alertbox);
return false;
}
else {
return true;
}
}
}
So the validator works and focuses on the empty fields, but for some of my fields I want them to fill themselves with a certain value if validation fails or if it isnt filled int. The business_name line of code is when I tried to make it work. Any help is much appreciated!
Ordinarilly, you wouldn't use alert, but would instead put error messages in a span or div either near the input or at the top (or bottom) of the form. Additionally (as mentioned by #Frits van Campen) it is generally bad practice to use with
Try something like this instead:
function textbox_validation(entered, errormsg) {
var errbox = document.getElementById(entered.id + '-errors'); // just to prevent writing it twice
// Note this requires the input to have an id, and the errer box's id to be the same with an '-errors' suffix.
// Instead of using with, just acces properties normally
if (!entered.value) { // The `!` "neggation" operater makes "falsy" values `true`
// "falsy" values include `false`, the empty string, `0`, `null`, `undefined`, `NaN` and a few others
// Put the error message in the DOM instead of alerting it
errbox.innerHTML = errormsg;
return false;
}
else {
// Wipe any previous error messages
errbox.innerHTML = '';
return true;
}
}
And for the form validator, again; let's not use with. But also, when attempting to assing "N/A" to the value, you've used the comparison operator instead of the assignment operator, and you've done it after returning:
function formvalidation(thisform) {
// just use the `!` "negation" operator
if (!textbox_validation(thisform.first_name,
"Please enter your first name."))
{
thisform.first_name.blur();
return false;
}
if (!textbox_validation(business_name,
"Please enter your business. Please enter N/A if you do not have one."))
{
thisform.business_name.focus();
thisform.business_name.value = "N/A"; // for assignment, use `=`. `==` and `===` are used for comparison
return false; // a return statement ends the function, make sure it's after anything you want to execute!
}
}
Use the DOM to set the placeholder for the fields. Like this.
var myInput = document.getElementById('input1');
myInput.placeholder = 'This validation has failed.';

Can I post JSON without using AJAX?

I have some data, lets say:
var dat = JSON.stringify(frm.serializeArray())
I want to submit this to the server using a roundtrip (aka, non ajax).
I know this is possible, but I can't find any literature on it. Ideas?
(I am using jQuery, if that makes it easier)
EDIT: while all of these answers so far answer the question, I should have included that I want an "content type" of "application/json"
Create an HTML form with unique "id" attribute. You can hide it using CSS "display:none". Also fill the action and method attributes.
Add a text or hidden input field to the form. make sure you give it a meaningful "name" attribute. That's the name that the server would get the data within.
Using JQuery (or plain old javascript) copy the variable "dat" into the input field
Submit the form using script
There is a working draft to support the so called HTML-JSON-FORMS, see:
http://www.w3.org/TR/2014/WD-html-json-forms-20140529/
So far use ajax or send the json into an input text field.
<form action="xxx.aspx" method="POST">
<input type='hidden' id='dat' />
<!-- Other elements -->
</form>
<script type='text/javascript'>
$('#dat').val(JSON.stringify(frm.serializeArray()));
</script>
You would need to assign the json string to an input's value inside a form tag in order for it to get POSTed to the server (either by the user submitting the form or by clicking the submit button programmatically).
Alternatively from javascript you could use window.location to send the variable as part of a GET request.
In another answer someone mentioned a W3 working draft which is outdated now and the newer version of the document says we can use enctype="application/json" attribute for the form and it will send the whole form fields as properties of an object.
It is still unclear to me how to send an array though, but refering to the above document you can send an object simply as:
<form enctype='application/json'>
<input name='name' value='Bender'>
<select name='hind'>
<option selected>Bitable</option>
<option>Kickable</option>
</select>
<input type='checkbox' name='shiny' checked>
</form>
// produces {"name": "Bender", "hind": "Bitable", "shiny": true}
I can't copy the whole doc here, so check out the document to see how to create more complex objects using array notation and sparsing arrays in input field names.
To create the form out of your object, you have to make a series of input elements, that produces the same JSON object you have in hand. You can either do it manually, or if your object is large enough, you can use a code snippet to convert your object to the desired input elements.
I ended up with something like the code below as the base.
You can change it to your need (e.g. make the form hidden or even produce more diverse input field types with styles for different property types for a real proper form)
(function () {
const json = {
bool: false,
num: 1.5,
str: 'ABC',
obj: {b:true, n: .1, s: '2', a: [1, '1']},
arr: [
true, 500.33, 'x', [1, 2],
{b:true, n: .1, s: '2', a: [1, '1']}
]
};
const getFieldHTML = (value, name) => {
if (name||name===0) switch (typeof value) {
case 'boolean': return `<input type="checkbox" name="${name}" ${value?'checked':''}>\n`;
case 'number': return `<input type="number" name="${name}" value="${value}">\n`;
case 'string': return `<input type="text" name="${name}" value="${value}">\n`;
}
return '';
};
const getFieldsHTML = (value, name) => {
const fields = [];
if (value instanceof Array)
fields.push(...value.map((itemValue, i) =>
getFieldsHTML(itemValue, name+'['+i+']')
));
else if (typeof value === "object")
fields.push(...Object.keys(value).map(prop =>
getFieldsHTML(
value[prop], //value is an object
name?(name+'['+prop+']'):prop
)
));
else
fields.push(getFieldHTML(value, name));
return fields.join('');
};
const fieldsHTML = getFieldsHTML(json);
const frm = document.createElement('form');
frm.enctype = 'application/json';
frm.method = 'POST';
frm.action = 'URL GOES HERE';
frm.innerHTML = fieldsHTML;
console.log(fieldsHTML);
console.log(frm)
})();
Check your browser's console to inspect the created form DOM and its children.

Categories

Resources