dcorsello
dcorsello

Reputation: 473

Can onprogress functionality be added to jQuery.ajax() by using xhrFields?

As suggested here: https://gist.github.com/HenrikJoreteg/2502497, I'm trying to add onprogress functionality to my jQuery.ajax() file upload. The upload works fine, and the onprogress event is firing, but not as I expected--instead of firing repeatedly at some time interval, it's firing only once, when the upload has completed. Is there a way to specify the frequency of onprogress refreshes? Or, am I trying to do something that can't be done? Here's my code:

    $.ajax(
    {
        async: true,
        contentType: file.type,
        data: file,
        dataType: 'xml',
        processData: false,
        success: function(xml)
        {
            // Do stuff with the returned xml
        },
        type: 'post',
        url: '/fileuploader/' + file.name,
        xhrFields:
        {
            onprogress: function(progress)
            {
                var percentage = Math.floor((progress.total / progress.totalSize) * 100);
                console.log('progress', percentage);
                if (percentage === 100)
                {
                    console.log('DONE!');
                }
            }
        }
    });

Well, it's been a few years. I revisited this, and using GetFree's answer, I updated my code to the following:

$('#file_input').change(function()
{
    var file = this.files[0];
    $('#upload_button').click(funtion(e)
    {
        req = new XMLHttpRequest();
        req.upload.addEventListener('progress', updateProgress, false);
        req.addEventListener('load', transferComplete, false);
        var url  = 'https://my.url'; 
        req.open('POST', url, true);
        req.setRequestHeader('Content-Type', myFileType);
        req.setRequestHeader('Content-Length', myFileLength);
        req.send(file);
    });
);
function updateProgress(e)
{
    var percent = Math.floor(e.loaded / e.total * 100);
    console.log("percent = " + percent);
}
function transferComplete(e)
{
    console.log("transfer complete");
}

I have marked GetFree's post as the accepted answer. Sorry for the delay.

Upvotes: 32

Views: 53108

Answers (3)

Andrew Foster
Andrew Foster

Reputation: 115

This is a bit of a hack of another answer elsewhere on Stack Overflow, but I think answers your question. I have doctored it to my own needs of pumping "streamed" data from the server to a scrollable div (which I can then force to always scroll to the bottom thus showing progress over time and thus not waiting for the entire record set to complete).

The client side code below adds the resulting content to a predefined div with id "scrollable_area" (which can then be scrolled)...

<div style="position:absolute; left:5px; right:5px; top:5px; height:35px;">
    <label for="auto_scroll">Auto Scroll</label> <input type="checkbox" id="auto_scroll" checked>
</div>
<div id="scrollable_area" style="position:absolute; overflow:auto; left:5px; right:5px; top:45px; bottom:5px;"></div>
<script type="text/javascript">
    var last_response_len = false;
    var auto_scroll = null;
    var scrollable_area = null;
    $().ready(function() {
        auto_scroll = document.getElementById("auto_scroll");
        scrollable_area = document.getElementById("scrollable_area");
        $.ajax("your_api_call.php", {
            xhrFields: {
                onprogress: function(e) {
                    var this_response, response = e.currentTarget.response;
                    if(last_response_len === false) {
                        this_response = response;
                        last_response_len = response.length;
                    } else {
                        this_response = response.substring(last_response_len);
                        last_response_len = response.length;
                    }
                    scrollable_area.innerHTML += this_response;
                    if(auto_scroll.checked) {
                        scrollable_area.scrollTop = scrollable_area.clientHeight + scrollable_area.scrollHeight + 500;
                    }
                }
            }
        })
        .done(function(data) {
            console.log("Completed response");
        })
        .fail(function(data) {
            console.log("Error: ", data);
        });
        console.log("your_api_call.php Request Sent!");
    });
</script>

The server side "your_api_call.php" file call would need to then flush its output (per data row so as to see progress over time) which can then be displayed immediately within the above "scrollable_area" div...

// Do Db loop
        while ($record = $recordset->fetch(PDO::FETCH_ASSOC)) {
            set_time_limit(10); // Don't timeout on large data sets seeing as this is a big task that we are wanting to watch progress!
            echo 'Do what you gotta do... ' . $record["register_id"] . '<br>';
            flush(); // Push to the client / ajax
            ob_flush(); // As above
        }

Short answer... YES. Hope this helps :-)

Upvotes: 1

GetFree
GetFree

Reputation: 42424

Short answer:
No, you can't do what you want using xhrFields.

Long answer:

There are two progress events in a XmlHttpRequest object:

  • The response progress (XmlHttpRequest.onprogress)
    This is when the browser is downloading the data from the server.

  • The request progress (XmlHttpRequest.upload.onprogress)
    This is when the browser is sending the data to the server (including POST parameters, cookies, and files)

In your code you are using the response progress event, but what you need is the request progress event. This is how you do it:

$.ajax({
    async: true,
    contentType: file.type,
    data: file,
    dataType: 'xml',
    processData: false,
    success: function(xml){
        // Do stuff with the returned xml
    },
    type: 'post',
    url: '/fileuploader/' + file.name,
    xhr: function(){
        // get the native XmlHttpRequest object
        var xhr = $.ajaxSettings.xhr() ;
        // set the onprogress event handler
        xhr.upload.onprogress = function(evt){ console.log('progress', evt.loaded/evt.total*100) } ;
        // set the onload event handler
        xhr.upload.onload = function(){ console.log('DONE!') } ;
        // return the customized object
        return xhr ;
    }
});

The xhr option parameter must be a function that returns a native XmlHttpRequest object for jQuery to use.

Upvotes: 72

CooCooCaCha
CooCooCaCha

Reputation: 91

You need to add an event handler to the request itself before it is sent out. jQuery.ajax allows this through the 'beforeSend' property http://api.jquery.com/jQuery.ajax/

for example: http://www.dave-bond.com/blog/2010/01/JQuery-ajax-progress-HMTL5/

* EDIT * Make sure to look at the second code sample of that example link. I believe the first one is out of date with modern versions of jQuery.

$.ajax({
  xhr: function()
  {
    var xhr = new window.XMLHttpRequest();
    //Upload progress
    xhr.upload.addEventListener("progress", function(evt){
      if (evt.lengthComputable) {
        var percentComplete = evt.loaded / evt.total;
        //Do something with upload progress
        console.log(percentComplete);
      }
    }, false);
    //Download progress
    xhr.addEventListener("progress", function(evt){
      if (evt.lengthComputable) {
        var percentComplete = evt.loaded / evt.total;
        //Do something with download progress
        console.log(percentComplete);
      }
    }, false);
    return xhr;
  },
  type: 'POST',
  url: "/",
  data: {},
  success: function(data){
    //Do something success-ish
  }
});

Upvotes: 4

Related Questions