blog1 - anuncio

viernes, 19 de octubre de 2012

File Uploads en Yii Framework (con "drag and drop")



Para descargarlo sigue el vinculo "Download" que esta al lado de Size 40.6 KB (download) en la ventana de leyenda a la derecha.  
Ir al repositorio de Coco !

'Coco' es un widget (aparte de ser el menor y mas temeroso de mis Yorkies) para manejar subidas de archivos a tu website. Con este widget se pretende crear una herramienta que te ayude a olvidarte de la complejidad de este asunto, sacando provecho de Yii Framework, jQuery, Ajax y UML/MVC
La implementación del widget en tu proyecto es muy simple, se hace en dos partes: Primero, Pones el widget en la vista o formulario en donde lo requieras y Segundo en algun controller pones un action fijo en cualquier controller, no solamente aquel del formulario (un action fijo es aquel que se coloca en el metodo de actions() del controller), este action tiene como proposito conectar el código javascript del widget con tu aplicación Yii.
Coco toma su funcionalidad de una librería PHP muy buena que consegui hace un año y que decidí implementar para Yii framework en forma de Widget. La licencia del autor de la libreria original es respetada y matiene sus creditos. Puedes hallarlo en el repositorio valums de github.


  1. DESCARGA O CLONA COCO.

    Si no usas GIT simplemente copia el contenido de la extension directamente dentro de 'extensions', si usas GIT haz lo siguiente:

    cd /home/blabla/myapp/protected
    mkdir extensions
    cd extensions
    git clone git@bitbucket.org:christiansalazarh/coco.git
  2. SETUP EN CONFIG/MAIN

    Edita tu archivo /protected/config/main.php
    'import'=>array(
            'application.models.*',
            'application.components.*',
            'application.extensions.coco.*',            // <------agrega esto
        ),

  3. CONECTA A "COCO" A TU APLICACION CON UN ACTION ESTATICO

    Edita protected/controllers/siteController.php (aunque puedes usar otra).
    Este action solo es requerido una vez para todo el proyecto !!
    y agregale lo siguiente (coloreado) :

    public function actions()
        {
            return array(
                'captcha'=>array(
                    'class'=>'CCaptchaAction',
                    'backColor'=>0xFFFFFF,
                ),
                'page'=>array(
                    'class'=>'CViewAction',
                ),
                'coco'=>array(
                    'class'=>'CocoAction',
                ),
            );
        }
  4. INSERTA EL WIDGET EN UNA VISTA

    <?php
        $this->widget('ext.coco.CocoWidget'
            ,array(
                'id'=>'cocowidget1',
                'onCompleted'=>'function(id,filename,jsoninfo){  }',
                'onCancelled'=>'function(id,filename){ alert("cancelled"); }',
                'onMessage'=>'function(m){ alert(m); }',
                'allowedExtensions'=>array('jpeg','jpg','gif','png'),
                'sizeLimit'=>2000000,
                'uploadDir' => 'assets/',
                // para recibir el archivo subido:
                'receptorClassName'=>'application.models.MyModel',
                'methodName'=>'onFileUploaded',
                'userdata'=>$model->primaryKey,
            ));
       ?>

¿ CÓMO FUNCIONA COCO ?
  1. Cuando alguien visite la vista en donde insertaste el Widget verás que aparece un botón con el texto que pusiste en el argumento "buttonText", el cual por defecto dice: "Find & Upload".
  2. Alguien podrá arrastrar un archivo a ese botón o podrá darle clic y éste le presentará una caja de selección de archivo a subir.
  3. El usuario envia el archivo, y coco internamente invoca a tu action: index.php?r=site/coco con algunos argumentos. Va a transferir a ese action el archivo a subir mediante llamadas ajax.  Tu no haces nada en este punto, solo mirar como coco lo hace, incluso te presentará una caja de progreso cancelable.
  4. Cuando el action determina que el archivo fue subido (lo sube en la carpeta que tu indicas en el atributo "uploadDir") entonces, hara una de estas dos cosas:

    a) Si no has configurado los argumentos: receptorClassName y methodName entonces Coco dejará el archivo en el directorio assets (o donde tu digas en "uploadDir") y solo recibiras una notificacion via ajax en el metodo javascript que has definido en "onCompleted".

    b) Si has configurado los argumentos: receptorClassName y methodName, entonces coco desde el lado del servidor creara una instancia de la clase que tu configures en "receptorClassName" y luego invocará un método: aquel que pusiste en "methodName".  En ese método podrás recibir la notificacion del archivo subido.

    Por tanto, para que esta opción B funcione deberás crear una clase asi:

    // creas la clase en: protected/models/MyModel.php  (o donde quieras)
    class MyModel {

        public function onFileUploaded($fullFileName,$userdata) {
            // userdata es el mismo valor que pusiste en config/main
            // fullFileName es la ruta del archivo listo para leer.
        }
    }

    y los argumentos a pasarle al widget serían:
                'receptorClassName'=>'application.models.MyModel',
                'methodName'=>'onFileUploaded',


