Comfortable AjaxUploads with CakePHP

By Tobias Funke (Warringer)
After looking for a way to upload files the AJAX way using prototype for quite some time, I have found a very good way to do just that, makeing an internet application react more like a desktop application, without the need for forms if I don't want to. Like getting a file selection dialog when you press a button or a link.
After looking for a way to upload files the AJAX way using prototype for quite some time, I have found a very good way to do just that, makeing an internet application react more like a desktop application, without the need for forms if I don't want to. Like getting a file selection dialog when you press a button or a link.

I have found the script here: http://valums.com/ajax-upload/
As the script doesn't really care what is used to call it up, be it prototype, jQuery or others, I have written a helper using prototype, as it is the default AJAX library used by Cake.

Helper Class:

Download code <?php 
/*
 * Created on 14.04.2009
 *
 * To change the template for this generated file go to
 * Window - Preferences - PHPeclipse - PHP - Code Templates
 */

 
class AjaxuploadHelper extends AppHelper {

     var 
$helpers = array('Javascript''Ajax''Html');

/*
 * Wrapper function generating the javascript for ajaxupload and integrating the
 * Javascript file only when the helper is actually used to save bandwidth and 
 * faster rendering of the page in a browser
 * 
 * @param    string    $id ID of the DOM element that sould be observed to upload
 * @param    string    $script the basic script informations to be wrapped
 * @return    string    A Javascript codeblock
 */

     
function _wrapper($id$script) {
        
$this->Javascript->link('ajaxupload.js'false);
        
$scr implode(','$script);
         
$script "document.observe(\"dom:loaded\", function() {new Ajax_upload('$id',{{$scr}});});";
         return 
$this->Javascript->codeBlock($script);
     }

/*
 * Function to create minimal options for ajaxupload
 * 
 * @param    array    $options Array with options for the upload script
 * @param    array    $options['data'] Array containing additional data to be 
 *                     transmitted
 * @param    boolean    $options['autoSubmit'] Submit after file selection
 * @param    string    $options['responseType'] The type of data that you're 
 *                     expecting back from the server. Html (text) and xml are 
 *                     detected automatically.
 * @return    string    basic options for ajaxupload
 */

     
function _options($options null) {
         
$return = array();
         if (!empty(
$options['data'])) {
             
$data $options['data'];
            
$ret "data: {";
             foreach (
$data as $key=>$value) {
                 
$retdata "$key : $value";
            }
            
$ret .= $retdata."} ";
            
$return[] = $ret;
         }
         if (!empty(
$options['autoSubmit'])) {
             
$return[] = "autoSubmit: {$options['autoSubmit']}";
         }
         if (!empty(
$options['responseType'])) {
             
$return[] = "responseType: {$options['responseType']}";
        }
        return 
$return;
    }

/*
 * Function to get the url from a Cake-relative URL or array of URL parameters, 
 * or external URL (starts with http://)
 * 
 * @param      mixed   $url Cake-relative URL or array of URL parameters, or 
 *                     external URL (starts with http://)
 * @return    string    an URL
 */
     
     
function _url($url) {
         return 
"action: \"".Router::url($url)."\"";
    }

/* 
 * Function to get the name for the data via the controller model of the link
 * 
 * @param      mixed   $url Cake-relative URL or array of URL parameters, or 
 *                     external URL (starts with http://)
 * @return    string    name of the file transfer object
 */
    
    
function _name($url) {
        
$name Inflector::classify($url['controller']);
        return 
"name: \"data[$name][File]\"";
    }

/*
 * Convienience Function to the the prototype element to a DOM id
 * 
 * @param    string    $id ID of a DOM element
 * @return    string    prototype element
 */

    
function _id($id) {
        return 
"$('$id')";
    }

/*
 * Function to create the onSubmit function for ajaxupload
 * 
 * @param     string     $id ID of the DOM element that sould be observed to upload
 * @param    array    $options Array with options for the upload script
 * @param    mixed    $options['files'] Type of the files allowed to be uploaded 
 *                     'image', 'text', 'video', 'audio'
 * @param    string    $options['busy'] ID of a upload indicator element
 * @param    string    $options['disable'] Disables the element used to upload
 * @return    string    generated onSubmit function
 */
    
    
function _submit($id$options) {
        
$submit "";
        if (!empty(
$options['files'])) {
            
$files = array();
            if (
is_array($options['files'])) {
                foreach (
$options['files'] as $type) {
                    
$files[] = $this->__files($type);
                }
                
$file implode("|"$files);
                
$type implode(", "$options['files']);
            } else {
                
$file $this->__files($options['files']);
                
$type $options['files'];
            }
            
$submit .= "if (ext && /^($file)$/.test(ext)){ } else { alert('Only $type files allowed'); return false; } ";
        }
        if (!empty(
$options['busy'])) {
            
$submit .= "$('{$options['busy']}').toggle; ";
        }
        if (!empty(
$options['disable'])) {
            
$submit .= "$id.diable; ";
        }
        return 
"onSubmit: function(file, ext){".$submit."}";
    }

/* 
 * Convenience function to get allowed filetypes
 * 
 * @param    string    $filetype Type of the files allowed to be uploaded 'image', 
 *                     'text', 'video', 'audio'
 * @return    string    A string to be used in a reg-ex
 */
    
    
function __files($filetype) {
        switch (
$filetype) {
            case 
"image":
                
$return "jpg|png|jpeg|gif";
                break;
            case 
"text":
                
$return "txt|html|htm|doc|odt";
                break;
            case 
"video":
                
$return "flv";
                break;
            case 
"music":
                
$return "mp3";
                break;
            default:
                
$return "";
                break;
        }
        return 
$return;
    }

/*
 * Function to create the onComplete function for ajaxupload
 * 
 * @param     string     $id ID of the DOM element that sould be observed to upload
 * @param    array    $options Array with options for the upload script
 * @param    string    $options['busy'] ID of a upload indicator element
 * @param    string    $options['disable'] Disables the element used to upload     
 * @param    string    $options['update']['id'] ID of the element to be updated
 *                     with returned data
 * @param    boolean    $options['update']['reply'] Indicates of you use the 
 *                     filename or the reply to update the element, true for 
 *                     response
 * @param    string    $options['update']['element'] type of a new element that
 *                     is going to be appended to the updated element
 * @return    string    generated onComplete function
 */

    
function _complete($id$options) {
        
$submit "";
        if (!empty(
$options['busy'])) {
            
$submit .= "$('{$options['busy']}').toggle; ";
        }
        if (!empty(
$options['disable'])) {
            
$submit .= "$id.enable; ";
        }
        if (!empty(
$options['update'])) {
            if (
is_array($options['update'])) {
                
$type $options['update']['reply']?'response':'file';
                if (!empty(
$options['update']['element'])) {
                    
$submit .= "$$('#{$options['update']['id']}')[0].insert(new Element('{$options['update']['element']}').update({$type})); ";
                } else {
                    
$submit .= $this->_id($options['update']['id']).".update({$type}); ";
                }
            }
        }
        return 
"onComplete: function(file, response){".$submit."}";
    }
    
/*
 * Main Function for uploading using Ajaxupload
 *
 * @param     string     $button_id Id of the DOM element that sould be observed 
 *                     to upload
 * @param      mixed   $url Cake-relative URL or array of URL parameters, or 
 *                     external URL (starts with http://)
 * @param    array    $options Array with options for the upload script
 * @param    array    $options['data'] Array containing additional data to be 
 *                     transmitted
 * @param    boolean    $options['autoSubmit'] Submit after file selection
 * @param    string    $options['responseType'] The type of data that you're 
 *                     expecting back from the server. Html (text) and xml are 
 *                     detected automatically.
 * @param    mixed    $options['files'] Type of the files allowed to be 
 *                     uploaded 'image', 'text', 'video', 'audio'
 * @param    string    $options['busy'] ID of a upload indicator element
 * @param    string    $options['disable'] Disables the element used to upload
 * @param    string    $options['update']['id'] ID of the element to be updated
 *                     with returned data
 * @param    boolean    $options['update']['reply'] Indicates of you use the 
 *                     filename or the reply to update the element, true for 
 *                     response
 * @param    string    $options['update']['element'] type of a new element that
 *                     is going to be appended to the updated element
 * @return    string    A javascript string
*/

    
function upload($button_id$url$options) {
        
$script $this->_options($options);
        
$script[] = $this->_url($url);
        
$script[] = $this->_name($url);
        
$script[] = $this->_submit($this->_id($button_id), $options);
        
$script[] = $this->_complete($this->_id($button_id), $options);
        return 
$this->_wrapper($button_id$script);
    }
 }
