I am looking for advice. I've got quite a large form I'm building and I've never done something like this before so I am trying to figure out the best way to do things. I'm working with vanilla JS to validate my fields. I want to set up essentially two layers of validation. The first layer would be for each individual field. On blur I want to check for little things, like, if it is a name input and they put numbers, or for the postal code that it follows the direct pattern.
For each of these I want to have a separate validator and, at the end, if all the little validations are true AND all the fields have been filled out, I want a disabled input to become enabled.
Can anyone guide me as to the best way to go about this?
Any help would be great.
Well, actually there is no best way, coz every dev will choose his own way to make validation. At least this is already automated by plugins.
But some advices regarding to validation and forms in general can be helpful:
1) Always mark your required fields.
2) Depending on validation tests append/remove class from your inputs, so user can see what exsactly he doing wrong.
3) You can combine by class(or other attribute) your input validation(input group validation) For example you could have several text inputs with same validation rules:
var input = document.querySelectorAll('.someinputgroup');
input.forEach(function (elem, index) {
var that = input[index];
validationRule(that);
});
function validationRule(elem) {
elem.addEventListener('blur', function (e) {
// validation....
})
}
4) If you want to use frontend email validation, take a look at this patterns: Validate email address in JavaScript?
5) If you expect dynamic inputs, that can be loaded through AJAX, you should wait for your inputs(solution - How to wait until an element exists?)
6) Use FormData() for collect large input values like base64 encoded images or large textarea text values(if you going to POST this data via AJAX)
7) And ofc always recheck validation on server side.
I hope it can be helpful.
You could build simple "model" of your form, and then pass it serialized form, with triggering callbacks on invalid data and success. Similar to what Backbone.Model do.
I haven't test it, but here some code to give you idea of what i mean.
function SomeForm(options) {
this.onSuccess = options.onSuccess;
this.onInvalid = options.onInvalid;
}
SomeForm.prototype = {
valiadate: function validate(data) {
// Your validation logic
},
submit: function submit(data) {
var validationErrors;
validationErrors = this.validate(data);
if (!validationErrors) {
this.onSuccess(data);
return;
}
this.onInvalid(validationErrors);
}
};
function serializeForm(formEl) {
var fields, data;
data = {};
fields = formEl.querySelectorAll('input, select, textarea');
for (var i = 0; i < fields.length; i++) {
if (fields[i].disabled === true || !fields[i].name) {
continue;
}
data[fields[i].name] = fields[i].value;
}
return data;
}
var someForm = new SomeForm({/*callbacks*/});
var formEl = document.querySelctor('.foo');
someForm.submit(serializeForm(formEl));
Related
total programming novice here - I don't know much of javascript, wasn't programming since university (about 10 years ago) - trying to solve one specific problem on my website.
I am using CRM Bitrix24 and I have an unsubscribe form from this CRM placed on my website. I need to setup the form the way that the email is loaded from URL parameter.
I have done that simply by loading the input and set input.value = "email from URL". My problem is that the form has some kind of validation, and even though there is a text filled in the input field, the form is giving me the error: Field is required.
Screenshot here: https://ibb.co/Ns33GVN
The code of external form look like this:
<script data-b24-form="inline/120/8y7xg2" data-skip-moving="true">(function(w,d,u){var s=d.createElement('script');s.async=true;s.src=u+'?'+(Date.now()/180000|0);var h=d.getElementsByTagName('script')[0];h.parentNode.insertBefore(s,h);})(window,document,'https://cdn.bitrix24.com/b7014957/crm/form/loader_120.js');</script>
My JS:
function emailPopup(){
var params = new window.URLSearchParams(window.location.search);
var email = params.get('email');
const emailCollection = document.getElementsByName("email");
for (let i = 0; i < emailCollection.length; i++) {
if (emailCollection[i].name == "email") {
//emailCollection[i].focus();
emailCollection[i].value = email;
}
}
} window.addEventListener('load', function () {
emailPopup();
})
I tried to understand how the validation is done, but with no luck. The field has autocomplete = yes, so once it is submitted, next time it's not shooting the error, but the form is sent with the email submited at the first attempt, even though it is showing another one when hitting the SUBMIT button. It seems like it's only showing the email address from URL parameter, but in fact it's using wrong value, it's even empty (first attempt) or wrong (second attempt).
Is there a way how to force the field to pretend it was modified by user? Any ideas?
I have tried to setup similar environment here in jsfiddle: https://jsfiddle.net/e395wf6m/17/
Thanks a lot for any feedback!
I have a theory and it seems to be correct, as I tested it in your fiddle.
My theory is that the validation is done by firing a change event, so you need to trigger it. Luckily JavaScript let us do it:
if (emailCollection[i].name == "email") {
//emailCollection[i].focus();
emailCollection[i].value = email;
// to trigger the change event
emailCollection[i].dispatchEvent((new Event('change')));
}
As I said, it worked when I tested it on your fiddle, let me know if it works for you =]
I have purchased a booking plugin (wordpress) to add to a site.
https://wpamelia.com/
I cannot show the site I am working on, but here a demo from plugin developers
https://sports.wpamelia.com/#book
Once you have chosen your date and time, you end up on a form with input fields.
I was able to pre-fill this form with data that I could pass via the URL.
My URL would look something like this: https://sports.wpamelia.com/?first=Jim&last=Tester&email=something%40something.com&phone=0222222222#book
But here is the problem:
Even though I managed to use jQuery to pre-fill the input fields of the form, as soon as I click confirm the fields' content is erased and the error "Please enter... " appears for each of them.
So again:
STEP 1: I open the booking page with an URL containing data in the query string
STEP 2: Using jQuery, I manage to pre-fill the form that appears after having chosen date and time (first name, last name ...)
STEP 3: I click "Confirm"
RESULT: all the fields are empty and for each one the error message "Please enter first name" (etc..) appears
I've messaged the plugin developers. Only answer was that there is indeed no functionality to take the data from the Query String into the form fields yet.
MY QUESTIONS:
1) How could I find out, with chrome inspector or other tools, why exactly the content I pre-fill into the form is ignored?
---> I've tried things like getEventListeners in the chrome inpector's console, but I don't really see how to get information out of that
2) Would anyone know what the issue is and/or how I could bypass it?
---> there is a lot of javascript from the plugin developers behind that and something is expecting manual entering of the data into the fields...
---> but even when trying to fake manual entering with things like $(this).trigger("change").val(function(i,val){return 'aaaa';}); this didn't solve the problem....
(If anyone is interested, I can post later my javascript/jQuery functionality to get the form fields pre-filled with data from Query String... interesting code as you have to wait until the fields appear for jQuery to recognise them..)
Thanks so much for any help!
cheers
Admino
#Admino - this may not be the best solution and I know this is an old question so you may not need it now but after not finding a better one it at least worked for me.
function getUrlVars() {
var vars = {};
var parts = window.location.href.replace(/[?&]+([^=&]+)=([^&]*)/gi, function(m,key,value) {
vars[key] = value;
});
return vars;
}
function valueOutput(element) {
element.dispatchEvent(new Event('input'));
}
jQuery(function() {
jQuery(document).on('change', 'input', function(e) {
valueOutput(e.target);
});
// you may want to perform more validations here if needed
// just checking here if email is present (but not checking for valid email address)
var fname = getUrlVars()["first"];
var lname = getUrlVars()["last"];
var email = getUrlVars()["email"];
var phone = getUrlVars()["phone"];
var custom1 = getUrlVars()["custom1"]; // you know this field label is Order Number
if (email.length > 0) {
// run an interval until the elements are present on the page (form displayed)
var checkInputs = setInterval(function() {
if (jQuery('.amelia-app-booking label[for="customer.email"]').length > 0) {
var em = jQuery('.amelia-app-booking label[for="customer.email"]').closest('.el-form-item').find('.el-input__inner');
// this checks to see if an Amelia customer is already present
if (em.val() == '') {
em.prop('value', email).val(email).trigger('change');
jQuery('.amelia-app-booking label[for="customer.firstName"]').closest('.el-form-item').find('.el-input__inner').prop('value', fname).val(fname).trigger('change');
jQuery('.amelia-app-booking label[for="customer.lastName"]').closest('.el-form-item').find('.el-input__inner').prop('value', lame).val(lame).trigger('change');
jQuery('.amelia-app-booking label[for="customer.phone"]').closest('.el-form-item').find('.el-input-group__prepend').siblings('.el-input__inner').prop('value', phone).val(phone).trigger('change');
}
// for custom fields I check the label text to find the correct input
if (custom1 != '') {
jQuery('.amelia-app-booking label:contains("Order Number")').closest('.el-form-item').find('.el-input__inner').prop('value', custom1).val(custom1).trigger('change');
}
// form info is updated so clear the interval
clearInterval(checkInputs);
}
}, 500);
}
});
You may want to try a different method than url params to sync this info so it's not so public in the url string. This code may not need both the prop and val jquery setters but I just left them for you to try. Hope it helps (and to others I'm open to a better solution)!
I am at the very beginning with my Angular learning and I implemented this form:
http://codepen.io/jgrecule/pen/WxgqqO
What it is supposed to do is very basic: it consumes Flickr public JSONP feed as per Flicker specs https://www.flickr.com/services/feeds/docs/photos_public/ and renders the retrieved pictures thumbnails
The form I implemented has a submit button as well as a reset one. My problems I am trying too find solutions in the order of their importance are:
The very first time when you typing tags everything works but when u try to submit the request again by either adding a new tag or an user Id or anything it no longer works. I can see this warning in the logs but I have no idea what is causing it WARNING: Tried to load angular more than once.
The reset only works for the thumbnails but not for the other controls in my page
I would like to find a way to show an error message when the user pushes on the search flicker button and both tags and user ids input fields are empty. I tried to implement a custom directive but it was no way to get it working.
Thank you in advance for your inputs.
You are loading Angular more than once.
Your resetForm function doesn't reset the form at all. It just calls $setValidity on two of the form elements. It looks like it does try and reset the form in another part of your code with
document.getElementById("searchCriteriaTags").innerHTML = "";
document.getElementById("searchCriteriaIds").innerHTML = "";
document.getElementById("images").innerHTML = "";
which means you are modifying the DOM directly, about which see point 4.
You can add a simple check as to whether $scope.form.tags === '' and so the same for the other fields in your form.
Having addressed your 3 points, I'm afraid to say your code has bigger problems. You are modifying the DOM directly all over the place and you have a lot of duplicate code, plus lots of very complex conditionals.
EDIT 1 in response to OP's comment:
The Angular way of clearing form fields would be to simply clear the scope objects that the form fields are bound to. In other words it is as simple as doing something like:
$scope.tags = [] // for arrays
$scope.name = '' // for strings
Because the form fields are bound to these scope variables through the ng-model directive, changing the variables will also change the form fields.
Setting an error message when two fields are empty you can do like this:
$scope.checkFields = function(field1, field2) {
var oneEmpty = field1 === '';
var twoEmpty = field2 === '';
if (oneEmpty && twoEmpty) {
// call the failure message here
}
}
EDIT 2 in response comments:
Firstly good to see that your code is looking a lot cleaner. Secondly, the reason it fails is because in your search function you set the search fields to null, eg searchCriteria.tags = null;. You should set them to empty strings instead: searchCriteria.tags = '';.
I don't know what the purpose of checkFields is so I don't know where you want to place it. If you want to show an error message if the fields are empty then I'd say have checkFields() return a boolean and use ng-show to display the error div if checkFields() === false.
HTML:
<div ng-show="checkFields() === false">Fields can't be empty</div>
JS:
$scope.checkFields = function(field1, field2) {
var oneEmpty = field1 === '';
var twoEmpty = field2 === '';
return (oneEmpty || twoEmpty);
}
I'm using mootools in 1 of my projects. I'm working on easy to be customised menu based on sortables.
So here's the issue - I've got the form which contains menu node informations. Form is updated with JS whenever user chooses different node (form is populated with new data). Problem is that "reset" button obviously "remembers" the initial data, so whenever user clicks it, it loads initial data form.
Is there anyway to update the "default" form status, whenever i load new data? (ofc i could write piece of code which do whatever i need, but if there is some simplier solution which allows default "reset" button to work with new data would be much less work to use it :))
Thanks in advance for help
i cant think of anything else except getting a new source through ajax with data prepopulated and replace the innerhtml hence replacing the form itself.
you can use something like toQueryString to serialize the form and then reverse it based upon the mootools-more's String.parseQueryString():
(function() {
Element.implement({
saveFormState: function() {
if (this.get('tag') !== 'form')
return;
this.store("defaults", this.toQueryString());
},
restoreFormState: function() {
if (this.get('tag') !== 'form')
return;
var vals = this.retrieve("defaults");
if (!vals.length)
return;
var self = this;
Object.each(vals.parseQueryString(vals), function(value, key) {
var el = self.getElement("[name=" + key + "]");
el && el.set('value', value);
});
}
});
})();
var f = document.id('f');
// save default
f.saveFormState();
document.getElement("button").addEvent("click", f.restoreFormState.bind(f));
this ough to cover most cases and you can always save a new defaults state. need to test it somewhat with radios and suchlike, though.
here's a basic fiddle with a save/restore: http://jsfiddle.net/UWTUJ/1/
relevant docs: http://mootools.net/docs/more/Types/String.QueryString (more) and http://mootools.net/docs/core/Element/Element#Element:toQueryString (core)
implied reliance on name attributes on all form elements you want to save/restore.
I have previously created more complex save/restore states that even return field CSS class names, validation messages, etc.
I have this fiddle: http://jsfiddle.net/y8Uju/5/
I am trying to save the numbers, because, when I submit, the list of numbers gets erased. I am a little new to JavaScript so am not quite familiar to what is available. In PHP I would use sessions to save the list, but what can I do in JavaScript to do this?
Here is the JavaScript code:
function bindName() {
var inputNames = document.getElementById("names").getElementsByTagName("input");
for (i = 0; i < inputNames.length; i++) {
inputNames[i].onkeydown = function() {
if (this.value == "") {
setTimeout(deletename(this), 1000);
}
}
}
}
document.getElementById("addName").onclick = function() {
var num1 = document.getElementById("name");
var myRegEx = /^[0-9]{10}$/;
var itemsToTest = num1.value;
if (myRegEx.test(itemsToTest)) {
var form1 = document.getElementById("names");
var nameOfnames = form1.getElementsByClassName("inputNames").length;
var newGuy1 = document.createElement("input");
newGuy1.setAttribute("class", "inputNames");
newGuy1.setAttribute("id", nameOfnames);
newGuy1.setAttribute("type", "text");
newGuy1.setAttribute("value", num1.value);
form1.appendChild(newGuy1);
num1.value = "";
bindName();
}
else {
alert('error');
}
};
function deletename(name) {
if (name.value == "") {
document.getElementById("names").removeChild(name);
}
}
You can use localStorage: http://jsfiddle.net/y8Uju/8/
Loading:
var saved = JSON.parse(localStorage["numbers"] || "[]");
for(var i = 0; i < saved.length; i++) {
document.getElementById("name").value = saved[i];
add(false);
}
Saving:
var saved = JSON.parse(localStorage["numbers"] || "[]");
saved.push(num1.value);
localStorage["numbers"] = JSON.stringify(saved);
And define the function of the addName button separately, so that you can call it when loading as well.
Edit: You have to execute a function when the page is loading to fetch the stored numbers, and add some code to save the entered number when one clicks the Add button.
For storing you can use localStorage, but this only accepts Strings. To convert an array (an array containing the entered numbers), you can use JSON.
When loading, you need to add the numbers just like happens when the user fills them in. So you can set the name input box value to the saved number for each element in the array, and then simulate a click on the Add button.
So you need an add function that is executed when:
User clicks Add button
Page is loaded
However, when simulating the click the numbers should not get stored again. You need to distinguish between a real click and a simulated one. You can accomplish this by adding an argument to the add function which represents whether or not to store.
Not entirely sure what the question is, but one problem I see with the code - id's can't be numbers, or start with numbers
var nameOfnames = form1.getElementsByClassName("inputNames").length;
//....
newGuy1.setAttribute("id", nameOfnames);
That might be slowing you down somewhat. Perhaps set id to 'newguy' + nameOfnames
Jeff, the reason that the page keeps getting erased is because the form submission triggers a page reload. You need to place a listener on the form submit event and then send the data through AJAX. This way the data is POSTed to "text.php" without reloading the page with the form.
You could place the values in a cookie but that is not ideal because you have a fairly limited amount of space to work with (4kb). I also get the feeling that you're trying to hand them off to some server side script so HTML5 local storge wouldnt be a good solution, not to mention that your eliminating over half of the people on the internet from using your site that way.
Since browsers are inconsistent in how they attach event listeners AND how they make AJAX requests. I think that most people would recommend that you use a library like jQuery, dojo, or prototype which abstract the process into one function that works in all browsers. (my personal fav is jQuery)
There are a few options available to you:
Save it client side using cookies (http://www.quirksmode.org/js/cookies.html)
Save it client side using HTML5 local storage (http://diveintohtml5.ep.io/storage.html)
Save it server-side using Ajax
The Ajax solution involves a server side page (in PHP for example) that reads a request (a POST request for example) and saves it into a database or other. You then query that page in JavaScript using XmlHTTPRequest or your favorite library.