Merida Design Blog

Publicado el | Tutoriales / , , , ,

Como crear plugin de jQuery para input file personalizado con vista previa y drag & drop

En el primer articulo, vimos como crear un input personalizado que muestre una vista previa de la imagen y tenga soporte para drag & drop, en esta ocasión, veremos como convertir ese código a un plugin de jQuery.

Al final del artículo el resultado final será el siguiente:


Plugins de jQuery

Crear un plugin para jQuery en realidad es bastante sencillo, los plugins son básicamente una función que por defecto tiene acceso al o los elementos asignados a través del selector ($('elemento')) y que adicionalmente puede recibir mas parámetros que nos pueden servir para las opciones de configuración del plugin.

Para hacer que la función sea parte del sistema de jQuery se asigna al objecto $.fn al cual tendrás acceso siempre que jQuery sea incluido antes.

Para el caso de nuestro plugin esa asignación sería algo asi:

// file-uploader.js
$.fn.fileUploader = function(options) {
    // funcionalidad del plugin
    ...
}

// Dentro de una etiqueta <script> en el index.html
$('.uploader').fileUploader();

Importante mencionar que this dentro de la función, hace referencia al objeto de jQuery que contiene el elemento al que se le asignó el plugin, para el código de arriba eso sería $('.uploader').


Nuestro plugin

A continuación colocaré el nuevo código fuente y posteriormente explicaré los cambios mas importantes.

El HTML

<!DOCTYPE html>
<html>

  <head>
    <title>jQuery Custom Input File</title>
    <link href="https://file.myfontastic.com/SLzQsLcd7FmmzjBYTcyVW3/icons.css" rel="stylesheet">
    <link rel="stylesheet" href="file-uploader.css">
    <link rel="stylesheet" href="style.css">
  </head>

  <body>
    <input type="file" accept="image/*" id="uploader">
    
    <script src="https://code.jquery.com/jquery-1.12.0.min.js"></script>
    <script src="file-uploader.js"></script>
    <script type="text/javascript">
        $('#uploader').fileUploader({activeColor: 'green', baseColor: '#ccc'});
    </script>
  </body>

</html>

Como se puede ver en la línea 12, en esta ocasión solamente se necesita incluir un input de tipo file y a través del plugin lo sustituiremos por la estructura html necesaria para la apariencia del plugin.

En la fila 17 llamamos al plugin asignándole el input file y le pasamos un par de opciones que sirven para definir el color del borde y el ícono; baseColor se refiere al estado normal y activeColor es el color que se asignará al momento de arrastrar una imagen sobre el plugin.


El CSS

.uploader input {
  display: none;
}

.uploader {
  align-items: center;
  background-color: #efefef;
  background-color: rgba(0, 0, 0, 0.02);
  cursor: pointer;
  display: -webkit-flex;
  display: flex;
  height: 300px;
  justify-content: center;
  outline: 3px dashed #ccc;
  outline-offset: 5px;
  position: relative;
  width: 300px;
}

.uploader img,
.uploader .icon {
  pointer-events: none;
}

.uploader,
.uploader .icon {
  transition: all 100ms ease-in;
}

.uploader .icon {
  color: #eee;
  color: rgba(0, 0, 0, 0.2);
  font-size: 5em;
}

.uploader img {
  left: 50%;
  opacity: 0;
  max-height: 100%;
  max-width: 100%;
  position: absolute;
  top: 50%;
  transition: all 300ms ease-in;
  transform: translate(-50%, -50%);
  z-index: -1;
}

.uploader img.loaded {
  opacity: 1;
}

Si nos fijamos en el código anterior y lo comparamos con este, notarán que únicamente se eliminó la clase .dragging que servía para cambiar el color al momento de arrastrar un objeto por encima, ahora eso lo realizamos desde jQuery en base a los colores recibidos como parámetro.


El JS

Y ahora viene la parte interesante.

