How to create callback for form submit event (without ajax) - javascript

I want to have a callback after my form has successfully been submitted. This form does not reload the page and the ajax alternative is not available to us because of "cross origin" issues.
What I have now is:
$('#uploadform form').on('submit', function(){
// DO STUFF HERE
});
But this is firing as soon as submit event is triggered and not as a callback. Without using ajax, how do I make code run after and only after the response is received (and get the response to do stuff with)? Is this even possible?
It is through AWS's S3 file hosting and cannot use JSONP.
I would rather not use an iframe if I don't have to for simplicity's sake.
EDIT
It doesn't reload the page just like a file download link doesn't reload the page. Otherwise it's exactly like any other form. It's not submitted inside of an iframe. It's a normal form, but the headers involved don't require the page to reload.

A solution has come to me that will allow me to submit my form without reloading the page, not use an iframe or JSONP, and while it probably technically counts as AJAX, it does not have this same "cross origin" issue.
function uploadFile() {
var file = document.getElementById('file').files[0];
var fd = new FormData();
fd.append('key', "${filename}");
fd.append("file",file);
xhr = new XMLHttpRequest();
xhr.upload.addEventListener("progress", uploadProgress, false);
xhr.addEventListener("load", uploadComplete, false);
xhr.addEventListener("error", uploadFailed, false);
xhr.addEventListener("abort", uploadCanceled, false);
xhr.open('POST', 'http://fake-bucket-name.s3-us-west-1.amazonaws.com/', true); //MUST BE LAST LINE BEFORE YOU SEND
xhr.send(fd);
}
function uploadProgress(evt) {
if (evt.lengthComputable) {
var percentComplete = Math.round(evt.loaded * 100 / evt.total);
document.getElementById('progressNumber').innerHTML = percentComplete.toString() + '%';
}
else {
document.getElementById('progressNumber').innerHTML = 'unable to compute';
}
}
function uploadComplete(evt) {
/* This event is raised when the server send back a response */
alert("Done - " + evt.target.responseText );
}
function uploadFailed(evt) {
alert("There was an error attempting to upload the file." + evt);
}
function uploadCanceled(evt) {
alert("The upload has been canceled by the user or the browser dropped the connection.");
}
With a simple form like this:
<form id="form1" enctype="multipart/form-data" method="post">
<div class="row">
<label for="file">Select a File to Upload</label><br>
<input type="file" name="file" id="file">
</div>
<div id="fileName"></div>
<div id="fileSize"></div>
<div id="fileType"></div>
<div class="row">
<input type="button" onclick="uploadFile()" value="Upload">
</div>
<div id="progressNumber"></div>
</form>
The uploadComplete(evt) function being the callback. As you can see, it also gives you the percentage complete you can show your users.
Note: To do this you have to set the correct upload policy and CORS
policy in your S3 account.
– RonSper

You will run into 'cross origin' issues with ajax and iframes equally if you need access to the response.
JSONP is the only way around your 'cross-origin' issues. It is what is used by all JSON APIs that are hosted on a different domain, unless you try to use CORS which isn't supported in legacy IE versions.
If you can control the server where the form is submitted you should be able to make it return a JSONP compatible response. If not, you are kind of out of luck.

Related

Stop Non-Ajax File Upload on Form Submit

I have a form which includes a bunch of text and date fields as well as a "file" input (see below)
<button class="contact100-form-upload-btn" id="uploadbutton" type="button" onclick="document.getElementById('uploadedfile').click()">Upload Picture Identification </button>
<input style="display:none" name="uploadedfile" id="uploadedfile" onchange="selectfile()" type="file" accept="image/*" />
function selectfile() {
var x = document.getElementById("progdiv");
x.style.display = "block";
var file = _("uploadedfile").files[0];
//alert(file.name+" | "+file.size+" | "+file.type);
var formdata = new FormData();
formdata.append("uploadedfile", file);
var ajax = new XMLHttpRequest();
ajax.upload.addEventListener("progress", progressHandler, false);
ajax.addEventListener("load", completeHandler, false);
ajax.addEventListener("error", errorHandler, false);
ajax.addEventListener("abort", abortHandler, false);
ajax.open("POST", "file_upload_parser.php?confnum=" + <?php echo $confnum ?> + "&file=1");
ajax.send(formdata);
formData.delete(uploadedfile);
document.getElementById("uploadbutton").style.backgroundColor = "#FF5733";
document.getElementById("uploadbutton").innerHTML = "Photo Uploading";
}
The selectfile() function is a nice file upload function using Ajax which shows a little upload status bar so the user knows what's going on. All of this functionality works perfectly and the file uploads to the sever. The issue is when I submit the form, the file get's uploaded again which sort of defeats the purpose of uploading it with Ajax. Is there a way to make an input field (file) not be part of a form, or delete the file element from the from altogether (as it has already been uploaded) before submitting it? Or maybe another way I am totally missing?
Any help will be greatly appreciated.
-Lawrence
What worked for me was to remove multipart/form-data from the form so it ignores file uploads.
"When you are writing client-side code:
use multipart/form-data when your form includes any elements
otherwise you can use multipart/form-data or application/x-www-form-urlencoded but application/x-www-form-urlencoded will be more efficient"
This solution will obviously not work if you want to upload some files but solved my issue.