OTRAS OPCIONES DEL WIDGET:

'buttonText'=>'Find & Upload',
'dropFilesText'=>'Drop Files Here !',
'htmlOptions'=>array('style'=>'width: 300px;'),
'defaultControllerName'=>'site',
'defaultActionName'=>'coco',

29 comentarios:

  1. un favor tengo un error me sale lo siguiente

    include(CJavaScriptExpression.php) [function.include]: failed to open stream: No such file or directory

    no se a que se debe te agradesco de antemano

    ResponderEliminar
    Respuestas
    1. https://bitbucket.org/christiansalazarh/coco/issue/2/include-cjavascriptexpressionphp

      Eliminar
  2. hola. eso es porque tu versión de yii framework es muy baja. actualizala. CJavaScriptExpression es usada para mapear eventos que serán invocados por javascript.

    ResponderEliminar
    Respuestas
    1. Buenisimo!!! ya funciona solo que al subir el archivo sale error estoy revisandotu pagina para encontrar cual el problema

      Eliminar
    2. ahora me sale el error failed despues de elegir el archivo intenta subirlo pero al final sale un error diciendo "Failed" probe con diferentes tamaños de archivos creyendo que era por el tamaño en todos me sale ese error.

      y una consulta una ves que se sube el archivo se guarda en alguna variale la direccion? ya que me serviria para almacenarlo en mi BD

      gracias por tu colaboracion

      Eliminar
    3. hola en la doc o en la lista de issues puedes ayudarte mejor, aunque te adelanto que eso normalmente ocurre por dos cosas:

      1. permisos insuficientes en el directorio de uploads, que se resuelven de primera mano con un chmod 777.

      2. el argumento sizeLimit del widget tiene un valor mayor que el definido en php.ini (max_upload_size).

      Eliminar
    4. Mira intente los dos metodos que dijiste en el primero use attrib ya que uso Windows en el tema de sizeLimit ya modifique en varias instancias y no resulto. Es mas intente cambiar de extension por eajaxupload y al final tengo el mismo error de Failed ya estoy super estresado que no sale una ayuda clamorosa

      gracias

      Eliminar
    5. hola raul.

      ya revisaste el log de coco ? que dice ?

      Eliminar
    6. Christian

      imagino que te refieres al archivo access.log

      encontre lo siguiente:

      127.0.0.1 - - [10/Nov/2012:19:35:21 -0400] "POST /proyecto/SiteController/coco&action=upload&nocache=1352590514170&data=a:6:%7Bs:17:%22allowedExtensions%22;a:4:%7Bi:0;s:4:%22jpeg%22;i:1;s:3:%22jpg%22;i:2;s:3:%22gif%22;i:3;s:3:%22png%22;%7Ds:9:%22sizeLimit%22;i:200000;s:9:%22uploadDir%22;s:7:%22assets/%22;s:17:%22receptorClassName%22;s:26:%22application.models.MyModel%22;s:10:%22methodName%22;s:14:%22onFileUploaded%22;s:8:%22userdata%22;N;%7D?qqfile=Desert.jpg HTTP/1.1" 404 364 "http://localhost/proyecto/documentos/create" "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:16.0) Gecko/20100101 Firefox/16.0"

      Eliminar
    7. no, me refiero al archivo de log de Yii framework. esta ahi anotado en la documentacion de coco.

      este archivo se halla en:
      /tu/app/protected/runtime/application.log

      coco informará bajo el key "info" o "error", ambos se deben habilitar en la seccion de log de tu config/main.php sino no verás el registro de error.

      Eliminar
    8. revise el archivo y el ultimo registro en ese archivo dice:

      2012/11/12 11:42:28 [error] [exception.CHttpException.404] exception 'CHttpException' with message 'No es posible resolver la solicitud "SiteController/coco&action=upload&nocache=1352734938165&data=a:6:{s:17:"allowedExtensions";a:4:{i:0;s:4:"jpeg";i:1;s:3:"jpg";i:2;s:3:"gif";i:3;s:3:"png";}s:9:"sizeLimit";i:200000;s:9:"uploadDir";s:7:"assets/";s:17:"receptorClassName";s:26:"application.models.MyModel";s:10:"methodName";s:14:"onFileUploaded";s:8:"userdata";N;}"' in C:\xampplite\htdocs\yii\framework\web\CWebApplication.php:287

      Stack trace:

      #0 C:\xampplite\htdocs\yii\framework\web\CWebApplication.php(142): CWebApplication->runController('SiteController/...')

      #1 C:\xampplite\htdocs\yii\framework\base\CApplication.php(162): CWebApplication->processRequest()

      #2 C:\xampplite\htdocs\proyecto\index.php(13): CApplication->run()

      #3 {main}
      REQUEST_URI=/proyecto/SiteController/coco&action=upload&nocache=1352734938165&data=a:6:%7Bs:17:%22allowedExtensions%22;a:4:%7Bi:0;s:4:%22jpeg%22;i:1;s:3:%22jpg%22;i:2;s:3:%22gif%22;i:3;s:3:%22png%22;%7Ds:9:%22sizeLimit%22;i:200000;s:9:%22uploadDir%22;s:7:%22assets/%22;s:17:%22receptorClassName%22;s:26:%22application.models.MyModel%22;s:10:%22methodName%22;s:14:%22onFileUploaded%22;s:8:%22userdata%22;N;%7D?qqfile=Desert.jpg

      HTTP_REFERER=http://localhost/proyecto/documentos/create
      ---

      Eliminar
    9. hola un 404 es un indicador de "page not found". algo malo tienes en tu codigo respecto a las URL. Muestrame cómo configuraste a coco en config/main y en El widget como tal. En ambas, no me mandes solo una

      Eliminar
    10. ok esta es mi vista con el wiget:


      renderPartial('_form', array('model'=>$model)); ?>

      widget('ext.coco.CocoWidget'
      ,array(
      'id'=>'cocowidget1',
      'onCompleted'=>'function(id,filename,jsoninfo){ }',
      'onCancelled'=>'function(id,filename){ alert("cancelled"); }',
      'onMessage'=>'function(m){ alert(m); }',
      'allowedExtensions'=>array('jpeg','jpg','gif','png'),
      'sizeLimit'=>200000,
      'uploadDir' => 'assets/', // coco will @mkdir it
      // this arguments are used to send a notification
      // on a specific class when a new file is uploaded,
      ... 'receptorClassName'=>'application.models.MyModel',
      'methodName'=>'onFileUploaded',
      'userdata'=>$model->primaryKey,
      ));
      ?>
      ....
      y el este parte del codigo de main
      ....
      'sourceLanguage'=>'en',
      'charset'=>'iso-8859-1',
      'preload'=>array('log'),
      'import'=>array(
      'application.models.*',
      'application.components.*',
      'application.extensions.coco.*',
      ),
      'modules'=>array(
      'gii'=>array(
      'class'=>'system.gii.GiiModule',
      'password'=>'prietor',

      ....

      Eliminar
    11. y la conexión de coco al site controller la pusiste ? el widget esta bien configurado segun me muestras, pero si éste no está conectado al sitecontroller (u otro controller) no va a funcionar.

      Eliminar
    12. igualmente agrego, porqué dice
      "... 'receptorClassName'=>'application.models.MyModel',"

      y no: (sin ...)
      'receptorClassName'=>'application.models.MyModel',

      tu clase MyModel existe ? y el metodo onFileUploaded existe en esa clase ?

      Eliminar
    13. el SiteController esta configurado coloque el codigo que se describe

      'page'=>array(
      'class'=>'CViewAction',
      ),
      'coco'=>array(
      'class'=>'CocoAction',
      ),


      La clase MyModel Existe, tiene el metodo onFileUploaded


      <?php

      class MyModel {

      public function onFileUploaded($fullFileName,$userdata) {

      }
      }


      ahora lo comente y tampoco funciona

      Eliminar
    14. 1. igualmente, asegurate que no hayas cambiado a defaultControllerName, si vas a pones a coco en un controller distinto, solo pones ahi el controllerName, es decir "site" no "siteController".

      2. limpia los assets. (no los de coco dentro de la extension), la ruta de los assets es: /tuapp/assets, no borres el directorio, borra su contenido y vuelve a iniciar la aplicación.

      Eliminar
    15. volvi a recrear tu caso, y efectivamente el error da si tu has modificado el valor de defaultControllerName por "siteController" en vez del valor por defecto "site".

      Debido a que en la config que me muestras no aparece tal modificación, asumo que hiciste modificaciones dentro del codigo fuente de "coco".

      Eliminar
    16. si efectivamente Chistian modifique el codigo, volvi a implementar todo de cero en una nuevo proyecto subio sin problemas, ya lo adaptae a mi proyecto original y fubciona de 10 adicione otras extensiones que son las que necesito y funciona

      te agradezco de mil por el tiempo que dedicaste a resolver mi problema ahora puedo avanzar con mi proyecto gracias de nuevo!!!!!

      Eliminar
    17. que bueno que ya resolviste, solo debiste haber puesto $defaultControllerName = 'site'; como viene de fabrica y listo debio funcionar.

      Eliminar
  3. Hola, ademas de subir el archivo debo guardar la ruta en base de datos,¿Como lo puedo hacer?

    ResponderEliminar
    Respuestas
    1. hola, bueno eso se sale un poco de este tutorial, pero te informo de todos modos a modo de guia, para que tengas mayor información te invito a visitar:
      http://facebook.com/Yiiframeworkenespanol
      y
      http://www.yiiframeworkenespanol.org

      como habrás visto, coco pide un metodo en una clase
      cualquiera, ese metodo sera invocado con la
      ruta del archivo recientemente cargado.

      public function onFileUploaded($fullFileName,$userdata)
      {
      // fullFileName es la ruta del archivo
      // listo para leer.

      podrias hacer:
      $userid = Yii::app()->user->id;
      o
      $userid = $userdata;
      siendo este ultimo algun ID pasado al widget
      en la vista en donde lo pusiste. supon que es
      el ID del usuario activo que tu tienes.

      bueno, simple:
      $nuevaRuta = crea_nueva_ruta($userid);
      mover_archivo($fullFileName, $nuevaRuta);

      $usuario = Tuusuario::model()->findByPk($userid);

      // podrias usar esa foto como perfil:
      $usuario->fotoPerfil = $nuevaRuta;

      // o podrias almacenar esa foto o archivo
      // en su lista de archivos...
      $almacen = $usuario->getAlmacen();
      $almacen->agregar($nuevaRuta);
      }



      Eliminar
  4. este tema de obtener la ruta como se logra?, tengo implementado el modelo MyModel.php

    <?php
    class MyModel {
    public function onFileUploaded($fullFileName,$userdata) {
    }
    }

    la idea es que pueda guardar en un registro(ubicacion) en mi BD la ruta del archivo que acabo de subir quedando asi:
    assets/prueba.xls

    ResponderEliminar
  5. Excelente trabajo. Una pregunta que widget compatible con este para la crop de imagenes.. Yo retorno la vista previa de la imagen subida pero no veo como integrarlo con algo como jiicrop para el cortado. Pistas quiza muchas gracias de nuevo por tu ayuda con Upload y esperando si alguien sabe de lo otro

    ResponderEliminar
    Respuestas
    1. hola, en el metodo onFileUploaded podrias tomar la imagen y convertirla a una version reducida usando esto:
      http://pastebin.com/REixhKaB

      Eliminar
  6. hola, en el metodo onFileUploaded podrias tomar la imagen y convertirla a una version reducida usando esto:
    http://pastebin.com/REixhKaB

    ResponderEliminar
  7. Christian como estas? logre hacer funcionar coco pero no funciona cuando lo ejecuto en una ventana modal cjuidialog, alguna solución?

    ResponderEliminar
    Respuestas
    1. Hola, aqui tienes un ejemplo, no va mas alla de insertar el CJui y listo funciona,

      pageTitle=Yii::app()->name;
      ?>

      beginWidget('zii.widgets.jui.CJuiDialog',array(
      'id'=>'mydialog',
      'options'=>array(
      'title'=>'Dialog box 1',
      'autoOpen'=>false,
      ),
      ));
      ?>

      widget('ext.coco.CocoWidget'
      ,array(
      'id'=>'cocowidget1',
      'onCompleted'=>'function(id,filename,jsoninfo){ }',
      'onCancelled'=>'function(id,filename){ alert("cancelled"); }',
      'onMessage'=>'function(m){ alert(m); }',
      'allowedExtensions'=>array('jpeg','jpg','gif','png'),
      //'sizeLimit'=>2000000,
      'uploadDir' => 'assets/', // coco will @mkdir it
      // this arguments are used to send a notification
      // on a specific class when a new file is uploaded,
      'receptorClassName'=>'application.models.ContactForm',
      'methodName'=>'onFileUploaded',
      'userdata'=>'',
      'multipleFileSelection'=>true,
      'maxConnections'=>3,
      'maxUploads'=>2,
      'maxUploadsReachMessage'=>'No puede subir mas archivos',
      ));
      ?>

      endWidget('zii.widgets.jui.CJuiDialog');
      echo CHtml::link('open dialog', '#', array(
      'onclick'=>'$("#mydialog").dialog("open"); return false;',
      ));
      ?>

      Eliminar
  8. Este comentario ha sido eliminado por el autor.

    ResponderEliminar