Kendo UI Upload Control With Existing Files

kendo-logo
If you hadn’t guessed by my last two posts, I’ve been using Telerik’s Kendo UI controls for a project I’m working on.
Today we’re going to talk about the Kendo UI Upload control, which I have been using in asynchronous mode. As I’ve stated plenty of times before, I’m not a huge fan of 3rd party controls, but the Upload control is pretty nice. That said, there is a nagging issue with the control.

The Problem

The project I am working on has a form that users use as an editor for a “forum” of sorts. They can enter messages and add attachments. They are also free to edit their entries, and make changes to the attachments (add or remove them). Well, that’s where the Kendo UI Upload control falls short. There isn’t a “built-in” way to add an existing list of files and let users remove them using the default Upload interface. You could always write your own UI for displaying the existing files, but that’s a bunch of extra work. Shouldn’t you just be able to use the existing interface and not have to come up with something different? That was my goal. You can hack things fairly easily so that you get a list of the files, but the built-in “Remove” button doesn’t work. That’s what we will discuss in this post. We’ll get the list generated AND have the “Remove” button work as expected (where it calls your Remove URL and actually removes the item from the list).

ASIDE: Before we go any further: I’m not saying “Go out and buy Kendo UI” with this post; there’s no incentive in endorsing the product. Of course if Telerik wants to provide me with a free license, you know, that would be cool ;) Again, I’m not a huge fan of 3rd party tools. I happen to be using them for a client and I encountered this specific issue, and I’m not the only one. The purpose of this post (like the majority of the posts on this blog) is to help others keep their sanity.

There have been numerous inquiries around the web regarding this nagging issue, at StackOverflow, the Telerik MVC Controls Forum (the precursor to Kendo UI), and the Kendo UI Forums. Despite this, there are no real solutions on the web, though that MVC Controls Forum post gets you close.

In case you aren’t familiar with asynchronous Upload control, it’s a very simple design.

Kendo UI Upload Control

Pretty simple, right? Figuring out how the Upload control works, lies in hacking around in the HTML and JavaScript source code.Let’s start with the generated HTML for the Upload control, specifically, after you’ve uploaded a file:

<div class="k-widget k-upload">
  <div class="k-dropzone">
    <div class="k-button k-upload-button">
      <input name="files" id="files"
             type="file" data-role="upload"
             multiple="multiple" autocomplete="off">
      <span>Select...</span>
    </div>
  </div>
  <ul class="k-upload-files k-reset">
    <li class="k-file">
      <span class="k-icon k-success">uploaded</span>
      <span class="k-filename" title="Earnings.docx">Earnings.docx</span>
      <button type="button" class="k-button k-button-icontext k-upload-action">Remove</button>
    </li>
  </ul>
</div>

NOTE: The ul portion is generated once you upload a file.

Keep this structure in mind as we move through the process as this is what we’ll be building manually. For the examples, I’ll make references to ASP.NET MVC, but this approach isn’t .NET specific. You can use Kendo UI with just about any framework.

The MVC View

The initial setup for the Upload control is a piece of cake. In my project I’m using Kendo’s MVVM approach to set up the Upload control (this is inside a form partial, named _Form.cshtml). We’ll simply add a file input and set some data-* attributes to support the setup.

<input name="files" id="files" type="file"
  data-role="upload"
  data-multiple="true"
  data-async='{
    "saveUrl":"@Url.Action("SaveAttachments")",
    "removeUrl":"@Url.Action("RemoveAttachments")",
    "autoUpload":true}' />

I like the MVVM approach for set up because I’m able to initialize multiple Kendo UI controls by simply calling kendo.init with the parent object (in my case it’s a form). That HTML coupled with the bit of JavaScript will set up the Upload control. But again, there isn’t any way to specify a pre-existing list “out of the box.” Let’s take care of that next.

In order to get the existing files list, I mimic the list (the ul) found in the snagged HTML snippet from earlier. There is one main difference, my list is hidden. It is hidden because the file list has to be appended to Kendo’s generated parent element for the Upload control, div.k-upload. That element can be found in the snippet that is generated, which I listed out earlier in the post. In addition, we’ll need to hook up some data to each of the lis after the Upload control is initialized, but that’s getting ahead of ourselves. With that in mind, let’s look at the rest of the code in _Form.cshtml:

ASIDE: I don’t want the list to generate if there isn’t any attachments on my model, hence the if statement

@if (Model.Attachments != null && Model.Attachments.Count > 0)
{
  <ul id="existing-files" class="k-upload-files k-reset" style="display: none;">
    @foreach (var file in Model.Attachments)
    {
      <li class="k-file" data-att-id="@file.Id">
        <span class="k-icon k-success">uploaded</span>
        <span class="k-filename" title="@file.Name">@file.Name</span>
        <button type="button" class="k-button k-button-icontext k-upload-action">
          <span class="k-icon k-delete"></span>
          Remove
        </button>
      </li>
    }
  </ul>
}

Fairly straight-forward, right? Just loop through and add an li that looks like the one that Kendo would create after you upload a file.

We’re on our way, but let’s take a minute to discuss something with that last bit of code that is pretty easy to overlook. It initially tripped me up when I was working on this solution, so I want to make sure you don’t do the same thing.

Digging In

When I was trying to get this approach to work, I ran into various issues. One thing preventing the Remove button from being wired up properly had to do with me not including that first span within the li.

<span class="k-icon k-success">uploaded</span>

It didn’t seem like that would be necessary. It just puts a little checkbox next to the file name in the list. But it is important. Without that, the Remove button won’t make a request to the server. All that would happen is the li will be removed from the list.

How exactly did I figure out what was happening? A bit of digging in the Kendo UI Upload source revealed the required span. In the onRemove function, you’ll find the following code:

if (fileEntry.children(".k-icon").is(".k-success")) {
  removeUploadedFile(fileEntry, this.upload, data);
} else {
  this.removeFileEntry(fileEntry);
}

NOTE: The removeUploadedFile method is the one that makes an AJAX call out to the server.

It makes perfect sense because if the file wasn’t uploaded correctly (as indicated by that span with the two classes .k-icon and .k-success), we wouldn’t need to call out to the server to delete anything.

Client-Side Code

Now that you are aware of the key elements in the HTML, let’s finish this solution up.

We’ve hooked up the HTML, but we’ll need some JavaScript to get this method working.

I actually use CoffeeScript instead of JavaScript. If you aren’t familiar with CoffeeScript, I would recommend you check it out. It reminds me of Ruby syntax, gets rid of the unnecessary noise (such as semicolons), and provides useful shortcuts for common, repeated patterns (e.g. function()). My goal of this post isn’t to teach nor convert you to CoffeeScript. Instead, I’ll present both. Note that the JavaScript has been generated from the CoffeeScript (CoffeeScript “compiles” into JavaScript) using js2coffee.org. It may look a little odd to you, but it works as expected.

NOTE: You’ll notice the “inline function” in the generated JavaScript, the _fn. The CoffeeScript do loop yields this to generate a closure. It’s probably not something you would have written out, but that closure keeps everything isolated inside the loop. You can read more about this on Justin Reidy’s blog.

First, the CoffeeScript:

$fileList = $("#existing-files")
  if $fileList.length > 0
    $(".k-upload").append($fileList)
    $files = $(".k-file")
    for item in $files
      do (item) ->
        $item = $(item)
        fileId = $item.data("att-id")
        filenames = [
          name: fileId
        ]
        $item.data "fileNames", filenames
    $fileList.show()

Now the JavaScript:

var $fileList, $files, item, _fn, _i, _len;

$fileList = $("#existing-files");

if ($fileList.length > 0) {
  $(".k-upload").append($fileList);
  $files = $(".k-file");
  _fn = function(item) {
    var $item, fileId, filenames;
    $item = $(item);
    fileId = $item.data("att-id");
    filenames = [
      {
        name: fileId
      }
    ];
    return $item.data("fileNames", filenames);
  };
  for (_i = 0, _len = $files.length; _i < _len; _i++) {
    item = $files[_i];
    _fn(item);
  }
  $fileList.show();
}

The code should be fairly self-explanatory, but let's quickly review what's happening. First we check if our existing-files element was generated (remember, I'm only outputting the block if there are existing attachments). From there, we loop through the k.file elements (the lis) and wire up the required data that Kendo needs. It's an array of JavaScript objects (in object literal notation) for the files that the li represents.

ASIDE: I'm not sure why it takes an array of "filenames." Each li should represent one file, but alas, that is what it needs.

The Kendo UI Upload control uses the data when posting to the saveUrland removeUrl.

If you were to inspect what is generated by Kendo after you do an upload, you'll see that it stores a bunch of information about the file, e.g. size, name, etc. in the data attribute. For our purposes, all we need to include is a way for us to distinguish each file on the server. Whatever you need to do that should be set to the name property of the object inside the fileNames array. In my case, I'm using GUID instead of the file name.

Wrap-Up

So there you have it. It is possible to add a pre-populated list of uploaded files. It's just two steps, the first is to create the list items, making sure to include the <span class="k-icon k-success"> element. After that, using JavaScript, set a collection (array) of "files" (even though it's a single entry) to a data-key of fileNames for the data of each li. It's actually not too difficult when you know what it entails.

I am a software architect with over 13 years of experience. I simply love coding! I have a driving passion for computers and software development, and a thirst for knowledge that just cannot be quenched. I'm happy to share what I knows in my quest to learn as much as possible. I focus most of my time on web development using Ruby on Rails and ASP.NET MVC.

  • Matthew Garber

    Thanks for this write up. I was able to repurpose it for use in our backbone.js/Spring MVC environment with minimal changes.

  • Andy Shellam

    This worked perfectly, many thanks!