Vuejs para programadores jQuery. Tablas simple. Paginación. XV

Vuejs para programadores jQuery.

Tablas simple. Paginación. XV


Hoy veremos el código de una tabla sencilla con paginación y modal para ver más datos del user elegido. Se ha usado el servicio: https://www.mockaroo.com/ que permitió crear 1000 datos fakes de diferente complejidad.




El archivo generado lo tenemos en el proyecto:

Datos de los usuarios: id, first_name, last_name, email, phone, avatar, language, animal, color, company. Los tipos de datos no son muy importantes para este proyecto pero es bueno tenerlos claro, ya que hasta que no usemos ajax crearemos datos fakes.


JQUERY


Comencemos con el código:

Dividiremos el trabajo en 3 partes: tabla, paginación y modal.

Tabla: html:


<table class="table table-striped" id="centralTable">

<thead>

<tr>

<th scope="col">#</th>

<th scope="col">Nombre</th>

<th scope="col">Apellido</th>

<th scope="col">Compañía</th>

<th scope="col">Ver</th>

</tr>

</thead>

<tbody id="dataTable">

</tbody>

</table>


Solo mostraremos algunos datos y los demás en un modal.

Tenemos el cuerpo de la tabla con un id y necesitamos controlar que datos de los 1000 vamos a mostrar así que empezamos por el objeto paginación


const pagination = {

current: 1,

views: 10,

counter: 5,

total: information.length

}

pagination.limit = Math.abs(information.length / pagination.views)

if (information.length !== (pagination.limit * pagination.views)) {

++pagination.limit

}


Está en dos partes. La primera es una asignación directa de valores y el campo limit necesita el objeto creado para hacer cálculos así que lo agregamos después. Agregar un campo nuevo a un objeto JS es tan rápido como tramposo. Veamos


let data = {

name: '',

surname: ''

}


Tenemos el objeto creado. ¿Cómo agregamos un campo?


data.telefono: '34434'


Y listo. Ahora el objeto tiene 3 variables: name / surname / telefono


Esto es lo que hemos hecho con el campo limit en pagination. Miremos los valores:


current // Tendrá la posición de la página actual que veremos.

views // Cuantos usuarios se mostrarán por página

counter // la cantidad máxima de números que aparecen en la paginación

total // El total de usuarios


information es el objeto que hemos importado del js externo


limit es la página máxima. Se calcula el total dividido en la cantidad por página. No obstante el total normalmente no es redondo así que se comprueba la operación inversa y si no coinciden se suma uno a limit. Veamos esto con números. Primero los reales.


limit = Math.abs( 1000 / 10) // limit tendrá 100


El if dará como válido el error y no entrara.

Ahora si el largo del array no fuera un número redondo. Supongamos que tenemos 933 datos.


limit = Math.abs( 933 / 10) // limit tendra 93


en el if calcula: 93 * 10 < 1000 // limit +1

Ahora habrá 94 páginas. 93 con 10 resultados y 1 con 3 resultados restantes.


Vamos directo al templateTableData que es un template mayor que los que hemos desarrollado hasta ahora. Pero aun manteniendo código limpio.


function templateTableData() {

let result = []

let start = pagination.current >1 ? ((pagination.current -1) * pagination.views ): 0

let end = start + pagination.views

if (end > pagination.total) {

end = pagination.total + 1

}

for (let i = start; i < end; i++) {

let item = information[i]

result.push(

`<tr>

<td>${item.id}</td>

<td>${item.first_name}</td>

<td>${item.last_name}</td>

<td>${item.company}</td>

<td><button onclick="expand(${item.id})" class="pointer" type="button" data-toggle="modal" data-target="#dataModal">${iconEye}</button></td>

</tr>`

)

}

return result.join('')

}

Primero creamos un array vacío. Luego calculamos el punto de inicio (recordar que current es la página actual NO el elemento actual)

pagination.current >1 ? ((pagination.current -1) * pagination.views ): 0

Si la página actual es mayor que uno, le quitamos uno y multiplicamos por el tamaño de la cantidad que vamos a mostrar. Si no es mayor que uno guardamos 0. Esto ultimo es porque las páginas, listas id's y demás lo contamos desde 1 pero el array cuenta desde 0

Veamos el resultado si current es mayor que uno con numeros. Para current 2

(2-1)*10 = 10 o sea empezará desde el elemento 10


Eso es correcto, ya que si era 1, guardamos 0 habremos mostrado del 0-9


Ahora el valor final:


end = start + pagination.views // en el caso de current 2 tendrá 10+10. Pero el último elemento no se mostrará como lo veremos ahora así que sencillamente se le suma la cantidad configurada para mostrar. El siguiente control es que no se pase del total y si es así se le suma uno más del total porque:


for (let i = start; i < end; i++) {


se usa desde start hasta end -1


Ahora que tenemos el rango de datos sencillamente recogemos el ítem del array externo y le damos formato guardando cada fila en el array.


Los datos usados son de forma tradicional menos él ultimo td:


<td><button onclick="expand(${item.id})" class="pointer" type="button" data-toggle="modal" data-target="#dataModal">${iconEye}</button></td>


Este paso envía el id a una función expand (para mostrarlo en el modal) y es un botón con un contenido SVG que es un dibujo de un ojo (iconEye) cuya variable hemos definido así:


const iconEye = '<svg class="bi bi-eye-fill" width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor" xmlns="http://www.w3.org/2000/svg">\n' +

' <path d="M10.5 8a2.5 2.5 0 1 1-5 0 2.5 2.5 0 0 1 5 0z"/>\n' +

' <path fill-rule="evenodd" d="M0 8s3-5.5 8-5.5S16 8 16 8s-3 5.5-8 5.5S0 8 0 8zm8 3.5a3.5 3.5 0 1 0 0-7 3.5 3.5 0 0 0 0 7z"/>\n' +

' </svg>'


Este código fue bajado de los iconos de Bootstrap.

Al final devolvemos el array como cadena de texto.

El primer uso que hacemos de esto es en:


dataTable.empty().append(templateTableData())


dataTable es una constante que apunta al body de la tabla:


const dataTable = jQuery('#dataTable')


Pasemos a la función expand y todo lo que tiene que ver con el modal. Primero que nada diremos que hemos tenido dificultades con las clases por defecto del modal de Bootstrap asi que creamos nuestro popio sistema para mostrarlo con un pequeño desplazamiento y un efecto fade. Efecto que se revierte cuando cierra.


El css es un archivo externo.


function expand(id) {

let item = information.find(item => item.id === id)

if (item) {

bodyModal.empty().append(templateCard(item))

dataModal.addClass('showModal')

}

}


expand espera un unico item. Sin arrays ni nada. lo que se envia es un id. Lo busca y si lo encuentra vacia el cuerpo del modal y lo rellena con el resultado de formatear dicho dato. Luego le asignamos la clase para mostrarlo.


bodyModal lo definimos así:


const bodyModal = jQuery('#dataModalBody')


y apunta al modal tal que:


<div class="modal fade" tabindex="-1" role="dialog" id="dataModal">

<div class="modal-dialog">

<div class="modal-content">

<div class="modal-header">

<h5 class="modal-title">Información del usuario</h5>

<button type="button" class="close" data-dismiss="modal" aria-label="Close" onclick="closeModal()">

<span aria-hidden="true">&times;</span>

</button>

</div>

<div class="modal-body" id="dataModalBody">

</div>

<div class="modal-footer">

<button type="button" class="btn btn-secondary" data-dismiss="modal" onclick="closeModal()">Cerrar</button>

</div>

</div>

</div>

</div>


solo toca el cuerpo del modal (dataModalBody)

Antes de seguir observemos que tenemos funciones para cerrar el modal. Miremos eso:


function closeModal() {

dataModal.removeClass('showModal').addClass('hideModal')

setTimeout(function () {

dataModal.removeClass('hideModal')

}, 400)

}


Quitamos la clase que usamos para mostrarlo. Lo cambiamos por una clase para hacer el efecto contrario y luego de 400 milisegundos quitamos esa clase también.


Veamos el formato del Modal:


function templateCard(item) {

return `<div class="card mb-3" style="max-width: 540px;">

<div class="row no-gutters">

<div class="col-md-4">

<img src="${item.avatar.replace('50x50', '150x150')}" class="card-img-top" alt="avatar del usuario: ${item.first_name}">

</div>

<div class="col-md-8">

<div class="card-body">

<h5 class="card-title">${item.first_name} ${item.last_name}</h5>

</div>

<ul class="list-group list-group-flush">

<li class="list-group-item">Compañia: ${item.company}</li>

<li class="list-group-item">Email: ${item.email}</li>

<li class="list-group-item">Idioma: ${item.language}</li>

<li class="list-group-item">Telefono: ${item.phone}</li>

<li class="list-group-item">Animal: ${item.animal}</li>

<li class="list-group-item">Color: ${item.color}</li>

</ul>

</div>

</div>

</div>`

}

Hay algunas cosas interesantes: Primero. Lo estamos mostrando dentro de una card de Bootstrap. Segundo: A la imagen le corregimos el tamaño. Eso es porque es un servicio externo que te devuelve una imagen tomando el tamaño que le envíes en la url.


Y ahora nos queda la paginación:

Primero el template:


function templatePaginationView() {

let result = []

let previous = pagination.current === 1 ? 'disabled' : ''

let posterior = (pagination.current * pagination.views) >= pagination.total ? 'disabled' : ''

let start = pagination.current > 1 ? pagination.current - 1 : pagination.current

let end = pagination.current + pagination.counter

if (end > pagination.limit) {

end = pagination.limit

}

for (let i = start; i < (end + 1); i++) {

if (i === pagination.current) {

result.push(

`<li class="page-item active min-item" aria-current="page">

<a class="page-link" href="#">${i}<span class="sr-only">(current)</span></a>

</li>`

)

} else {

result.push(

`<li class="page-item min-item" onclick="paginationClick(${i})"><a class="page-link" href="#" >${i}</a></li>`

)

}


}

return `<li class="page-item ${previous}">

<a class="page-link" href="#" tabindex="-1" aria-disabled="true" onclick="paginationClick(${pagination.current -1})">Previo</a>

</li>

${result.join('')}

<li class="page-item ${posterior}">

<a class="page-link" href="#" onclick="paginationClick(${pagination.current + 1})">Proximo</a>

</li>`

}

El formato es el siguiente:




Tenemos un botón de previo, unos botones para cambiar de página y al final un próximo. Las variables previous y posterior calculan si tiene utilidad:

previous = pagination.current === 1 ? 'disabled' : ''


Los disables en los <li> de la paginación se hacen con clases. Aquí preguntamos si estamos en la primer página de ser así guardamos un valor string (disabled) que agregaremos a las clases, ya que no tiene sentido ese botón al inicio de la tabla


Hacemos lo mismo para el botón siguiente solo que ahora los cálculos son diferentes:


let posterior = (pagination.current * pagination.views) >= pagination.total ? 'disabled' : ''


Si la página actual + la cantidad de datos a mostrar es mayor o igual que el total de datos entonces el boton no nos llevara a ningún sitio por lo tanto lo ponemos a disabled.


Vemos que tenemos una función paginationClick para resolver el cambio de página. Veamos:


function paginationClick(value) {

if (value === pagination.current) {

return

}

pagination.current = value

dataTable.empty().append(templateTableData())

paginationView.empty().append(templatePaginationView())

}


Si el valor enviado es el mismo que el actual no se hace nada y se sale de la función.

Si no es así se cambia el valor del pagination.current y se vuelve a rellenar la tabla de datos como la paginación.


Aquí lo tenemos todo resuelto. Pasemos a Vuejs.


VUEJS


Al igual que con jQuery iremos por partes.


Enlaces:

<table class="table table-striped" id="centralTable">

<thead>

<tr>

<th scope="col">#</th>

<th scope="col">Nombre</th>

<th scope="col">Apellido</th>

<th scope="col">Compañía</th>

<th scope="col">Ver</th>

</tr>

</thead>

<tbody id="dataTable">

<tr v-for="item in tableData" :key="item.index">

<td>{{item.id}}</td>

<td>{{item.first_name}}</td>

<td>{{item.last_name}}</td>

<td>{{item.company}}</td>

<td>

<button @click="expand(item)" class="pointer" type="button" data-toggle="modal" data-target="#dataModal"

v-html="iconEye"></button>

</td>

</tr>

</tbody>

</table>


Vemos la parte de los títulos igual. En cambio el cuerpo usamos, como ya es frecuente, el v-for refiriendo a tableData. Veamos esa computada.


tableData() {

let result = Array.from(information)

let start = this.pagination.current > 1 ? ((this.pagination.current - 1) * this.pagination.views) : 0

let end = start + this.pagination.views

if (end > this.pagination.total) {

end = this.pagination.total

}

return result.splice(start, end-start)

}


Primero creamos un array nuevo a partir del array original que traemos del js externo.

En el anterior post repasábamos el caso de la asignación de los arrays y como su valor se mantenía por referencia cambiando el contenido del original cuando se cambiaba el valor de un array asignado por igualdad.


Array.from es una de las maneras de resolver esto. Ya que crea un array totalmente nuevo a partir de otro.


Seguimos la misma lógica que en jQuery para los cálculos de start y end y usamos splice para extraer los datos que nos interesan. Esta es la razón por la que hemos creado un array nuevo, ya que si no los estaríamos eliminando del array original.


Revisemos el objeto pagination:


pagination: {

current: 1,

views: 10,

counter: 5,

total: information.length

}


Usamos los mismos valores y limit que usa valores del objeto para resolver su valor lo creamos en el hook, que ya conocimos cuando creamos un componente, created()


created () {

this.pagination.limit = Math.abs(information.length / this.pagination.views)

if (information.length !== (this.pagination.limit * this.pagination.views)) {

++this.pagination.limit

}

}


Exactamente igual que en jQuery.


Revisemos él ultimo td de la lista de datos del HTML:


<button @click="expand(item)" class="pointer" type="button" data-toggle="modal" data-target="#dataModal"

v-html="iconEye"></button>

Una de las grandes diferencias entre la forma de agregar datos de jQuery y Vuejs es que append procesa los símbolos HTML en cambio cuando en Vue usamos el {{}} se sobreentiende que el resultado es en formato de texto (aunque incluya símbolos HTML) por lo que si queremos que procese HTML debemos usar una marca especial llamada v-html como hacemos en este elemento.


Tanto en jQuery como en Vuejs podría haber usado el SVG directamente en el código. Son marcas reconocidas por el HTML. Pero me pareció interesante aprender este detalle.


Recordemos que iconEye es:


iconEye: '<svg class="bi bi-eye-fill" width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor" xmlns="http://www.w3.org/2000/svg">\n' +

' <path d="M10.5 8a2.5 2.5 0 1 1-5 0 2.5 2.5 0 0 1 5 0z"/>\n' +

' <path fill-rule="evenodd" d="M0 8s3-5.5 8-5.5S16 8 16 8s-3 5.5-8 5.5S0 8 0 8zm8 3.5a3.5 3.5 0 1 0 0-7 3.5 3.5 0 0 0 0 7z"/>\n' +

' </svg>'

Vayamos a expand:


expand(item) {

this.userSelect = item

this.modalShow = true

}


Recordemos que el modal tiene 3 estados: * con la clase para mostrar, * con la clase para ocultar, * sin clase. Veamos las variables relacionadas:


userSelect: {

avatar: ' '

}


modalShow: false

modalHide: false

Y lo explicaremos con el body del modal:


<div class="modal-body" id="dataModalBody" >

<div class="card mb-3" style="max-width: 540px;">

<div class="row no-gutters">

<div class="col-md-4">

<img :src="userSelect.avatar.replace('50x50', '150x150')" class="card-img-top" :alt="'avatar del usuario: ' + userSelect.first_name">

</div>

<div class="col-md-8">

<div class="card-body">

<h5 class="card-title">{{userSelect.first_name}} {{userSelect.last_name}}</h5>

</div>

<ul class="list-group list-group-flush">

<li class="list-group-item">Compañía: {{userSelect.company}}</li>

<li class="list-group-item">Email: {{userSelect.email}}</li>

<li class="list-group-item">Idioma: {{userSelect.language}}</li>

<li class="list-group-item">Telefono: {{userSelect.phone}}</li>

<li class="list-group-item">Animal: {{userSelect.animal}}</li>

<li class="list-group-item">Color: {{userSelect.color}}</li>

</ul>

</div>

</div>

</div>

</div>

<div class="modal-footer">

<button type="button" class="btn btn-secondary" data-dismiss="modal" @click="closeModal()">Cerrar</button>

</div>


Los datos los estamos mostrando, con los cambios habituales, igual que en jQuery pero nos basta cambiar el contenido de userSelect para que el contenido del modal cambie.

Los boolean se usan en la cabecera del modal:


<div class="modal fade" tabindex="-1" role="dialog" id="dataModal" :class="{showModal: modalShow, hideModal: modalHide}">


Asociando cada uno con cada clase para lograr el efecto deseado.

Veamos entonces closeModal:


closeModal() {

this.modalHide = true

this.modalShow = false

const me = this

setTimeout(function () {

me.modalHide = false

}, 400)}

}


