Javascript Multiple File Upload, Sequentially One at a Time - javascript

We have a form with five <input type="file"/> elements that is in production and working great. We get request timeouts and MaxRequestLength exceeded errors on occasion. To prevent these errors, I planned to write some Javascript to upload the files one-at-a-time instead of all at once. Here is how I planned on doing this...
On document.ready, inject a hidden iframe into page
Change the <form> to target the iframe
Disable all elements on the form (which prevents them from being POSTed)
Enable one file-upload at a time and submit the form
Wait for the response from the server
When server response is printed into iframe, start the next upload
When all uploads are done, refresh the page, which will invoke some server-side logic that populates a grid.
My problem is with number 5. Normally I think I could figure this out no problem, but I am just having one of those days where my brain is on strike. Here is my code thus far...
$(function() {
$("<iframe/>").attr("src", "test.htm").attr("name", "postMe").hide().appendTo("body");
$("form").attr("target", "postMe").submit(function(e) {
e.preventDefault();
$("#btnSubmit").attr("disabled", "disabled").val("Please Wait, Files are Uploading");
for(var i = 1; i < 6; i++) {
$("input[type=file]").attr("disabled", "disabled");
$("#FileUpload" + i).removeAttr("disabled");
$("form")[0].submit();
// HELP!!!
// How do I wait for server before next iteration?
}
location.reload(true);
});
});
What kind of construct do I need here in order to "wait" for the server response before kicking off the next upload?

I've had a lot of success lately using Uploadify--it's very configurable, free, and allows for multiple-uploads. It also provides the option for callback functions allowing you to really configure it any way you want.
http://www.uploadify.com/

I think you should listen for iframe's load event and perform input's switching in the handler. I completed with my own uploader today and this solution worked for me.

Just FYI: jquery.forms plugin is all about making ajax form submitions. I use this plugin to submit a form (such as a file upload) in a separate iframe which the plugin takes care of automatically, and gives you a nice callback when completing.
This way most work for you is done.
http://jquery.malsup.com/form/

It can be done with the help of jQuery's queue method and load event.
<!DOCTYPE HTML>
<html>
<head>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.4/jquery.min.js"></script>
<script>
//here's an upload script
(function($){
//make 'em accessible within the scope
var $iframe, $form;
$(document).ready(function(){
//create 'em only once, but use 'em many times
$iframe = $('<iframe name="iframe" id="iframe" style="display:none"></iframe>').appendTo('body');
$form = $('<form method="post" enctype="multipart/form-data" target="iframe" style="display:none"></form>').appendTo('body');
});
var iframeUpload = $({});
$.iframeUpload = function(s){
iframeUpload.queue(function(next){
//as we only wanna this new event
$iframe.load(function(){
//we must unbind the old one
$iframe.unbind('load');
//success or error, the question is up to you
s.success();
//but remember to remove or replace the old stuff
$form.find('input').remove();
next();
});
$form.attr('action', s.url).append(s.file).submit();
});
};
})(jQuery);
//and this is how to use the script
(function($){$(document).ready(function(){
$('input[type="submit"]').click(function(){
$('input[type="file"]').each(function(){
$.iframeUpload({
url: 'http://example.com/upload.php',
file: this,
success: function(){
console.log('uploaded');
}
});
});
});
})})(jQuery);
</script>
</head>
<body>
<!-- here are multiple files -->
<input type="file" name="file" />
<input type="file" name="file" />
<input type="file" name="file" />
<!-- to upload -->
<input type="submit" />
</body>
</html>

I was able to do this, by starting with the code at A Strategy for Handling Multiple File Uploads Using Javascript. That code uses an XMLHttpRequest for each file, but actually doesn't check the result from the server. I modified it to wait for the result from the server, sequentially, as follows:
var fileNumber = 0
var fileList = [] // see the code linked above for how to handle the fileList
var resultPane = document.getElementById('resultpane') // a textarea box
sendNext = function() {
if (fileNumber >= fileList.length) {
resultPane.value += 'Done uploading '+fileNumber+' files\n'
return 0
}
var formData = new FormData()
var request = new XMLHttpRequest()
request.onreadystatechange = function() {
if (request.readystate == XMLHttpRequest.DONE) {
resultPane.value += request.responseText // show whatever the server said about each file
sendNext() // and send the next file
}
}
formData.set('file', fileList[fileNumber])
request.open('POST', 'https://example.com/upload-receiver')
request.send(formData)
resultPane.value += 'Sending file number '+fileNumber+'\n'
fileNumber++
}

