XHR2 Uploads and Downloads

Sencha Touch provides the new xhr2 configuration parameter and the Ext.ProgressIndicator class for use with AJAX and AJAX2 development. For more information on XHR2, see XMLHttpRequest Level 2 W3C Working Draft.

XHR2 in Sencha Touch provides a new progress indicator so that apps can keep users informed of data transfer progress.

For information on which browsers support XHR2, see Can I Use XHR2?. For older Android devices, check activity at the Android Usage Dashboard to see the trend - as of this writing, Android 2.2 is almost obsolete and Android 2.3 is trending downward (it was much higher a year ago).

You can test for the presence of XHR2 in a browser or platform using Ext.feature.has.XHR2:

if (Ext.feature.has.XHR2) {
    // Provide upload and download indicators with XHR2 
}

You can also use XHRUploadProgress with Ext.feature to determine if a browser supports progressive uploads:

if (Ext.feature.has.XHRUploadProgress) {
    // Check Browser for progressive uploads
}

New Features

XHR2 features:

Value Description
Ext.field.File Added the FileField, which provides an improved label and a layout to the file input.
Ext.field.FileInput Provides file input.
Ext.ProgressIndicator A Component that is able to visualize progress (upload, download, etc.) This Component is integrated into the Sencha Touch data package and automatically works with all AJAX requests.
xtype:"filefield" Creates an instance of the Ext.field.File class.
xtype:"fileinput" Creates an instance of the Ext.field.FileInput class.
FormData An object in newer browsers used to package data and send it with XHR2. Example 9 provides an additional usage.
responseType: "blob", Describes the type of data that is returned by a server, it can be `text`, `document`, `arraybuffer`, or `blob`. See Blob for more information.
xhr2: true, Enables or disables XMLHttpRequest Level 2 functionality.

Example 1: Making Requests

The following example requests a DOM element from a server. This example shows how to perform a cross-domain access to a server. The example acquires a PHP object and returns it as JSON. The code that follows uses a vbox and an output panel. Below the button in the preview is text output. When you click the button, the example calls the Ext.Ajax.request function to pass in the DOM object. The new variable in the example is xhr2: true which enables the XMLHttpRequest functionality in Touch.

When you’re not sending information across the network such submitting a form or sending an image, the xhr2 variable can be true or false - the results are the same.

Source code: ajax-simple.html

Ext.setup({
    requires: [
        'Ext.Panel',
        'Ext.Button',
        'Ext.form.Panel'
    ],

    onReady: function() {
        var request = {
            url: 'http://sencha-xhr2-demos.herokuapp.com/simple-json.php',
            method: 'POST',
            xhr2: true,
            success: function(response) {
                var out = Ext.getCmp("output");
                response = Ext.JSON.decode(response.responseText, true);
                if(response) out.setHtml(response.message);
            },
            failure: function(response) {
                var out = Ext.getCmp("output");
                out.setHtml(response.message);
            }
        };

        Ext.Viewport.add({
            xtype:"panel",
            layout:"vbox",
            fullscreen:true,
            items: [
                {
                    xtype:"button",
                    text: "Ajax",
                    ui: 'confirm',
                    handler: function(){
                        Ext.Ajax.request(request);
                    }
                },
                {
                    xtype: "panel",
                    id: "output",
                    scrollable: true,
                    flex:1
                }
            ]
        });
    }
});

Example 2: Passing Parameters

The ajax-params example provides AJAX with two parameters that it posts back to the application. This application consists of a button and an output panel. In this example, the request object is modified to include these parameters:

params: {
    firstName: "John",
    lastName: "Doe"
},

Source Code: ajax-params.html