Exactamente la mismo lógica que en jQuery. Efecto de salida y eliminación de la clase 400 milisegundos después.

Revisemos la paginación:


El botón de previo:


<li class="page-item" :class="{disabled: pagination.current === 1}">

<a class="page-link" href="#" tabindex="-1" aria-disabled="true" @click="pagination.current--">Previo</a>

</li>


Resolvemos la clase en el elemento y también la acción en el click


<li class="page-item " :class="{disabled: (pagination.current * pagination.views) >= pagination.total}">

<a class="page-link" href="#" @click="pagination.current++">Proximo</a>

</li>


Y lo mismo para el botón de próximo.


<li class="page-item min-item"

@click = "pagination.current = item + paginationView.start"

:class="{active: (item+paginationView.start)===pagination.current}"

:aria-current="(item+paginationView.start)===pagination.current ? 'page' : ''"

v-for="(item) in paginationView.diff" :key="item">

<a class="page-link" href="#" >{{item + paginationView.start}}

<span :class="{'sr-only': (item+paginationView.start)===pagination.current}">

{{(item+paginationView.start)===pagination.current ? '(current)' : ''}}</span>

</a>

</li>


Pondremos aquí la computada a la que se hace referencia en el HTML:


paginationView() {

let start = this.pagination.current > 1 ? this.pagination.current - 1 : this.pagination.current

let end = this.pagination.current + this.pagination.counter

if (end > this.pagination.limit) {

end = this.pagination.limit +1

}

return {start: start > 1 ? start -1 : 0, end, diff: end === this.pagination.limit ? (end-start) +1 : end-start}

}

La computada en si no es sorpresa. Es la resolución de que valores mostrar en la paginación y devuelve un objeto con start, end y diff (diferencia entre ambas)


Vamos por partes con el <li> que parece confuso.


v-for

Usamos un rango de valores de 0 a diff


click

Asignamos a pagination.current el valor del item + start dela computada (que es el valor del primero de los números mostrados en la paginación)


aria-current

Se le asigna la semántica que pide Bootstrap si la pagína es la actual


<a>

Se muestra el valor del ítem + start


<span>

Se resuelve la semántica si el ítem + start es igual a pagination.current (pagína actual)


Espero haber dejado todo claro sino estoy abierto a preguntas y sugerencias.


Enlaces:


Consultas, dudas, comentarios: Slack de PEUM o en Twitter.

Vuejs para programadores jQuery. Select de selección múltiple. Menús. XIV

Vuejs para programadores jQuery.

Select de selección múltiple. Menús. XIV


Antes de empezar con los menús trataremos un ultimo select que no hemos visto: Select de selección múltiple.




Será bastante corto por sencillo y además miraremos unas (pocas) cositas nuevas.

Una vez más usaremos una lista externa.


Enlaces

Vamos al código


JQUERY- SELECCIÓN MÚLTIPLE


Enlaces:

<div class="container top20">

<div class="row">

<div class="col-6">

<div class="form-group">

<label for="exampleFormControlSelect2">Selección múltiple</label>

<select multiple class="form-control" id="exampleFormControlSelect2" onclick="mostrar()">

</select>

</div>

</div>

<div class="col-6">

    Elegidos:

<ul id="elegidos">

</ul>

</div>

</div>

</div>


No hay nada demasiado destacable. Un select que tiene un onclik, un id por supuesto y como algo nuevo el atributo múltiple. Indispensable para que el select permite selección múltiple.


Más abajo un UL vacío

Esta vez hacemos todo el trabajo en el JS:


const listSelect = jQuery('#exampleFormControlSelect2')

const elegidos = jQuery('#elegidos')

listSelect.empty()

listaTotal.every(item => listSelect.append(templateOption(item)))