(function ($) {
    'use strict';
    
    $.fn.fileUploader = function (options) {
        
        var settings = $.extend({
            activeColor: 'orangered',
            baseColor: '#ccc',
            overlayColor: 'rgba(255,255,255,0.7)'
        }, options);
        
        var $el = $(this),
          $label, $fileInput, $img, $icon;
        
        setup();
        suscribe();
        
        function setup() {
          $label = $('<label></label>')
            .addClass('uploader')
            .attr('ondragover', 'return false')
            .insertAfter($el);
            
          $fileInput = $el.clone()
            .appendTo($label);
          
          $img = $('<img />')
            .appendTo($label);
            
          $icon = $('<i></i>')
            .addClass('icon icon-upload')
            .appendTo($label);
              
          $el.remove();
              
            _setInactive();
        }
        
        function suscribe() {
            $fileInput.on('change', _handleInputChange);
            $img.on('load', _handleImageLoaded);
            $label.on('dragenter', _handleDragEnter);
            $label.on('dragleave', _handleDragLeave);
            $label.on('drop', _handleDrop);
        }
        
        function unsuscribe() {
            $fileInput.off('change', _handleInputChange);
            $img.off('load', _handleImageLoaded);
            $label.off('dragenter', _handleDragEnter);
            $label.off('dragleave', _handleDragLeave);
            $label.off('drop', _handleDrop);
        }
        
        function _handleDragEnter(e){
            e.preventDefault();
            _setActive();
        }
        
        function _handleDragLeave(e) {
            e.preventDefault();
            _setInactive();
        }
        
        function _handleDrop(e) {
            e.preventDefault();
            _setInactive();
            $fileInput[0].files = e.originalEvent.dataTransfer.files;
            _handleInputChange();
        }
        
        function _handleImageLoaded() {
            if (!$img.hasClass('loaded')) {
                $img.addClass('loaded');
            }
            
            _setInactive();
        }
        
        function _handleInputChange(e) {
            var file = (undefined !== e)
              ? e.target.files[0]
              : $fileInput[0].files[0];

            var pattern = /image-*/;
            var reader = new FileReader();

            if (!file.type.match(pattern)) {
                alert('invalid format');
                return;
            }

            if ($label.hasClass('loaded')) {
                $label.removeClass('loaded');
            }

            reader.onload = _handleReaderLoaded;
            reader.readAsDataURL(file);
        }
        
        function _handleReaderLoaded(e) {
            var reader = e.target;
            $img[0].src = reader.result;
            $label.addClass('loaded');
        }
        
        function _setActive() {
          $label.css('outline-color', settings.activeColor);
          $icon.css('color', settings.activeColor);
        }
        
        function _setInactive() {
          $label.css('outline-color', settings.baseColor);
          $icon.css('color', ($img.hasClass('loaded') ? settings.overlayColor : settings.baseColor));
        }
        
        return this;
    }
    
} (jQuery)); 

Para evitar cualquier conflicto con otros plugins que usen el signo de $, debemos encerrar el código del plugin en una función de ejecución inmediata o IIFE por sus siglas en inglés (Immeadiately Invoked Function Expression), a esa función le pasamos el objeto global jQuery y definimos el parámetro como $, de esa forma podemos usarlo como ya estamos acostumbrados.

Las IIFE se usan para crear un contexto interno y evitar poblar el contexto global, es decir, todas las variables declaradas dentro de una IIFE solo pueden ser manipuladas de manera interna.

En las líneas 6 – 10 definimos los valores de configuración por defecto y los sustituimos por los proporcionados en las opciones usando la función $.extend.

Inmediatamente después de declarar las variables iniciales, ejecutamos la función setup que se encarga de crear los elementos necesarios e insertarlos en el DOM en lugar del input.

Las nuevas funciones son _setActive y _setInactive, que se usan para cambiar la apariencia del plugin en base a los colores definidos en las propiedades baseColor y activeColor.

Para el resto de las funciones solamente se cambia la sintaxis para usar la de jQuery, en asignación de eventos y atributos.

Por último, es muy importante que la función del plugin regrese this, ya que esto permite la contatenación de funciones de jQuery. ($('#elemento').find('.clase').css(..), etc).


Dependencias

Te invito a copiar el código del plugin, al que puedes acceder desde el demo, puedes modificarlo para ajustarlo a tus necesidades, solamente debes tener en cuenta incluir también el archivo de estilos file-uploader.css y el enlace que incluye el ícono desde Fontastic.

Aunque no pienso eliminar la fuente para el ícono creada en Fontastic, te recomiendo que visites el sitio y crees tu propia fuente, ahí podrás encontrar otros iconos que tal vez prefieras.


Demo: http://embed.plnkr.co/4SR5v7


Conclusión

Como puedes ver, si estas familiarizado con jQuery crear tus propios plugins es muy sencillo, de esa forma puedes reutilizar tu código y compartirlo con la comunidad :D.

En el siguiente tutorial de esta serie veremos como crear una directiva para AngularJS 1, si estas interesado(a), no olvides que puedes suscribirte al boletín (formulario abajo) para estar al tanto de las nuevas publicaciones.



Publicaciones que pueden interesarte

    Deja un comentario

      tope
    Derechos Reservados, Merida Design 2017
    %d bloggers like this: