Thursday, 6 January 2011

AJAX File Upload with Web2py

It was not that long, since I experienced a problem while trying to upload a file using an ajax  trapped form. I thought, it must be me doing something wrong. I was using web2py to embed another page into a page via ajax. That is better known to web2py folk as LOADing a component.

It's just happened that one of such component contains a file upload form. It was my first time using LOAD function provided by web2py. Basically it make use of jQuery to load the page via ajax into a target div and traps input of any form in that page, so that page doesn't reload. Oh, I forgot to say that web2py is bundled with jQuery.

It's always boring and tedious to understand a problem without experiencing it. So, Let's play with an example, (PS: I"m using web2py a full stack python framework, but you can use any language at server side and this problem will be there because, it's a problem with ajax)

My model which defines table like this,



In RDBMS world, it column 'file' of table 'image' will transformed to a type of CHAR(or VARCHAR) and column must not be empty (notnull)
Web2py can enforce this at many levels.
notnull = True is enforced by database
required = True is enforced by DAL (database abstraction layer) of web2py
requires = IS_NOT_EMPTY() is enforced by SQLFORM.
You are free to use any of them and it's up to you to use all of them or only one of them. (we give choice :-) )


Now, the index function which maps a url and renders output



As you see web2py automates almost everything (yes, you can customize everything, and do it manually, if you want). This code generate an upload form as per database table IMAGE. automatically upload file into database, and if there's errors shows them to the user. After successful upload page is redirected to /upload/default/index.html. Wow, Magick! :-)

Now go to myform.html > You will be greeted with a file upload form and it just work.

OK, now we decided to embed myform.html into another page(say index.html).

For that we wrote {{=LOAD(URL(c='default', f='myform.load'))}} in index.html and it got transformed to:



for non-web2py folks, URL function generate url, and here .load is served with a content type of text/html. By using .load extension page is rendered without any template we set up for regular html display. The url /example/default/myform.load is of course a relative url, it get interpreted as http://xyz.com/upload/component/index.load. Here 'example' is application, 'default' is controller and myform is a function. ie., http://xyz.com/example/default/myform.load will map to a function called myform() in a file default.py in an application called example and rendered based on extension(.load, .html, .json, .xml etc).

I can't figure out what's going on. It always showed an error message stating that file is empty.
And I fire up my browser's developer plugins and it become clear that file is not being sent to server via ajax. Yes, it's because ajax can't sent files (read XMLHTTP object).

There are two remedies for this (as far as I can think of)
1) use an iframe to load component. Since there is no AJAX there, it'll work.
this is as simple as writing {{=IFRAME(_src=URL(c='default', f='myform.load'))}} in your view. (here index.html).

But, I hate iframe you may say (whether you say it or not, I'll). I learned a lesson in past that users care about functionality and not a bit about whether we use this or that and follows standard. So, I'm ready to break some if that cause better usability. (So I used iframe). Don't be disappointed, I have another option for you folks,

2) Use one of many ajax file upload plugins. I am going to show a solution based on uploadify library. It uses swfupload. and jQuery. So, if you really want to avoid iframe and burden users by making them download few more dependencies download uploadify library and extarct it into your server. (That said, this library is great if you want to implement multi-file upload). By using uploadify, you are going to do DB insert yourself. (if that's OK go ahead). Extract uploadify download into a folder called 'uploadify' under static directory.

change myform function to:




However, I tried it without embedding into another page, but it'll work even if it's embedded.
There are many different plugins available to do the same, each differs in their approach(like submitting form to an embed iframe.
So take your time to find them and go through them. (So server side code may change depending on your library of choice).
Please look these libraries:

  1. valums ajax upload
  2. jQuery Form plugin


Happy experimentations  :)

2 comments:

  1. Hi, if I set "auto" option = false, have this error:
    AttributeError: 'NoneType' object has no attribute 'file'. How fix it?

    ReplyDelete
  2. It had been months since I wrote this.


    A possible cause for error might be erroneous ajax calls that the library is making.
    Can you open firebug and give the failed request url&params for auto=false, and succes request url&params for good scenario.

    then I can better help you.

    ReplyDelete

'