Definimos las constantes (dos: Una el Select y la otra el UL), vaciamos el select (Paso innecesario en realidad porque lo tenemos vacío pero por reflejo siempre lo coloco antes de cambiar el contenido) y luego recorremos el array externo y rellenamos el Select.


Para ello usamos una función de template. Miremos las dos funciones template que hemos creado:


function templateOption({titulo, index}) {

return `<option value="${index}">${titulo} (${index})</option>`

}

function templateLI({titulo: texto}) {

return `<li>${texto}</li>`

}


El primero, que es el que usamos para el select, no tiene misterios. Usa la desestructuración como lo hemos hecho en los anteriores posts y devolvemos el elemento HTML con el formato.


El segundo trae una novedad en el parámetro. Repasemos la estructura de la lista que estamos trayendo:


const listaTotal = [

{

titulo: 'Albaricoque',

index: 201

}

...

Cuando usamos la desestructuración tomamos del objeto que recibimos los campos que necesitamos y, hasta ahora, lo usamos con el nombre que tiene en el objeto. Esto de por si no esa mal pero si por las razones que sea necesitamos asociarlo a un nombre diferente la forma de hacerlo es la siguiente:


function ({campo: variable})


Siendo campo el valor del objeto esperado y variable el nombre de la variable que guardara el contenido en la función. templateLI es un ejemplo de ello. Lo hice así como una muestra del uso para aprovechar este ejemplo sencillo.


Solo nos queda ver la función asociada al click del select:


function mostrar() {

const listOptions = jQuery('#exampleFormControlSelect2 option:selected')

elegidos.empty()

listOptions.each(function (){

const item = listaTotal.find(element => element.index===parseInt(jQuery(this).val()))

if (!item) {

return

}

elegidos.append(templateLI(item))

})

}


Primero recogemos los elementos elegidos. En jQuery se hace igual cuando es un select simple o múltiple. Variamos el <ul> y luego recorremos el resultado de los elementos recogidos.Para eso usamos la función each. Esta es una función de jQuery. Su semántica es:


each(function(){})


Y nos entrega cada elemento de forma individual que podemos identificar con this y para usarlo con jQuery es necesario hacer jQuery(this).

La primer linea es buscar el elemento de la lista externa cuyo index coincida con el val() del item actual.

Si tenemos resultado agregamos al <ul> el elemento con el formato de nuestra función templateLI.

Y listo


Vayamos a Vuejs.


VUEJS - SELECCIÓN MÚLTIPLE


Enlaces

HTML


<div class="container" id="app">

<div class="row">

<div class="col-6">

<div class="form-group">

<label for="exampleFormControlSelect2">Selección múltiple</label>

<select multiple class="form-control" id="exampleFormControlSelect2" v-model="selectLista">

<option v-for="item in listaTotal" :key="item.index" :value="item.index">{{item.titulo}}</option>

</select>

</div>

</div>

<div class="col-6">

Elegidos:

<ul id="elegidos">

<li v-for="item in listaForm" :key="item.index+100">{{item.titulo}}</li>

</ul>

</div>

</div>

</div>


Y ponemos ya el código y comentamos todo:


<script>

new Vue({

el: '#app',

data: {

selectLista: [],

listaTotal

},

computed: {

listaForm() {

let response = []

this.selectLista.forEach(item => {

let search = this.listaTotal.find(element => element.index === parseInt(item))

if (search) {

response.push(search)

}

})

return response

}

}

})

</script>


Hay un cambio en la definición de variables. listaTotal no define el tipo. En realidad es un atajo de JS.

Lo primero recordar que nuestra lista externa se llama listaTotal. En vuejs debemos definir una variable, métodos, computada, etc. que apunte a ella para poder usarlo en el HTML.

En este caso es una variable así que la podríamos definir con el mismo nombre así:


listaTotal: listaTotal


Para estos casos Js te permite ahorrarlo poniendo solo una vez el nombre y asume que se usa esa semántica. Así que:


listaTotal: listaTotal es lo mismo que listaTotal para la definición.


Es lo que estábamos usando en la desestructuración hasta ahora. Al hacer:


const item = {

titulo: 'Gonzalo',

index: 234

}


function titulos({titulo}) {}


para luego usarla así:


titulos(item)


En la definición de la función ({titulo}) en realidad estamos haciendo ({titulo: titulo}). Para cambiar el nombre que usaremos para referenciar a cada campo de una desestructuración tenemos el ejemplo que vimos anteriormente en este post más arriba.


Hay otro cambio. En los v-model de los selects hasta ahora estábamos usando un string que apuntaba al value pero esta vez usaremos un array. Si. Es así de simple. Si el select es múltiple basta con usar una variable que es un array para tener la lista de elegidos.


Ahora miraremos la computada que usamos en los <li>


listaForm() {

let response = []

this.selectLista.forEach(item => {

let search = this.listaTotal.find(element => element.index === parseInt(item))

if (search) {

response.push(search)

}

})

return response

}


Definimos un array vacío. Recorremos el array que usamos en el select (Que contiene los value), realizamos el mismo find que en jQuery y si hay elemento lo guardamos en el array local. Devolvemos el array local.


Como es una computada, cada cambio en el select modificara el array asociado, y eso hará que los resultados devueltos cambien automáticamente.


Listo. Con esto ya tenemos el uso de select múltiples en vuejs.

Enlaces proyecto total:


JQUERY - MENÚS


Vayamos a los menús

Usaremos el formato de menús de Bootstrap pero este punto es uno de los que más difieren los diferentes frameworks CSS por lo que si se usa otro diferente mirar con atención la documentación relacionada.




El HTML de los menús suele ser complejo y largo. Esta vez no es la excepción así que presentaremos el HTML de a poco. Primero los enlaces:


Enlaces.

Tenemos opciones simples:


<li class="nav-item ">

<a class="nav-link option active" href="#" title="home">Home <span class="sr-only">(current)</span></a>

</li>

La clase opción es obligatoria para este proyecto porque será como pillaremos todas las opciones en JS. title también es un atributo necesario que relaciona la opción con su contenido. y active es una clase de Bootstrap que define la estética del elemento activo en ese momento.


Los contenidos:


<div id="content-home" class="content-item active">

<h3>Home</h3>

<blockquote>

....

</blockquote>

</div>


Vemos que el id es "content-" más el title de la opción del menú relacionada. Y que también aquí usaremos la clase active con las opciones elegidas. Importante que todas tengan la clase content-item


Tenemos submenús:


<li class="nav-item dropdown">

<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">

Dropdown

</a>

<div class="dropdown-menu" aria-labelledby="navbarDropdown">

<a class="dropdown-item option" href="#" title="accion">Acción</a>

<a class="dropdown-item option" href="#" title="otra">Otra acción</a>

<div class="dropdown-divider"></div>

<a class="dropdown-item option" href="#" title="relevante">Contenido relevante</a>

</div>

</li>


El <li> tiene una clase extra de Bootstrap llamada dropdown para definir que es un menú que abre otro menú.

Los <a> debajo del div también tienen el title y el option. Son 3 opciones con un separador entre la segunda y la tercera.


Veamos como resolvemos todo esto con las clases en JS:


const navItem = jQuery('.option')

navItem.on('click', actualiza)

Definimos una constante que apunta a todos los elementos con la clase option.

Y asociamos el click en cualquiera de ellos con la función actualiza.


Veamos esa única función:


function actualiza(evt) {

const target = jQuery(evt.target)

navItem.removeClass('active')

target.addClass('active')

const anterior = jQuery('.content-item.active')

const actual = jQuery('#content-' + target.attr('title'))

anterior.fadeOut( 300, function() {

anterior.removeClass('active')

actual.fadeIn(300, function() {

actual.addClass('active')

})

})

}


Lo primero que hacemos es identificar el elemento sobre el que se hizo click (evt.target), luego removemos la clase active de todas las opciones del menú (navItem) y le asignamos la clase al elemento sobre el que se hizo click.


Lo siguiente es buscar el contenido (content-item) que está activo (active) por eso se usa .content-item.active, porque se busca el contenido que tenga ambas clases.

Luego buscamos el nuevo contenido a mostrar. Buscamos el id con "content-" + el title del elemento sobre el que se hizo click.


Los siguientes pasos son funciones de efectos de jQuery:


fadeOut(tiempo, function(){}) Aplica opacidad a un contenido hasta hacerlo transparente del todo en el tiempo determinado.


fadeIn(tiempo, function(){}) Quita la opacidad a un contenido hasta mostrarlo completamente en un tiempo determinado.


Aplicamos opacidad al elemento activo actualmente con una demora de unos 300 milisegundos y al terminar le quitamos la clase active, y comenzamos el proceso inverso en el contenido que ahora queremos mostrar y al terminar le agregamos la clase active.


Listo. Hemos completado el proceso en jQuery. Vayamos a Vuejs:


VUEJS - MENÚS


Enlaces

También iremos mirando el HTML en partes:


<ul class="navbar-nav mr-auto">

<li class="nav-item ">

<a class="nav-link option" :class="{active: option==='home'}" href="#" @click="option='home'">Home <span class="sr-only">(current)</span></a>

</li>

</ul>


Para definir la clase active usamos el valor de una variable llamada option. Si es igual a determinado valor (el mismo que usabamos en el atributo title en jQuery) entonces se le asigna la clase. El evento es directo. Si se pulsa click se le asigna el valor a la variable.


Tenemos un cambio aquí:


<li class="nav-item dropdown" :class="{show: dropdownShow}">

<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" @click="dropdownShow=!dropdownShow">

Dropdown

</a>

<div class="dropdown-menu" aria-labelledby="navbarDropdown" :class="{show: dropdownShow}">


Mostrar o no el submenú dependerá de un boolean (dropdownShow) que cambiara de estado cuando se pulse en el menú. O sea que se abría si está cerrado y si volvemos a pulsar se cerrar.


También en el Js tenemos un watch:


watch: {

option: function () {

this.dropdownShow = false

}

}


Sobre la variable option. Cada vez que se pulse en una opción del menú el dropdown se cerrara


Y en lugar de un div contenedor de todas las opciones tenemos algo nuevo:


<transition-group name="list" tag="div" mode="out-in">


Transition es una característica de Vue para realizar efectos visuales entre elementos HTML. Usa dos maneras de trabajar. Con CSS y con hook JS. Aquí usaremos el CSS. Si quieres saber mas de transition: https://es.vuejs.org/v2/guide/transitions.html


Transition se divide en dos formas: entre dos elementos <transition> y entre varios elementos <transition-group> que es la que usaremos en este post.


Vamos a mirar los atributos:


mode: Indica la prioridad de la transición. out-in: Primero el que sale, luego el que entra. in-out: Primero el que entra y después el que sale.


tag: indica que elemento HTML es sustituido. En este caso es div pero si fuera una lista ul-li lo lógico seria que ocupara el lugar del ul por eso es importante este atributo.


name: indica el slug para las clases css. Las clases que podemos crear para los eventos son varios según el momento de la transición que queramos afectar. La lista completa es:

  • enter
  • enter-active
  • enter-to
  • leave
  • leave-active
  • leave-to

Y se usaría en este caso list-enter, lista-enter-active, etc.

Este grafico muestra una idea aproximada de donde se ubica cada evento:


En este proyecto el css relacionado es:


.list-enter-active {

transition: all 0.5s;

transition-delay: 0.6s;

}

.list-leave-active {

transition: all 0.5s;

}

.list-enter, .list-leave-to {

opacity: 0;

}


Marcamos cuando entre un elemento y cuando sale como opacidad 0


La salida del elemento se ha puesto una transacción que tarde 0.5 segundos.

Al entrada del siguiente es el mismo tiempo con un retraso (transition-delay) de una milésima de segundo mas que lo tarde en salir el item


El uso aquí es muy sencillo y es solo por presentar una alternativa al fadeIn fadeOut de jQuery. Tanto con transition de vuejs como con los keyframe y transitions de CSS se pueden lograr muchos efectos.


Si desea saber más de transitions de CSS: https://learn.shayhowe.com/advanced-html-css/transitions-animations/


Los contenidos HTML los tenemos así:


<div id="content-home" class="content-item" key="1" v-if="option==='home'">

<h3>Home</h3>

<blockquote>

....

</blockquote>

</div>


El mismo id, la misma clase pero aquí no usamos active sino que v-if cuando la variable sea igual al valor. En este caso 'home'.

¿Que es key? Key tiene que ser un valor único que identifique cada elemento que va a participar en las transiciones. En este caso y por ser pocas hemos usado una string de números seguidos.


Ahora repasemos el código JS:


<script>

new Vue({

el: '#app',

data: {

option: 'home',

dropdownShow: false

},

watch: {

option: function () {

this.dropdownShow = false

}

}

})

</script>


Definimos la variable 'option' para tener el valor de la opción elegida en el menú. El dropdown como boolean para controlar si el menú descendente está abierto o cerrado y el watch que antes cualquier cambio de opción de menú cierra el menú desplegable.


Enlaces.

Codesandbox: https://githubbox.com/Gonzalo2310/jQuery-Vuejs/blob/master/Navs/Menus/indexNavBarMenu.html

GitHub: https://github.com/Gonzalo2310/jQuery-Vuejs/blob/master/Navs/Menus/indexNavBarMenu.html



Y otra vez llegamos al final. Espero que halla sido de ayuda y educativo.


Consultas, dudas, comentarios: Slack de PEUM o en Twitter.



Vuejs para programadores jQuery. Galeria de imagenes. Componente Vue reusable. XIII

Vuejs para programadores jQuery.

Galeria de imagenes. Componente Vue reusable. XIII


En este post haremos una galería sencilla de imágenes. Para las imágenes hemos usado el servicio de: https://pixabay.com/es que es un servicio gratuito que permite bajar imágenes de gran calidad libres de licencia.


Hemos optado por un creador que tenía imágenes que cuadraban para este proyecto. Su perfil: https://pixabay.com/es/users/valiphotos-1720744/


Empecemos.

La idea general es esta:



  • El paso 1 muestra como esta todo al inicio.
  • El paso 2 es la acción del usuario y lo que sucede con las imágenes (observar que a nivel de código NO movemos la imagen sino el div que la contiene.)
  • El paso 3 pasa cuando la animación ha terminado y no es visible para el usuario. Duplicamos la imagen que se ve en el cuadro del medio.
  • El paso 4 tampoco es visible para el usuario. Hemos hecho un cambio de cuadros y vuelto al paso 1 preparados para el siguiente movimiento. El cuadro que antes se veía ahora se le a agregado una imagen nueva.

Esto mantiene la ilusión de una galería continua con solo 3 imágenes activas. Podría ser infinita si al llegar al final del array de imágenes volviéramos a comenzar desde el inicio.


He intentado hacer el código más sencillo que se me ha ocurrido.


Usamos dos archivos externos: Un js con la lista de imágenes (todas locales en una sub carpeta) y un CSS. Ya hemos dicho un par de veces que estos post no son para tratar CSS, sin embargo este si lo miraremos en algunos puntos, ya que la galería usara animaciones CSS.


JQUERY


Vamos a ello.

Enlaces del JS:

Enlaces del css:

En el CSS el interés se centra en dos keyframes:


@keyframes slidein {

from {

transform: translateX(-100%);

}

to {

transform: translateX(-200%);

}

}



@keyframes slideout {

from {

transform: translateX(-100%);

}

to {

transform: translateX(0%);

}

}


Keyframe, para quien no lo conozca, permite poderosas animaciones css. El uso que aquí le damos es muy básico. Marcamos un estado inicial del elemento HTML y un estado final. slidein y slideout son los nombres que les hemos puesto a estas animaciones.


Como vemos es un movimiento lateral (eje X) del objeto. Al principio de establecer los cuadros de imágenes el que queda en el centro es el que tenemos más a la izquierda y los otros dos se crean a la derecha de este. Por eso los movemos todos al inicio por el eje de las X todos un lugar a la izquierda.


Eso lo definimos aquí:


div.image {

transform: translateX(-100%);

}


Esto nos dará la posición del punto 1 de la explicación gráfica. El valor negativo indica movimiento hacia la izquierda.


Por eso es que los keyframes empiezan en -100% ya que todo los cuadros están en esa posición originalmente con respecto a su sitio al ser creados.


El keyframe slidein mueve aún más a la izquierda los cuadros. Este movimiento afecta en la galería al cuadro central y al cuadro a la derecha (si, si se mueve a la izquierda el cuadro esta a la derecha) al usar el keyframe damos el efecto visual de movimiento y pasamos al paso 2. Los demás pasos se dan inmediatamente después sin intervención del usuario (y si se ha hecho bien sin que se entere)


El keyframe slideout hace el movimiento contrario y por lo tanto afecta al cuadro central y al cuadro a la izquierda.


Los keyframe se aplican usando clases (al poner la clase empieza la animación y si quitamos la clase el cuadro vuelve a su posición o estado original)


Las clases creadas para esto son dos:


.move {

animation-name: slidein;

animation-duration: 1s;

animation-fill-mode: forwards;

}


.retromove {

animation-name: slideout;

animation-duration: 1s;

animation-fill-mode: forwards;

}


Cada una usa una animación diferente identificada en animation-name, tardan un segundo en completar la animación medido en animation-duration y el último campo lo trataremos un poco más.


Cuando se hace una animación con keyframe, por defecto, hace la animación y luego vuelve a su posición original. Se puede modificar ese comportamiento de varias maneras. En este caso con el valor forwards en animation-fill-mode le estamos indicando que al terminar la animación conserve el último estado. Así el movimiento al terminar no vuelve el cuadro a su estado original.


Hay mucho material muy interesante sobre keyframe y es muy recomendable conocerlo si se piensan animaciones de elemento HTML.


Dejo aquí un link donde empezar: https://marksheet.io/css-animations.html

Y un generador online de animaciones CSS: https://cssanimate.com/


Ahora si nos podemos sumergir en el código.

Enlaces:

HTML:

<div class="content-image">

<div class="image" id="imagen1">

<img src="" alt="..." class="image" id="imagen_screen1">

</div>

<div class="image" id="imagen2">

<img src="" alt="..." class="image" id="imagen_screen2">

</div>

<div class="image" id="imagen3">

<img src="" alt="..." class="image" id="imagen_screen3">

</div>

</div>

Esta es la galería en si. Lo primero es decir que los alt DEBERÍAN tener siempre una descripción de las imágenes que den información útil a los dispositivos que no leen imágenes y para las personas con problemas de vista o ciegas. Dejamos mucho de lado la accesibilidad e inconscientemente estamos discriminando a parte activa de la población.


Sigamos. El src en todas está vacío. Cada div y cada imagen tiene un id diferente (en teoría la posición visual es 1 2 3 siendo el 2 el cuadro central antes de cualquier interacción del usuario)


Además tenemos dos botones para los dos movimientos de imágenes posibles.


<div class="grilla sombra">

<button class="btn btn-primary" id="button_left"><<</button>

<div>&nbsp;</div>

<button class="btn btn-primary" id="button_right">>></button>

</div>

Tampoco aquí hay muchas sorpresas.

Vayamos con el JS:


let posicion_actual = 0

let direccion = 0

const image1 = jQuery('#imagen1')

const image2 = jQuery('#imagen2')

const image3 = jQuery('#imagen3')


const screen1 = jQuery('#imagen_screen1')

const screen2 = jQuery('#imagen_screen2')

const screen3 = jQuery('#imagen_screen3')


const button_left = jQuery('#button_left')

const button_der = jQuery('#button_right')

Definimos las constantes que apuntan a los botones, los div que contienen las imágenes y a las imágenes.


La variable posicion_actual apuntará al lugar del array (que tenemos en el archivo externo y se llama listUrls) de la imagen del cuadro central de ese momento.

La variable direccion nos indicará si el último movimiento fue a la derecha o a la izquierda para saber que cuadros modificar y como reordenarlos luego.


image2.on('animationend', animationEnd)

button_der.on('click', mover_der)

button_left.on('click', mover_left)

button_left.attr('disabled', true)

screen1.attr('src', listUrls[posicion_actual])

screen2.attr('src', listUrls[posicion_actual])

screen3.attr('src', listUrls[posicion_actual + 1])


Al que contiene la imagen central (tiene que ser esta porque es la única afectada con los dos movimientos posibles) le asociamos una función al evento animationend que se dispara cuando al animación CSS llego a su fin.


Los botones se asocian a funciones en los eventos clics y deshabilitamos el de la izquierda, ya que en teoría estamos en el primer elemento del array y por lo tanto no hay más elementos para desplazarnos hacia ese lado.


Y al fin damos las urls a los src de las imágenes. Para comenzar solo importan el central y el de la derecha, ya que al estar en el primer elemento no nos podemos mover a la izquierda así que se asigna al de la izquierda y al central el primer elemento del array y al cuadro que esta a la derecha el siguiente (posicion_actual + 1)


Ya lo tenemos montado. Ahora vamos a darle movimiento. Veamos las funciones de los botones


function mover_left() {

direccion = 1

image1.addClass('retromove')

image2.addClass('retromove')

}


function mover_der() {

direccion = 2

image2.addClass('move')

image3.addClass('move')

}


Son super sencillas. Asignan un valor a direccion para saber que movimiento se ha hecho y luego aplican las cases según el botón pulsado. De izquierda a derecha afecta al primer y segundo elemento y de derecha a izquierda afecta al segundo y tercer elemento.


Repito que movemos los div's NO las imágenes.


Y termina ahí el botón. Lo que pase ahora depende del cuadro central y el tiempo que se tarde en terminar la animación que se ha iniciado cuando se asignó la clase.


En ese momento se dispara el evento animationend y se inicia la función:


function animationEnd() {

if (direccion === 1) {

screen2.attr('src', screen1.attr('src'))

setTimeout(()=> {

image1.removeClass('retromove')

image2.removeClass('retromove')

screen3.attr('src', listUrls[posicion_actual + 1])

screen1.attr('src', listUrls[posicion_actual - 1] )

}, 500)

--posicion_actual

}

if (direccion === 2) {

screen2.attr('src', screen3.attr('src'))

setTimeout(()=> {

image2.removeClass('move')

image3.removeClass('move')

screen3.attr('src', listUrls[posicion_actual + 1])

screen1.attr('src', listUrls[posicion_actual - 1] )

}, 500)

++posicion_actual

}

if (posicion_actual > 0) {

button_left.removeAttr('disabled')

} else {

button_left.attr('disabled', true)

}

if (posicion_actual < (listUrls.length -1) ) {

button_der.removeAttr('disabled')

} else {

button_der.attr('disabled', true)

}

direccion = 0

}


Parece largo pero lo iremos aplicando según el esquema y veremos que es sencillo en realidad.

Lo primero es comprobar el valor guardado en direccion para entender que debemos hacer. En este momento estamos en el paso 2 de la explicación gráfica.

Estas lineas


screen2.attr('src', screen1.attr('src'))

screen2.attr('src', screen3.attr('src'))


Realizan el paso 3. Se usará una u otra dependiendo de direccion.


Lo que hacemos es copiar el src de la imagen que (temporalmente) está en el centro de la animación (que es la imagen1 o la imagen3) a la imagen2 (temporalmente fuera del centro).


He intentado evitar setTimeout para el siguiente paso pero los eventos de img me han fallado o no he sabido aplicarlos adecuadamente así que tenemos que darle tiempo a <img> para que renderice la imagen que ha cambiado. Si no hacemos esto cuando hagamos el paso 4 habrá un pequeño pero evidente salto visual que afea lo que estamos haciendo.


En realidad no necesitamos mucho tiempo. Con medio segundo es suficiente (y seguramente menos. Invito a cambiarlo y probar)


Para quienes no conozcan setTimeout es una función que permite la ejecución de código después de unos determinados milisegundos (Y hay que recordar eso, los valores que pasamos son en milisegundos)


El formato es sencillo:


setTimeout(function(){}, tiempo)


Veamos los dos usos:


setTimeout(()=> {

image1.removeClass('retromove')

image2.removeClass('retromove')

screen3.attr('src', listUrls[posicion_actual + 1])

screen1.attr('src', listUrls[posicion_actual - 1] )

}, 500)

--posicion_actual


Aquí estamos haciendo el paso 4. Eliminamos las clases relacionadas con el keyframe por lo que los divs vuelven a su lugar original (El usuario no nota el cambio porque hemos guardado en el cuadro central (que es el que ahora ve) la misma imagen que estaba viendo. Y actualizamos las imágenes de derecha e izquierda con los valores relacionados según la posición actual en el array listUrls.


Ultimo paso, restar uno a posicion_actual.


if (direccion === 2) {

screen2.attr('src', screen3.attr('src'))

setTimeout(()=> {

image2.removeClass('move')

image3.removeClass('move')

screen3.attr('src', listUrls[posicion_actual + 1])

screen1.attr('src', listUrls[posicion_actual - 1] )

}, 500)

++posicion_actual

}


Esta es lo mismo que la anterior pero con los cambios lógicos. Se copia la imagen del otro lado. Se quita la otra clase asociada al keyframe y al igual que antes actualizamos las imágenes de los lados.


Esta vez sumamos uno a posicion_actual


Pero la función no acaba ahí aunque lo que queda es protocolar.


if (posicion_actual > 0) {

button_left.removeAttr('disabled')

} else {

button_left.attr('disabled', true)

}

if (posicion_actual < (listUrls.length -1) ) {

button_der.removeAttr('disabled')

} else {

button_der.attr('disabled', true)

}

direccion = 0


Verificamos que no estamos en el primer elemento del array y si es así desmontamos el botón del desplazamiento hacia la izquierda.

Verificamos que no estamos al final del array y si es así desmontamos el botón del desplazamiento a la derecha.

Ponemos direccion a 0 para evitar repeticiones de acciones.


Y lo tenemos. Ya podemos verlo en Vuejs pero toda la galería la haremos en un componente.


VUEJS


Primero el archivo principal de Vuejs:

Enlaces:

El código es tan pequeño que lo pondremos todo:


<div class="top20 container" id="app">

<gallery :list-content="listUrl"></gallery>

</div>

<script src="links.js"></script>

<script src="galeryComponent.js"></script>

<script>

new Vue({

el: '#app',

data: {

listUrl: listUrls

}

})

</script>


Traemos el componente. Traemos el array externo. Asociamos el array a una variable local y se lo pasamos al componente.

TODO lo demás lo hace el componente. Este es el primer componente 100% reutilizable que creamos en esta serie


Vayamos al componente entonces:

Iremos por partes:

Primero:


const head = document.getElementsByTagName('head')[0]

const style = document.createElement('link')

style.href = 'estilo.css'

style.type = 'text/css'

style.rel = 'stylesheet'

head.append(style)


En vista que la animacion de basa en clases CSS seria un poco hipocrita si el archivo que contiene el componente no fuera capaz de cargar el CSS. Este codigo es para lograr eso y nada mas.


Props ya lo hemos usado y es lo que permite recibir un valor o dato por medio de la marca HTML. Seguimos con el mismo prop que usamos antes, ya que era un array y en este caso también:


props: {

listContent: {

type: Array,

require: true,

}

}


Lo usamos en el HTML cuando hicimos:


:list-content="listUrl"


Ahora veamos un cambio. La definición de las variables en Vue. Hasta ahora usábamos el formato:


data {

variable:valor

}


Pero en los componentes el formato es algo diferente:


data() {

return {

variable: valor

}

}


Unos pocos mas de caracteres. Por lo demás funciona igual. Veamos las variables del componente:


data() {

return {

posicion: 0,

direccion: 0,

imagenes_actuales1:'',

imagenes_actuales2:'',

imagenes_actuales3:'',

retro: false,

move: false,

button_left_desactived: true,

button_right_desactived: false

}

}

posicion y direccion las usaremos con la misma intención que lo hicimos en jQuery. retro y move nos servirán para activar / desactivar las clases correspondientes en los divs contenedores de imágenes.

button_left_desactived / button_right_desactived controlarán el disabled de los botones. Igual que en jQuery comenzamos con el botón izquierdo desactivado.

Hablemos de las string imagenes_actuales. Estas podrían ser un array, sin embargo la reactividad y la actualización de arrays no siempre se llevan bien. Ese tema lo trataremos más adelante pero en vista que solo necesito 3 variables tampoco agobia tener 3 string así que dejo eso para después que aún nos quedan cosas nuevas aquí.

Ahora hablemos de los hook. Vue tiene un ciclo de vida y durante el mismo podemos hacer determinadas cosas según la necesidad. En el gráfico siguiente se puede ver más (los ciclos de vida son los encerrados en rectángulos rojos y escritos en rojo también) y si se desea conocer en profundidad se puede visitar: https://es.vuejs.org/v2/guide/instance.html



En este componente hemos usado uno de ellos: created(). Su nombre es bastante revelador. Se dispara cuando el componente es creado y lo hemos usado para inicializar las strings. Este hook solo se ejecuta una vez en todo el ciclo de vida por lo que no hay posibilidad de sobreescribir los valores.

created(){

this.imagenes_actuales1 = this.listContent[0]

this.imagenes_actuales2 = this.listContent[0]

this.imagenes_actuales3 = this.listContent[1]

}

Hemos inicializado las variables exactamente igual que en jQuery. El primer valor para la imagen1 e imagen2 y el segundo valor para la imagen3 que estará a la derecha.


Antes de pasar al HTML hablemos de los templates dentro de un componente. Hay una regla que no hemos dicho en la creación del componente en el otro post y es que los templates (o sea la parte de HTML) debe tener una marca como padre de todos los demás componentes. Esa marca HTML puede tener tanta descendencia como necesite pero no permite marcas hermanas. Lo explico mejor:


<template>

<div>

....

<div>

</template>


Correcto


<template>

<div>

...

</div>

<div>

...

</div>

</template>


Incorrecto


Como el código que vamos a usar ahora tiene marcas hermanas le hemos agregado a todo un div padre. Si todo esto no quedo claro comentarlo y lo aclararemos. Si da vergüenza comentar me pueden encontrar en el Slack (Y preguntar de forma privada o publica) o en Twitter y preguntar de forma privada o publica.


Veamos el template.


template: `

<div>

<div class="row">

<div class="col-12 grilla">

<div>

&nbsp;

</div>

<div class="content-image">

<div class="image" id="image1" :class="{retromove: retro }">

<img :src="imagenes_actuales1" alt="..." class="image" id="imagen_screen1" >

</div>

<div @animationend="animationEnd" class="image" id="image2" :class="{retromove: retro , move: move }">

<img :src="imagenes_actuales2" alt="..." class="image" id="imagen_screen2" >

</div>

<div class="image" id="image3" :class="{move: move}">

<img :src="imagenes_actuales3" alt="..." class="image" id="imagen_screen3" >

</div>

</div>

<div>

&nbsp;

</div>

</div>

</div>

<div class="row">

<div class="col-12 grilla">

<div>

&nbsp;

</div>

<div class="grilla sombra">

<button class="btn btn-primary" @click="mover_left" :disabled="button_left_desactived"><<</button>

<div>&nbsp;</div>

<button class="btn btn-primary" @click="mover_der" :disabled="button_right_desactived">>></button>

</div>

<div>

&nbsp;

</div>

</div>

</div>

</div>`

RECORDAR: las comillas que usamos no son ni las simples ni las dobles sino de las otras (posiblemente acentos). Veamos las contendores de imágenes. Repetiré el código central que es el más completo:

<div @animationend="animationEnd" class="image" id="image2" :class="{retromove: retro , move: move }">

<img :src="imagenes_actuales2" alt="..." class="image" id="imagen_screen2" >

</div>

Ni una sorpresa. Usamos el mismo evento que en jQuery, las clases dependen de los boolean que vimos en las variables (es la primera vez que mostramos como validar más de una clase) el :src está asociado a la variable y ya lo tenemos.

<button class="btn btn-primary" @click="mover_der" :disabled="button_right_desactived">>></button>

El boton también es predecible. Una función asociada al evento click y el estado disabled según el boolean que explicamos en la parte de las variables.


Veamos los métodos:


Primero los asociados a los botones:


mover_left() {

this.direccion = 1

this.retro = true

}


mover_der() {

this.direccion = 2

this.move=true

}


Damos (como en jQuery) un valor a direccion y ponemos en true la variable correspondiente a la clase según el caso. Ahora solo que la función asociada al final de la animación:


animationEnd() {

const me = this

if (this.direccion === 1) {

this.imagenes_actuales2 = this.imagenes_actuales1

setTimeout(()=> {

me.retro = false

me.imagenes_actuales3= me.listContent[me.posicion + 1]

me.imagenes_actuales1= me.listContent[me.posicion -1 ]

}, 500)

--this.posicion

}

if (this.direccion === 2) {

this.imagenes_actuales2 = this.imagenes_actuales3

setTimeout(()=> {

me.move = false

me.imagenes_actuales3= me.listContent[me.posicion + 1]

me.imagenes_actuales1= me.listContent[me.posicion -1 ]

}, 500)

++this.posicion

}

this.button_left_desactived = this.posicion <= 0;

this.button_right_desactived = this.posicion >= (this.listContent.length - 1);

this.direccion = 0

}


Volvemos con setTimeout pero para tratar otra cosa. Ya hemos hablado de porque lo usamos y como funciona. No obstante en Vuejs nos presenta otra característica. Tiene su propio this. No se puede usar esa palabra reservada para referenciar al objeto de Vuejs. Pero es una dificultad menor, ya que lo que tenemos que hacer es guardar en una constante con otro nombre la referencia al this de Vuejs para poder usar dentro del setTimeout.


Eso es lo que es la primer linea de esta función:


const me = this


Los valores dentro de los if's funcionan igual lo único que manejamos variables. Primero cambiamos (recordemos que aquí estamos pasando del paso 2 al 3 con esta acción) hacia el cuadro2 la imagen que se ve en ese momento. Dentro de setTimeout y usando el me ponemos a false la variable que usamos para colocar las clases del keyframe (lo que lleva al paso final) y actualizamos los cuadros de los lados a la imagen2 (ahora, sin la clase, central)


Sumamos o restamos posicion según el caso.


Actualizar las variables de estado de los botones también es más sencillo. Simplemente es validar el valor actual de posicion y devolver TRUE o FALSE haciendo la condición adecuada a la derecha del signo de asignación (=)


direccion al final le ponemos 0


Listo. Tenemos el componente y lo podemos rehusar en los proyectos que lo necesitemos. Vamos avanzando. Espero que vayamos a un ritmo adecuado. Comentarios bienvenidos.

Enlaces generales:


Consultas, dudas, comentarios: Slack de PEUM o en Twitter.


Vuejs para programadores jQuery. Galería. Load More XVI

Vuejs para programadores jQuery. Galería. Load More XVI En el artículo de hoy vamos a tratar el tema de plugins de jQuery (crearemos dos) ...