CouchApp uploading attachments (part 3): using KendoUI


I have already covered two cases:

  1. Upload a CouchDB document attachment using just HTML (read it here).
  2. Upload a CouchDB document attachment ajaxSubmit from jquery.forms.js: similar to what futon does (read it here).

This third post will use KendoUI Upload widget (and jQuery Upload widget) for saving an attachment into a document.

Security regarding uploading files

It’s pretty common find in forums questions about how to assign an initial value to a file inputin order to upload it into a server or how to automatically upload a file into a server. There are good security reason for not allowing it: that would allow a malicious web page to send files from your machine without you knowing it.

Upload modes

In addition to issues concerning the visual aspect, there are two main modes of uploading files into a server:

  1. Synchronous: you choose when to upload it (typically a submit / save button).
  2. Asynchronous: As soon as you choose the file it starts being uploaded into the server and in the meantime you can keep doing other tasks.

But from CouchDB point of view there are two modes related to HTTP vocabulary:

  1. PUT: Replace the addressed attachment of a document or, if it doesn’t exist, create it.
  2. POST: The address is a collection (either a document or database) and create a new attachment (actually or replace it) in the document.

So even that you can use both for creating and both for replacing documents, there is a subtle difference regarding that with POST the target is a collection and the attachment is not (simply) named and in PUT you exactly specify which attachment you want to replace (or create).

KendoUI upload using PUT

This is the easiest: no way! KendoUI does not support it, period. Since I do prefer to use only one framework I plan to endup implementing it. Subscribe to this posts and stay tuned.

KendoUI upload using POST (synchronous)

In the following example I’m going to define a synchronous Kendo Upload and then, when clicked for submit, send the attachment to the server.

Lets start analyzing the initialization:

db.openDoc(docId, {
    success:function (result) {
        doc = result;
        form = $("#form");
        form.attr("action", "/" + dbName + "/" + doc._id);
        $("input[name='_rev']", form).val(doc._rev);
        upload = $("#files").kendoUpload().data("kendoUpload");
    },
    error:function (a, b, c) {
        alert("Error: " + c);
        console.log("Error on reading doc:", a, b, c);
    }
});
[

What I do is read a document from CouchDB which identifier is docId and if successful I update in the form:

  1. The revision of the document since it is needed when the form is submitted.
  2. The action attribute that is composed by the database name (stored in dbName) plus the document identifier (that I have it both in docId and in the retrieved document (doc._id).

The HTML code is:

<form id="form"
      method="post"
      enctype="multipart/form-data">
    <label class="ob-hidden">Rev:<input type="text" class="k-textbox" name="_rev"/></label>
    <input class="k-filename" id="files" type="file" name="_attachments"/>
    <input class="k-button" type="submit" value="Send" name="send"/>
</form>

NOTE: It is important to note that I must specify our form Encoding Type.

The form would look like:

KendoUI upload form
KendoUI upload form

After selecting the file to upload:

KendoUI upload form with file selected
KendoUI upload form with file selected

And if I submit the form (click on Send button):

KendoUI upload form submitted
KendoUI upload form submitted

If I take a look into CouchDB futon I see:

CouchDB attachment uploaded using KendoUI
CouchDB attachment uploaded using KendoUI

KendoUI upload using POST (synchronous) – controlling result

As I’ve explained in a previous post, if I let the browser submit the form (actually upload the attachment) I cannot validate the file or avoid CouchDB result page. In order to control this I will slightly modify previous code:

<form id="form"
      method="post"
      enctype="multipart/form-data">
    <input class="ob-hidden">type="text" class="k-textbox" name="_rev"/>
    <input class="k-filename" id="files" type="file" name="_attachments"/>
    <input class="k-button" type="button" value="Send" name="send"/>
</form>

What I did is redefining the type of the button from submit to button so now the form will not be submitted when I click on Send.

So, I have to bind a function (onUpload) to click the click event of Send button to verify that a file has been selected and submit the form.

db.openDoc(docId, {
    success:function (result) {
        doc = result;
        form = $("#form");
        $("input[name='_rev']", form).val(doc._rev);
        $("input[type='button']", form).click(onUpload);
        upload = $("#files").kendoUpload().data("kendoUpload");
    },
    error:function (a, b, c) {
        alert("Error: " + c);
        console.log("Error on saving doc:", a, b, c);
    }
});

The function bound to click event is:

function onUpload(e) {
    e.preventDefault();
    var empty = true;
    $.each($("input[type='file'][name='_attachments']", form), function (idx, input) {
        if (input.value !== "") {
            empty = false;
        }
    });
    if (empty === true) {
        alert("No attachment file!");
        return;
    }

    // Submit the form with the attachment
    form.ajaxSubmit({
        url:"/" + dbName + "/" + $.couch.encodeDocId(doc._id),
        success:function (response) {
            alert("saved");
            console.log("response", response);
        },
        error:function (a, b, c) {
            alert("error");
            console.log("errors", a, b, c);
        }
    });
}

Where I iterate over all input fields of type=”file” and which name is _attachment checking if any is empty, if so, I alert about it and do not send the form.

But this produces an undesired effect that is that I endup having an empty attachment on this document!!!

KendoUI upload with submitted empty file
KendoUI upload with submitted empty file

Why? KendoUI upload widget introduces an additional input field, so we actually have to disable it before sending it to the server.

Avoid to send empty input fields:

if (input.value !== "") {
    empty = false;
} else {
    // Prevent submitting an empty input
    input.disabled = true;
}

The question now is that if I did not select any file, the input is disabled and I cannot re-enable it and as consequence I cannot select any further file. The solution, is either enable it for all possible cases (no form submission or error while submitting) or play a little trick: set a timeout long enough for allowing the browser to execute the submission of the form and restore disabled input field in timeout handler (I opted for the timeout):

$.each($("input[type='file'][name='_attachments']", form), function (idx, input) {
    if (input.value !== "") {
        empty = false;
    } else {
        // Prevent submitting an empty input
        input.disabled = true;

        window.setTimeout(function() { input.disabled = false; }, 500);
    }
});

Finally, we still have to clean the form it was successfully sent:

$(".k-upload-files", this.wrapper).remove();

Getting:

KendoUI upload with no empty file
KendoUI upload with no empty file

No additional file, no empty file!!!

KendoUI upload using POST (asynchronous) – controlling result

Not much different than synchronous. The main difference is that for asynchronous KendoUI already defines events and there is one fired just before sending the files and I can hook-up my onUpload function to it. In addition, if this function does not preventDefault then if takes care of actually sending the file (saving us the call to ajaxSubmit).

function onUpload(e) {
    var empty = true;
    $.each($("input[type='file'][name='_attachments']", form), function (idx, input) {
        if (input.value !== "") {
            empty = false;
        }
    });
    if (empty === true) {
        e.preventDefault();
        alert("No attachment file defined!");
    }
}

While the initialization code is:

upload = $("#files").kendoUpload({
    async: {
        saveUrl: "/" + dbName + "/" + $.couch.encodeDocId(doc._id),
        autoUpload: false
    },
    upload: onUpload
}).data("kendoUpload");

NOTE: Here I set it as do not auto-upload but it works also for auto-upload.

In addition and since CouchDB expects the revision while KendoUI is not actually sending the form, we need to insert the revision in e.data
The final code is:

function onUpload(e) {
    var empty = true;
    $.each($("input[type='file'][name='_attachments']", form), function (idx, input) {
        if (input.value !== "") {
            empty = false;
        }
    });
    if (empty === true) {
        e.preventDefault();
        alert("No attachment file defined!");
    }
    console.log("e.data", e.data);
    e.data = {
        _rev:doc._rev
    }
}
Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s