?>

It only needs the DOM ID of the element that is to be used to initiate the upload and the URL to the action handling the uploaded file, which has to be used in the Cake format.

A simple example:

Download code
echo $ajaxupload->upload('upload_button', array('controller' => 'image', 'action' => 'upload');

Or a more complex example, containing some additional data, allowed file types, a busy indicator and disabling the link/button that is used to transfer the data, as well as adding the returned data into a preexisting list.

Download code
$options = array(
  'data' => array(
    'meaningless_data1' => '1',
    'meaningless_data2' => 'Some data'
  ),
  'files' => 'image',
  'busy' => 'busy_indicator',
  'disable' => true,
  'update' => array(
    'reply' => true,
    'id' => 'updatelist_id',
    'element' => 'li'
  )
);
echo $ajaxupload->upload('upload_button', array('controller' => 'image', 'action' => 'upload', $options);

 

Comments 994

CakePHP Team Comments Author Comments
 

Bug

1 Doesn't work for me

View Template:

echo $ajaxupload->upload('upload_button', array('controller' => 'image', 'action' => 'upload');  misses a ) @ the end, it has to be

View Template:

echo $ajaxupload->upload('upload_button', array('controller' => 'image', 'action' => 'upload'));  The same is for the 2nd sample.

After these corrections I still didnt get it to work. There are problems with the options (for the simple example described in your article). The complex example didnt show up.

I found an error in line 132: $id.diable, supposed it is $id.disable.

How do I get an file-field? (Or do I use an existing file-field?)
Posted May 16, 2009 by henrique
 

Comment

2 Thanks! Works for me, but some notes

Helper don't render any buttons or links :(
Place this code after ajaxupload echo:
echo $form->create('Image');
echo $form->submit('Upload',array('id'=>'upload_button'));
echo $form->end();
You can get uploaded file on server-side from $this->data['Image']['File'] But disabled button and busy functions don't work.
Posted Jun 12, 2009 by Stanislav Sokolov
 

Comment

3 Bad..

eorror code :
if (!empty($options['data'])) {
             $data = $options['data'];
            $ret = "data: {";
             foreach ($data as $key=>$value) {
                 $retdata = "$key : $value";
            }
            $ret .= $retdata."} ";
            $return[] = $ret;
         } 

And this helper not work with the component Security



how to make Comfortable AjaxUploads with CakePHP ????
Posted Jun 13, 2009 by Максим
 

Question

4 FileSize?

Hey,

is it possible to check the uploaded Filesize and return an error?


Posted Jun 29, 2009 by traedamatic
 

Question

5 Hello I am new to this framework

Can any one tell me how to use ajaxuploadHelper, i have integrated the Helper but i am not able to understand what code i have to write in controller in upload method.

Thanks
Posted Aug 20, 2009 by CakeJack
 

Comment

6 I would appreciate if you will help me to understand the flow further

Helper don't render any buttons or links :(
Place this code after ajaxupload echo:
echo $form->create('Image');
echo $form->submit('Upload',array('id'=>'upload_button'));
echo $form->end();
You can get uploaded file on server-side from $this->data['Image']['File'] But disabled button and busy functions don't work.

Hey, i am trying to use this part of the code but am not able to understand what code do i have to write in controller, and how will the values be retrieved in controller and tell me if the method upload in view is really working fine?

Thanks in advance
Posted Aug 20, 2009 by CakeJack
 

Comment

7 My Understanding so far

Because the author does not seem to be contributing to these comments, here's what I've figured out so far.

1) Download the code he provided and place it in app/views/helpers/ajaxupload.php

2) Go to the Ajaxupload page he linked to and download ajaxupload.js (http://valums.com/ajax-upload/). Place this file in webroot/js/ajaxupload.js

3) Download the prototype javascript library (http://www.prototypejs.org/) and place it in webroot/js/prototype.js

4) Create a reference to the javascript files we just downloaded. There are a few different ways to do this. For my needs, in default.ctp, I have this:

View Template:


<head>
<?php echo $javascript->link(array('prototype')); ?>
</head>
You don't need to reference ajaxupload.js, it gets called when it's needed.

5) In your controller, make sure you have this:

Controller Class:

<?php 
var $helpers = array('Javascript''Ajaxupload''Ajax');
?>

6) In the view which you will use to upload the file, you will need to set some options, call the ajaxupload helper, and setup a trigger button (in that order):

View Template:


// set some options
// $options['data'] passes additional information along with the file
// $options['files'] specifies the filetype 'image','text','video', or 'audio'
// $options['update']['id'] defines a DOM object that you want to update
//     with the result of the AJAX request.
// $options['update']['reply'] can be set to false to use the name of
//     the file you just uploaded in the update, or true if you want to
//     use the response returned by your controller/action.
$options = array(
    'data' => array(
        'name' => 'value'
    ),
    'files' => 'image',
    'update' => array(
        'id' => 'test',
        'reply' => true
    )
); 

// call the ajaxupload helper
// 'trigger_id' refers to the button, so whatever id you give
// the button, you need to put here. 'controller' and 'action'
// define which controller and method will be called to handle
// the file upload.
echo $ajaxupload->upload('trigger_id', array('controller' => 'posts', 'action' => 'upload_img'),$options);

