Comfortable AjaxUploads with CakePHP
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.
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:
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.
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:
<?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:
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.
$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);
