Upload progress bar en CodeIgniter sin Flash

Publicado: 15/10/2010 en AJAX, CodeIgniter, desarrollo, JavaScript, jQuery, JSON, php, Programación, Servidor
Etiquetas:, , , , , , , , , , , ,

You can read the English version of this post in http://phpsblog.agustinvillalba.com/upload-progress-bar-in-codeigniter-without-flash/

Hoy vamos a ver cómo podemos crearnos una barra de progreso de subida de archivos en CodeIgniter sin la necesidad de recurrir a librerías o plug-ins hechos en Flash (del tipo SWFUpload, etc) que escapan a nuestro control, dado que habitualmente estas librerías nos ofrecen los archivos .swf ya compilados, por lo que nos es imposible modificar nada en ellos, en el caso de que tengamos conocimientos de programación en ActionScript 2 o 3.

Antes que nada hemos de decir que crear una barra de progreso de subida de archivos en PHP no es tan sencillo como pudiera parecer. El primer problema es que las versiones de PHP anteriores a la 5.2 no ofrecen las herramientas necesarias para poder ofrecer información sobre cómo la subida del archivo en cada momento. El segundo problema es que AJAX, por sí solo, no nos permite consultar el estado de la subida del archivo, dado que, por razones de seguridad obvias, JavaScript no tiene acceso a los archivos del sistema operativo del cliente, por lo que necesitaremos un “truco” utilizando un iframe.

1. Preparando nuestro servidor

Para poder implementar nuestra Upload progress bar necesitamos que nuestro servidor PHP sea de la versión 5.2 o superior. Lo siguiente que necesitamos es tener instalado en el servidor PHP el módulo APC (Alternative PHP Cache). Este módulo será el que nos permita conocer el estado de la subida del archivo en todo momento. Para comprobar esto podemos escribir el siguiente código en cualquier archivo .php que tengamos en el servidor:

<?php phpinfo(); ?>

Si todo está bien, hemos de ver que nuestra versión de servidor es 5.2 o superior y que el módulo APC está instalado y con los siguientes parámetros y valores:

  • apc.rfc1867 = On
  • apc.rfc1867_freq = 5k

Con esto ya tendríamos preparado nuestro servidor para reportar el estado de la subida del archivo.

2. El formulario de subida del archivo

Ahora pasamos a preparar el formulario de subida del archivo para el que necesitamos la barra de progreso. Para ello creamos la vista de CodeIgniter que va a contener el formulario, aunque para ello no vamos a utilzar ninguna función del Form_helper, sino que lo haremos en código HTML puro:

<form name="uploadForm" id="uploadForm" action="<?php echo base_url(); ?>upload/upload_file" method="POST" enctype="multipart/form-data" target="uploadIframe" onsubmit="startProgress('<?php echo $upload_id;?>');">
<input type="hidden" name="APC_UPLOAD_PROGRESS" id="progress_key" value="<?php echo $upload_id;?>"/>
<input type="file" name="uploadedFile" size="50" id="uploadedFile"/>
<span>(max. 1.5 GB)</span>
<br />
<input type="submit" value="Upload" />
</form>
<br />
<div id="progressBar"></div>
<i frame id="uploadIframe" name="uploadIframe" src=""></iframe>
<script type="text/javascript">var url = "<?php echo base_url();?>";</script>
<script type="text/javascript" src="<?php echo base_url();?>js/uploadprogress.js"></script>

Como podemos ver, por último añadimos el archivo JavaScript que va a gestionar el progreso de la barra mediante consultas AJAX. Es un formulario HTML estándar excepto por las siguientes peculiaridades:

  • El envío se realiza contra un iframe. Esto es necesario así el código JavaScript puede seguir ejecutándose mientras se realiza la subida del archivo, de otra forma la página se recargaría y se perdería el código JavaScript (AJAX). Cuando hayamos procesado el formulario y el archivo se haya subido, en el iframe es donde mostraremos la respuesta que nos devuelva la función “upload_file” (que es la que procesará el formulario).
  • Hay un campo oculto especial llamado “APC_UPLOAD_PROGRESS” un valor único generado previamente en el controlador. Este valor se pasará también a la función que controla el estado de la subida del archivo y sirve para identificar el archivo del cual queremos obtener el estado de la subida en el servidor. Es importante que este campo oculto esté siempre antes que el campo de tipo “file” en el formulario.
  • Hay un div llamado “progressBar” que será dónde mostremos la barra de progreso.
  • Añadimos el evento “onsumbit” al formulario, para que en cuanto comience el envío del formulario se llame a la función startProgress de JavaScript que será la que lance el control de la subida del archivo por AJAX.

3. Maquetando la barra de progreso

Aquí es donde reside una de las grande ventajas de este método, y es poder maquetar y modificar los colores, tamaños, etc de los componentes de la barra como nosotros queramos y como mejor se adapte a nuestro sitio. En el archivo .css correspondiente donde queramos maquetar la barra de progreso hemos de añadir las siguientes reglas:

iframe#uploadIframe {
 border: 0px none;
 display: none;
 width: 500px;
}
.progressGrey {
 background: #ccc;
 border:1px solid #000;
 color: #666;
 font-size: 13px;
 font-weight:bold;
 height: 15px;
 overflow:hidden;
 padding:1px 0px 1px 3px;
 position:relative;
 white-space:nowrap;
 width: 500px;
}
.progressRed {
 background: #f00;
 border-right:1px solid #000;
 color: #fff;
 font-size: 13px;
 font-weight:boldwhite-space:nowrap;
 height: 15px;
 left:0px;
 overflow:hidden;
 padding:1px 0px 1px 3px;
 position:absolute;
 top:0px;
 white-space:nowrap;
}

4. Nuestra función que reporta el estado de la subida y la función que procesa la subida

Pasamos ahora a crear la función que controlará el estado de la subida del archivo en todo momento. Para ello tenemos un controlador llamado “upload” y dentro tendremos una función llamada “json_get_uploadprogress”. La razón de que comience el nombre por “json_” es porque utilizamos esta “convención” para indicar que la respuesta que nos devuelve esta función es por JSON. El código sería el siguiente:

public function json_get_uploadprogress()
 {
 $data = array();
 if($this->input->post('upload_id'))
 {
 $uploadKey = $this->input->post('upload_id');
 $status = false;
 $percentage = 0;
 $result = "INITIALISING UPLOAD";
 $data["percentage"] = $percentage;
 $data["result"] = $result;
 if (function_exists('apc_fetch'))
 {
 $status = apc_fetch('upload_'.$uploadKey);
 }
 if (is_array($status))
 {
 log_message("error", "STATUS: ".print_r($status,TRUE));
 if (array_key_exists("total", $status) && array_key_exists("current", $status))
 {
 $percentage = round((($status['current'] / $status['total']) * 100),2);
 if ($status['current'] >= $status['total'])
 {
 $percentage = 100;
 $result = "UPLOAD COMPLETE";
 }
else
 {
 $bytes = array('B','KB','MB','GB','TB');
foreach($bytes as $val)
{
if($status["total"]  > 1024)
{
$status["total"] = $status["total"] / 1024;
$status["current"] = $status["current"] / 1024;
}
else
{
break;
}
}
$result =  $percentage."% (".round($status["current"], 2)." ";
$result.= $val." of ".round($status["total"], 2)." ".$val.")";
}
$data["percentage"] = $percentage;
$data["result"] = $result;
}

}
}
$this->load->view('json/json_response_view',array("array" => $data));
}

En la vista “json_response_view” no tenemos más que la siguiente instrucción:

<?php echo json_encode($array); ?>

Esta es la función a la que llamaremos desde AJAX para que nos vaya reportando el estado de la subida del archivo.

Ahora veremos la función dentro del mismo controlador que se encarga de recoger el formulario y procesar la subida del archivo. Esta función devolverá una respuesta cuando finalice, la cual será mostrada en el iframe del formulario.

