Vuejs para programadores jQuery. Selects relacionados IX

Vuejs para programadores jQuery.


Selects relacionados IX


Ahora usaremos dos select para mostrar en uno el contenido según la selección del otro.


JQUERY


Usaremos un js para definir el array de contenidos en ambos códigos. Lo hemos externalizado por varias razones

  • Tiene 112 lineas que solo servirían para distraernos de lo que estamos haciendo
  • Lo usamos exactamente igual en ambos códigos ¿Porque repetir?
  • Queríamos mostrar una manera de definir una variable externa para el uso dentro del código sin necesidad de los clásicos import / export de los proyectos de Webpack / Parcel /etc.

Enlace del archivo para ambos códigos:

No pondremos aquí las 112 lineas de código pero si la parte inicial:


const listaTotal = [

{

titulo: 'Animales',

index: 1,

child: [

{

titulo: 'Gato',

index: 101

}

...

Definimos una constante listaTotal de tipo array, y cada elemento tiene 3 campos: Un título, un index único y un array llamado child que contiene objetos con el mismo tipo de campo.


Si no queda claro usar los enlaces, revisar, mirar el código.


Ahora si. Empecemos con el código. Primero los enlaces:

El HMTL:


<form>

<div class="form-row align-items-center">

<div class="col-auto my-1">

<label class="mr-sm-2 sr-only" for="inlineFormCustomSelect">Tema principal</label>

<select class="custom-select mr-sm-2" id="inlineFormCustomSelect">

</select>

</div>

<div class="col-auto my-1">

<label class="mr-sm-2 sr-only" for="inlineFormCustomSelect1">SubTema</label>

<select class="custom-select mr-sm-2" id="inlineFormCustomSelect1">

</select>

</div>

</div>

</form>


0 sorpresas. Un código simple con dos select totalmente vacíos.


Antes del JS hablemos de esto:


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


Aquí estamos trayendo el archivo JS con la constante como explicamos antes. ¿Pero que es lo que significa realmente? En realidad <script> lo que hace es una inyección de código. O sea que el contenido de lista.js será escrito en el código cuando se procese la información (aunque el navegador seguirá mostrando la marca y no el contenido) por lo tanto es como si tuviéramos el contenido del archivo escrito en el mismo archivo donde estamos trabajando.


¿Cuál es la diferencia con un import?. Import permite traer una o varias funciones o variable o incluso una combinación de ellas desde un mismo archivo. <script> no permite esa discriminación por lo tanto si queremos tener el mismo resultado con <script> debemos tener las funciones o variables definidas cada una en un archivo diferente y cargar aquellos que sean relevantes en cada caso.


Supongamos que tenemos el código:


Y usamos:


Para el intérprete de js será lo mismo que:


Ahora si.Vayamos con el jQuery:


const selectPadre = jQuery('#inlineFormCustomSelect')

const selectHijo = jQuery('#inlineFormCustomSelect1')

const elementosPadre = extractElementosPadre()

CreateSelect(selectPadre, elementosPadre)


selectPadre.on('change', actualiza)

actualiza()

Es casi tradicional este código ya. selectPadre apunta al select principal que tendrá los elementos que no son filtrados. selectHijo apunta al select que tendrá los elementos filtrados según la selección del primer select, asociamos el evento change del select principal con la función actualiza y lanzamos actualiza por primera vez.


Pasemos a explicar el const y la función que hemos omitido.


Antes de seguir quiero explicar algo. jQuery tiene muchas maneras de hacer las cosas y no todas son la mejor forma, es típico encontrar código que mezcla en un append un largo string de marcas HTML, código js, valores, contenido de texto, etc. lo que provoca un código sumamente caótico. Por ejemplo:


jQuery('#accordion_container').append('<div class="accordian_container"><a href="#" class="accordian_trigger"><h4>Co-Borrower Information</h4></a><hr/><div class="accordian_item" id="accord_item_2"><label> First Name</label><br/><input type="text"/><br/><label>Middle Name</label><br/> <input type="text"/><br/> <label>Last Name</label><br/> <input type="text" /><br/> <label>Home Number</label><br/> <input type="text"/><br> <label>Work Number</label><br/> <input type="text"/><br> <label>Cell Number</label><br/> <input type="text"/><br> </div> </div>')

(Lo he visto bastante peores)

Esto me parece bastante desafortunado. Complejo de mantener y sobre todo de lectura confusa para lo que se quiere lograr, así que para evitar ese tipo de código usaremos funciones templates de las marcas HTML que vamos a agregar, ejemplo:


function templateOption(value, texto) {

return '<option value="' + value + '">' + texto + '</option>'

}

Esto mantiene la marca HTML <option> independiente de más código js. También nos evita tener que estar repitiendo largas cadenas de string. Devuelve un option completo con un value y un texto que hemos enviado en los parámetros de la función. Por ejemplo:


templateOption(0, 'No se ha elegido nada')


nos devolverá:


<option value='0'>No se ha elegido nada</option>


Creamos una función más pero creo que a cambio obtenemos un código más armónico.


Ahora miremos esta otra función:


function extractElementosPadre() {

let result = []

listaTotal.every(item => result.push(templateOption(item.index, item.titulo)))

return result

}

Definimos un array vacío. La siguiente linea la desmontaremos:


listaTotal.every(item => result.push(templateOption(item.index, item.titulo)))


Primero:


listaTotal.every()


¿De dónde sale listaTotal? No la estamos definiendo en ningún lugar del código. listaTotal es la constante que hemos cargado en nuestro código cuando hemos usado la marca script para traer lista.js


every() es una de las opciones que existen para acceder a cada elemento de un array.


Usamos la notación arrow. Para despistados daremos unas indicaciones:


function () {} es igual que () => {}

function (parametro) {} es igual que parametro => {}

function (parametro, posicion) es igual que (parametro, posicion) => {}


La notación arrow se usa cada vez con más frecuencia y el resumen que hemos aquí es muy muy suave. Realmente hay más cambios. Si se quiere saber más: https://developer.mozilla.org/es/docs/Web/JavaScript/Referencia/Funciones/Arrow_functions


A every le podemos pasar una función que resuelva lo que queremos hacer con cada item o definir una función anónima ahí mismo, por ejemplo:


listaTotal.every(function(item) {

result.push(templateOption(item.index, item.titulo))

})


Donde item es cada uno de los elementos del array. Y podemos reducir la función anónima:


listaTotal.every((item) => {

result.push(templateOption(item.index, item.titulo))

})


Y al ser un único parámetro lo podemos dejar así:


listaTotal.every(item => {

result.push(templateOption(item.index, item.titulo))

})


Además como solo se ejecuta una sentencia podemos reducirlo más:


listaTotal.every(item => result.push(templateOption(item.index, item.titulo)))


Expliquemos el push:


result.push(templateOption(item.index, item.titulo))


push es lo propio de un array para agregar contenido. templateOption es la función que hemos visto antes y que nos devuelve un <option>, y cuando hemos mirado el código de lista.js hemos visto que los elementos tiene index, titulo y child. Aquí usamos index y titulo para construir el option.


listaTotal.every(item => result.push(templateOption(item.index, item.titulo)))


Al final hemos visto que esta línea significa una interacción con el dato traído de lista.js para construir con cada uno de ellos un <option> que guardamos dentro de un array


El último paso de la función extractElementosPadre() es un return que devuelve el array construido.


Ahora queda más claro la definición de la constante siguiente:


const elementosPadre = extractElementosPadre()


Es un arrays de <options> creado con los datos de listaTotal (traído con lista.js)


Miremos la siguiente función:


function CreateSelect(select, element) {

select.empty().append(element.join(''))

}


Esta función es genérica, pide la constante que apunta a un select y un array de elementos y agrega esos elementos al select vaciándolo previamente


join() es una función de Js que convierte un array en una cadena de texto, por ejemplo:


[1,2,3].join() Devuelve: "1,2,3"


join() por defecto devuelve los elementos separados por comas, pero en el caso del select los options no deben tener separador para eso le damos el parámetro que será lo que lo separara, en este caso un valor null ("")


usando el ejemplo anterior:


[1,2,3].join("") Devuelve "123"


Y así completamos de explicar las definiciones del inicio del script. Solo nos queda por ver dos funciones


function extractElementosHijos(index) {

const elegido = listaTotal.find(item => item.index === index)

if (!elegido) {

return []

}

let result = []

elegido.child.every(item => result.push(templateOption(item.index, item.titulo)))

return result

}

Esta función recibe un index de un elemento de listaTotal, busca ese elemento en la lista con find, si no hay resultado devuelve un array vacío y si hay resultado crea un array de options del contenido de la característica child del elemento.


listaTotal.find(item => item.index === index)


Devuelve el primer elemento que cumpla la condición o null si no encuentra nada. Hemos dicho que cada index es único o sea que el primer elemento es también el único elemento que cumple la condición.


Con respecto a la función arrow hay una particularidad:


Esto es correcto:


find(item => item.index === index)


Pero esto no


find(item => { item.index === index })


Porque necesita si o si un return (boolean) del resultado de la operación así que para validar un find con sentencias más complejas hay que usar el return:


find(item => { return item.index === index })


Y ahora si lo podemos usar con llaves ({}) aunque repito que con una única sentencia no son necesarias


El if pregunta ¿Es elegido un valor NOT (!) válido? si es así devuelve un array vacío.


Hablemos un momento sobre los if else inútiles. Else es útil muchas veces pero se usa en exceso en mucho código, me explico:


let valor = 0

if (condicion) {

valor =1

} else {

valor =2

}


Es lo que denomino un else inutil, ya que ese if se resuelve asi:


let valor=2

if (condicion) {

valor =1

}


Lo veo más legible y sobre todo se evita un innecesario, pero el caso más frecuente de else inútil que veo es en código como este:


if (condicion) {

return

} else {

...

}


Lo veo inútil por innecesario. Si if tiene un return cualquier código escrito después del if, este o no dentro de un else se ejecutara si la condición es falsa, ya que el if termina la ejecución del código.


El if anterior hace el mismo efecto así:


if (condicion) {

return

}

...


No es una apología en contra del else, pero si en contra del exceso de uso.


Siguiendo con el código luego del if se crea un array vacío. Acto seguido se rellena con OJO los elementos del child del elemento encontrado con find. Y como hicimos antes con every y templateOptions creamos el array correspondientes como <option> y al final devolvemos el array resultante.


Y ahora la reina del baile: actualiza


function actualiza() {

CreateSelect(selectHijo, extractElementosHijos(parseInt(jQuery('#inlineFormCustomSelect option:selected').val())))

}

Desmontemos desde dentro hacia afuera:


jQuery('#inlineFormCustomSelect option:selected').val()


Esta línea selecciona, en el select padre, el elemento elegido y recoge su value (val()) que como recordamos lo estamos creando con el valor index del los elementos del array traído con lista.js


parseInt(jQuery('#inlineFormCustomSelect option:selected').val())


Convertirlos el value a un valor entero. Recordar que el value es un string y el index del elemento es un valor número entero.


extractElementosHijos(parseInt(jQuery('#inlineFormCustomSelect option:selected').val()))


Esta es la función que busca y devuelve los elementos hijos (child) según el index indicado (el value parseado a número entero)


CreateSelect nos pedía un select (selectHijo en este caso) y un array de elementos que es lo que devuelve extractElementosHijos.


De esta manera ya tenemos el codigo jQuery. Limpio y claro. Si ven que hay una manera mejor de realizar este código soy todo oídos. Sobre todo porque nos esperan varios post de variantes de Select y estaría bien tener todo optimizado.


VUEJS


Pasemos a Vuejs

Usaremos el mismo lista.js de antes así que pondremos solo los enlaces del código vuejs:

HTML:


<form>

<div class="form-row align-items-center">

<div class="col-auto my-1">

<label class="mr-sm-2 sr-only" for="inlineFormCustomSelect">Tema principal</label>

<select class="custom-select mr-sm-2" id="inlineFormCustomSelect"v-model="selectPadre">

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

</select>

</div>

<div class="col-auto my-1">

<label class="mr-sm-2 sr-only" for="inlineFormCustomSelect1">SubTema</label>

<select class="custom-select mr-sm-2" id="inlineFormCustomSelect1" v-model="selectHijo">

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

</select>

</div>

</div>

</form>


Las explicaciones en Vuejs serán más cortas porque estamos usando cosas que hemos visto. Repasemos.


Los selects están asociados a variables a través de v-model. Recordemos que v-model en este caso guarda el value de los options contenidos.


Antes de mirar los options vamos a poner el código JS porque estamos usando computadas y asi nos podemos explicar mejor


<script>

new Vue({

el: '#app',

data: {

selectPadre: '1',

selectHijo: '101'

},

watch: {

selectPadre:function () {

this.selectHijo = this.elementosHijo[0].index

}

},

computed: {

elementosPadres(){

return listaTotal

},

elementosHijo() {

const list = listaTotal.find(item => item.index === parseInt(this.selectPadre) )

return list ? list.child : []

}

}

})

</script>


El option del select padre:


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


Volvemos a usar v-for con el formato que ya hemos visto en ocasiones anteriores (item in array) pero en lugar de usar directamente listaTotal usamos una computada (que en este caso en particular también podría ser una variable de Vuejs) y el porqué de esto lo hemos explicado cuando hablamos del apuntado this en el post: https://comunidad.programaresunamierda.com/2020/06/vuejs-para-programadores-jquery-form_11.html.


Como explicamos en posts anteriores key debe tener un valor único en cada option, para esto tenemos a index que es un valor único así que lo asignamos con :key="item.index" y el mismo valor usamos para el value y por último mostramos el campo titulo ({{}}).


Si hacemos una comparativa con el código de jQuery se parece al selectCreate pero con otra lógica y trabajando directamente sobre el HTML.


El select hijo es igual con el v-model asignado a otra variable y el option:


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


Usamos casi casi el mismo código de antes con solo una variante (que lo cambia todo) que es el array de donde extraemos los items. Miremos el código JS de esa computada:


elementosHijo() {

const list = listaTotal.find(item => item.index === parseInt(this.selectPadre) )

return list ? list.child : []

}


Buscamos el elemento elegido exactamente igual (usando el find) que en jQuery pero cambia el valor con el que comparamos:


parseInt(this.selectPadre)


parseInt ya lo hemos explicado en numerosas veces. Convierte un carácter o cadena de caracteres a un valor entero ('0' => 0)


this.selectPadre es la variable usada en el v-model del select padre. Esta variable SIEMPRE tendrá el valor del value del option elegido. Y recordemos lo que decíamos en un post anterior:


"Las computadas son variables complejas que se actualizan cuando cualquiera de los valores que usa cambia y siempre deben devolver un valor."


Esto indica que cuando el selectPadre cambia el valor de esa variable y sin necesidad de acción extra ninguna la computada elementosHijo también cambiara porque usa esa variable para generar su resultado.


Sé que a veces es difícil de ver pero la lógica es sencilla vista así:


Cuando se crea una computada, Vuejs le da herramientas para que "vigile" los cambios de las variables involucradas. Cuando sucede un cambio se relanza automáticamente la computada.


Más adelante si es necesario podemos ahondar en el tema si hace falta. Sería un artículo técnico básicamente. Si les interesa espero comentarios al respecto.


Luego la computada hace un if ternario para resolver lo que devolverá. Si se encontró el elemento devuelve el array del child y si no un array vacío.


Con esto ya tenemos el sistema funcionando casi perfectamente. ¿Qué falla? Es apenas un detalle: cuando se actualizan los options del select hijo ningún elemento es elegido por defecto, ya que la variable del v-model selectHijo no ha sido actualizada y tiene un valor de index que no corresponde con ningún elemento de los hijos que se han elegido.


Para resolver este problema usamos watch:


watch: {

selectPadre:function () {

this.selectHijo = this.elementosHijo[0].index

}

}


watch (lo hemos comentado alguna vez) es relacionar una función con un cambio de una variable. Aunque las variables se actualizan según los eventos van sucediendo a veces (como en este caso) necesitamos hacer algo más.


En este caso estamos haciendo esto: cuando selectPadre cambie actualiza la variable selectHijo con el valor del index del primer elemento de los elementosHijo y de esa manera al cambiar el primer elemento estará seleccionado por defecto.


En resumen cuando cambiamos el selectPadre se lanzan los eventos de actualizar el select hijo tanto en los options (con la computada) como en el valor del option elegido en ese momento (con el watch)


Y ahora si lo tenemos listo.


El elemento select del HTML me parece el lugar más evidente de las diferencias entre un framework reactivo y una librería que no lo es.

Espero que los conceptos quedaran claro a pesar de lo largo del post y que ayude a comprender mejor. Seguiremos mirando cosas con los select algunos post más.


Codigo general:



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

Vuejs para programadores jQuery. Select simple VIII

Vuejs para programadores jQuery

Select simple VIII


Iremos viendo a través de varios post variantes alrededor del elemento HTML Select.


El primer código que tocaremos es un Select básico, sin mucha magia que sencillamente muestra un resultado según la opción elegida.



JQUERY

Enlaces:



<form>

<div class="form-row align-items-center">

<div class="col-auto my-1">

<label class="mr-sm-2 sr-only" for="inlineFormCustomSelect">Preferencia</label>

<select class="custom-select mr-sm-2" id="inlineFormCustomSelect">

<option selected value="0">Elija...</option>

<option value="1">Haz el amor y no la guerra</option>

<option value="2">Cada día sabemos más y entendemos menos</option>

<option value="3">El mundo es bello, pero tiene un defecto llamado hombre</option>

</select>

</div>

</div>

</form>

<div>

<span id="selectContent"></span>

</div>


No hay nada nuevo aquí, es un Select normal con 4 opciones pero se ha puesto el código para tomar en cuenta ciertas observaciones, por ejemplo el id del Select y los value de las opciones (números continuados como caracteres) y el espacio con id selectContent que será donde mostraremos el mensaje relacionado.


Pasemos al código JS:


<script>

const opciones = ['Nada elegido', 'Frase atribuida a John Lennon', 'Frase atribuida a Albert Einstein', 'Frase atribuida a Friedrich Nietzsche']

const origen = jQuery('#inlineFormCustomSelect')

const objetivo = jQuery('#selectContent')

origen.on('change', actualizar)

actualizar()


function actualizar() {

objetivo.empty().append(opciones[parseInt(jQuery('#inlineFormCustomSelect option:selected').val())])

}

</script>


Lo primero que hacemos es crear un array (opciones) con los mensajes relacionados con los options del Select. La única condición es que el array debe tener tantos elementos como options existan en el HTML.

Seguidamente creamos una constante llamada origen que apunta al Select y una constante llamada objetivo que apunta al id donde mostraremos el mensaje.


Luego asociamos la función actualizar al evento change del Select y por último lanzamos actualizar por primera vez.


Revisemos la función actualizar:


objetivo.empty().append(opciones[parseInt(jQuery('#inlineFormCustomSelect option:selected').val())])


Desmontemos la línea:


jQuery('#inlineFormCustomSelect option:selected').val()


Nos devolverá el value del option elegido. Recordemos que el value es de 0 a 3


parseInt(jQuery('#inlineFormCustomSelect option:selected').val())


El problema del value es que es un valor string así que usamos parseInt para convertirlo a un valor integer


opciones[parseInt(jQuery('#inlineFormCustomSelect option:selected').val())]


Ese valor entero lo usamos para iterar en el array en la posición que coincida con el value del option elegido. Recordemos que opciones es la primer constante que definimos


objetivo.empty()


Vaciamos el contenido del span donde vamos a poner el mensaje


objetivo.empty().append()


append agrega luego que hemos limpiado el span.


objetivo.empty().append(opciones[parseInt(jQuery('#inlineFormCustomSelect option:selected').val())])


Así que lo que hacemos es: limpiar el span y agregar el mensaje que está en el array en la posición correspondiente al option elegido.


VUEJS


Enlaces:

HTML:


<form>

<div class="form-row align-items-center">

<div class="col-auto my-1">

<label class="mr-sm-2 sr-only" for="inlineFormCustomSelect">Preferencia</label>

<select class="custom-select mr-sm-2" id="inlineFormCustomSelect" v-model="origen">

<option selected value="0">Elija...</option>

<option value="1">Haz el amor y no la guerra</option>

<option value="2">Cada día sabemos más y entendemos menos</option>

<option value="3">El mundo es bello, pero tiene un defecto llamado hombre</option>

</select>

</div>

</div>

</form>

<div>

<span>

{{opciones[parseInt(origen)]}}

</span>

</div>

Ya hemos visto tanto HTML en Vuejs que pocas sorpresas tendremos aquí. La más destacada es otra vez v-model. Ya lo hemos usado con valores string y valores boolean pero siempre actuaban sobre el elemento de forma directa. Sin embargo en este caso v-model usado en el Select guardara en la variable origen el value del option elegido y se actualizara con cada cambio del Select sin necesidad de hacer nada más.


En el span mostramos ({{}}) el array (es una suposición porque no hemos visto el JS aun) cuya posición es el valor entero (parseInt) del value del option elegido (origen)


El código JS es sencillo:


<script>

new Vue({

el: '#app',

data: {

origen: '0',

opciones: ['Nada elegido', 'Frase atribuida a John Lennon', 'Frase atribuida a Albert Einstein', 'Frase atribuida a Friedrich Nietzsche']

}

})

</script>


Solamente es necesario definir las dos variables que usaremos: origen con un valor inicial '0' (string) y el array (ahora si sabemos seguros que es un array) con los mensajes relacionados. Las condiciones son las mismas que para jQuery. Y de hecho es el mismo contenido.


Listo. Esto lo tenemos.


Enlaces del Index:


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


Vuejs para programadores jQuery. Tabuladores VII

Vuejs para programadores jQuery.


Tabuladores VII


En este post trataremos algo sencillo: Tabulación.



JQUERY


Es tan sencillo que hemos evitado usar el sistema de tabulación de Bootstrap porque lo hace todo automáticamente y no nos valía eso para lo que estamos desarrollando. Perdón por la estética.

Se ha agregado un archivo css local. Si hay dudas de como funciona la clase active se puede revisar el código (para los amantes de sass, como yo, el archivo original es un scss que también está en esa carpeta)


Enlaces:


HTML:


<div class="container top20">

<div class="row">

<div class="tabulacion">

<ul class="tab" id="tab">

<li class="nav-option active" title="home">Home</li>

<li class="nav-option" title="informacion">Información</li>

<li class="nav-option" title="servicios">Servicios</li>

<li class="nav-option" title="contacto">Contacto</li>

</ul>

<div class="content" id="contenidos">

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

Este es el contenido de home

</div>

<div class="nav-content" id="informacion">

Esta es la información

</div>

<div class="nav-content" id="servicios">

Estos son los servicios

</div>

<div class="nav-content" id="contacto">

Datos de contacto

</div>

</div>

</div>

</div>

</div>


Las opciones son los li del ul con id tab y los contenidos están en dentro del div con id contenidos y cada opción en un div propio con un id igual al title del li correspondiente. Se renderizara diferente la opción que tenga la clase active. Se mostrará el contenido que tenga la clase active.


Veamos como lo resolvemos en jQuery:


const elementos = jQuery('#tab li')

const contenidos = jQuery('#contenidos div')

Este proceso se nos está volviendo rutinario. La constante elementos apunta a todos los elementos li (o sea las opciones de tabulación) y la constante contenidos apunta a todos los div con contenidos.


elementos.on('click', actualiza)


Aquí asociamos todos los eventos click de los li con la función actualiza:


function actualiza(evt) {

contenidos.removeClass('active')

elementos.removeClass('active')

jQuery(evt.target).addClass('active')

jQuery('#' + jQuery(evt.target).attr('title')).addClass('active')

}


Aquí estamos usando un parámetro de la función que no enviamos cuando asociamos la función al evento. Esto es porque los eventos de JS envían un parámetro por defecto. Y también es la razón por la que no usamos los parámetros en la asociación del evento.


Si lo hiciéramos así:


elementos.on('click', actualiza())


Le estaríamos diciendo al evento que no envíe ningún parámetro y por lo tanto el código anterior no funcionaria.


He llamado al evento evt, pero es por costumbrismo. Se le puede llamar como quede más claro.

Una vez dentro de la función eliminamos la clase active de todos los contenidos (recordemos que se mostrara el contenido que tenga esa clase asociada)

Volvemos a eliminar la clase active también de los elementos li.

El evento (evt) es un objeto con varios elementos. Uno de ellos es target que es el objeto sobre el que el evento tuvo efecto. Usaremos ese dato para pasárselo a jQuery y referenciar ese elemento. De esa manera le agregamos la clase active.


La siguiente linea la desmontaremos:


jQuery('#' + jQuery(evt.target).attr('title')).addClass('active')


La clave reside aui:

jQuery(evt.target).attr('title')


Tomamos el atributo title del elemento sobre el que se hizo click y lo asociamos como un id con jQuery ('#' + jQuery(evt.target).attr('title')) que es lo que explicamos al inicio. Los titles de los li son iguales a los ids de los contenidos que se quieren mostrar y le agregamos la clase active.


Y listo. Ya lo tenemos.


VUEJS


Veamos ahora lo mismo con VueJs


Enlaces:


HTML:


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

<div class="row">

<div class="tabulacion">

<ul class="tab" id="tab">

<li class="nav-option" :class="{active: option===item}" v-for="item in ['home', 'información', 'servicios', 'contacto']" :key="item" :title="item" @click="option=item">

{{item.charAt(0).toUpperCase() + item.slice(1)}}</li>

</ul>

<div>

<div class="nav-content" v-show="option==='home'">

Este es el contenido de home

</div>

<div class="nav-content" v-show="option==='información'">

Esta es la información

</div>

<div class="nav-content" v-show="option==='servicios'">

Estos son los servicios

</div>

<div class="nav-content" v-show="option==='contacto'">

Datos de contacto

</div>

</div>

</div>

</div>

</div>


En este ejemplo hemos cambiado la manera. Podríamos seguir usando la metodología de la clase active que usamos en jQuery pero he querido usar una característica conocida pero un poco olvidada de VueJS v-show.


Pero primero miremos los cambios hechos:


<li class="nav-option" :class="{active: option===item}" v-for="item in ['home', 'información', 'servicios', 'contacto']" :key="item" :title="item" @click="option=item">

{{item.charAt(0).toUpperCase() + item.slice(1)}}</li>


Lo primero es v-for. Otra característica nueva. Quienes ya hayan usado preprocesadores de HTML como Pug (anteriormente Jade) entenderán más rápidamente de que va la idea.


De todas maneras es bastante intuitiva la intención: repetir un elemento una cantidad X de veces.


v-for usa un único parámetro que marca la secuencia de inicio y de final. Se usa la semántica "item in array", hay variantes que iremos viendo poco a poco. Si hay más curiosidad se puede revisar su documentación: https://es.vuejs.org/v2/guide/list.html


En este caso hemos creado el array en el mismo v-for pero podría ser una variable. Solo que hemos pensado que si solo lo usaremos ahí no hay necesidad de crear una variable para este caso.

El v-for creará un elemento li por cada elemento del array (4). El atributo key (:key porque pasaremos una variable y no una cadena de texto directamente) le sirve a Vuejs para identificar de forma única cada elemento del v-for. Es lo único que pide ese parámetro: tener un valor único para cada elemento creado con el v-for


Volvemos a la lógica (no necesaria aquí pero por resolver como se haría en este caso) del title. Y se asocia :title con el valor de cada item.


Usaremos una variable llamada option. En el click le damos a option el valor del item en cada li creado.


En :class Le decimos que se le pase la clase active cuando option sea igual el item actual. O sea cuando se haga click.


Esto lo explicaremos un poco más:


{{item.charAt(0).toUpperCase() + item.slice(1)}}


JS tiene funciones del string para mayúsculas (uppercase) y minúsculas (lowercase) pero no para poner solo en mayúscula la primer letra de una palabra. Como podemos observar en el array todos los valores están en minúsculas pero los textos de las pestañas de la tabulación no quedarían bien así. Por lo que debemos convertir la primer letra de la cadena a mayúsculas y el resto mostrarlo tal cual.


item.charAt(0).toUpperCase()


Convierte la primer letra (charAt(0) ) del item en mayúsculas (toUpperCase()), el resto del texto se agrega con item.slice(1) que devuelve la cadena desde el segundo (1) carácter.


Y con esta parte se ha resuelto toda la lógica de elección de pestaña y de renderizado de las mismas. Veamos el código del contenido de nuevo:


<div>

<div class="nav-content" v-show="option==='home'">

Este es el contenido de home

</div>

<div class="nav-content" v-show="option==='información'">

Esta es la información

</div>

<div class="nav-content" v-show="option==='servicios'">

Estos son los servicios

</div>

<div class="nav-content" v-show="option==='contacto'">

Datos de contacto

</div>

</div>


Hablemos de v-show. Podíamos usar v-if perfectamente o incluso la misma lógica para la clase active que en los li pero hemos optado por v-show para hablar de esta característica de VueJs


Cuando hablamos de v-if decíamos que el elemento no se renderizaba y de esa manera no se entregaba al navegador ahorrándole tiempo y trabajo. Esto está bien en muchos casos. Sobre todo cuando se trabaja con una cantidad de datos determinados que necesitan mucho trabajo del navegador para mostrarlos correctamente. Por ejemplo un listado cuyas filas permitan ampliar los datos relacionados. Pero en muchos casos v-show es la opción adecuada. Simplemente oculta los elementos que no cumplen la condición. Al igual que v-if la condición debe tener una resolución lógica o sea TRUE o FALSE.


v-show no tiene un v-else pero si hiciera falta es fácil de solucionar:


<div v-show="valor">

<span>Verdadero</span>

<div>

<div v-show="!valor">

<span>Falso</span>

</div>


Volviendo al código. La condición es que option sea igual a una cadena de caracteres que coinciden con los valores del array del v-for de los li.


Tenemos todo resuelto así que el código JS es sencillo:


<script>

new Vue({

el: '#app',

data: {

option: 'home'

}

})

</script>


Simplemente definimos la variable option y le asignamos uno de los valores del array del v-for para que haya ya una opción y un contenido elegidos.


Enlaces Index:


Este post ha vuelto a ser más corto para no saturar. Se han conocido dos conceptos nuevos que seguiremos usando. Espero que haya sido útil.


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


Vuejs para programadores jQuery. Clases y estilos VI

Vuejs para programadores jQuery. 

Clases y estilos VI

Gracias a las sugerencias y comentarios realizados en las diferentes redes sociales sobre los artículos anteriores.

En este caso trabajaremos con botones en dos formas diferentes. El primer código permitirá que se escriba un estilo inline y se agregue a un contenido para probar el resultado.

JQUERY - ESTILOS INLINE
Ponemos los enlaces.

Empezamos con el código HTML.

<form> <div class="form-group row"> <label for="inputStyle" class="col-sm-6 col-form-label">Agregue estilo</label> <input type="text" class="form-control" id="inputStyle" placeholder="Estilo"> </div> </form> <button type="button" class="btn btn-primary mb-2" id="buttonStyle">Aplicar estilo</button> <blockquote id="bloque"> Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas et sem varius, pretium diam eget, hendrerit nisl. Pellentesque ac gravida ante. Nullam gravida purus ac arcu laoreet consectetur. Mauris porta felis venenatis magna sollicitudin, vel pellentesque felis rutrum. Vestibulum posuere ornare blandit. Donec at euismod odio. Aenean eleifend faucibus dui, nec pellentesque est rhoncus malesuada. Suspendisse a laoreet libero. Donec ac mauris in lorem congue bibendum. Curabitur vulputate pretium massa, non rhoncus purus imperdiet lacinia. In vitae est finibus, cursus nisi in, facilisis nulla. </blockquote> Creamos aquí 3 cosas: * Un input que permita que escribamos el estilo * Un botón para aplicar el estilo * Un texto para reflejar los cambios de estilos El código js no tiene sorpresas: <script> const bloque = jQuery('#bloque') const inputStyle = jQuery('#inputStyle') jQuery('#buttonStyle').on('click', actualiza) function actualiza() { bloque.attr('style', inputStyle.val()) } </script> Creamos la constante bloque apuntando al contenido de texto, la constante inputStyle apuntando al form input y luego asociamos la función con el evento click del boton. La función aplica el contenido del input en el atributo style del bloque de texto.

VUEJS - ESTILOS INLINE

Veamos esto mismo en Vue.

Codigo: <form> <div class="form-group row"> <label for="inputStyle" class="col-sm-6 col-form-label">Agregue estilo</label> <input type="text" class="form-control" id="inputStyle" placeholder="Estilo" v-model="inputStyle"> </div> </form> <button type="button" class="btn btn-primary mb-2" @click="runStyle=inputStyle">Aplicar estilo</button> <blockquote :style="runStyle"> Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas et sem varius, pretium diam eget, hendrerit nisl. Pellentesque ac gravida ante. Nullam gravida purus ac arcu laoreet consectetur. Mauris porta felis venenatis magna sollicitudin, vel pellentesque felis rutrum. Vestibulum posuere ornare blandit. Donec at euismod odio. Aenean eleifend faucibus dui, nec pellentesque est rhoncus malesuada. Suspendisse a laoreet libero. Donec ac mauris in lorem congue bibendum. Curabitur vulputate pretium massa, non rhoncus purus imperdiet lacinia. In vitae est finibus, cursus nisi in, facilisis nulla. </blockquote> Veamos los cambios que hemos hecho: En el input usamos v-model para tenemos el contenido en una variable. En button usamos el evento click para una acción. No necesitamos un método para la accion aqui requerida (una simple asignacion) aunque podria usarse un método perfectamente. @click="runStyle=inputStyle" Cuando suceda el evento click guarda en runStyle el contenido de inputStyle. El último cambio es en el bloque de texto en donde asociamos el resultado de runStyle al style. Vemos otra vez el uso de : con un atributo HTML para indicar a vuejs que debe parsear el valor. ¿Porque usamos dos variable? Esto es porque el style debe actualizarse solo cuando se pulse el botón. Como el v-model actualiza su valor inmediatamente debemos usar una segunda variable que se actualice solo cuando se pulse el botón. El js aplicado no trae sorpresas: <script> new Vue({ el: '#app', data: { inputStyle: '', runStyle: '' } }) </script> Lo único que necesitamos es declarar las variables que vamos a usar Index - Estilos:


JQUERY - CLASES CSS

Enlaces:

Codesandbox: https://githubbox.com/Gonzalo2310/jQuery-Vuejs/blob/master/Button/Clases/jQueryButtonClass.html

GitHub: https://github.com/Gonzalo2310/jQuery-Vuejs/blob/master/Button/Clases/jQueryButtonClass.html

Vayamos ahora a un código algo mas completo. Configuraremos un botón con varios radioButtons para estilo y dos checkbox, uno para un estilo y el otro para el estado disable.




El codigo es largo en esta ocasión. Vamos por partes: <form> <div class="custom-control custom-checkbox mb-3"> <input type="checkbox" class="custom-control-input" id="customControlValidation10"> <label class="custom-control-label" for="customControlValidation10">Disable</label> </div> <div class="custom-control custom-checkbox mb-3"> <input type="checkbox" class="custom-control-input" id="customControlValidation11"> <label class="custom-control-label" for="customControlValidation11">Outline</label> </div> </form> Creamos dos checkbox de la forma tradicional. A continuación ponemos uno de los checkbox pero se repite 6 veces mas variando el id: <div class="custom-control custom-radio mb-3"> <input type="radio" class="custom-control-input" value="primary" id="customControlValidation2" name="buttonStyle" checked> <label class="custom-control-label" for="customControlValidation2">Primary</label> </div>

La clave está en los valores value, ya que cada uno de ellos tendrá el nombre de las diferentes variantes de los botones de Bootstrap. Si no conoce ese estilo de los botones de Bootstrap revise el siguiente link: https://getbootstrap.com/docs/4.5/components/buttons/ Lo único a recordar es que los RadioButtons deben tener el mismo name para que solo se pueda elegir uno de ellos. Finalmente agregamos: <div> <button id="buttonDestiny" type="button">Ver código generado</button> </div> <div class="code" id="code"> </div> Un botón donde veremos los cambios y un espacio para mostrar el código generado (en caso que se pulse el botón) y si queremos ocultar el código nuevamente simplemente volvemos a pulsar el botón. Todo reside en el código js así que vayamos a ello: Esta vez iremos por partes: const radioButton = jQuery('input:radio[name=buttonStyle]') const checkDisable = jQuery('#customControlValidation10') const checkOutline = jQuery('#customControlValidation11') const buttonDestiny = jQuery('#buttonDestiny') let status = 0 La primer constante apunta a TODOS los radioButtons (los indentifica por el mismo name) La segunda y la tercer constante apuntan a los checkbox. Uno controlara el estilo disable y el otro el estilo outline.


La última constante apunta al botón y al final creamos una variable con un valor inicial 0. Esta variable controlará el estado del botón. Si se ha pulsado por primera vez (status === 0) se mostrara el código debajo del botón y el status pasara a 1. Si por el contrario se está mostrando el código (status === 1) el código se ocultara y el status pasara a 0. Eso se resuelve en la siguiente función: function buttonCode() { if (!status) { status = 1 showCode() } else { status = 0 vaciaCode() } } Cuando status tiene 0 es para el if (en JS) un valor FALSE, invertimos el resultado en el if con !. Actualizamos la variable y mostramos / ocultamos el código según la lógica explicada antes. Las funciones usadas ahí son: function vaciaCode() { jQuery('#code').empty() } function showCode() { jQuery('#code').empty().append('<blockoute>&lt;button class=\'btn ' + createClass() + '\'&gt;&lt;/button&gt;</blockoute>') } Las funciones son sencillas y podrían ser sustituidas por el código en lugar de invocar la función pero como lo usaremos en varios sitios del código es mejor tenerlo en una función para no repetir ese código continuamente. vaciaCode es sencillo. Vacía el div con el id code que está bajo el botón. showCode vacía el contenido y agrega un bloque de texto con el contenido del código. Para que HTML no interprete el botón como un botón real se codifican los símbolos de < (&lt;) y de >(&gt;) y se evita el conflicto de las comillas usando el \' que indica que es una comilla para imprimir y no el final de la cadena. Sobre símbolos ascii en HTML: https://ascii.cl/es/codigos-html.htm Para el uso de caracteres especiales en cadenas de texto mirar: https://developer.mozilla.org/es/docs/Web/JavaScript/Referencia/template_strings La funcion createClass() construye la clase usando los últimos valores elegidos function createClass() { let btn = jQuery('input:radio[name=buttonStyle]:checked').val() if (checkOutline.prop('checked')) { btn = 'outline-' + btn } return 'btn-' + btn

} Se recoge el value del radioButton elegido, se verifica si el checkbox de outline esta checkeado (de ser así se agrega el estilo) y se devuelve las clases en un string. Lo primero es recoger los valores Sigamos: buttonDestiny.on('click', buttonCode) radioButton.on('input', actualiza) // cuando cambien los radiobutton se actualizara el botón checkOutline.on('input', actualiza) // cuando cambie el checkbox de outline se actualizara el botón checkDisable.on('input', actualiza) // cuando se cambie el checkbox del disable se actualizara el botón

actualiza() Asociamos los checkBox y los radioButtons a la función actualiza, el botón de click a la función mostrada antes y lanzamos actualiza por primera vez. Veamos esa función:

function actualiza() { buttonDestiny.removeClass().addClass('btn ' + createClass()) buttonDestiny.attr('disabled', checkDisable.prop('checked')) if (status) { showCode() } else { vaciaCode() } } Lo primero es remover todas las clases existentes para evitar sobrescritura. Lo siguiente es agregar las clases según los últimos valores elegidos (a través de la función createClass()) En la siguiente linea asociamos el estado checked del checkbox al atributo disable del boton: checkDisable.prop('checked') Devolvera true o false si segun este chequeado o no y en: buttonDestiny.attr('disabled', checkDisable.prop('checked')) asociamos ese resultado al atributo HTML disabled Luego verificamos stauts (Si se esta mostrando el código o no) y actuamos en consecuencia.

VUEJS - CLASES CSS

Ha sido una larga explicación. Espero no haberme saltado nada. Ahora vamos a Vuejs. Enlaces:

Codigo: <form> <div class="custom-control custom-checkbox mb-3"> <input type="checkbox" class="custom-control-input" id="customControlValidation10" v-model="checkDisable"> <label class="custom-control-label" for="customControlValidation10">Disable</label> </div> <div class="custom-control custom-checkbox mb-3"> <input type="checkbox" class="custom-control-input" id="customControlValidation11" v-model="checkOutline"> <label class="custom-control-label" for="customControlValidation11">Outline</label> </div> </form> Vemos el cambio esperado. v-model asocia el resultado a una variable. En cuanto a los radioButton pasa lo mismo: <div class="custom-control custom-radio mb-3"> <input type="radio" class="custom-control-input" value="primary" id="customControlValidation2" name="buttonStyle" v-model="checkButton"> <label class="custom-control-label" for="customControlValidation2">Primary</label> </div> Con la diferencia de que usaremos la misma variable para TODOS los radiobuttons Y algunos cambios finales: <div> <button id="buttonDestiny" :class="styleButton" type="button" :disabled="checkDisable" @click="showCode=!showCode">Ver código generado</button> </div> <div class="code" > <blockquote v-if="showCode"> {{code}} </blockquote> </div> En button hay varios cambios. El tributo HTML class con los dos puntos antes :class asocia ese atributo a una variable, lo mismo para :disabled. y por ultimo el evento click ejecuta una acción. La acción es sobre una variable llamada showCode que es Boolean (TRUE o FALSE).

showCode =!showCode Indica: Guardar en showCode el valor opuesto (!) al que tiene en ese momento. Lo que significa que si es TRUE lo cambiara a FALSE y a la inversa. Por último el bloque de texto tiene un condicional (v-if) cuya consecuencia es que renderiza el elemento solo si el valor tiene verdadero. Hablemos de v-if. Es un condicional que funciona como cualquier otro if de cualquier lenguaje. Espera un resultado boolean (TRUE o FALSE) y se puede usar para devolver el valor una variable, método o una acción directa. v-if no renderiza el elemento. Eso quiere decir que el navegador NO tendrá ese elemento. Estoy es mas eficaz que un display: none porque esta acción le da el elemento al navegador, se renderiza y luego se oculta. Este punto se entiende mejor si se conoce el Virtual DOM. Un DOM controlado por Vue y que actúa sobre el navegador entregado solamente lo que necesita, cuando lo necesite. Y cuando suceden cambios no se renderiza todo de nuevo sino solo aquellos elementos que cambiaron.

El virtual DOM no es parte de estos artículos pero pueden saber más del tema aquí: (ingles) https://medium.com/js-dojo/whats-the-deal-with-vue-s-virtual-dom-3ed4fc0dbb20 v-if permite también un v-else: <div v-if="valor"> <span>Verdadero</span> <div> <div v-else> <span>Falso</span> </div> En este caso mostrara el mensaje Verdadero si valor es TRUE y si no mostrara el mensaje Falso. Lo veremos en mas detalle más adelante. Volviendo al código, dentro del bloque de texto mostramos el resultado de una variable (el código del botón) Miremos el JS: <script> new Vue({ el: '#app', data: { checkDisable: false, checkOutline: false, checkButton: 'primary', showCode: false }, computed: { code () { return '<button class="btn '+this.styleButton+'"></button>' }, styleButton() { let btn='btn btn-' if (this.checkOutline) { btn += 'outline-' } return btn + this.checkButton } } }) </script> Hemos definido 4 variables que definen estados iniciales: * checkDisable se usa en el checkbox de Disable * checkOutline se usa en el checkbox de Outline * checkButton se usa en los radioButton y se define como chequeado el que tiene el value primary (recordar que los value son los estilos de botones de bootstrap) * showCode para mostrar o no el codigo del boton. En este código usamos la palabra computed para definir computadas. Las computadas son variables complejas que se actualizan cuando cualquiera de los valores que usa cambia y siempre deben devolver un valor. Una variable normal en Vuejs se actualiza en todos los sitios donde se use pero las variables solo permiten guardar valores propios de Js. Si necesitamos algo más (como en este código) podemos recurrir a computadas. Se definen como un método (styleButton()) pero se usa como una variable (styleButton) por ahora diremos que definidas así lo único que no permiten es una asignación (ya veremos ese punto más adelante) pero por todo lo demás son para el sistema variables normales. Definimos dos computadas: styleButton() { let btn='btn btn-' if (this.checkOutline) { btn += 'outline-' } return btn + this.checkButton } Esta computada es bastante similar a la función createClass de jQuery. Primero guardamos un valor en btn que es lo básico de las clases de botones, en el if verificamos si checkOutline esta chequeado y si es así modificamos la variable agregando el estilo outline. Finalmente devolvemos el resultado agregando el value actual de los radioButton. La computada siguiente: code () { return '<button class="btn '+this.styleButton+'"></button>' } Sencillamente devuelve el código con el último valor actualizado. Este post ha sido un poco largo y tiene algunos conceptos nuevos. Si hay dudas preguntar. Tanto v-if como las computadas las seguiremos usando (Que es la mejor manera de entenderlas) en próximos códigos. Espero que sea útil. Index de clases:


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) ...