Related

Javascript updates content asynchronously

I wonder how it is possible that using javascript, images can be loaded asynchronously by changing the src of the img element after the page has been loaded. I thought that AJAX is for things like that(getting data from server without refreshing the page). Please clarify why it is working that way. The images are on server side, so I thought that i should refresh the page before the result will be visible.
Here is a sample code:
<!DOCTYPE html>
<html lang="en">
<head>
<script>
var photos = ["baloon", "game", "cliff"];
function changePhoto() {
var input=document.getElementById("ph1");
var iValue=input.value.trim();
for(var tmp in photos) {
if(photos[tmp] === iValue){
var img=document.getElementById("photo");
img.setAttribute("src", "img/"+iValue+".jpg");
}
}
}
</script>
</head>
<body>
<input class="form-control" id="ph1" type="text" onkeyup="">
<p>Photo: <span id="txtHint" onclick="changePhoto()"></span></p>
</div>
<div class="container" id="photocontainer">
<img id="photo">
</div>
</body>
</html>
The user agent simply sends a GET request in response to the changing of the src attribute, the same that is done when a page loads initially.
AJAX is a technology that allows for asynchronous requests in JavaScript on the client. Browsers can make any requests they want at any time, as in this case, but without AJAX that couldn't be done in client-side code loaded by a website.
For example, I just changed the src property of an element in a page through Chrome Developer Tools and watched the GET request execute.
What you're doing in your code is not Ajax. Is simple javascript.
To make this work with Ajax you need a server side aplication that actually renders the image or get the contents from an existing file, and after that you show your loaded file on the browser.
Look for a jQuery ajax calls.
On the return of the call you can put your code.
Like this:
$.ajax({
url: "SomeUrl/SomeMethod/"
})
.done(function (response) {
//Do stuff here with the response to show the image
});
When you change src atrribute of an img element browser automatically starts downloading this image asynchronously.
Your code is almost ok. You iterate through array incorrectly.
You can try doing it this way instead:
var photos = ["baloon", "game", "cliff"];
function changePhoto() {
var input=document.getElementById("ph1");
var iValue=input.value.trim();
if(photos.indexOf(iValue) > -1) {
var img=document.getElementById("photo");
img.setAttribute("src", "img/"+iValue+".jpg");
}
}
Iterating through array
You can iterate through array for example like this:
for(var i=0; i<photos.length; i++) {
var photo = photos[i];
//...
}
or like this:
for(var k in Object.keys(photos)) {
var photo = photos[k];
//...
}

how get a php value inside a self-invoked jquery function?