// setup a trigger button
// This is just a simple button. It can be inside or outside a form
echo $form->button('Text For Button', array('type'=>'button','id'=>'trigger_id'));



Handling the upload:



In your controller, the file is accessed with:

Controller Class:

<?php 
$this
->data['XXXXX']['File']
?>
where XXXXX is the name of the model which corresponds to the controller you specified when you called the ajaxhelper in your view. If I followed the proper naming conventions in my example, the controller is 'posts' (see it?), which would correspond to the model 'Post'. If you're using the 'images' controller, you should be entering 'Image' for XXXXX.

Now for a few quirks:



1) $options['data'] only accepts one name=>value pair.
2) The value of $options['data'] cannot include a dot '.' (a period).
3) As mentioned earlier, $options['busy'] and $options['disable'] don't seem to work.

I've rewritten part of the code posted above to correct issues 1 and 2, but haven't gotten to 3 yet. Find the _options() method and replace it with this:

Helper Class:

<?php 
function _options($options null) {
    
$return = array();
    if (!empty(
$options['data'])) {
        
$data $options['data'];
        
$ret "data: {";
        foreach (
$data as $key=>$value) {
            
$retdata[] = '\''.$key.'\' : \''.$value.'\'';
        }
        
$retdata join(' , ',$retdata);
        
$ret .= $retdata."} ";
        
$return[] = $ret;
    }
    if (!empty(
$options['autoSubmit'])) {
        
$return[] = "autoSubmit: {$options['autoSubmit']}";
    }
    if (!empty(
$options['responseType'])) {
        
$return[] = "responseType: {$options['responseType']}";
    }
    return 
$return;
}
?>

I hope this helps some people.
Posted Oct 15, 2009 by David Cox
 

Comment

8 Interesting case

Intersting! I make only the first steps in programming. I need to know it for my job. I'm an IT controller and to fullfill my job well I have to learn to read and make up logarithms. It turned out to be a retty tough task. I found some books in programming at the rapidshare http://rapid4me.com but sometimes I have even more questions than before reading. The case you gave here is of some interest to me as a professional. So thanks for practice.
Posted Oct 18, 2009 by Nataly
 

Bug

9 About options['disable']

Well, maybe the options['disable'] doesn't work because in the helper code, the author wrote:


if (!empty($options['disable'])) {
   $submit .= "$id.diable; ";
}

If you already noticed, you should edit this part, and write
 $submit .= "$id.disable; "; 
, instead of

 $submit .= "$id.diable; ";  .

:D

Thanks for the helper!
Posted Dec 21, 2009 by tlacomiztli
 

Comment

10 about ajax

Ajax have good functions and its good working with other ones
get pregnant quick
Posted Jan 20, 2010 by saki
   

Comment

12 onclick code

When I attempt to use this, the button onclick code in the source ends up encoding the single quotes into ' for Seo Service i recommend seo services uk and I can’t figure out why. Because of this, the form won’t do anything onclick. Any ideas?
Posted Mar 21, 2010 by shams