AJAX Image upload at Wizard

I recently had a requirement to implement upload of images through AJAX. The Event Background at the Wizard was one of the fields. One big advantage of this was immediate upload of images so the user doesn’t have to submit the entire form to save the image. Plus, less data had to be sent through the form.

Client Side

On the client side, the image was first cropped to a specific resolution and then uploaded with the form. We were using Croppie for cropping images. The requirement wasn’t uploading the file through AJAX, but uploading the output of Croppie.

The element bound to Croppie was inside a bootstrap modal.


<!-- Bootstrap Modal -->
    <div class="modal-body">
        <div id="upload-cropper">

        </div>
    </div>
    <div class="modal-footer">
        <div class="btn-group">
            <button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
            <button type="button" id="save-crop" class="btn btn-success">Done</button>
        </div>
    </div>

$uploadCropper = $('#upload-cropper').croppie({
    viewport: {
        width: 490,
        height: 245,
        type: 'square'
    },
    boundary: {
        width: 508,
        height: 350
    }
});

The resulting image of croppie could be taken after the user clicked on the #save-crop button. The response value could be saved in an input field and a preview of the resulting image could be shown to the user. So to save the image now, the user could submit the form. The #background_url field would contain the data (base64 encoded) that could be processed at the server.

$("#save-crop").click(function () {
    $uploadCropper.croppie('result', {
        type: 'canvas',
        size: 'original'
    }).then(function (resp) {
        $('#cropper-modal').modal('hide')
        $("#background_url").val(resp);
        $("#image-view-group").show().find("img").attr("src", resp);
        $("#image-upload-group").hide();
    });
});

To upload image through AJAX I had to make a call at the proper endpoint (created below. See Back-end) with the response data. So when the user clicks on #save-crop button, a request to the server is made, and if the upload was successful, only then the preview of the image would be available.

Back-end: Upload endpoint

Let’s assume /files/bgimage is the endpoint where the background image data needs to be uploaded.


@expose('/files/bgimage', methods=('POST','DELETE'))
def bgimage_upload(self, event_id):
    if request.method == 'POST':
        background_image = request.form['bgimage']
        if background_image:
            background_file = uploaded_file(file_content=background_image)
            background_url = upload(
                background_file,
                UPLOAD_PATHS['event']['background_url'].format(
                    event_id=event_id
                ))
            event = DataGetter.get_event(event_id)
            event.background_url = background_url
            save_to_db(event)
            return jsonify({'status': 'ok', 'background_url': background_url})
        else:
            return jsonify({'status': 'no bgimage'})
    elif request.method == 'DELETE':
        event = DataGetter.get_event(event_id)
        event.background_url = ''
        save_to_db(event)
        return jsonify({'status': 'ok'})

The endpoint is defined for POST and DELETE methods. A request with the DELETE method removes the background_url for the Event. An AJAX request with the data should be simple in jQuery:


/* `then` method of Croppie */
.then(function (resp) {
    $('#cropper-modal').modal('hide')
    $("#event-image-upload-label").html(loadingImage);
    $.ajax({
        type: 'POST',
        url: "/files/bgimage",
        data: {bgimage: resp},
        dataType: 'json'
    }).done(function(data) {
        console.log(data);
        $("#image-view-group").show().find("img").attr("src", resp);
        $("#image-upload-group").hide();
    }).fail(function(data) {
        alert("Something went wrong. Please try again.");
    });
});

The AJAX request is sent after Croppie has cropped the image (then method).