public function upload_file()
 {
 $path_file = "files/";

 if(!is_dir($path_file))
 {
 mkdir($path_file);
 }

 $config['upload_path'] = $path_file;
 $config['allowed_types'] = 'mp4';
 $tam_gigas = 1.5; //Tamanyo maximo del archivo en gigas
 $config['max_size'] = $tam_gigas * 1048576; //
 $this->load->library('upload');
 $this->upload->initialize($config);
 $div_start = "<div>";
 $div_end = "</div>";
if (!$this->upload->do_upload('uploadedFile'))
 {
 $this->data["params"] = array('success_msg' => $this->upload->display_errors());
 log_message('error','El error: '.$this->upload->display_errors());
 }
 else
 {
 $uploaded_file = $this->upload->data();
 $this->load->view("ajax/generic_response_view",array("resp" => $div_start."The file has been uploaded successfully.".$div_end));
 return;
 }

5. AJAX con jQuery

Por último, tan sólo nos falta crear el archivo JavaScript que consulta el estado de la subida del archivo por AJAX con jQuery. En nuestro caso la consulta se realiza cada 1 segundo, cada cual puede modificar este tiempo como le plazca, pero han de tener en cuenta que si hacen las consultas muy seguidas (por debajo de 1 seg) pueden saturar el navegador del cliente o provocar que las consultas y respuestas se machaquen unas a otras si la conexión del cliente no es muy buena, si el tiempo de consulta es muy grande (por encima del segundo) pueden hacer que el feedback que el usuario recibe sobre el estado de la subida sea muy esporádico, dando la sensación de que la subida se realiza “a saltos”.

Este es el código jQuery de la consulta que colocaremos en el archivo “js/uploadprogress.js”:

$(function(){
 $("input#uploadedFile").bind('change', null, function(){check_file_type()});
});

function getProgress(uploadKey){
$.post(url+"match/json_get_uploadprogress",{"upload_id":uploadKey},
function(data)
{
if(!data) return;
var result = data.result;
var percentage = 0;
percentage = data.percentage;
if (percentage > 0) {
$("#progressBar").html("<h3>Upload progress...</h3><div class='progressGrey'>"+result+"<div class='progressRed' style='width:"+percentage+"%'>"+result+"</div></div>");
} else {
$("#progressBar").html("<h3>Upload progress...</h3><div class='progressGrey'>"+result+"</div>");
}
if (percentage < 100) {
var timeoutID = window.setTimeout("getProgress(theUploadKey)", 1000); //Se consulta cada 1 segundo
}
if(percentage >= 100)
{
$("iframe#uploadIframe").css('display','block');
var body = $("iframe#uploadIframe").contents().find("body");
//Remaquetamos el body del iframe por culpa de IE
body.css('border','0px none');
body.css('background-color',$("body").css('background-color'));
body.css('text-align','center');
}
}, "json"
);
}

function startProgress(uploadKey) {
 theUploadKey = uploadKey;
 $("#progressBar").css('display','block');
 $("#progressBar").html("<h3>Upload progress...</h3><div class='progressGrey'> </div>");
 $("form#uploadForm").css('display','none');
 getProgress(uploadKey);
 return null;
}

Con esto ya tendríamos funcionando nuestra barra de progreso para la subida de archivos

You can read the English version of this post in

Anuncios
comentarios
  1. Adrian dice:

    Dos problemas la línea 2 de uploadprogress.js llama a otra función inexistente: check_file_type
    $(“input#uploadedFile”).bind(‘change’, null, function(){check_file_type()});
    Que se puede obviar, pero lo más importante.
    es que no veo desde donde se llama a la funcion startProgress. Por ej. con:
    $(‘#uploadform’).submit(function() {
    var uid = $(“#uid”).val();
    startProgress(uid);
    });

    El tema es que si lo hago de esa forma, no entra en el loop que se propone en la linea
    var timeoutID = window.setTimeout(“getProgress(theUploadKey)”, 1000); //Se consulta cada 1 segundo
    he notado, inspeccionando en la consola de google Chrome, que el post a la función php se cancela ni bien el form es enviado, es decir que es imposible que envie la info cada 1 segundo.
    Si alguien me puede ayudar con esto se lo agradeceré

  2. Bravito dice:

    Hola,

    Me falla en la siguiente línea y no tengo ni idea porqué:

    $this->load->library(‘upload’);

    El error es el siguiente:

    Fatal error: Call to a member function library() on a non-object in C:\xampp\htdocs\upload1\upload\upload_file.php on line 20

    Gracias.

  3. Rocco Di Chiara dice:

    Justo lo que estaba buscando.

    No se mucho PHP pero se entiende.

    Lo que no se hacer, es la interfaz desde HTML. ¿Puedes enviar un ejemplo de como llamar al Upload?

    No tengo instalado el APA. ¿Como lo consigo?

    Gracias,

    Rocco Di Chiara.

  4. Jall dice:

    oie disculpa podrias poner las etiquetas de que son soy un poco torpe, y no se que son cada una.

    por ejemplo si son php, o son js, css, etc. pero como veo el codigo poes se ve que es bueno y me gustaria probarlo.

  5. Alex dice:

    Hola, lo tengo a medio funcionar…. que es el $upload_id, algun contador o un numero aleatorio?
    Gracias por compartir 😉

    • Hola,
      el $upload_id no es más que una string generada en PHP con el siguiente código:
      $upload_id = md5(uniqid(rand()));
      que sirve para identificar el canal de subida del archivo que se crea entre el navegador cliente y el servidor, de forma que con ese $upload_id luego puedas preguntarle al servidor por dónde va el porcentaje de esa subida identificada con el $upload_id, por si acaso hubieran varios uploads al mismo tiempo desde varios navegadores. Por eso es importante que ese upload_id sea único mientras existe, porque identificará el stream creado entre cliente y servidor.

      Espero haber aclarado tu duda y espero te sirva el artículo.
      Saludos!

  6. Cesar Auris dice:

    Creo que seria mejor q dejes un archivo para descargar para los q tenemos problemas

  7. Cesar Auris dice:

    Bueno especifica cada archivo con que nombre debe ir por q no llego a entender muy bien
    donde se pone cada archivo o si lo debo llamar con require y la pososcion de cada archivo

    graciass…

  8. kratos dice:

    se ve interesante lo tengo que probar

  9. Dairon Medina dice:

    Muy útil este post. Me ha servido de muucho y está muy bien explicado. Saludos desde Cuba.

  10. Martin dice:

    Sos un capooo!!! justo lo que estaba necesitando, la verdad te agradezco muchisimo este codigo, espero que lo pueda usar libremente para mis proyectos

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s