I have a page that generates n links in a foreach loop:
...some html and php code
<?php foreach ($tables as $table):?>
... some elements generated ...
<td><a onclick="setPortalId(<?php echo $table['id']?>);$('#fileupload').trigger('click');" class="btn-success btn-sm"><i class="icon-plus white bigger-125"></i>Add / Change</a></td>
... another elements ...
<?php endforeach;?>
As you can see, the onclick event in each link execute 2 js functions,the first sets a js var with the php value $table['id'] because i will need this value to determine my zend route and the last function trigges the input fileUpload of the type file:
<input id="fileupload" type="file" class="hidden" multiple="" name="files[]">
and in the scripts i have this:
<script src="/js/vendor/jquery.ui.widget.js"></script>
<!-- The Iframe Transport is required for browsers without support for XHR file uploads -->
<script src="/js/jquery.iframe-transport.js"></script>
<!-- The basic File Upload plugin -->
<script src="/js/jquery.fileupload.js"></script>
<!-- Bootstrap JS is not required, but included for the responsive demo navigation -->
<script src="//netdna.bootstrapcdn.com/bootstrap/3.2.0/js/bootstrap.min.js"></script>
<script>
var idPortal;
function setPortalId(valor) {
idPortal = valor;
}
/*jslint unparam: true */
/*global window, $ */
$(function () {
'use strict';
// Change this to the location of your server-side upload handler:
var url = '/precos/upload/id/'+ idPortal;
$('#fileupload').fileupload({
url: url,
dataType: 'json',
done: function (e, data) {
$.each(data.result.files, function (index, file) {
$('<p/>').text(file.name + " adicionado").appendTo('#files');
window.alert(file.name + " Adicionado.");
});
},
progressall: function (e, data) {
var progress = parseInt(data.loaded / data.total * 100, 10);
$('#progress .progress-bar').css(
'width',
progress + '%'
);
}
}).prop('disabled', !$.support.fileInput)
.parent().addClass($.support.fileInput ? undefined : 'disabled');
});
</script>
My question is how I can get the idPortal of the clicked link in the last self-invoqued funtion?Any sugestions?
This is horrible design. You should try to keep your JS as unobtrusive as possible, ie. don't use event handler attributes like onclick. Attach the event handler via JS. There are times when this is impractical but I don't see any evidence that that is the case here.
What I would do on the PHP side is to add some classes and a data attribute that I can hook in to from JS:
<?php foreach ($tables as $table):?>
<td>
<a data-portal-id="<?php echo $table['id']?>" class="btn-success btn-sm btn-upload"><i class="icon-plus white bigger-125"></i>Add / Change</a>
</td>
<?php endforeach;?>
Now on the JS side I would simply read the data-portal-id from the clicked link, use it to set the URL on the file uploader, and then trigger the click to begin the upload workflow:
$(selectorForTheTable).on('click', 'a[data-portal-id].btn-upload', function (e) {
// pull the portalId from the link's data-portal-id attribute
var portalId = $(this).data('portalId'),
$uploader = $('#fileupload');
// set the url for the upload based on out portalId
$uploader.fileupload('option', 'url', '/precos/upload/id/'+ portalId);
// invoke the click
$('#fileupload').trigger('click');
});
The one thing missing here is that you might want to set something up so that when the uploader is closed or all the uploads complete the URL is set back to null or a URL of no consequence. This would help to ensure something going wrong on the client cant mistakenly upload files to the wrong endpoint.
Here is an example Fiddle that works as much as a Fiddle can :-)
You need to make your url global and update it later in that context. Use it like
var idPortal;
var url;
function setPortalId(valor) {
idPortal = valor;
url = '/precos/upload/id/'+ idPortal;
}
The easiest approach to seperate PHP (serverside business logic) and Javascript (non-business critical GUI enhencement), is to put all variables from PHP into the DOM and then later work with it:
<script>
var phpValues = <?php echo json_encode($yourPhpValuesArrayOrObject); ?>;
</script>
....
<script>
The attributes connected with business data from inside the HTML (=semantic structure) should go with a data-* attribute as already mentioned.
You're setting url when the page is first loaded, not after the user clicks on the link. Add that to the setPortalId function:
function setPortalId(valor) {
idPortal = valor;
url = '/precos/upload/id/'+ idPortal;
}
thank you everyone,but I used another approach to get the correct value of the clicked element.like was said,the function is self-invoked in the page loading,so in this moment the global var still null.I as using the blueimp jquery file upload,so reading the documentation I saw that is possible send another values during the ajax request just adding news inputs in the form.with this I solved my problem.

problems with javascript function loading content from other files

