I am trying to implement HTML5 history with an AJAX form.
The form contains some radio buttons and dropdowns. Upon changing any of these inputs, the form is automatically submitted and results are returned via AJAX.
Now having implemented history, the URL gets updated, so it looks like this for example:
/currencies?type=usd&year=2015
Here is how I perform the AJAX and update the URL:
$('#currency-form input, #currency-form select').change(function() {
var form = $('#currency-form');
var url = form.attr('action');
var data = form.serialize();
$.ajax({
url: url,
type: 'get',
dataType: 'json',
data: data,
success: function(response) {
// update the page content
processResponse(response.data);
// update the page url
window.history.pushState({}, response.meta_title, response.new_url);
}
});
});
To detect the back button, I have done the following:
$(window).on('popstate', function(event) {
var state = event.originalEvent.state;
if (state !== null) {
console.log(state);
}
});
There is one thing I am struggling with. Upon pressing the back button, it should pre-select the previous form values and submit the form again.
Does anybody know how I can achieve this?
1- HTML5 Tag Method:
Use Autocomplete tag for the form, HTML5, but can't be sure about all browsers' compatibility.
<form action="" method="post" autocomplete="on">
Check this post
2- jQuery Method:
See this example:
Using jQuery plugin for cookies.
To set a cookie; use something like this example:
(function(){
// get elements values
var checkbox1 = $('#checkbox1').val();
var radio1 = $('input[id="radio1"]:checked').val();
var textbox1 = $("#textbox1").val()
//save elements values into cookies
$.cookie("radio1", checkbox1);
$.cookie("checkbox1", radio1);
$.cookie("textbox1", textbox1);
});
Then to load the values upon your desired event:
(function (){
//read value from cookie.
var radio1= $.cookie("radio1");
var checkbox1= $.cookie("checkbox1");
var textbox1= $.cookie("textbox1");
$('#radio1').prop('checked', radio1);
$('#checkbox1').prop('checked', checkbox1);
$('#textbox1').val(textbox1);
// Etc...
});
When submission or on live editing; try to save the entry in cookies and when you back to the form...load that values into fields.
3- Populate from JSON Data:
Reference:
// reset form values from json object
$.each(data, function(name, val){
var $el = $('[name="'+name+'"]'),
type = $el.attr('type');
switch(type){
case 'checkbox':
$el.attr('checked', 'checked');
break;
case 'radio':
$el.filter('[value="'+val+'"]').attr('checked', 'checked');
break;
default:
$el.val(val);
}
});
The way to accomplish this is to push the new input field values in to state as follows:
var state = {
field1: $('#input1').val(),
field2: $('#input2').val()
};
window.history.pushState(state, response.meta_title, response.new_url);
Then when you detect back button event, do the following to repopulate the input fields:
$('#input1').val(state.field1);
$('#input2').val(state.field2);
Related
I have an email sign-up form on a website.
The form appears in two areas of each web page: the header and the footer
It's the same exact form, just available on the top and bottom of the page for better UX and accessibility.
The form uses a jQuery/AJAX script to provide success and error responses to the user. (i.e., "Success! Your subscription is complete." and "Error. Please review and re-submit")
The problem I'm having is that the header form processes but the footer form does not.
Any ideas what's wrong with this code? Thanks.
P.S. The form was working perfectly when the header and footer forms each had their own script. The problem started when the scripts were consolidated into one file. I've posted the original scripts at the bottom. Also, nothing has been changed in the PHP, so I don't think the problem is there.
$(function() {
// get the forms
var form = $('#header-form, #footer-form');
// set up event listener
$(form).submit(function(e) {
// disable html submit button
e.preventDefault();
// get the submit button
var submitButton = $('[type=submit]', this);
// get the messages element
var formResponses = $('#header-form-responses, #footer-form-responses', this);
formResponses.text(" ");
// serialize form data
var formData = $(form).serialize();
// disable submit button to prevent unnecessary submission
submitButton.attr('disabled', 'disabled');
// tell users that form is sending
submitButton.text('Processing...');
// submit form via AJAX
$.ajax({
type: 'POST',
url: $(form).attr('action'),
data: formData
})
.done(function(response) {
// make sure formResponses element has 'success' class
$(formResponses).removeClass('error');
$(formResponses).addClass('success');
// set message text
$(formResponses).text('Your subscription is complete. Thank you!');
// clear form
$('input').val('');
})
.fail(function(data) {
// make sure formResponses element has 'error' class
$(formResponses).removeClass('success');
$(formResponses).addClass('error');
// set the message text
$(formResponses).text('Input error. Please review and re-submit.');
})
.always(function(data) { // this will always fire even if the request fails
submitButton.removeAttr('disabled');
submitButton.text('Send');
});
});
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<!-- simplified HTML -->
<form action="form_processing.php" method="post" id="header-form">
<input type="email" name="email_subscription">
<button type="submit" id="header-form-submit">Submit</button>
<div id="header-form-responses"></div>
</form>
<form action="form_processing.php" method="post" id="footer-form">
<input type="email" name="email_subscription">
<button type="submit" id="footer-form-submit">Submit</button>
<div id="footer-form-responses"></div>
</form>
Here's the original header code (works perfectly):
$(function() {
var form = $('#header-form');
var formResponses = $('#header-form-responses');
var submitButton = $("#header-form-submit");
$(form).submit(function(e) {
e.preventDefault();
var formData = $(form).serialize();
submitButton.attr('disabled', 'disabled');
submitButton.text('Processing...');
$.ajax({
type: 'POST',
url: $(form).attr('action'),
data: formData
})
.done(function(response) {
$(formResponses).removeClass('error');
$(formResponses).addClass('success');
$(formResponses).text('Your subscription is complete. Thank you!');
$('input').val('');
})
.fail(function(data) {
$(formResponses).removeClass('success');
$(formResponses).addClass('error');
$(formResponses).text('Input error. Please review and re-submit.');
}).always(function(data) {
submitButton.removeAttr('disabled');
submitButton.text('Send');
});
});
});
Here's the original footer code (works perfectly):
$(function() {
var form = $('#footer-form');
var formResponses = $('#footer-form-responses');
var submitButton = $("#footer-form-submit");
$(form).submit(function(e) {
e.preventDefault();
var formData = $(form).serialize();
submitButton.attr('disabled', 'disabled');
submitButton.text('Processing...');
$.ajax({
type: 'POST',
url: $(form).attr('action'),
data: formData
})
.done(function(response) {
$(formResponses).removeClass('error');
$(formResponses).addClass('success');
$(formResponses).text('Subscription complete.');
$('input').val('');
})
.fail(function(data) {
$(formResponses).removeClass('success');
$(formResponses).addClass('error');
$(formResponses).text('Input error. Please review and re-submit.');
}).always(function(data) {
submitButton.removeAttr('disabled');
submitButton.text('Send');
});
});
});
Within the $(form).submit( you're still using $(form), eg
var formData = $(form).serialize();
as form = $('#header-form, #footer-form') any call to $(form) (or just form) will affect/apply to/read from both forms. This depends on what the call is, eg form.attr("action") will always get the action from the first form.
Within the handler, change all $(form) (or just form) to $(this):
var formData = $(this).serialize();
...
url: $(this).attr('action'),
be careful using this inside a callback, so if you do need the relevant form then instead, change to
$('#header-form, #footer-form').submit(function(e) {
var form = $(this);
and continue to use form.
Note that in your code form is already a jquery object, but jquery allows you to "double wrap" - ie $(form) is the same as $($(form))
I recommend you remove the outer form variable completely, ie change to
// set up event listener
$('#header-form, #footer-form').submit(function(e) {
which will help to remove the issue of using form not meaning this form.
Sorry I am a beginner with jQuery and Javascript. I want to be able to get the results into my modal from any form on the page that has class ajax. My code is below but not working correctly. Currently it opens the post result in a new page and not in the modal. Can anyone shed any light on my code?
Many thanks
$(document).ready(function() {
$('.ajax').click(function() {
var that = $(this),
url = that.attr('action'),
type = that.attr('method'),
data = {};
that.find('name').each(function(index, value) {
var that = $(this),
name = that.attr('name'),
value = that.val();
data[name] = value;
});
console.log(value);
// AJAX request
$.ajax({
url: url,
type: type,
data: data,
success: function(response){
// Add response in Modal body
$('.modal-body').html(response);
// Display Modal
$('#aaModal').modal('show');
}
});
});
});
This probably happens because your browser submits the form by default. It doesnt know youre doing AJAX stuff. To prevent this, use preventDefault().
In addition to that, jQuery has a built in function for serializing (1 and 2) form data.
$(document).ready(function() {
$('form.ajax').click(function(event) {
event.preventDefault(); // prevents opening the form action url
var $form = $(this),
url = $form.attr('action'),
type = $form.attr('method'),
data = $form.serialize();
// console.log(value); // value doesnt exist outside of your loop btw
// AJAX request
$.ajax({
url: url,
type: type,
data: data,
success: function(response){
// Add response in Modal body
$('.modal-body').html(response);
// Display Modal
$('#aaModal').modal('show');
}
});
});
});
Also, its not quite clear if you bind the click event handler to a form or a button, I guess the first one. You should change the handler to the following:
$(document).ready(function() {
$('form.ajax').on('submit', function(event) {
I would like to validate a form with an AJAX request to the server and then swap the form html in the web browser with the form html from the server because this would be an easy implementation in theory. It is proving a nightmare though because the change event is triggered without the user interacting further after the first interaction which triggered the first change event. Consequently an infinite loop of AJAX requests to the server is happening.
The html form sits inside a div which has classes 'container mb-4'. This is the JS code -
var _cont = $('.container.mb-4')
var _form = $('.custom-form')
function ajax_validation(form) {
form.on('change', 'input, select, textarea', function() {
form_data = form.serialize()
$.ajax({
url: "/form/6/",
type: "POST",
data: form_data,
success: function(data) {
if(!(data['success'])) {
_cont.empty()
_cont.append(data['form_html'])
form = _cont.find('form')
ajax_validation(form)
}
},
error: function () {
form.find('.error-message').show()
}
});
})
}
ajax_validation(_form)
The change event I am assuming is triggered because the server returns a form input field with a different csrf token as the value to the previous input field - all other fields are the same. So an obvious solution would be to keep the same csrf token. But I want to understand why the JS code isn't working. I thought destroying the form would destroy the change event bound to it. So am at a loss to explain this infinite loop. How do I change this so I can just swap the form and not trigger another change event until the user really does change something?
It's not a good thing to use events in function no need to do that
Also your event here for input , select , textarea for serialize you need to select the closest() form
Try the next code
var _cont = $('.container.mb-4');
var _form = $('.custom-form');
_cont.on('change', 'form input,form select,form textarea', function() {
var ThisForm = $(this).closest('form');
var form_data = ThisForm.serialize();
$.ajax({
url: "/form/6/",
type: "POST",
data: form_data,
success: function(data) {
if(!(data['success'])) {
_cont.html(data['form_html']);
}
},
error: function () {
ThisForm.find('.error-message').show()
}
});
});
And logically if(!(data['success'])) { should be if(data['success']) {
First let's understand the issue that you have. You have a function called ajax_validation that is defining a change event on the form's elements which, on response will call ajax_validation. So, if any change happens on your elements, then a new request is sent to the server. So, if any value is changed, like a token, the request will be sent again. You could use a semaphore, like this:
var semaphore = true;
function ajax_validation(form) {
form.on('change', 'input, select, textarea', function() {
if (!semaphore) return;
semaphore = false;
form_data = form.serialize()
$.ajax({
url: "/form/6/",
type: "POST",
data: form_data,
success: function(data) {
if(!(data['success'])) {
_cont.empty()
_cont.append(data['form_html'])
form = _cont.find('form')
ajax_validation(form)
}
semaphore = true;
},
error: function () {
form.find('.error-message').show()
}
});
})
}
Something like this should solve your issue for the time being, but you should consider refactoring your code, because what you experience is well-known and is called callback hell.
Turns out the password field was coming back blank from the server - this django must do out of the box if the PasswordInput widget is used. So the form is replaced with a new form which lacks the password input from the before. The browser was then applying the autofill password value to the form which was triggering the change event.
This is my code now. It checks that the form_data about to be sent for validation really is different to before minus the csrf token which will be different.
It is based on Mohamed's answer -
var _cont = $('.container.mb-4');
var _form = $('.custom-form');
var prev_data = undefined
_cont.on('change', 'form input,form select,form textarea', function() {
var ThisForm = $(this).closest('form');
var form_data_wo_csrf = ThisForm.find("input, textarea, select").not("input[type='hidden']").serialize()
if(form_data_wo_csrf == prev_data) {
return
}
var form_data = ThisForm.serialize()
$.ajax({
url: "/form/6/",
type: "POST",
data: form_data,
success: function(data) {
if(!(data['success'])) {
_cont.html(data['form_html']);
prev_data = form_data_wo_csrf
}
},
error: function () {
ThisForm.find('.error-message').show()
}
});
});
Trying to create a simple crud operation using edit and delete. Currently on edit functionality.
Functionality works like this:
1) user clicks on edit icon, bootstrap modal opens up with form information that is fetched from ajax request containing users id. Forms value fields are auto populated based on fetched ajax request.
2) User can edit the auto populating form fields editing the users information and a separate ajax request goes out that edits the users record.
Problem:
This all works how I wanted to but I noticed a small bug that I'm stuck on to where when I go edit a users information, hit send and then close out of the modal to open and edit another users information and hit send again, the previous forms data is added onto the data I'm currently editing and I don't want that. I just want to open up the modal and edit and send the current users data that I'm on.
In addition, I also noticed If I keep trying to edit multiple users information, multiple ajax requests are sent each time I try and edit another record. I tried looking up my problem and I think it's because of my events bubbling up in which I tried e.stopPropagation; and return false; and it did nothing to solve my problem. I feel like I'm close but there is something that I'm missing.
If you need my HTML code as well I'll be happy to show that.
Here is my code:
$(document).on('click', 'i.fas.fa-edit', function(userId){
emailField.value = 'Please wait...!';
fullnameField.value = 'Please wait...!';
areaField.value = 'Please wait...!';
personField.value = 'Please wait...!';
// By Default
adminAccess.checked = false;
personField.style.display = 'block';
adminAccess.addEventListener('click', function(){
if(adminAccess.checked == true) {
areaField.style.display = "none";
} else {
personField.style.display = "block";
}
});
var usersId = {
usersId: userId.target.id
}
// Select user data
$.ajax({
method: 'POST',
url: 'select-user-action.php',
data: usersId,
success: function(data){
var parseData = JSON.parse(data);
parseData.forEach(function(element){
emailField.value = element.email;
fullnameField.value = element.name;
areaField.value = element.areaField;
personField.value = element.personField;
});
}
});
});
$('#editModal').on('show.bs.modal', function () {
submitForm.addEventListener('submit', function(e){
// prevent form from submitting
e.preventDefault();
var editData = {
"email": emailField.value,
"fullName": fullnameField.value,
"areaField": areaField.value,
"personField": personField.value
};
// Previous data is being added onto current edited data.. This is what I'm stuck on.
console.log(editData);
// $.ajax({
// method: 'POST',
// url: 'edit-user-action.php',
// data: editData,
// success: function(data){
// console.log(data);
// }
// });
// this didnt work
return false;
});
});
i have two forms on a page. 1st is shown and 2nd one is hidden.
1st form shows list of users and their emails in grid and pagination. when i select a multiple users from grid then a div shows up on the right side of the gird. which will be used to send email to selected user.
2nd form that shows up will input email subjects from users and then pass these whole data to django views.
i have user this ajax code to send whole data to the views function.
$("#_send").click(function(){
var frm = $('#messageform');
frm.submit(function () {
var selectedID = [];
$(':checkbox[name="selectedvalues[]"]:checked').each(function(){
selectedID.push($(this).data("email"));
});
$.ajax({
type: 'POST',
url: '/sendemail/',
processData: true,
data: {'frm':frm.serialize(),
'selectedID': selectedID},
success: function (data) {
alert(data);
},
error: function(data) {
alert("error");
}
});
return false;
});
});
and in django views i am catching like this;
def Send_Message(request):
checked_items = request.POST.getlist('selectedID[]')
Msg_Content = str(request.POST.get('content'))
frm = request.POST.get('frm')
print checked_items
print frm
print Msg_Content
it outputs like this;
abc#gmail.com,abc123#cogilent.comm
csrfmiddlewaretoken=dP7VkSQdWx0fXuX0kJC46arv6HFElvgz&subject=Hi+This+is+testing+message&content=ddddd
but i want these data seperately, message content and message subject.
When you serialize the data; you should be able to do request.POST.get('subject') etc