Getting around CORS with embedded google forms

I'm trying to send form data to google via an embedded form.
I found this post that seems to answer my question but I'm getting CORS errors. Is there a way to solve this?
Other posts seem to say that CORS isn't an issue but I'm getting the errors.
Here is my code:
-JS-
function ajax_post() {
var field1 = $('#email').val();
$.ajax({
url: "https://docs.google.com/forms/d/e/xxxxxxxxxxxxxxxxxxxxxx/formResponse",
data: {"entry.xxxxxxxxxx": field1},
type: "POST",
dataType: "xml",
statusCode: {
0: function() {
//Success message
},
200: function() {
//Success Message
}
}
});
}
-HTML-
<form id="emailForm" target="_self" onsubmit="" action="javascript: ajax_post()">
<input id="email" type="text" autocomplete="off" tabindex="0" name="entry.xxxxxxxxxx" required>
<button id="send" type="submit">Submit</button>
</form>
The “No 'Access-Control-Allow-Origin' header is present on the requested resource” message indicates that responses from https://docs.google.com/forms/d/e/xxxx/formResponse URLs currently don’t include the Access-Control-Allow-Origin response header, so browsers won’t allow your frontend JavaScript code to access the response.
Given that, from your frontend code there’s no way you can tell if the POST request succeeds or not. But barring any other problems, it seems like the request will always succeed. If the request doesn’t reach the server at all (due to some network error) then you’ll hit a different failure condition that is observable from your frontend code so you can actually catch it.
So the way you know the request has successfully reached the server is just that you don’t get any other failure that’s observable from your frontend code.
I've found that it's actually easier to just POST the form with a hidden iframe as its target, and capture that iframe reload when the response is submitted.
For example, if this is your form:
<form id="my-form" target="my-response-iframe" action="https://docs.google.com/forms/u/1/d/e/<YOUR-ID>/formResponse" method="post">
<input type="text" name="entry.12345678" value="" required>
<input type="submit">
</form>
Then include an iframe on the same page, with the same id AND name you put as target in the form:
<iframe id="my-response-iframe" name="my-response-iframe"></iframe>
When the form is submitted, it should reload that iframe with the "Your response has been recorded." page from Google. We can catch that reload with JavaScript, so include this after your form and iframe:
<script type="text/javascript">
// set the target on the form to point to a hidden iframe
// some browsers need the target set via JavaScript, no idea why...
document.getElementById('my-form').target = 'my-response-iframe';
// detect when the iframe reloads
var iframe = document.getElementById('my-response-iframe');
if (iframe) {
iframe.onload = function () {
// now you can do stuff, such as displaying a message or redirecting to a new page.
}
}
</script>
You can't check whether the response was submitted correctly because you can't inspect the contents of a cross-origin iframe (that'd be a huge security risk), so just assume that if this iframe reloads, the response was ok.
You can hide the iframe with the following CSS:
visibility: hidden
height: 1px;
Much cleaner if you ask me, with no console errors or failed requests.

Submitting an image to Facebook with Javascript and HTML form

I’m going crazy with image upload to Facebook. I’ve tried HTML5 drag and drop methods, Dropzone.js, as well as uploading to my own server before submitting the image via PHP. But the only one I can make work (because of my inexperience, I’ll admit) and that doesn't involve uploading the image to my own server, is by using a HTML form as shown in the Facebook documentation:
<form id=“upload_form” enctype="multipart/form-data" action=“https://graph.facebook.com/event_id/photos?access_token=an_access_token” method="POST">
Please choose a photo
<input name="source" type="file"><br/><br/>
Say something about this photo:
<input name="message" type="text" value=""><br/><br/>
<input type="submit" value="Upload"/><br/>
</form>
I dynamically generate it in Javascript and use var’s to fill in event_id and access_token.
This works fine, so all my permissions and authorising are correct. Now what I’d like to do is handle the response because the browser does as you’d expect when the user clicks submit and displays basic text showing the post id and whatnot.
So, I created a button and bound the following to it’s click event:
var fd = document.getElementById('upload_form');
if (fd) {
console.log('Sending');
var XHR = new XMLHttpRequest();
XHR.addEventListener('load', function(data) {
console.log('XHR finished:');
console.log(data);
});
XHR.addEventListener('error', function(data) {
console.log('XHR ERROR:');
console.log(data);
});
var graph_url = 'https://graph.facebook.com/'+event_id+'/photos?access_token=' + access_token;
XHR.open('POST', graph_url);
XHR.send(fd);
}
Once the user has selected an image and clicks my button to execute the above XHR completes the send and reports as finished, but Facebook replies with:
(#324)Requires upload file.
Please can someone show me where I’ve gone wrong - it’s been a problem for days now!
If you willing to use jquery and jquery.ajaxForm plugin
<!-- You form code stay Make sure your form.action url is valid ajaxForm use that as url -->
<form id=“upload_form” enctype="multipart/form-data" action=“https://graph.facebook.com/event_id/photos?access_token=an_access_token” method="POST">
Please choose a photo
<input name="source" type="file"><br/><br/>
Say something about this photo:
<input name="message" type="text" value=""><br/><br/>
<input type="submit" value="Upload"/><br/>
</form>
//your javascript to upload the image togather with message
// put this in a button, not submit button
$('#upload_form').ajaxForm({
complete: function(data) {
//process fb response
}
});
I suggest you use Fiddler to catch both connections, with and without XMLHttpRequest and see which is the actual difference between both request, I don't actually know what XHR.send(fd); does, but maybe it's sending the form content itself, not submitting it?
Fiddler is a very useful tool when connecting to external APIs

create file upload that work in IE

I want to write a file upload script that works in IE but the two types of code that I'm writing have problems in IE.
Please help. How can you write a file upload script that works in IE?
Type 1
Problem Not Support File Api In IE (Is the trick not to use it?)
<!DOCTYPE html>
<html>
<head runat="server">
<title></title>
<script src="Scripts/jquery-1.6.2.js" type="text/javascript"></script>
<script type="text/javascript">
function updateSize() {
var nBytes = 0;
var nFiles=0;
oFiles = document.getElementById("uploadInput").files;
nFiles = oFiles.length;
for (var nFileId = 0; nFileId < nFiles; nFileId++) {
nBytes += oFiles[nFileId].size;
}
var sOutput = nBytes + " bytes";
// optional code for multiples approximation
for (var aMultiples = ["KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"], nMultiple = 0, nApprox = nBytes / 1024; nApprox > 1; nApprox /= 1024, nMultiple++) {
sOutput = nApprox.toFixed(3) + " " + aMultiples[nMultiple] + " (" + nBytes + " bytes)";
}
document.getElementById("fileNum").innerHTML = nFiles;
document.getElementById("fileSize").innerHTML = sOutput;
}
// end of optional code
</script>
</head>
<body>
<form id="form1" runat="server">
<p><input id="uploadInput" type="file" name="myFiles" onchange="updateSize();" multiple /> selected files: <span id="fileNum">0</span>; total size: <span id="fileSize">0</span></p>
<p><input type="submit" value="Send file"></p>
</form>
</body>
</html>
Type 2
Problem Not Support document.getElementById('fileToUpload').files[0](Is the trick not to Get Files[0]?)
<script src="Scripts/jquery-1.4.1.js" type="text/javascript"></script>
<script type="text/javascript">
function fileSelected() {
var file = document.getElementById('fileToUpload').files[0];
if (file) {
var fileSize = 0;
if (file.size > 1024 * 1024)
fileSize = (Math.round(file.size * 100 / (1024 * 1024)) / 100).toString() + 'MB';
else
fileSize = (Math.round(file.size * 100 / 1024) / 100).toString() + 'KB';
document.getElementById('fileName').innerHTML = 'Name: ' + file.name;
document.getElementById('fileSize').innerHTML = 'Size: ' + fileSize;
document.getElementById('fileType').innerHTML = 'Type: ' + file.type;
}
}
function uploadFile() {
var fd = new FormData();
fd.append("fileToUpload", document.getElementById('fileToUpload').files[0]);
var xhr = new XMLHttpRequest();
xhr.upload.addEventListener("progress", uploadProgress, false);
xhr.addEventListener("load", uploadComplete, false);
xhr.addEventListener("error", uploadFailed, false);
xhr.addEventListener("abort", uploadCanceled, false);
$.post("UploadHandler.ashx");
//xhr.open("POST", "UploadHandler.ashx");
xhr.send(fd);
}
function uploadProgress(evt) {
if (evt.lengthComputable) {
var percentComplete = Math.round(evt.loaded * 100 / evt.total);
document.getElementById('progressNumber').innerHTML = percentComplete.toString() + '%';
document.getElementById('prog').value = percentComplete;
}
else {
document.getElementById('progressNumber').innerHTML = 'unable to compute';
}
}
function uploadComplete(evt) {
/* This event is raised when the server send back a response */
alert(evt.target.responseText);
}
function uploadFailed(evt) {
alert("There was an error attempting to upload the file.");
}
function uploadCanceled(evt) {
alert("The upload has been canceled by the user or the browser dropped the connection.");
}
</script>
</head>
<body>
<form id="form1">
<div>
<label for="fileToUpload">
Select a File to Upload</label>
<input type="file" name="fileToUpload[]" id="fileToUpload" onchange="fileSelected();" />
</div>
<div id="fileName">
</div>
<div id="fileSize">
</div>
<div id="fileType">
</div>
<div>
<input type="button" onclick="uploadFile()" value="Upload" />
</div>
<div id="progressNumber">
</div>
<progress id="prog" value="0" max="100.0"></progress>
</form>
</body>
Please Help :(
You can't use these functions unless you're using IE10 or another modern browser. Workarounds are possible for earlier versions of Internet Explorer (and other browsers), but you'll need to adjust your back-end code too.
Why it doesn't work
Internet Explorer up until version 10 doesn't support a number of these features, the key ones being the FormData and FileReader APIs. Both of your code snippets rely on the FileReader API, and the second one also relies on FormData to upload the file dynamically.
How to determine whether to execute the code or not
I recently wrote a file upload widget that detected these features and served different code depending on support. I used the feature detections from Modernizr, because it's tests are regularly put to the test by the open source community:
var support = {
// Are files exposed to JS?
// As used by Modernizr #
// https://github.com/Modernizr/Modernizr/blob/master/feature-detects/file/api.js
'fileReader' : (function testFileReader(){
// Test: look for global file class.
return !!(window.File && window.FileList && window.FileReader);
}()),
// AJAX file upload via formData?
'formData' : window.FormData !== void 0
};
For your fileSelected function, you'll need support.fileReader to evaluate true; for uploadFile, you need support.formData.
A workaround for browsers that don't support these features
Without these features it's impossible to read a file from the front-end or to send a file using AJAX. What you can do, though, is send your file via a hidden <iframe/> inside your current page, and get UploadHandler.ashx to respond differently to non-XHR requests.
This solution is technically synchronous (just happening in another, hidden page), so you won't get updates — the only feedback is when the upload is complete and the server has responded. So you will only be able to inform the user as to file name, size, and success once they have completely uploaded it — which might take a while!
Anyway, the HTML for this would look as follows:
<form
id="form1"
runat="server"
action="UploadHandler.ashx"
target="fileIframe">
<iframe
name="fileIframe"
style="display:none"
onload="parseIframeResponse"
tabindex="-1">
</iframe>
<p>
<input id="uploadInput" type="file" name="myFiles" onchange="updateSize();" multiple />
selected files:
<span id="fileNum">
0
</span>
; total size:
<span id="fileSize">
0
</span>
</p>
<p>
<input type="submit" value="Send file">
</p>
</form>
A few changes:
The form now has a target, which means that when it posts its content to the URI in action, it will load the response in there, as opposed to on the current page.
The target is a name reference to an iframe we've included. It is hidden with display:none, and given a negative tabindex just to make sure the user doesn't stumble into it. It also has an onload property specified. This is the only way of binding functions to the load event in older versions of IE.
So when the form is submitted, we stay on the current page and the server response loads in our hidden iframe. When that happens, the browser will execute the function named in the onload attribute. Sadly, this means that function needs to be in the global scope!
Back-end stuff
I don't know how your back-end works, but if the iframe is to load the response instead of downloading it, it will need to be HTML or plain text (and that will need to be specified in the mime-type). You can tell whether the form was posted via AJAX from the back-end by looking for the X-Requested-With header, which should have a value of XMLHttpRequest — if that isn't there, then the iframe is asking for the response and you need to send text or HTML. You may want to stringify a JSON response that exposes the values you wanted to feed back to the user like fileName, fileSize & fileType. I'm hoping you can do this yourself or get a colleague to handle it.
Capturing the iframe response
As mentioned, the response handler function will need to be in the global scope for the onload attribute to bind to, because of old IE being very quirky. I see you're using jQuery, so if you went down the route of stringifying the server response, you could write this function as follows:
function parseIframeResponse(){
var response = $('#fileIframe').contents().find('body').text();
var object = $.parseJSON(response);
}
Issues with the iframe load event binding
As mentioned earlier, the iframe load event needs to be bound inline as an attribute of the iframe itself — this is because IE will simply fail to register it otherwise. But this is problematic in and of itself because even an empty iframe (an empty or non-present src will default to about:blank) fires a load event. To mitigate this you will need to discard any response that evaluates to an empty string as a false positive, and make sure your back-end responds with some content even if it encounters a fault.
Presumably, you would then want to use whatever information is in there to execute some of the code you've currently got in the functions fileSelected, uploadProgress, etc.
Hope this helps.
EDIT 1: Out-of-the-box solutions
In hindsight, despite writing this off the back of having developed my own solution to the problem, it could be considered negligent not to mention Fine Uploader, a heavily tested (over 700 closed issues!) and well maintained stand-alone plugin that aims to achieve the best file upload experience possible for IE7 and up. There's also a good selection of back-end server components — including ASP.NET — to parse the upload. You might find it easier to tweak this than roll your own!
EDIT 2: Neglected to mention issues with iframe's load event. Amended answer in place.

Submit form and stay on same page?

I have a form that looks like this
<form action="receiver.pl" method="post">
<input name="signed" type="checkbox">
<input value="Save" type="submit">
</form>
and I would like to stay on the same page, when Submit is clicked, but still have receiver.pl executed.
How should that be done?
99% of the time I would use XMLHttpRequest or fetch for something like this. However, there's an alternative solution which doesn't require javascript...
You could include a hidden iframe on your page and set the target attribute of your form to point to that iframe.
<style>
.hide { position:absolute; top:-1px; left:-1px; width:1px; height:1px; }
</style>
<iframe name="hiddenFrame" class="hide"></iframe>
<form action="receiver.pl" method="post" target="hiddenFrame">
<input name="signed" type="checkbox">
<input value="Save" type="submit">
</form>
There are very few scenarios where I would choose this route. Generally handling it with javascript is better because, with javascript you can...
gracefully handle errors (e.g. retry)
provide UI indicators (e.g. loading, processing, success, failure)
run logic before the request is sent, or run logic after the response is received.
The easiest answer: jQuery. Do something like this:
$(document).ready(function(){
var $form = $('form');
$form.submit(function(){
$.post($(this).attr('action'), $(this).serialize(), function(response){
// do something here on success
},'json');
return false;
});
});
If you want to add content dynamically and still need it to work, and also with more than one form, you can do this:
$('form').live('submit', function(){
$.post($(this).attr('action'), $(this).serialize(), function(response){
// do something here on success
},'json');
return false;
});
The HTTP/CGI way to do this would be for your program to return an HTTP status code of 204 (No Content).
When you hit on the submit button, the page is sent to the server.
If you want to send it async, you can do it with ajax.
Use XMLHttpRequest
var xhr = new XMLHttpRequest();
xhr.open("POST", '/server', true);
//Send the proper header information along with the request
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
xhr.onreadystatechange = function() { // Call a function when the state changes.
if (this.readyState === XMLHttpRequest.DONE && this.status === 200) {
// Request finished. Do processing here.
}
}
xhr.send("foo=bar&lorem=ipsum");
// xhr.send(new Int8Array());
// xhr.send(document);

Categories