Basically I'm trying to build a functionality in which I only really edit my index.php, I got a lot of other php files with just a form in them or just a few lines of text.
What I want to achieve is to load these other files in the contentwrapper of my index.php.
I have been successfull on doing this with an iframe and with a html <object>.
The problem with these though is that first of all they load an all new #document in the DOM, and also my webpage has no set height so height: 100% won't work on those and I would get these ugly scrollbars and stuff.
after searching a lot on SO today I found a few interesting solutions which I combined, this is what I'm trying now:
<script type="text/javascript" href="js/csi.min.js"></script>
<script type="text/javascript">
function load_content(target){
document.getElementById('contentwrapper').innerHTML='<div data-include="' + target + '" ></div>';
return false;
}
</script>
now you may question what data-include is, this is a very nice workaround I found on SO.
THIS is what it does, it basically calls a .js file that replaces the containing element with the data that is in the file (target in the above example)
I call this functionality like this:
Update profile
It works as far as adding this to the DOM:
<div id="contentwrapper">
<div data-include="update.php" ></div>
</div>
but besides that it does nothing, I think that it doesn't call the .js file for the data-include attribute. But I can't find a solution for this nowhere.
(BTW: the data-include attribute does work if I put it in a tag manually without javascript)
I Hope I didn't overexplain the situation, and I thank everyone that tries to help in advance!
The csi.js script is only run once after the page is loaded. It just goes over all the elements with the data-include attribute and runs the fragment function.
<script type="text/javascript">
function fragment(el, url) {
var localTest = /^(?:file):/,
xmlhttp = new XMLHttpRequest(),
status = 0;
xmlhttp.onreadystatechange = function() {
/* if we are on a local protocol, and we have response text, we'll assume
* things were sucessful */
if (xmlhttp.readyState == 4) {
status = xmlhttp.status;
}
if (localTest.test(location.href) && xmlhttp.responseText) {
status = 200;
}
if (xmlhttp.readyState == 4 && status == 200) {
el.outerHTML = xmlhttp.responseText;
}
}
try {
xmlhttp.open("GET", url, true);
xmlhttp.send();
} catch(err) {
/* todo catch error */
}
}
function load_content(target){
fragment(document.getElementById('contentwrapper'), target);
return false;
}
</script>
Then call it like this:
Update profile
So, the only thing you need is to call this function for the new created element. Pass the DOM element and the url to this function and it will take care of loading the contents of the requested resource in the corresponding element.
May we assume that you followed this advise from the repository: The only caveat is Chrome, which restricts access to local files via AJAX. To resolve this, simply add --allow-file-access-from-files to your Chrome runtime.
If you didn't, and you're using Chrome, then this stands out to me, and you didn't indicate that you'd corrected the security block that Chrome puts in place.
The csi.js only runs on window.onload.
Try
<a href="#" onclick="function() {load_content('update.php'); window.onload(); }">
Update profile</a>

File uploads: Percentage completed progress bar