Ext.setup({
    requires: [
        'Ext.Panel',
        'Ext.Button',
        'Ext.form.Panel'
    ],

    // Request will be sent as standard post data
    onReady: function() {
        var request = {
            url: 'http://sencha-xhr2-demos.herokuapp.com/post-json.php',
            method: 'POST',
            xhr2: true,
            params: {
                firstName: "John",
                lastName: "Doe"
            },
            success: function(response) {
                var out = Ext.getCmp("output");
                response = Ext.JSON.decode(response.responseText, true);
                out.setHtml(response.message);
            },
            failure: function(response) {
                var out = Ext.getCmp("output");
                out.setHtml(response.message);
            }
        };

        Ext.Viewport.add({
            xtype:"panel",
            layout:"vbox",
            fullscreen:true,
            items: [
                {
                    xtype:"button",
                    text: "Ajax",
                    ui: 'confirm',
                    handler: function(){
                        Ext.Ajax.request(request);
                    }
                },
                {
                    xtype: "panel",
                    id: "output",
                    scrollable: true,
                    flex:1
                }
            ]
        });
    }
});

Example 3: Sending Form Data Using AJAX

In the ajax-formdata example, form data is sent to the server and received in the application. XHR2 supported browsers provide access to the FormData object, which appends data into the payload to be sent to the server. In this example, the response is in JSON.

This code shows the use of FormData and appending information to create a payload:

var formData = new FormData();
formData.append("firstName", "John");
formData.append("lastName", "Doe");

This data comes to the application as multi-part form data, not as parameters, which are passed as URL-encoded data.

Source Code: ajax-formdata.html

Ext.setup({
    requires: [
        'Ext.Panel',
        'Ext.Button',
        'Ext.form.Panel'
    ],

    onReady: function() {
        var formData = new FormData();
        formData.append("firstName", "John");
        formData.append("lastName", "Doe");

        // Request is sent as part of the payload instead of as standard post data
        var request = {
            url: 'http://sencha-xhr2-demos.herokuapp.com/post-json.php',
            method: 'POST',
            xhr2: true,
            data: formData,
            success: function(response) {
                var out = Ext.getCmp("output");
                response = Ext.JSON.decode(response.responseText, true);
                out.setHtml(response.message);
            },
            failure: function(response) {
                var out = Ext.getCmp("output");
                out.setHtml(response.message);
            }
        };

        Ext.Viewport.add({
            xtype:"panel",
            layout:"vbox",
            fullscreen:true,
            items: [
                {
                    xtype:"button",
                    text: "Ajax",
                    ui: 'confirm',
                    handler: function(){
                        Ext.Ajax.request(request);
                    }
                },
                {
                    xtype: "panel",
                    id: "output",
                    scrollable: true,
                    flex:1
                }
            ]
        });
    }
});

Example 4: Uploading Using AJAX

The ajax-upload example shows the use of the Ext.field.FileInput class and a progress indicator for sending files from your application over the web to the server. The xtype:"fileinput" creates an instance of the Ext.field.FileInput class.

You can use the file input in the same way as a field:

Ext.Viewport.add({
    xtype:"panel",
    layout:"vbox",
    fullscreen:true,
    items: [
        {
            xtype:"fileinput",
            accept:"image/jpeg"
        }, 
    ...

In this example, the xtype:"fileinput" specifies the type of file users choose on their device.

Progress Indicator

The progress indicator displays a graphic with either text and the percentage of how much time remains to complete an activity, or the graphic lists “Loading”. This indicator lets you monitor the progress of an upload or download transfer over time. The indicator is integrated directly in the data package.

var progressIndicator = Ext.create("Ext.ProgressIndicator", {
    loadingText: "Uploading: {percent}%"
});

accept Parameter

The accept parameter can be audio, video, image, etc. While Sencha Touch supports all MIME media types, browsers may or may not support all media types. You can use a short form with audio, video, and image where you specify the keyword and Touch adds /* - for example:

accept:"image"

Is the same as specifying:

accept:"image/*"

Note: If you specify multiple keywords, use the pipe “|” delimiter and spell out the full form of the keyword, not the short form. For example:

accept:"image/*|video/*|audio/*"

Any of the IANA MIME media types can also be added to the accept string using the pipe delimiter.

For more information on the accept parameter, see W3Schools.

To accept data from a form, use the xtype:"filefield" parameter.

To change the example to support PNG graphics, change the parameter to accept: "image/png" and change the Ext.Msg.alert message in the example.

Capture Options

You can also specify a capture device from where to accept input, which can be camera, camcorder, microphone, or null. If you specify null, the device determines what your intent is depending on the device’s current context. If you specify camera, an Android device opens the device’s camera to satisfy the request for input. In Android, if you specify null, Android opens a list of possible options for the user to choose where to get the image, such as from its camera, the photo gallery, Dropbox, or other options depending on what is configured in the device. If the capture parameter is omitted, the device default is used to supply the image. In Android, the default is to prompt to choose an app from where to get the file.

Note: Capture depends on the platform and the browser for implementation. In iOS, setting capture:"camera" prompts you to take a photo or choose an image from the Gallery.

For example:

{
    xtype:"fileinput",
    accept:"image/png",
    capture:"camera"
}

Multiple Files

You can use the multiple:"true" parameter to indicate the need for multiple media files; however, different platforms have different behaviors. iOS only supports multiple images.

In iOS, if you set multiple:"true", only the Photo Gallery opens and the camera is not permitted to supply the image.

In Android, if you specify capture:"camera", the multiple parameter is ignored.

Example 4 Source Listing

Source Code: ajax-upload.html

Ext.setup({
    requires: [
        'Ext.Panel',
        'Ext.MessageBox',
        'Ext.Button',
        'Ext.ProgressIndicator',
        'Ext.form.Panel',
        'Ext.field.FileInput'
    ],

    onReady: function() {
        var progressIndicator = Ext.create("Ext.ProgressIndicator", {
            loadingText: "Uploading: {percent}%"
        });

        var request = {
            url: 'http://sencha-xhr2-demos.herokuapp.com/simple-json.php',
            method: 'POST',
            xhr2: true,
            progress:progressIndicator,
            success: function(response) {
                var out = Ext.getCmp("output");
                response = Ext.JSON.decode(response.responseText, true);
                out.setHtml(response.message);
            },
            failure: function(response) {
                var out = Ext.getCmp("output");
                out.setHtml(response.message);
            }
        };

        Ext.Viewport.add(progressIndicator);
        Ext.Viewport.add({
            xtype:"panel",
            layout:"vbox",
            fullscreen:true,
            items: [
                {
                    xtype:"fileinput",
                    accept:"image/jpeg"
                },
                {
                    xtype:"button",
                    text: "Upload",
                    ui: 'confirm',
                    handler: function(){
                        var input = Ext.Viewport.down("fileinput").input;
                        var files = input.dom.files;
                        if (files.length) {
                            request.binaryData = files[0];
                            Ext.Ajax.request(request);
                        }else {
                            Ext.Msg.alert("Please Select a JPG");
                        }
                    }
                },
                {
                    xtype: "panel",
                    id: "output",
                    scrollable: true,
                    flex:1
                }
            ]
        });
    }
});

Specifying Other Inputs

You can add other input types such as a color, date, email, telephone keypad, and more. All possible types are described in HTML5 Input Types. Depending on which browser or platform you’re using, when you specify an input type, the browser displays a user interface control for entering the values for the input. For example, if you specify type:"tel", the device displays a telephone keypad that accepts 0-9, #, and *.

See Can I use Date/time input types? for information on which browsers work with the date input type.

To specify a date input, create a textfield and override the type in the component:

{
    xtype:"textfield",
    label:"hello",
    component: {
        type:"date"
    }
}

Example 5: Sending an Image Using AJAX

This example receives an image with AJAX and shows how to download an image with a progress bar.

Source Code: ajax-image.html

Ext.setup({
    requires: [
        'Ext.Panel',
        'Ext.Button',
        'Ext.form.Panel'
    ],

    onReady: function() {
        var progressIndicator = Ext.create("Ext.ProgressIndicator");

        var request = {
            url: 'http://sencha-xhr2-demos.herokuapp.com/simple-image.php',
            responseType:"blob",
            method: 'POST',
            progress: progressIndicator,
            xhr2: true,
            success: function(response) {
                var createObjectURL = window.URL && window.URL.createObjectURL ? 
                    window.URL.createObjectURL : webkitURL && webkitURL.createObjectURL ? 
                    webkitURL.createObjectURL : null;
                if (createObjectURL) {
                    var image = Ext.Viewport.down("image");
                    var url = createObjectURL(response.responseBytes);
                    image.setSrc(url);
                }
            },
            failure: function(response) {
                var out = Ext.getCmp("output");
                out.setHtml(response.message);
            }
        };

        Ext.Viewport.add({
            xtype:"panel",
            layout:{
                type: "vbox",
                pack: "center",
                align: "center"
            },
            fullscreen:true,
            items: [
                {
                    xtype:"image",
                    height: 400,
                    width: 250,
                    style: {
                        "background-position": "0 0"
                    },
                    src: 'http://placehold.it/250x400'
                },
                {
                    xtype:"button",
                    text: "Ajax",
                    ui: 'confirm',
                    handler: function(){
                        Ext.Ajax.request(request);
                    }
                },
                {
                    xtype: "panel",
                    id: "output",
                    scrollable: true,
                    flex:1
                }
            ]
        });
    }
});

Example 6: AJAX Array Buffer Response

This example uploads an image to the server using XHR2. The server processes this image and sends it back as binary data. This data is received as an array buffer inside response.responseBytes.

Source Code: ajax-upload-arraybuffer.html

Ext.setup({
    requires: [
        'Ext.Panel',
        'Ext.Img',
        'Ext.MessageBox',
        'Ext.Button',
        'Ext.ProgressIndicator',
        'Ext.form.Panel',
        'Ext.field.FileInput'
    ],

    onReady: function() {
        var progressIndicator = Ext.create("Ext.ProgressIndicator");

        var request = {
            url: 'http://sencha-xhr2-demos.herokuapp.com/upload-arraybuffer.php',
            method: 'POST',
            responseType: "arraybuffer",
            xhr2: true,
            progress:progressIndicator,
            success: function(response) {
                var createObjectURL = window.URL && window.URL.createObjectURL ? 
                    window.URL.createObjectURL : webkitURL && webkitURL.createObjectURL ? 
                    webkitURL.createObjectURL : null;
                if (createObjectURL) {
                    var image = Ext.Viewport.down("image");

                    var blob = new Blob([response.responseBytes], 
                        {type: response.getResponseHeader("Content-Type")});
                    var url = createObjectURL(blob);
                    image.setSrc(url);
                }
            },
            failure: function(response) {
                var out = Ext.getCmp("output");
                out.setHtml(response.message);
            }
        };

        Ext.Viewport.add(progressIndicator);
        Ext.Viewport.add({
            xtype:"panel",
            layout:"vbox",
            fullscreen:true,
            items: [
                {
                    xtype:"image",
                    height: 300,
                    width: 300,
                    style: {
                        "background-position": "0 0"
                    },
                    src: 'http://placehold.it/300x300'
                },
                {
                    xtype:"fileinput",
                    accept:"image/jpeg"
                },
                {
                    xtype:"button",
                    text: "Upload",
                    ui: 'confirm',
                    handler: function(){
                        var input = Ext.Viewport.down("fileinput").input;
                        var image = Ext.Viewport.down("image");
                        var files = input.dom.files;
                        if (files.length) {
                            request.binaryData = files[0];

                            //size is in bytes
                            if(request.binaryData.size <= 2097152) {
                                Ext.Ajax.request(request);
                            } else {
                                Ext.Msg.alert("JPG Must be less then 2MB");
                            }
                        }else {
                            Ext.Msg.alert("Please Select a JPG");
                        }
                    }
                },
                {
                    xtype: "panel",
                    id: "output",
                    scrollable: true,
                    flex:1
                }
            ]
        });
    }
});

Example 7: AJAX Blob Response

This example uploads an image to the server using XHR2. The server then processes this image and sends it back as binary data. This data is received as a Blob inside response.responseBytes.

Source Code: ajax-upload-blob.html

Ext.setup({
    requires: [
        'Ext.Panel',
        'Ext.Img',
        'Ext.MessageBox',
        'Ext.Button',
        'Ext.ProgressIndicator',
        'Ext.form.Panel',
        'Ext.field.FileInput'
    ],

    onReady: function() {
        var progressIndicator = Ext.create("Ext.ProgressIndicator");

        var request = {
            url: 'http://sencha-xhr2-demos.herokuapp.com/upload-blob.php',
            method: 'POST',
            responseType: "blob",
            xhr2: true,
            progress:progressIndicator,
            success: function(response) {
                var createObjectURL = window.URL && window.URL.createObjectURL ? 
                    window.URL.createObjectURL : webkitURL && webkitURL.createObjectURL ? 
                    webkitURL.createObjectURL : null;
                if (createObjectURL) {
                    var image = Ext.Viewport.down("image");
                    var url = createObjectURL(response.responseBytes);
                    image.setSrc(url);
                }
            },
            failure: function(response) {
                var out = Ext.getCmp("output");
                out.setHtml(response.message);
            }
        };

        Ext.Viewport.add(progressIndicator);
        Ext.Viewport.add({
            xtype:"panel",
            layout:"vbox",
            fullscreen:true,
            items: [
                {
                    xtype:"image",
                    height: 300,
                    width: 300,
                    style: {
                        "background-position": "0 0"
                    },
                    src: 'http://placehold.it/300x300'
                },
                {
                    xtype:"fileinput",
                    accept:"image/jpeg"
                },
                {
                    xtype:"button",
                    text: "Upload",
                    ui: 'confirm',
                    handler: function(){
                        var input = Ext.Viewport.down("fileinput").input;
                        var image = Ext.Viewport.down("image");
                        var files = input.dom.files;
                        if (files.length) {
                            request.binaryData = files[0];

                            //size is in bytes
                            if(request.binaryData.size <= 2097152) {
                                request.params = {
                                    width: image.getWidth(),
                                    height: image.getHeight()
                                };
                                Ext.Ajax.request(request);
                            } else {
                                Ext.Msg.alert("JPG Must be less then 2MB");
                            }
                        }else {
                            Ext.Msg.alert("Please Select a JPG");
                        }
                    }
                },
                {
                    xtype: "panel",
                    id: "output",
                    scrollable: true,
                    flex:1
                }
            ]
        });
    }
});

Example 8: AJAX Document Response

In this example, the server responds with HTML markup. Response.responseXML is set to a fully traversable Document object.

Source Code: ajax-document.html

Ext.setup({
    requires: [
        'Ext.Panel',
        'Ext.MessageBox',
        'Ext.Button',
        'Ext.form.Panel'
    ],

    onReady: function() {
        var request = {
            url: 'http://sencha-xhr2-demos.herokuapp.com/simple-document.php',
            method: 'POST',
            responseType:"document",
            xhr2: true,
            success: function(response) {
                var dom = response.responseXML,
                    out = Ext.getCmp("output"),
                    el = dom.querySelector(".response");
                out.innerElement.appendChild(el);
            },
            failure: function(response) {
                console.log(response);
            }
        };

        Ext.Viewport.add({
            xtype:"panel",
            layout:"vbox",
            fullscreen:true,
            items: [
                {
                    xtype:"button",
                    text: "Request",
                    ui: 'confirm',
                    handler: function(){
                        Ext.Ajax.request(request);
                    }
                },
                {
                    xtype: "panel",
                    id: "output",
                    scrollable: true,
                    flex:1
                }
            ]
        });
    }
});

Example 9: Submitting Simple Form Data

In this example, form data is sent across using the new FormData object. The firstName and lastName are appended into this object and sent across as FormData.

Source Code: form-simple.html

Ext.setup({
    requires: [
        'Ext.form.Panel',
        'Ext.Button',
        'Ext.form.FieldSet',
        'Ext.field.Text',
        'Ext.Toolbar'
    ],

    onReady: function() {
        var request = {
            url: 'http://sencha-xhr2-demos.herokuapp.com/post-json.php',
            method: 'POST',

            // Commenting out xhr2 sends this form with normal parameters.
            // When xhr2 is true, the form panel creates the HTML5
            // FormData object to send all fields.
            xhr2: true,

            success: function(form, response) {
                var out = Ext.getCmp("output");
                if(response) out.setHtml(response.message);
            },
            failure: function(form, response) {
                var out = Ext.getCmp("output");
                out.setHtml(response.message);
            }
        };

        Ext.Viewport.add({
            xtype:"formpanel",
            layout:"vbox",
            fullscreen:true,
            items: [
                {
                    xtype: 'fieldset',
                    title: 'My Uploader',
                    items: [
                        {
                            xtype: "textfield",
                            name: "firstName",
                            label: "First Name:"
                        },
                        {
                            xtype: "textfield",
                            name: "lastName",
                            label: "Last Name:"
                        }
                    ]
                },
                {
                    xtype: 'toolbar',
                    layout: {
                        pack: 'center'
                    },
                    ui: 'plain',
                    items: [
                        {
                            xtype: 'button',
                            text: 'Submit',
                            ui: 'confirm',
                            handler: function() {
                                var form = Ext.Viewport.down("formpanel");
                                form.submit(request);
                            }
                        }
                    ]
                },
                {
                    xtype: "panel",
                    id: "output",
                    scrollable: true,
                    flex:1
                }
            ]
        });
    }
});

Example 10: Submitting Multi-Upload Form Data

This example uses the new FormDataobject to not only send firstName and lastName, but also multiple files chosen by the user.

Source Code: form-upload.html

Ext.setup({
    requires: [
        'Ext.form.Panel',
        'Ext.Button',
        'Ext.form.FieldSet',
        'Ext.field.Text',
        'Ext.field.File',
        'Ext.Toolbar',
        'Ext.ProgressIndicator'
    ],

    onReady: function() {

        // If we do not add the progress indicator to anything, it is automatically
        // added to the Viewport when progress starts.
        var progressIndicator = Ext.create("Ext.ProgressIndicator");

        var request = {
            url: 'http://sencha-xhr2-demos.herokuapp.com/form-upload.php',
            method: 'POST',

            // Commenting out xhr2 causes Touch to attempt to send this form through 
            // an iframe for the upload. When xhr2 is true, a new HTML5 FormData object
            // creates to send all fields.
            xhr2: true,

            progress: progressIndicator,

            //Progress can also be a simple callback
            /*progress: function(e) {
                console.log((e.loaded / e.total) * 100);
             },*/

            success: function(form, response) {
                var out = Ext.getCmp("output");
                if(response) {
                    out.setHtml(response.message);
                    for(var file in response.files) {
                        file = response.files[file];
                        out.setHtml(out.getHtml() + "<br/>-" + file.name +": " + file.size);
                    }
                }
            },
            failure: function(form, response) {
                var out = Ext.getCmp("output");
                out.setHtml(response.message);
            }
        };

        Ext.Viewport.add({
            xtype:"formpanel",
            layout:"vbox",
            fullscreen:true,
            items: [
                {
                    xtype: 'fieldset',
                    title: 'My Uploader',
                    items: [
                        {
                            xtype: "textfield",
                            name: "firstName",
                            label: "First Name:"
                        },
                        {
                            xtype: "textfield",
                            name: "lastName",
                            label: "Last Name:"
                        },
                        {
                            xtype:"filefield",
                            label: "Select image(s):",
                            name: "photos",
                            accept:"image/jpeg",
                            multiple: true
                        }
                    ]
                },
                {
                    xtype: 'toolbar',
                    layout: {
                        pack: 'center'
                    },
                    ui: 'plain',
                    items: [
                        {
                            xtype: 'button',
                            text: 'Submit',
                            ui: 'confirm',
                            handler: function() {
                                var form = Ext.Viewport.down("formpanel");
                                var input = Ext.Viewport.down("filefield").getComponent().input;
                                var files = input.dom.files;
                                for(var i = 0 ; i<files.length ; i++){
                                    var file = files[i];
                                    if(file.size > 2097152) {
                                        Ext.Msg.alert("JPG Must be less then 2MB");
                                        return;
                                    }
                                }
                                form.submit(request);
                            }
                        }
                    ]
                },
                {
                    xtype: "panel",
                    id: "output",
                    padding: "10px",
                    scrollable: true,
                    flex:1
                }
            ]
        });
    }
});
Last updated