No es una buena idea usar jQuery y Vue.js en la misma interfaz. No lo hagas si puedes evitarlo, pero probablemente si estás leyendo esto no porque quieras usar jQuery y Vue juntos, sino porque tienes que hacerlo. Tal vez un cliente insiste en usar un plugin de jQuery en particular y no hay tiempo para reescribirlo para Vue.
Si tienes cuidado de cómo lo haces, puedse usar jQuery y Vue juntos de forma segura. En este artículo vamos a demostrar cómo añadir el plugin jQuery UI Datepicker a un proyecto Vue.
Y para presumir un poco, incluso vamos a enviar datos entre este plugin jQuery y Vue!
El problema de usar jQuery y Vue juntos
¿Por qué es potencialmente peligroso hacer esto?
Vue es una biblioteca celosa en el sentido de que debes dejar que posea completamente el trozo de DOM que le des (definido por lo que le pases a el
). Si jQuery realiza un cambio en un elemento que Vue está gestionando, por ejemplo, añade una clase a algo, Vue no se dará cuenta del cambio y lo sobrescribirá en el siguiente ciclo de actualización.
Solución: utilizar un componente como wrapper
Sabiendo que Vue y jQuery nunca van a compartir parte del DOM, tenemos que decirle a Vue que acordone un área y se la entregue a jQuery.
Usar un componente para envolver un plugin de jQuery parece ser el camino a seguir porque:
- Podemos utilizar ganchos de ciclo de vida para el montaje y desmontaje del código jQuery
- Podemos usar la interfaz de componentes para comunicarnos con el resto de la aplicación Vue a través de accesorios y eventos.
- Los componentes pueden optar por no participar en las actualizaciones con
v-once
Configurar jQuery UI Datepicker
Obviamente, primero debes incluir las bibliotecas jQuery y jQuery UI en tu proyecto. Una vez que los tengas, el selector de fechas sólo necesita un elemento de input
al que conectarse:
Date: <input id="datepicker"/>
A continuación, se puede instanciar seleccionándolo y llamando al método:
$('#datepicker').datepicker();
El componente Datepicker
Para hacer nuestro componente datepicker, la plantilla será el siguiente elemento de input
:
Vue.component('date-picker', function() { template: '<input/>' }); new Vue({ el: '#app' });
<div id="app"> Date: <date-picker></date-picker></div>
Nota: este componente no debe ser más que una envoltura (wrapper) para el plugin. No fuerces tu suerte y le des propiedades de datos o utilices directivas o slots.
Instanciación del widget
En lugar de dar a nuestra input
un ID y seleccionarlo, podemos usar this.$el
, ya que cada componente puede acceder a su propio nodo raíz de esa manera. El nodo raíz será, por supuesto, la input
.
Luego podemos envolver la referencia del nodo en un selector jQuery para acceder al método datepicker
,es decir $(this.$el).datepicker()
.
Ten en cuenta que utilizamos el hook (gancio) del ciclo de vida mounted
(montado) ya que this.$el
es indefinido hasta que se monta el componente.
Vue.component('date-picker', function() { template: '<input/>', mounted: function() { $(this.$el).datepicker(); } });
Desmontaje
Para desmontar el datepicker podemos seguir un enfoque similar y utilizar un hook (gancho) de lifecycle (ciclo de vida). Tenga en cuenta que debemos usar beforeDestroy
para asegurarnos de que nuestra entrada está todavía en el DOM y por lo tanto puede ser seleccionada (no está definida en el hook (gancho) de destroy
).
Vue.component('date-picker', { template: '<input/>', mounted: function() { $(this.$el).datepicker(); }, beforeDestroy: function() { $(this.$el).datepicker('hide').datepicker('destroy'); } });
Passar configuración con props
Para que nuestro componente sea reutilizable, sería bueno permitir una configuración personalizada, como especificar el formato de fecha con la propiedad de configuración dateFormat
. Podemos hacerlo con props
:
Vue.component('date-picker', { template: '<input/>', props: [ 'dateFormat' ], mounted: function() { $(this.$el).datepicker({ dateFormat: this.dateFormat }); }, beforeDestroy: function() { ... } });
<div id="app"><date-picker date-format="yy-mm-dd"></date-picker></div>
Dejar que jQuery maneje las actualizaciones
Digamos que, en lugar de pasar el prop dateFormat
como una cadena, lo convertiste en una propiedad data
de tu instancia raíz, es decir:
var vm = new Vue({ data: { ... dateFormat: 'yy-mm-dd' } });
<div id="app"><date-picker date-format="dateFormat"></date-picker></div>
Esto significaría que dateFormat
sería una propiedad de datos reactiva. Puede actualizar su valor en algún momento de la vida de su aplicación:
// change the date format to something new vm.dateFormat = 'yy-dd-mm';
Dado que la propriedad dateFormat
es una dependencia del hook mounted
(montado) del componente datepicker la actualización del mismo provocaría que el componente se volviera a renderizar. Esto no estaría bien. jQuery ya ha configurado su selector de fecha en la input
y ahora lo está manejando con sus propias clases personalizadas y oyentes de eventos. Una actualización del componente resultaría en el reemplazo de la input
y por lo tanto la configuración de jQuery se restablecería instantáneamente.
Tenemos que hacerlo de forma que los datos reactivos no puedan desencadenar una actualización en este componente....
v-once
La directiva v-once
se utiliza para almacenar en caché un componente en el caso de que tenga mucho contenido estático. Esto hace que el componente se excluya de las actualizaciones.
Esto es realmente perfecto para usar en nuestro componente plugin, ya que hará que Vue lo ignore. Esto nos da cierta confianza en que jQuery va a tener un control sin trabas sobre este elemento durante el ciclo de vida de la aplicación.
<div id="app"><date-picker date-format="yy-mm-dd"v-once></date-picker></div>
Pasar datos de jQuery a Vue
Sería bastante inútil tener un selector de fecha si no pudiéramos recuperar la fecha elegida y usarla en otro lugar de la aplicación. Hagámoslo de manera que después de que un valor es escogido, se imprima en la página.
Comenzaremos dando a nuestra instancia raíz una propiedad date
:
new Vue({ el: '#app', data: { date: null } });
<div id="app"><date-picker date-format="yy-mm-dd"v-once></date-picker><p>{{ date }}</p></div>
El widget datepicker tiene una llamada onSelect
que se llama cuando se escoge una fecha. Entonces podemos usar nuestro componente para emitir esta fecha a través de un evento personalizado:
mounted: function() { var self = this; $(this.$el).datepicker({ dateFormat: this.dateFormat, onSelect: function(date) { self.$emit('update-date', date); } }); }
Nuestra instancia raíz puede escuchar el evento personalizado y recibir la nueva fecha:
<div id="app"><date-picker @update-date="updateDate"date-format="yy-mm-dd"v-once></date-picker><p>{{ date }}</p></div>
new Vue({ el: '#app', data: { date: null }, methods: { updateDate: function(date) { this.date = date; } } });
Esto es todo, espero vuestros comentarios abajo!