I'm trying to add a 'percentage completed so far' progress bar to avatar uploads in BuddyPress. The aim is to stop users navigating away from the page before the upload is completed.
The upload process is handled in BuddyPress by bp_core_avatar_handle_upload() in file bp-core/bp-core-avatars.php. The function starts off by checking that the file has been uploaded properly using bp_core_check_avatar_upload(). It then checks that the file size is within limits, and that it has an accepted file extension (jpg, gif, png). If everything checks out, the user is allowed to crop the image (uses Jcrop) and then the image is moved to its real location.
The actual upload is handled by the WordPress function wp_handle_upload.
How can I create a 'percentage completed' progress bar and display it when the file is uploading?
I'm not familiar with BuddyPress, but all upload handlers (including the HTML5 XHR one that androbin outlined) will have a file progress hook point that you can bind to.
I've used uploadify, uploadifive and swfupload, and they can all interact with the same progress function handler in order to acheive the same progress bar result.
// SWFUpload
$elem.bind('uploadProgress', function(event, file, bytesLoaded) { fnProgress(file, bytesLoaded); })
// Uploadify
$elem.uploadify({ onUploadProgress: function (file, bytesUploaded, bytesTotal, totalBytesUploaded, totalBytesTotal) { fnProgress(file, bytesUploaded); });
// Uploadfive
$elem.uploadifive({ onProgress: function(file, e) { fn.onProgress(file, e.loaded); });
Uploadifive, being an HTML5 based uploader, simply binds to the XHR 'progress' event, so all these properties will be available to any HTML5 uploader.
As for the actual progress bar code..
HTML:
<div class='progressWrapper' style='float: left; width: 100%'>
<div class='progress' style='float: left; width: 0%; color: red'></div>
<div class='progressText' style='float: left;></div>
</div>
JS:
var fnProgress = function(file, bytes) {
var percentage = (bytesLoaded / file.size) * 100;
// Update DOM
$('.progress').css({ 'width': percentage + '%' });
$('.progressText').html(Math.round(percentage + "%");
}
You should use an XHR object. I don't now if it helps you, but I have a simple XHR uploader written.
HTML:
<form id="uploader" enctype="multipart/form-data" action="uploadimage.php" method="post">
<input type="file" id="file" name="file[]" multiple="multiple" accept="image/jpeg" /><br/>
<input type="submit" value="Upload" />
<div class="list" style="background-color:#000;color:#FFF;padding:5px;display:none;border-radius:5px;">
</div>
</form>
JS:
$("#uploader").submit(function(){
$('#uploader .list').fadeIn(100).css("width","0px");
var data = new FormData();
// if you want to append any other data: data.append("ID","");
$.each($('#file')[0].files, function(i, file) {
data.append('file-'+i, file);
});
$.ajax({
url: 'uploadimage.php',
data: data,
cache: false,
contentType: false,
processData: false,
type: 'POST',
xhr: function() { // custom xhr
myXhr = $.ajaxSettings.xhr();
if(myXhr.upload){ // check if upload property exists
myXhr.upload.addEventListener('progress',progressHandlingFunction, false); // for handling the progress of the upload
}
return myXhr;
},
success: function(data2){
$('#uploader .list').css({
"width":"200px",
"text-align":"center",
"margin":"10px 0 10px 0"
}).html("DONE!").delay(2000).fadeOut(500);
if (data2 == "ERROR_FILESIZE"){
return alert("Choose another file");
}
else{ /*change location*/ }
});
return false;
});
In this case I uploaded the file with uploadimage.php and if it printed: "ERROR_FILESIZE" then it alerted the error.
I think that before you worry about the client-side of things you should be aware of the server-side requirements to actually be able to accomplish this.
For PHP you need to have the session.upload_progress enabled unless the wp_handle_upload() function uses something different, so I'm here just guessing, but chances are they do use the regular PHP session stuff hence it needs to be enabled.
If you look at the comments for the given link many users say that progress state does not work under certain environments such as PHP on FastCGI which is what you'll get in shared hosting environments most of the time.
Now many people here are telling you to use the XHR uploader but the problem is that they are giving you an example of a custom upload.php script or something like that to send the data, but you are using a wordpress plugin which you don't control (kinda)
So considering that the wp_handle_upload() does not actually works in an AJAX way then you would have to hook an event when the file upload form submit button is clicked and set a timer in JS which calls some URL where you pass the form data like an ID, and then query the session with that ID to check the progress of the file:
$_SESSION["upload_id"]["content_length"]
$_SESSION["upload_id"]["bytes_processed"]
With that data you can calculate how much has been transfered. You could set the JS timer to be called like each second but if the files they are uploading are not very large (say, larger than 1mb) and they have a good connection then there won't be much progress to be notified.
Check this link for a step by step example on how to work with this session upload data.
You need to inject the progress bar.
I think the only way is to over-ride the function bp_core_avatar_handle_upload using the filter hook apply_filters( 'bp_core_pre_avatar_handle_upload' etc. )
You'll end up duplicating most of the function but you should be able to add your progress bar code.
If you get this working, you should submit it as an enhancement ticket; it's a good idea.

Insert HTML into a page with AJAX

I am currently developing a website and i need that the pages loads dynamically based on what actions the user does.
Example: If the user clicks on the button 'Settings' an ajax function will load from an external page the code and will put into the div with tag 'settings'.
This is the code i use to make the Ajax request:
function get_page_content(page, target_id)
{
xmlhttp = new XMLHttpRequest();
xmlhttp.onreadystatechange = function()
{
if(xmlhttp.readyState == 4 && xmlhttp.status == 200)
{
document.getElementById(target_id).innerHTML = xmlhttp.responseText;
// After getting the response we have to re-apply ui effects or they
// won't be available on new elements coming from request.
$('button').sb_animateButton();
$('input').sb_animateInput();
}
}
xmlhttp.open('GET', 'engine/ajax/get_page_content.php?page=' + page, true);
xmlhttp.send();
}
And this is where the ajax results will be put by first snippet:
<div id="settings_appearance">
</div>
The code is called from a function here:
<div class="left_menu_item" id="left_menu_settings_appearance" onclick="show_settings_appearance()">
Appearance
</div>
And this is the html that the ajax function will put into the settings_appearance div:
<script type="text/javascript">
$(function()
{
$('#upload_hidden_frame').hide();
show_mybrain();
document.getElementById('avatar_upload_form').onsubmit = function()
{
document.getElementById('avatar_upload_form').target = 'upload_hidden_frame';
upload_avatar();
}
});
</script>
<div class="title">Appearance</div>
<iframe id="upload_hidden_frame" name="upload_hidden_frame" src="" class="error_message"></iframe>
<table class="sub_container" id="avatar_upload_form" method="post" enctype="multipart/form-data" action="engine/ajax/upload_avatar.php">
<tr>
<td><label for="file">Avatar</label></td>
<td><input type="file" name="file" id="file" class="file_upload" /></td>
<td><button type="submit" name="button_upload">Upload</button></td>
</tr>
<tr>
<td><div class="hint">The image must be in PNG, JPEG or GIF format.</div></td>
</tr>
</table>
I would like to know if there's a way to execute also the javascript code that's returned by the ajax function (upload button in the returncode doesn't work because of this) and if it's possible to apply some customized ui effects i build that are loaded with the main page.
Thanks for helping.
P.S. This is the script that applies the UI effects:
<script type="text/javascript">
// UI effects
$(document).ready(function()
{
$('button').sb_animateButton();
$('input').sb_animateInput();
$('.top_menu_item').sb_animateMenuItem();
$('.top_menu_item_right').sb_animateMenuItem();
$('.left_menu_item').sb_animateMenuItem();
});
</script>
P.P.S. ui effects are not applied to html elements (such as input and buttons) returned by the Ajax function. I used a little workaround by applying again ui-effects after ajax function returns the response. Probably there's another way of doing it... the same that will help me solve this problem.
If you use the jQuery ajax function (or the simplified jQuery get function), and set the datatype to html, then jQuery will evaluate the contents of any script tags included in the results.
Your $.get call would look something like:
$.get('engine/ajax/get_page_content.php?page=' + page,null,function(result) {
$("#"+target_id).html(result); // Or whatever you need to insert the result
},'html');
I also suggest you don't, but after loading the content in the div, pass the element ID to this function. This will even handle document.write
function do_JS(e){
var Reg = '(?:<script.*?>)((\n|.)*?)(?:</script>)';
var match = new RegExp(Reg, 'img');
var scripts = e.innerHTML.match(match);
var doc = document.write;
document.write = function(p){ e.innerHTML = e.innerHTML.replace(scripts[s],p)};
if(scripts) {
for(var s = 0; s < scripts.length; s++) {
var js = '';
var match = new RegExp(Reg, 'im');
js = scripts[s].match(match)[1];
js = js.replace('<!--','');
js = js.replace('-->','');
eval('try{'+js+'}catch(e){}');
}
}
document.write = doc;
}
A better solution will be to add a function that you can call at the end of the update to show the effects.
I recommend you not to, it might lead to a security breach.
If you already use jquery, use it's ajax functionallity instead of the raw one.
When the ajax request completes execute the animation code (just leave it on the page that does the ajax call).
In your content HTML (the one you get from the call) make a common javascript function for every content page, that will be called every time the content is loaded on the master page...
the function name will be something like: loadContentJavascript() {}
and this function is in charge of loading all the functionalities that it will be load on a onload event.

Categories