shorturl

Descubre cómo Acortar y Gestionar tus Enlaces con Tecnops URL Shortener

Descubre cómo Acortar y Gestionar tus Enlaces con Tecnops URL Shortener

En el vertiginoso mundo digital, compartir enlaces largos y complejos puede resultar engorroso. Es en este escenario donde Tecnops URL Shortener, una herramienta desarrollada con tecnologías tan conocidas y usadas como Node.js, Express, JavaScript, CSS y HTML, se presenta como la solución ideal para simplificar y optimizar tus enlaces, ofreciéndote una experiencia de navegación más eficiente.

Creando una URL Corta Personalizada

  1. Accede a la plataforma Tecnops URL Shortener visitando //tecnops.es:10000 en tu navegador favorito.
  2. En el campo URL, introduce la URL que deseas acortar.
  3. Asigna un alias único y fácil de recordar en el campo Alias. Por ejemplo, podrías utilizar mi-alias.
  4. Haz clic en el botón Acortar para generar tu URL corta personalizada con el alias.

crear url

Accediendo a tu URL Acortada

Para acceder a la URL acortada que has creado, sigue estos simples pasos:

  1. Utiliza el siguiente formato de URL: //tecnops.es:10000/code/mi-alias.
  2. Reemplaza mi-alias con el alias que asignaste al acortar la URL.

Al abrir esta URL en tu navegador, experimentarás una redirección automática hacia la URL original asociada con el alias.

Este proceso, respaldado por las tecnologías líderes de Node.js, Express, JavaScript, CSS y HTML, hace que compartir enlaces sea más sencillo y elegante, permitiéndote mantener el control sobre tus enlaces y mejorar la experiencia de tus usuarios.

Beneficios de Tecnops URL Shortener:

  • Personalización: Asigna alias significativos para tus enlaces y crea una identidad única.
  • Gestión Sencilla: Accede a una interfaz intuitiva para crear tus enlaces acortados.
  • Eficiencia: Comparte enlaces de manera efectiva, ya sea en redes sociales, correos electrónicos o mensajes.

Conclusión: Tecnops URL Shortener, construido con tecnologías avanzadas, redefine la forma en que interactuamos con los enlaces en línea. ¡Simplifica tu experiencia en la web hoy mismo y descubre el poder de tus enlaces acortados con Tecnops!

Explorando el Mundo de la Química con VueJS y Realidad Aumentada: Una Aplicación Interactiva de la Tabla Periódica

Explorando el Mundo de la Química con VueJS y Realidad Aumentada: Una Aplicación Interactiva de la Tabla Periódica

La química es una ciencia fascinante que nos permite comprender el mundo que nos rodea a nivel molecular y atómico. Una herramienta esencial para cualquier estudiante de química, profesional o simplemente un entusiasta de la ciencia es la tabla periódica de los elementos. ¿Pero qué pasaría si pudieras llevar la tabla periódica contigo en tu dispositivo móvil o explorarla en tu PC de una manera completamente nueva y emocionante? Ahí es donde entra en juego una asombrosa aplicación creada con Vue.js.

Vue.js y Model Viewer: La Magia detrás de la Aplicación

Vue.js es un framework de JavaScript que se ha convertido en una elección popular para el desarrollo de aplicaciones web interactivas y receptivas, que junto a la librería de Google model-viewer nos permite crear experiencias de realidad aumentada.

Gracias a estas dos tecnologías, la potencia que ofrecen y su facilidad de uso hacen que sea la elección perfecta para crear una aplicación que transforma la forma en que interactuamos con la tabla periódica.

Una Experiencia Interactiva

Esta aplicación web no es una tabla periódica estática, sino una experiencia interactiva que brinda una nueva perspectiva sobre los elementos químicos.

atom ar

Ya sea que estés en tu PC o en tu dispositivo móvil, puedes explorar cada elemento de la tabla periódica de una manera completamente nueva.

atom ar

Desde aquí podéis acceder a la aplicación web, ya sea en vuestro ordenador o en un dispositivo móvil para aprovechar las capacidades de realidad aumentada.

Realidad Aumentada: Más Allá de lo Tradicional

Una de las características más emocionantes de esta aplicación es su capacidad de realidad aumentada (RA). Al seleccionar un elemento, puedes activar la cámara de tu dispositivo para visualizarlo en el mundo real a través de la pantalla. ¿Te imaginas poder ver el elemento helio flotando en tu escritorio o el hierro en tu mano? Con esta aplicación, es posible.

Captura de Momentos Químicos

La aplicación también permite capturar estos momentos químicos únicos. Puedes tomar fotos de los elementos en su entorno de RA y guardarlos para futuras referencias o compartirlas con tus amigos y colegas. Esto hace que el aprendizaje y la exploración de la química sean más divertidos y memorables que nunca.

¿Cómo Funciona?

La aplicación está diseñada para ser intuitiva y fácil de usar.

Únicamente debes desplazarte por el panel inferior hasta encontrar el elemento deseado, pulsar sobre el y automáticamente se mostrara en la pantalla el elemento 3d animado y una amplia gama de información.

Cuando desees explorar un elemento en 3D con RA, simplemente selecciona la opción correspondiente y encuadra el elemento con la cámara de tu dispositivo. La aplicación superpondrá una representación 3D del elemento en el mundo real, lo que te permitirá verlo desde todos los ángulos.

Aprende y Explora

Ya sea que seas un estudiante que busca una forma más atractiva de estudiar la química, un profesional que necesita una herramienta útil en el trabajo o simplemente un amante de la ciencia que quiere explorar el mundo de la química de manera interactiva, esta aplicación tiene algo para todos.

En resumen, esta aplicación de la tabla periódica creada con Vue.js es una prueba más de cómo la tecnología puede hacer que el aprendizaje y la exploración sean emocionantes y accesibles para todos. ¡Descárgala hoy y descubre el fascinante mundo de los elementos químicos como nunca antes!

jsgiphy

Javascript y Giphy

Javascript y Giphy

Para este artículo he creado una aplicación usando Javascript, CSS y HTML que se conecta con uno de los mayores proveedores de GIF´s.

El proveedor de GIF´s en cuestión es GIPHY y nos ofrece miles de GIF´s animados que son usados por decenas de aplicaciones.

Javascript y giphy

Además nos ofrecen desde esta web acceso a sus servicios, con múltiples ejemplos y con SDK´s para diferentes plataformas.

Javascript y giphy

El funcionamiento de la aplicación es muy sencilla:

  1. Buscamos cualquier GIF´s como lo haríamos tanto en su web como en muchas de la aplicaciones que tienen este servicio.
  2. Pulsamos sobre el / los GIS´s que quieras descargar y solo faltaría pulsar el botón de descarga.

La ventaja de esta aplicación es que te permite descargarlos para incluirlos donde quieras o no puedas tener soporte para este servicio.

Javascript y giphy. Mi aplicación

Puedes acceder a la aplicación pulsando aquí, y espero que os guste y la disfrutéis.

svelte cover

SVELTE, usando el Store

Stores en Svelte

Bienvenidos a la última entrega del curso de Svelte, donde aprenderemos a usar el Store y completaremos el código de nuestro pequeño proyecto que hemos ido realizando en las entregas anteriores.

Para continuar con el curso puedes acceder al repositorio en Github y pulsando aquí puedes ver el proyecto en funcionamiento.

A continuación el índice del curso para recordar todo lo que hemos aprendido:

  1. Svelte, el nuevo framework del barrio
  2. Svelte, creando componentes
  3. Svelte, sistema de rutas
  4. Svelte, usando el Store

Que es un Store

Si en algún momento has usado otro framework de Javascript, el tema del Store te resultara familiar.

Bien, de una forma simple podemos definir que un Store es un sitio al cual se tiene acceso desde cualquier parte de nuestra aplicación pudiendo leer y modificar su contenido.

Para ello Svelte nos ofrece los siguientes métodos:

  • Writable: Modificar y leer el store.
  • Readable: Leer el store.

Ya sabemos como manipular el store en Svelte, así que ahora vamos a continuar con nuestro proyecto añadiendo esta característica.

Continuando el proyecto

Lo primero que haremos será crear un archivo de Javascript llamado store.js.

Como aclaración, no es necesario llamar al fichero store, pero por convención y la finalidad he decidido llamarlo así.

A continuación un ejemplo de uso:

store.js

import { writable } from 'svelte/store';

const dataStore = writable(0);

export default dataStore;

App.svelte

<script>
   ...
   import dataStore from './store';
</script>
<main>
   ...
   <div class="card">
      <h1>STORE</h1>
      <hr>
      Svelte store { $dataStore }
      <hr>
   </div>
</main>

El código anterior es el mínimo necesario para hacer uso del store, y es la continuación del proyecto que se encuentra en Github.

Esta es la forma más sencilla de tener acceso al store, y para ello usaremos el prefijo ‘$‘ seguido del nombre del store.

Ahora veremos otra forma de usarlo realizando una suscripción y desuscripción al store.

Subscribe / unsubscribe

Como habíamos indicando anteriormente, svelte nos ofrece diferentes formas de usar el store y la anterior es la más simple ya que solo necesitaremos añadir el prefijo ‘$‘.

Pero…, ¿ que ocurre si queremos hacer algo mas con el store ?

Para resolver esa cuestión, svelte nos ofrece otros dos métodos más:

  • subcribe: Para suscribirse a un store.
  • unsubcribe: Para desuscribirse de un store.

A continuación un ejemplo de uso:

<script>
  import { onDestroy } from 'svelte';
  import dataStore from '../store';
  let value = null;
  // Suscripción
  const unsubStore = dataStore.subscribe( data => value = data);
  // Desuscripción
  onDestroy(unsubStore);
</script>
<main class="component-store">
  <p>Valor del store dentro del componente {value}</p>
</main>
<style>
.component-store p {
  box-shadow: 0 0 9px 1px rgba(0,0,0,0.3);
  padding: 10px;
  width: 200px;
  margin: 0 auto;
}
</style>

Te preguntaras, ¿y cual es la diferencia que existe entre ambos?.

Usando el prefijo ‘$‘ la suscripción y desuscripción se realiza automáticamente y en la segunda opción la tenemos que realizar nosotros.

En este punto pensaras en usar siempre la primera opción ya que es más optima, pero en la segunda opción cuando nos suscribirnos usamos una función, y dentro podemos añadir más código.

Como podemos observar  la diferencia es importante ya que ofrece la posibilidad de realizar otras acciones pero, tenemos que recordar siempre realizar la desuscripción.

Ya sabemos como leer el contenido del store, ahora veremos como modificar el valor.

Modificando el store

Svelte nos ofrece diferentes formas de modificar el store:

  • update: Modificamos el store usando una función.
  • set: Modificamos el store usando un valor directo.

Como podemos observar las dos formas modifican el valor del store, la diferencia es que mientras en el update podemos realizar otras operaciones, el set realiza una modificación directa.

store.js

import { writable } from 'svelte/store';

export const dataStore = writable(0);

export const personalData = writable({
  data1: '',
  data2: '',
  data3: {
    data31: '',
    data32: {
      data321: '',
    },
  },
});

changeStore.svelte

<script>
  import { onDestroy } from 'svelte';
  import { dataStore, personalData } from '../store';
  let value = null;
  const unsubStore = dataStore.subscribe( data => value = data);
  onDestroy(unsubStore);
  const suma = () => dataStore.update(value => value + 1);
  const resta = () => dataStore.update(value => value - 1);
  const obj = () => personalData.update(data => {
    const nRand = Math.floor(Math.random() * 100);
    data.data1 = nRand;
    data.data2 = nRand;
    return data;
  });
</script>
<main class="component-store">
  <p>Valor del store dentro del componente {value}</p>
  <button on:click={suma}>Suma 1 Update</button>
  <button on:click={resta}>Resta 1 Update</button>
  <button on:click={obj}>Random Object Update</button>
</main>
<style>
.component-store p {
  box-shadow: 0 0 9px 1px rgba(0,0,0,0.3);
  padding: 10px;
  width: 200px;
  margin: 0 auto;
}
</style>

App.svelte

<script>
   ...
   // Usamos set para modificar el store
   const suma = value => dataStore.set(value + 1);
   const resta = value => dataStore.set(value - 1);
   
   import PsButton from './components/button.svelte';
   import Router from 'svelte-spa-router';
   import { link, push } from 'svelte-spa-router';
   import routes from './routes';
   import { dataStore, personalData } from './store';

   // Dentro del componente PsChangeStore usamos update para modificar el store
   import PsChangeStore from './components/changeStore.svelte';
</script>
<main>
   ...
   <div class="card">
      <h1>STORE</h1>
      <hr>
      Svelte store principal { $dataStore }
      <button on:click={() => suma($dataStore)}>Suma 1 Set</button>
      <button on:click={() => resta($dataStore)}>Resta 1 Set</button>
      <hr>
      <PsChangeStore />
      {JSON.stringify($personalData)}
      <hr>
   </div>
</main>

En el ejemplo anterior se ha usado tanto set como update para modificar el contenido del store.

Para finalizar el tutorial de Svelte, veremos como unificar el código del store mediante store personalizados y un ejemplo usando el método readable.

Personalizando el store

En el siguiente paso usaremos el store general, tanto para los datos como la lógica.

Para ello vamos a modificar el método suma que se encuentra tanto en el componente como en la página principal, y lo crearemos en el store.

store.js

import { writable } from 'svelte/store';

export const dataStore = writable(0);

export const personalData = writable({
  data1: '',
  data2: '',
  data3: {
    data31: '',
    data32: {
      data321: '',
    },
  },
});

const cStore = () => {
  return {
    suma: (newValue) => dataStore.update(value => value + 1),
    resta: (newValue) => dataStore.update(value => value - 1),
  }
}

export const customStore = cStore();

App.svelte

<script>
   ....
   import { dataStore, personalData, customStore } from './store';
   ....
</script>
<main>
   ...
   <div class="card">
      <h1>STORE</h1>
      <hr>
      Svelte store principal { $dataStore }
      <button on:click={() => suma($dataStore)}>Suma 1 Set</button>
      <button on:click={() => resta($dataStore)}>Resta 1 Set</button>
      <hr>
      <PsChangeStore />
      {JSON.stringify($personalData)}
      <hr>
      Método en el store (Store personalizado)
      <button on:click={() => customStore.suma($dataStore)}>Suma custom store Update</button>
      <button on:click={() => customStore.resta($dataStore)}>Resta custom store Update</button>
      <hr>
   </div>
</main>

Con el código anterior hemos comprobado que no es necesario declarar el método suma en cada uno de los sitios donde necesitemos usarlo, bastaría con crear la lógica en el store.

Ya solo nos quedaría el método readable, que tiene una sintaxis similar a writeable.

A continuación algunos ejemplos para usar readable.

store.js

import { readable, writable } from 'svelte/store';

export const dataStore = writable(0);

export const personalData = writable({
  data1: '',
  data2: '',
  data3: {
    data31: '',
    data32: {
      data321: '',
    },
  },
});

const cStore = () => {
  return {
    suma: (newValue) => dataStore.update(value => value + 1),
    resta: (newValue) => dataStore.update(value => value - 1),
  }
}

export const customStore = cStore();

const getDate = () => {
  const options = {
    year: 'numeric',
    month: 'numeric',
    day: 'numeric',
    hour: 'numeric',
    minute: 'numeric',
    second: 'numeric'
  };
  const dateTimeFormat = new Intl.DateTimeFormat('es-ES', options);
  return dateTimeFormat.format(new Date());
}
export const getTime = readable(null, set => {
  set(getDate());
});

export const getRealTime = readable(null, set => {
  setInterval(() => set(getDate()), 1000);
});

App.svelte

<script>
   ...
   import { dataStore, personalData, customStore, getTime, getRealTime } from './store';
   ...
</script>
<main>
   ...
   <div class="card">
      <h1>STORE</h1>
      <hr>
      Svelte store principal { $dataStore }
      <button on:click={() => suma($dataStore)}>Suma 1 Set</button>
      <button on:click={() => resta($dataStore)}>Resta 1 Set</button>
      <hr>
      <PsChangeStore />
      {JSON.stringify($personalData)}
      <hr>
      Método en el store (Store personalizado)
      <button on:click={() => customStore.suma($dataStore)}>Suma custom store Update</button>
      <button on:click={() => customStore.resta($dataStore)}>Resta custom store Update</button>
      <hr>
      Método <b>readable</b> para obtener la fecha y la hora: {$getTime}<br>
      Método <b>readable</b> para obtener la fecha y la hora en tiempo real: {$getRealTime}<br>
   </div>
</main>
chat realtime cover

Un chat creado con NodeJS, Socket.io, Express.js y Vanilla JS

Un chat creado con NodeJS, Socket.io, Express.js y Vanilla JS

Con esta nueva entrada quiero mostrar como he creado un chat usando NodeJS Socket.io. Para realizar el chat he usado la guía que ofrece la librería socket.io y su extensa documentación.

El código del proyecto

Para realizar este pequeño proyecto no he usado ningún framework basado en Javascript, ya que en principio iba a ser una prueba de concepto con pocas líneas para entender como funciona la librería socket.io.

Revisando la documentación oficial de socket.io se puede desarrollar un chat básico sin problema, pero como la idea era investigar, he probado algunas cosas añadiendo las siguientes funcionalidades:

  • Subida de ficheros
  • Chat privados
  • Información visual sobre los nuevos mensajes.
  • Cerrar y tener salas privadas

A continuación veremos el código correspondiente a la vista que he realizado usando bulma, el cual aconsejo echar un ojo ya que además de ligero, es bastante completo tanto en la documentación como en la cantidad de ejemplos de código.

index.html

<!DOCTYPE html>
<html lang="es">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>CHAT</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/css/bulma.min.css">
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.14.0/css/all.min.css" />
  <link rel="stylesheet" href="./style.css">
</head>
<body>
  <div class="notification hidden">
    <p></p>
  </div>
  <div class="container">
    <div class="columns">
      <div class="column is-one-quarter mt-6">
        <div class="card  has-background-black">
          <div class="card-content">
            <div class="media mb-0">
              <div class="media-content">
                <p class="title is-6 has-text-white">Usuarios conectados</p>
                <hr>
              </div>
            </div>
            <div class="content has-text-white" id="userConnected">
            </div>
          </div>
        </div>
      </div>
      <div class="column">
        <div class="tabs is-boxed pt-6 mb-0">
          <ul>
            <li id="tabGeneral" data-content="chatGeneral">
              <a class="active">
                <span class="has-text-white">General</span>
                <i class="fas fa-comment newMessage hide"></i>
              </a>
            </li>
          </ul>
        </div>
        <div class="columns">
          <div class="column containerChats pb-0">
            <div id="chatGeneral" class="chat textarea contentTab"></div>
            <div class="infoInput"></div>
          </div>
        </div>
        <div class="columns is-mobile">
          <div class="column is-one-quarter pr-0 pt-1">
            <input class="input has-text-white has-background-black" type="text" id="user" placeholder="Usuario">
          </div>
          <div class="column ml-1 mt-1 mr-3">
            <div class="columns is-mobile">
              <input class="input has-text-white has-background-black" type="text" id="message" placeholder="Mensaje" title="Subir fichero">
              <form>
                <button class="button is-info" id="btnUploadFile">
                  <i class="fas fa-file-upload"></i>
                  <input class="file-input" type="file" name="uploadFile" accept="image/x-png,image/gif,image/jpeg">
                </button>
              </form>
            </div>
          </div>
        </div>
        <div class="columns is-mobile hidden" id="containerProgress">
          <div class="column">
            <progress id="upload-progress-bar" max="100" value="0">0</progress>
            <div class="info-progress" />
          </div>
        </div>
      </div>
    </div>
  </div>
  <script src="/socket.io/socket.io.js"></script>
  <script src="./index.js"></script>
</body>
</html>

style.css

@import url('https://fonts.googleapis.com/css2?family=Montserrat:wght@300&display=swap');
html {
  overflow: hidden;
}
body {
  font-family: 'Montserrat', sans-serif;
  height: 100vh;
  width: 100vw;
  margin: 0;
  padding: 0;
  background: #354b52;
  color: #ffffff;
  overflow: hidden;
}
input {
  padding: 10px;
  font-size: 14px;
}
.chat {
  width: 100%;
  height: 250px;
  padding: 10px;
  font-size: 14px;
  overflow-y: auto;
  border: 1px solid #dddddd;
  background: #000000;
  color: #ffffff;
  resize: none !important;
}
.infoInput {
  font-size: 11px;
  position: relative;
  left: 10px;
  top: -5px;
}
.disabled {
  pointer-events: none;
  color: #aaaaaa !important;
}
.chatUser {
  font-weight: bold;
  margin-right: 5px;
}
::placeholder {
  color: #ffffff !important;
  opacity: .3 !important;
}
.chat::-webkit-scrollbar,
#userConnected::-webkit-scrollbar,
.tabs ul::-webkit-scrollbar {
  width: 10px;
}
.chat::-webkit-scrollbar-track,
#userConnected::-webkit-scrollbar-track,
.tabs ul::-webkit-scrollbar-track {
  border-radius: 10px;
}
.chat::-webkit-scrollbar-thumb,
#userConnected::-webkit-scrollbar-thumb,
.tabs ul::-webkit-scrollbar-thumb {
  background: #777777;       
  border-radius: 3px;
}
.chat::-webkit-scrollbar-thumb:hover,
#userConnected::-webkit-scrollbar-thumb:hover,
.tabs ul::-webkit-scrollbar-thumb:hover {
  background: #ffffff; 
  cursor: pointer;
}
.tabs ul {
  border-bottom: 0 !important;
  font-size: 14px;
  overflow: auto;
}
.tabs.is-boxed a:hover, .active {
  background-color: #000000;
  border: 1px solid #ffffff;
}
.containerChats .infoInput {
  text-align: right;
  left: 0;
  top: 0px;
  height: 15px;
}
.hide {
  display: none;
}
.card {
  border: 1px solid #dddddd;
  max-height: 343px;
  height: 343px;
}
.notification {
  transition: 300ms ease-in-out;
  position: fixed;
  bottom: -24px;
  width: 100%;
  opacity: 1;
  z-index: 10;
}
.notification.hidden {
  transition: 300ms ease-in-out;
  bottom: -100px;
  opacity: 0;
  z-index: -1;
}
#userConnected {
  overflow-y: auto;
  max-height: 225px;
  height: 225px;
}
#userConnected .chatUser {
  cursor: pointer !important;
}
@media (max-width: 768px) {
  .tabs  {
    padding-top: 0px !important;
  }
}
.newMessage {
  font-size: 11px;
  left: 5px;
  position: relative;
  color: #2196F3;
}
.closeChat {
  position: relative;
  left: 12px;
  top: -11px;
  color: #d46a10;
  font-size: 10px;
}
@media all and (max-width: 1023px) {
  .container {
    max-width: 960px;
    margin-left: 32px !important;
    margin-right: 32px !important;
  }
}
progress {
  width: 100%;
  position: relative;
  bottom: 25px;
  transition: 200ms ease-in-out;
  opacity: 1;
}
.info-progress {
  font-size: 11px;
  text-align: right;
  width: 100%;
  bottom: 30px;
  position: relative;
}
.hidden {
  opacity: 0;
  transition: 200ms ease-in-out;
}

Sobre el código anterior no hay mucho que añadir, una página simple pero suficiente para la prueba de concepto.

El último trozo de código que veremos, es el encargado de que la aplicación funcione en nuestro navegador.

index.js

// Definición de constantes y variables para la gestión del chat
let socket = null;
let user = null;
let message = null;
let usersPanel = null;
let notification = null;
let uploadFile = null;
let webrtc = null;
const CHAT_GENERAL = 'chatGeneral';
const LENGTH_MIN_USERNAME = 3;
const EMPTY = 0;
const LITERAL = {
  sameUser: 'No puedes enviarte un mensaje a ti mismo',
  minSizeUser: `El nombre del usuario debe tener <b>mínimo ${LENGTH_MIN_USERNAME} caracteres</b>`,
  uploadFile: 'Ha compartido un fichero',
  uploadSuccess: 'El fichero se ha subido y se esta compartiendo correctamente',
};

/** 
 * @description Cierra una sala de chat privada
 * @param {string} selectorID ID de la sala a cerrar
 */
const closeChat = selectorID => {
  document.querySelector(`.tabs ul li[data-content='${selectorID}']`).removeEventListener('click', selectedChat);
  document.querySelector(`.tabs ul li[data-content='${selectorID}']`).remove();
  document.querySelector(`.containerChats #${selectorID}`).remove();
  document.querySelector(`li[data-content='chatGeneral']`).click();
};

/** 
 * @description Creamos una sala privada
 * @param {object} data Información para crear la sala
 */
const chatTo = data => {
  const selectorID = data.idHTML || data.idOrigenHTML;
  const selectorIdUser = data.idUser ||data.idOrigen;
  const selectorUser = data.user || data.userOrigen;
  const existChat = document.querySelectorAll(`#${selectorID}`).length;
  if (existChat === EMPTY) {
    if (user.dataset.idhtml !== selectorID || data.idDestinoHTML !== data.idOrigenHTML) {
      document.querySelectorAll('.tabs ul li').forEach(item => {
        item.children[0].classList.remove('active');
      });
      document.querySelectorAll('.containerChats .chat').forEach(item => {
        item.classList.add('hide');
      });
      const chat = document.createElement('div');
      const li = document.createElement('li');
      const a = document.createElement('a');
      const span = document.createElement('span');
      const iNewChat = document.createElement('i');
      const iCloseChat = document.createElement('i');
      chat.setAttribute('id', selectorID);
      chat.classList.add('chat','textarea', 'contentTab');
      document.querySelector('.containerChats').prepend(chat);
      li.setAttribute('data-content', selectorID.toString());
      li.setAttribute('data-idUser', selectorIdUser.toString());
      a.classList.add('active');
      span.style.color = data.color;
      span.classList.add('has-text-white', 'chatUser');
      span.innerHTML = selectorUser;
      a.appendChild(span);
      iNewChat.classList.add('fas', 'fa-comment', 'newMessage', 'hide');
      a.appendChild(iNewChat);
      iCloseChat.classList.add('fas', 'fa-times-circle', 'closeChat');
      iCloseChat.onclick = () => { closeChat(selectorID.toString()); };
      a.appendChild(iCloseChat);
      li.appendChild(a);
      document.querySelector('.tabs ul').appendChild(li);
      document.querySelector(`.tabs ul li[data-content='${selectorID}']`).addEventListener('click', selectedChat);
    } else {
      notify(LITERAL.sameUser, 'danger');
    }
  }
};

/** 
 * @description Seleccionar una sala para charlar
 * @param {object} evt Evento implicito en la acción ejecutada
 */
const selectedChat = evt => {
  document.querySelectorAll('.containerChats .chat').forEach(item => {
    item.classList.add('hide');
  });
  const showChat = evt.currentTarget.dataset['content'];
  const chatElement = document.querySelector(`#${showChat}`);
  if (chatElement) {
    chatElement.classList.remove('hide');
  }
  document.querySelectorAll('.tabs a').forEach(item => item.classList.remove('active'));
  evt.currentTarget.children[0].classList.add('active');
  if (!Array.from(evt.currentTarget.children[0].querySelector('i').classList).includes('hide')) {
    evt.currentTarget.children[0].querySelector('i').classList.add('hide')
  }
};

/** 
 * @description Envia mensajes al chat general y a un usuario privado
 * @param {object} evt Evento implicito en la acción ejecutada
 * @param {boolean} status Indicamos el estado para saber que usuario esta escribiendo
 */
const sendMessage = (evt, status) => {
  if (evt.key === 'Enter') {
    if (user.value.length >= LENGTH_MIN_USERNAME && message.value.trim().length > EMPTY) {
      const parent = document.querySelectorAll('.tabs a.active')[0].parentElement;
      const content = parent.dataset.content;
      const currentlyChat = document.querySelector(`#${content}`);
      const isChatGeneral = content === CHAT_GENERAL;
      const infoMensaje = {
        idOrigen: user.dataset.iduser,
        idOrigenHTML: user.dataset.idhtml,
        idDestino: isChatGeneral ? CHAT_GENERAL : parent.dataset.iduser,
        idDestinoHTML: content,
        userOrigen: user.value,
        userDestino: isChatGeneral ? CHAT_GENERAL : parent.innerText,
        message: message.value
      };
      if (content !== CHAT_GENERAL) {
        const color = document.querySelector(`#userConnected .chatUser[data-idhtml='${user.dataset.idhtml}']`).style.color;
        const messageSend = document.querySelector(`.containerChats #${content}`);
        const div = document.createElement('div');
        const span = document.createElement('span');
        span.style.color = color;
        span.classList.add('chatUser');
        span.appendChild(document.createTextNode(`${infoMensaje.userOrigen}:`));
        div.appendChild(span);
        div.innerHTML += infoMensaje.message;
        messageSend.appendChild(div);
      }
      socket.emit(`message-chat-${isChatGeneral ? 'general' : 'private'}`, infoMensaje);
      message.value = '';
      currentlyChat.scrollTo(0, currentlyChat.scrollHeight);
    } 
    user.classList[user.value.length >= LENGTH_MIN_USERNAME ? 'remove' : 'add']('has-background-danger');
  } else {
    if (user.value.length >= LENGTH_MIN_USERNAME) {
      if (evt.key !== 'Tab') {
        socket.emit('write-client', { data: status ? user.value : ''});
      }
    }
  }
};

/** 
 * @description Muestra texto indicando quien esta escribiendo
 * @param {object} payload Información que pinta cuando pulsamos un tecla
 */
const clientBeenWriting = payload => {
  document.querySelector('.containerChats .infoInput').innerHTML = payload;
};

/** 
 * @description Muestra texto en el chat general
 * @param {object} data Información relativa al usuario
 */
const reciveMessage = data => {
  const {
    color,
    idDestinoHTML,
    idOrigenHTML,
    userOrigen,
    message 
  } = data;
  let activeChatID = document.querySelector(`#${idDestinoHTML}`) || document.querySelector(`#${idOrigenHTML}`);
  if (!activeChatID) {
    chatTo(data);
    activeChatID = document.querySelector(`#${idDestinoHTML}`) || document.querySelector(`#${idOrigenHTML}`);
  }
  if (idDestinoHTML !== idOrigenHTML) {
    const div = document.createElement('div');
    const span = document.createElement('span');
    span.style.color = color;
    span.classList.add('chatUser');
    span.appendChild(document.createTextNode(`${userOrigen}:`));
    div.appendChild(span);
    div.innerHTML += message;
    activeChatID.appendChild(div);
    activeChatID.scrollTo(0, activeChatID.scrollHeight);
  }
  const newMessage = document.querySelector('.tabs a.active').parentElement.dataset.content
  if (newMessage !== activeChatID.id && newMessage !== idDestinoHTML) {
    const destino = document.querySelector(`.tabs li[data-content='${idDestinoHTML}'] i`);
    const origen = document.querySelector(`.tabs li[data-content='${idOrigenHTML}'] i`);
    (destino || origen).classList.remove('hide');
  }
};

/** 
 * @description Añade y actualiza el panel lateral con el listado de los usuarios
 * @param {object} payload Información de cada uno de los usuarios
 */
const registerUser = payload => {
  Array.from(usersPanel.children).forEach(item => item.remove())
  payload.forEach(item => {
    const div = document.createElement('div');
    div.style.color = item.color;
    div.classList.add('chatUser');
    div.onclick = () => { chatTo(item); };
    div.setAttribute('data-idUser', item.idUser);
    div.setAttribute('data-idHTML', item.idHTML);
    div.appendChild(document.createTextNode(item.user));
    usersPanel.appendChild(div);
    if (user.value === item.user) {
      user.setAttribute('data-idHTML', item.idHTML);
      user.setAttribute('data-idUser', item.idUser);
    }
  });
};

/** 
 * @description Muestra mensaje de error al registrar el usuario
 * @param {object} payload Información de cada uno de los usuarios
 * @param {object} evt Evento implicito en la acción ejecutada
 */
const errorRegisteredUser = (payload, evt) => {
  const { error } = payload;
  notify(error, 'danger');
  evt.target.classList.remove('disabled');
  user.classList.add('has-background-danger');
};

/** 
 * @description Realiza la conexion al servidor
 * @param {object} evt Evento implicito en la acción ejecutada
 */
const connectedToServer = evt => {
  const target = evt.target;
  if (target.value.length >= LENGTH_MIN_USERNAME) {
    target.classList.add('disabled');
    user.classList.remove('has-background-danger');
    socket = io.connect();
    socket.on('recive-message', reciveMessage);
    socket.on('registered-user', registerUser);
    socket.on('error-registered-user', payload => errorRegisteredUser(payload, evt));
    socket.on('client-been-writing', clientBeenWriting);
    socket.on('upload-progress', data => uploadProgress(data));
    const idHTML = generateIDHtml();
    socket.emit('connected-to-server', { user: target.value, idHTML });
  } else {
    notify(LITERAL.minSizeUser, 'danger');
  }
};

/** 
 * @description Mostramos el progreso de la subida
 * @param {object} data Información sobre la subida del archivo
 */
const uploadProgress = data => {
  const { recived, total, who } = data;
  const porcent = Math.floor((recived * 100) / total);
  const currentlySize = (recived / 1024) / 1024; // MB
  const totalSize = (total / 1024) / 1024; // MB
  const progress = document.querySelector('#upload-progress-bar');
  const infoProgress = document.querySelector('.info-progress');
  progress.value = Math.floor(porcent);
  progress.innerHTML = porcent.toFixed(2);
  infoProgress.innerHTML = `${currentlySize.toFixed(2)} MB ${totalSize.toFixed(2)} MB ${porcent} %`;
};

/** 
 * @description Cargamos un fichero y se sube al servidor
 * @param {object} evt Evento implicito en la acción ejecutada
 */
const upload = async evt => {
  if (user.value.length >= LENGTH_MIN_USERNAME) {
    const uploadProgress = document.querySelector('#containerProgress');
    uploadProgress.classList.remove('hidden');
    const btnUploadFile = document.querySelector('#btnUploadFile');
    btnUploadFile.classList.add('is-loading');
    btnUploadFile.setAttribute('disabled', true);
    const files = evt.target.files;
    const data = new FormData();
    data.append('archivo', files[0]);
    socket.emit('upload-file', { idUser: user.dataset.iduser });
    const result = await (await fetch('/upload-file', {
      method: 'POST',
      body: data
    })).json();
    btnUploadFile.classList.remove('is-loading');
    btnUploadFile.removeAttribute('disabled');
    const { statusCode, path, statusMessage } = result;
    if (statusCode === 200) {
      message.value = `${LITERAL.uploadFile} <a href='${statusMessage}' target='_blank'>[${files[0].name}]</a>`;
      sendMessage(evt = {key: 'Enter'}, true);
      notify(LITERAL.uploadSuccess, 'success', 4000);
    } else {
      notify(statusMessage, 'danger', 4000);
    }
    setTimeout(() => {
      uploadProgress.classList.add('hidden');
    }, 2000);
  } else {
    document.querySelector('form').reset();
    notify(LITERAL.minSizeUser, 'danger');
  }
};

/** 
 * @description Inicialización del chat
 */
const load = () => {
  const tabGeneral = document.querySelector('#tabGeneral');
  user = document.querySelector('#user');
  message = document.querySelector('#message');
  usersPanel = document.querySelector('#userConnected');
  notification = document.querySelector('.notification');
  message.addEventListener('keydown', evt => sendMessage(evt, true));
  message.addEventListener('keyup',  evt => sendMessage(evt, false));
  user.addEventListener('blur', connectedToServer);
  tabGeneral.addEventListener('click', selectedChat);
  uploadFile = document.querySelector('input[type=file]');
  uploadFile.addEventListener('change', upload);
};

/** 
 * @description Muestra notificaciones en el chat
 * @param {string} msn Mensaje que se muestra en la notificación
 * @param {string} type Tipo de mensaje (danger, info, success, warning)
 * @param {number} timeout Duración de la notificación
 */
const notify = (msn = '', type = 'info', timeout = 2000) => {
  notification.children[0].innerHTML = msn;
  notification.classList.add(`is-${type}`);
  notification.classList.remove('hidden');
  setTimeout(() => {
    notification.classList.add('hidden');
    notification.classList.remove(`is-${type}`);
  }, timeout);
};

/** 
 * @description Generamos un ID para cada usuario
 */
const generateIDHtml = () => new Date().getTime().toString().split('').map(i => String.fromCharCode(parseInt(i) + 65)).join('');

/** 
 * @description Espera a que este la página completamente cargada
 */
document.addEventListener("DOMContentLoaded", load);

En un futuro me gustaría realizar esta misma aplicación usando React Vue, entre otras cosas para aprovechar la magia que ofrecen y poder añadir mas características como, llamadas de audio y video, compartir pantalla, guardar conversaciones, stickers, gifs, etc…

Para finalizar esta entrada, si estás interesado en usar el código, lo puedes obtener desde Github y ademas puedes probar el chat en la siguiente url: https://chat.tecnops.es/

svelte cover

SVELTE, Sistema de rutas

Sistema de rutas en Svelte

Como ocurre con otros frameworks, Svelte también tiene un sistema de rutas para la creación de SPA (Single Page Applications).

A continuación veremos algunas de las múltiples opciones que podemos usar para tener nuestro sistema de rutas:

  • Sapper:  Es un framework para crear aplicaciones web de alto rendimiento. No he tenido la oportunidad de usarlo pero según indican en su web, se han inspirado en Next.js de React para el desarrollo.
  • svelte-spa-router: Es una librería muy simple y que me recuerda bastante al sistema de rutas de Vue.

Para nuestros ejemplos usaremos esta última librería, ya que la sintaxis es muy simple y como dije antes me recuerda bastante al sistema de rutas que ofrece Vue, con el cual me siento bastante cómodo.

Lo primero que necesitamos es instalar la librería y para ello usaremos la linea de comandos.

npm install svelte-spa-router

Una vez terminada la instalación ya podemos empezar a realizar nuestras pruebas.

Empecemos a programar

Lo primero que haremos es descargar desde Github el código que hemos generado en los temas anteriores.

A continuación vamos a crear un menú para probar el sistema de rutas que nos ofrece la librería svelte-spa-router.

...
<div class="menu">
   <ul>
      <li><a>Opción 1</a></li>
      <li><a>Opción 2</a></li>
      <li><a>Opción 3</a></li>
      <li><a>Opción 4</a></li>
      <li><a>Opción 5</a></li>
   </ul>
</div>
...
<style>
...
.menu ul {
   list-style: none;
   margin: 0;
   padding: 0;
   text-align: left;
   display: flex;
}
.menu ul li {
   width: 25%;
   text-align: center;
   cursor: pointer;
   transition: 300ms ease-in-out;
   margin: 5px;
   padding: 5px;
}
.menu ul li:hover {
   background-color: #0e4efd;
   color: #ffffff;
   font-weight: bold;
   transition: 300ms ease-in-out;
}
...
</style>

Una vez creado el menú, necesitaremos crear las páginas sobre las que realizaremos la navegación y así comprobar que todo funciona correctamente.

Para ello crearemos un fichero por cada opción del menú mas otro fichero que se mostrara cuando intentemos acceder a una ruta que no exista.

El siguiente paso que debemos realizar, es la creación de un fichero donde se definirán todas las rutas de nuestra aplicación.

routes.js

import op1 from './pages/opcion1.svelte';
import op2 from './pages/opcion2.svelte';
import op3 from './pages/opcion3.svelte';
import op4 from './pages/opcion4.svelte';
import op5 from './pages/opcion5.svelte';
import error from './pages/error.svelte';

const routes = {
  '/op1': op1,
  '/op2': op2,
  '/op3': op3,
  '/op4': op4,
  '/op5': op5,
  '*': error
};

export default routes;

Como podemos observar el código para definir las rutas es bastante simple.

Lo primero que debemos hacer es importar cada una de las «vistas» sobre las que necesitamos realizar la navegación.

El siguiente paso sera definir las rutas usando un objeto donde indiquemos la ruta y la «vista» asociada a esa ruta.

Y para terminar exportaremos las rutas para usarlas en nuestra aplicación.

De esta forma ya tenemos nuestro sistema de rutas y la base para tener nuestra SPA (Single Page Aplication).

A continuación necesitamos importar la librería y nuestro fichero con las rutas para usarlo en nuestra aplicación.

App.svelte

<script>
...
import Router from 'svelte-spa-router';
import routes from './routes';
...
</script>
...
<div class="card">
   <h1>SVELTE ROUTER</h1>
   <div class="menu">
      <ul>
         <li><a>Opción 1</a></li>
         <li><a>Opción 2</a></li>
         <li><a>Opción 3</a></li>
         <li><a>Opción 4</a></li>
         <li><a>Opción 5</a></li>
      </ul>
    </div>
    <hr>
    <!-- Si la variable se llama igual que la propiedad,Svelte nos permite usar solo el nombre -->
    <Router {routes} />
    <!--
       Esto sería la otra forma
       <Router routes="{routes}" /> 
    -->
    <hr>
</div>
...

Todavía falta añadir algo de código para que sea funcional, pero en el siguiente vídeo podemos ver el resultado de integrar la librería en nuestro proyecto.

Para realizar el siguiente paso necesitamos añadir el código necesario para realizar la navegación desde la página, ya que ahora funciona cuando nosotros modificamos la URL de forma manual.

Si queremos realizar la navegación mediante Javascript, la librería svelte-spa-router nos ofrece link, push, pop y replace para realizar esa navegación.

La sintaxis para cada una es la siguiente:

  • link: Nos permite realizar la navegación dentro de un tag de HTML.
  • push(): Realiza la navegación a la ruta indicada como parámetro, añadiendo la ruta a la pila de historial del navegador.
  • pop(): El comportamiento sería el equivalente a el botón volver atras, eliminado la ruta de la pila de historial del navegador.
  • replace(): Realiza la navegación pero no añade la ruta a la pila de historial del navegador.

 

<script>
...
const goPage = (evt) => {
  const page = evt.detail.page;
  push(`/${page}`)
};
...
</script>
...
<div class="card">
   <h1>SVELTE ROUTER</h1>
   <div class="menu">
      <hr>
      <ul>
         <li><a href="/op1" use:link>Opción 1</a></li>
     <li><a href="/op2" use:link>Opción 2</a></li>
     <li><a href="/op3" use:link>Opción 3</a></li>
     <li><a href="/op4" use:link>Opción 4</a></li>
     <li><a href="/op5" use:link>Opción 5</a></li>
      </ul>
      <hr>
      <ul>
         <li>
       <PsButton text="Opción 1" type="default" on:click-evt={goPage} page="op1">
         <span slot="info-text" class="btn-special" />
       </PsButton>
     </li>
     <li>
       <PsButton text="Opción 2" type="default" on:click-evt={goPage} page="op2">
         <span slot="info-text" class="btn-special" />
       </PsButton>
     </li>
     <li>
       <PsButton text="Opción 3" type="default" on:click-evt={goPage} page="op3">
         <span slot="info-text" class="btn-special" />
       </PsButton>
     </li>
         <li>
       <PsButton text="Opción 4" type="default" on:click-evt={goPage} page="op4">
         <span slot="info-text" class="btn-special" />
       </PsButton>
     </li>
     <li>
       <PsButton text="Opción 5" type="default" on:click-evt={goPage} page="op5">
         <span slot="info-text" class="btn-special" />
       </PsButton>
     </li>
     </ul>
  </div>
</div>

Para terminar, si necesitas enviar parámetros entre las páginas, deberemos definir dentro del fichero de rutas (routes.js) los parámetros en cada ruta.

A continuación la sintaxis básica para definir las rutas con sus parámetros:

  • /path coincide con la ruta (/path) exactamente.
  • /path/:id coincide con la ruta (/path) y a continuación cualquier cadena.
  • /path/:id/:other? coincide con la ruta (/path) y a continuación cualquier cadena (:id) siendo opcional el segundo parámetro (:other?).
  • /path/* coincide con la ruta (/path) y a continuación cualquier cosa.

routes.js

import op1 from './pages/opcion1.svelte';
import op2 from './pages/opcion2.svelte';
import op3 from './pages/opcion3.svelte';
import op4 from './pages/opcion4.svelte';
import op5 from './pages/opcion5.svelte';
import error from './pages/error.svelte';

const routes = {
  '/op1/:data1?/:data2?': op1,
  '/op2': op2,
  '/op3': op3,
  '/op4': op4,
  '/op5': op5,
  '*': error
};

export default routes;

opcion1.svelte

<script>
  export let params = {};
</script>
<hr>
<h1>OPCION 1</h1>
<div>
  <p>Parámetros en la URL</p>
  <ul>
    <li>Primer parámetro: { params.data1 }</li>
    <li>Segundo parámetro: { params.data2 }</li>
  </ul>
</div>
<hr>

Con esto damos por terminado el sistema de rutas en Svelte, no obstante si queremos aprovechar las posibilidades que ofrece recomiendo revisar la documentación básicaavanzada que tiene esta gran librería.

svelte cover

SVELTE, Creando componentes

Creando componentes en Svelte

Como ocurre con otros frameworks, Svelte también nos permite crear nuestros propios componentes consiguiendo de esta forma reutilizar nuestro código.

Antes de comenzar a crear nuestro componente es importante entender cual es el ciclo de vida de un componente en Svelte.

El ciclo de vida de un componente

Se puede definir como los diferentes estados por los que pasa o puede pasar un componente.

En Svelte tenemos los siguientes métodos:

  • Creación
    • onMount(): Sucede cuando se monta el componente.
    • onDestroy(): Sucede cuando se destruye el componente.
  • Actualización
    • beforeUpdate(): Sucede antes de actualizar el DOM.
    • afterUpdate(): Sucede después de actualizar el DOM.
    • tick(): Sucede cuando todos los métodos del ciclo de vida estén finalizados. (Este método debe ir dentro de un método del ciclo de vida).

A continuación un pequeño ejemplo:

import { createEventDispatcher, beforeUpdate, adterUpdate, onMount, onDestroy, tick } from 'svelte';

// Ciclo de vida
console.log('1 - Ejecución de script');
  
onMount(async () => {
  console.log('2 - Mounted');
});

beforeUpdate(async () => {
  await tick();
  console.log('3 - Before Update');
});

afterUpdate(async () => {
  await tick();
  console.log('4 - After Update');
});

onDestroy(() => {
  console.log('5 - Destroy');
});

En la consola podemos ver el ciclo de vida de un componente en Svelte.

Empecemos con nuestro componente

Para definir un componente se usa la siguiente estructura dentro del fichero:

<script></script>
<template></template>
<style></style>

Debemos tener en cuenta, que en ningún caso los tres tags (<script>, <style> y <template>) son imprescindible ni dependientes unos de otros en el mismo fichero.

Una vez creado nuestro código, lo guardaremos con la extensión .svelte, y ya tendríamos nuestro componente creado.

Para quien haya usado Vuejs esta sintaxis le resultara muy conocida y fácil de asimilar, ya que Vuejs trabaja de la misma forma, un único archivo con la extensión .vue y los tres tags (<script>, <style> y <template>).

Como ocurre con Vuejs, en Svelte cada fichero .svelte puede ser un pequeño componente sencillo, uno más complejo, una página o una aplicación.

Ahora que sabemos como es la estructura básica de un componente en Svelte, empecemos a crear nuestro primer componente.

Aunque no es obligatorio, si que es recomendable crear una carpeta donde iremos guardando todos nuestros componentes y para nuestro ejemplo la llamaremos components.

Empezaremos con el clásico botón, así que crearemos un fichero llamado button.svelte.

button.svelte

<script>

</script>

<a href="." class="btn">button</a>

<style>
.btn {
  text-decoration: none;
  color: #fff;
  background-color: #26a69a;
  text-align: center;
  letter-spacing: .5px;
  transition: background-color .2s ease-out;
  cursor: pointer;
  font-size: 14px;
  outline: 0;
  border: none;
  border-radius: 2px;
  display: inline-block;
  height: 36px;
  line-height: 36px;
  padding: 0 16px;
  text-transform: uppercase;
  vertical-align: middle;
  -webkit-tap-highlight-color: transparent;
  margin: 10px;
}
</style>

El componente es muy sencillo y no tiene de momento nada de código Javascript.

Ahora veremos como integrarlo y el resultado.

App.svelte

<script>
   import PsButton from './components/button.svelte';
</script>
<div class="card">
   <h1>COMPONENTES</h1>
   <div class="components">
      <PsButton />
   </div>
</div>

Ahora que sabemos como crear un componente en Svelte, vamos a evolucionar el componente un poco mediante el uso las propiedades.

Añadiendo propiedades a nuestro componente

Para añadir propiedades a nuestro componente usaremos la palabra reservada export seguido de la definición de nuestra propiedad.

// Definición de una propiedad en el componente
export let type;

// Definición de una propiedad con un valor por defecto en el componente
export let type = 'default';

Con esta pequeña sentencia ya tendríamos las propiedades definidas y listas para usar.

button.svelte

<script>
  export let text = '';
  export let type = 'default';
  export let disabled = false;
  $: isDisbled = disabled ? 'disable' : '';
</script>

<button class="btn {type} {isDisbled}">{text}</button>

<style>
.btn {
  text-decoration: none;
  color: #fff;
  text-align: center;
  letter-spacing: .5px;
  transition: background-color .2s ease-out;
  cursor: pointer;
  font-size: 14px;
  outline: 0;
  border: none;
  border-radius: 2px;
  display: inline-block;
  height: 36px;
  line-height: 36px;
  padding: 0 16px;
  text-transform: uppercase;
  vertical-align: middle;
  -webkit-tap-highlight-color: transparent;
  margin: 10px;
}
.default {
  background-color: #26a69a;
}
.error {
  background-color: #e43d3d;
}
.warning {
  background-color: #c3bd43;
}
.info {
  background-color: #2599e4;
}
.disable {
  pointer-events: none;
  opacity: .4;
}
</style>

De nuevo veremos como integrar el componente pero ahora con las propiedades que hemos definido.

button.svelte

<script>
   import PsButton from './components/button.svelte';
</script>
<div class="card">
   <h1>COMPONENTES</h1>
   <div class="components">
      <PsButton text="Click" />
      <PsButton text="Click" type="error" />
      <PsButton text="Click" type="warning" />
      <PsButton text="Click" type="info" />
      <PsButton text="Click" type="default" />
      <PsButton text="Click" type="error" disabled={true} />
   </div>
</div>

Como hemos podido comprobar, tanto la definición de las propiedades como su uso han resultado ser bastante simples.

Ya tenemos nuestro componente creado y gracias a las propiedades podemos personalizarlo, realizando la comunicación desde fuera (padre) hacia dentro (hijo).

Pero…, ¿que ocurre si queremos comunicarnos desde dentro (hijo) hacia fuera (padre)?.

Bien, para realizar esta tarea debemos importar un método que es el encargado de emitir nuestro eventos personalizados (custom events).

Eventos para comunicarnos con el exterior

Importando el método createEventDispatcher ya podríamos emitir desde nuestro componente hacia fuera cualquier dato.

button.svelte

<script>
  import { createEventDispatcher } from 'svelte';
  export let text = '';
  export let type = 'default';
  export let disabled = false;
  $: isDisbled = disabled ? 'disable' : '';
  const dispatch = createEventDispatcher();
  const clickFn = () => {
    dispatch('click-evt', { msn: 'inside' });
  };
</script>

<button class="btn {type} {isDisbled}" on:click={clickFn}>{text}</button>

App.svelte

<script>
   const clickMethod = evt => {
      // Dentro del objeto evt, llega el valor detail con el payload
      console.log(evt, evt.detail);
   };
   import PsButton from './components/button.svelte';
</script>

<PsButton text="Evento" type="default" on:click-evt={clickMethod} />

Podemos ver que en el componente hemos creado un evento llamado click-evt y en el componente de nuestra página mediante on: hemos vinculado ese evento.

Como habíamos dicho al principio, en ese evento podemos emitir un valor (payload) que lo recibiríamos fuera.

Bien, ya hemos visto como podemos comunicarnos desde fuera hacia dentro mediante las propiedades, y desde dentro hacia fuera mediante los eventos.

Existe otra forma de comunicación desde fuera hacia dentro de un componente, y es mediante el uso de slots.

Usando slots en Svelte

Que bien un Slot, pero…. ¿que es un Slot?

Un Slot lo podemos definir como una zona dentro del componente donde podemos añadir nuestro propio contenido desde fuera del componente.

A continuación un pequeño ejemplo.

button.svelte

<script>
  import { createEventDispatcher } from 'svelte';
  export let text = '';
  export let type = 'default';
  export let disabled = false;
  $: isDisbled = disabled ? 'disable' : '';
  const dispatch = createEventDispatcher();
  const clickFn = () => {
    dispatch('click-evt', { msn: 'inside'});
  };
</script>

<button class="btn {type} {isDisbled}" on:click={clickFn}>
  <slot>{text}</slot>
  <slot name="info-text">Texto defecto</slot>
</button>

App.svelte

<PsButton>
   <span class="btn-special">Slot botón 0</span>
</PsButton>
<PsButton>
   <span class="btn-special">Slot botón 1</span>
   <span slot="info-text" class="btn-special">Texto info</span>
</PsButton>

Con el ejemplo anterior podemos comprobar que el uso de Slots en nuestros componentes puede ser una características bastante potente que nos permite personalizar y ampliar el propio componente.

Para finalizar, añadiré el código del componente y el repositorio a Github.

Button.svelte

<script>
  import { createEventDispatcher, beforeUpdate, afterUpdate, onMount, onDestroy, tick } from 'svelte';
  export let text = '';
  export let type = 'default';
  export let disabled = false;
  $: isDisbled = disabled ? 'disable' : '';
  const dispatch = createEventDispatcher();
  const clickFn = () => {
    dispatch('click-evt', { msn: 'inside'});
  };

  // Ciclo de vida
  console.log('1 - Ejecución de script');
  
  onMount(async () => {
    console.log('2 - Mounted');
  });

  beforeUpdate(async () => {
    await tick();
        console.log('3 - Before Update');
    });

    afterUpdate(async () => {
    await tick();
        console.log('4 - After Update');
    });

  onDestroy(() => {
        console.log('5 - Destroy');
  });
  
  let status = false;
  const enter = () => { 
    status = true;
  };
  const exit = () => {
    status = false;
  };
</script>

<button class="btn {type} {isDisbled}" on:click={clickFn} on:mouseenter={enter} on:mouseleave={exit}>
  <slot status={status}>{text}</slot>
  <slot name="info-text">Texto defecto</slot>
</button>

<style>
.btn {
  text-decoration: none;
  color: #fff;
  text-align: center;
  letter-spacing: .5px;
  transition: background-color .2s ease-out;
  cursor: pointer;
  font-size: 14px;
  outline: 0;
  border: none;
  border-radius: 2px;
  display: inline-block;
  height: 36px;
  line-height: 36px;
  padding: 0 16px;
  text-transform: uppercase;
  vertical-align: middle;
  -webkit-tap-highlight-color: transparent;
  margin: 10px;
}
.default {
  background-color: #26a69a;
}
.error {
  background-color: #e43d3d;
}
.warning {
  background-color: #c3bd43;
}
.info {
  background-color: #2599e4;
}
.disable {
  pointer-events: none;
  opacity: .4;
}
</style>
svelte cover

SVELTE, El framework nuevo del barrio

¿Que es Svelte?

Es un nuevo framework de Javascript que intenta hacerse hueco entre los 3 grandes frameworks mas usados y extendidos en la actualidad, React, Vue Angular.

A continuación vamos a listar algunas de las ventajas que ofrece Svelte:

  • Hace el trabajo en tiempo de compilación consiguiendo que sea mas eficiente.
  • No utiliza Virtual DOM, actualizando directamente el DOM.
  • Es sencillo de aprender y es muy ligero.
  • Es reactivo como los otros frameworks
  • Tiene también su propio gestor de estados.

Ahora que sabemos que es y las ventajas que nos ofrece vamos a comenzar a utilizarlo.

Iniciando un proyecto

Según indica la documentación oficial del framework usaremos degit para obtener la copia del repositorio ya que es mucho mas rápido que clonar el repositorio con git, y esto es debido a que no tienes que descargar todo el historial de git.

Para usar degit usaremos el siguiente comando:

npm install -g degit

Cuando todo este instalado, bajaremos el framework Svelte:

degit sveltejs/template myApp
cd myApp
npm i
npm run dev

Con los comandos anteriores ya tendríamos el proyecto descargado, la instalación de las dependencias y su ejecución en nuestro ordenador.

Comencemos con el código

Vamos a empezar por lo mas simple que se puede hacer en Svelte (y cualquier lenguaje, librería, framework, etc..), y es definiendo que son las variables y como se usan.

Podemos definir una variable como un elemento que se usa para almacenar un dato, para posteriormente acceder a el.

Svelte que usa como lenguaje base Javascript, la definición y uso de las variables es la misma.

Cuando necesitemos usar valores que puedan cambiar su contenido usaremos la palabra reservada let, y cuando necesitemos definir una constante usaremos la palabra reservada const.

Debemos recordar que const solo funciona para los tipos de datos primitivos, para los objetos no funcionaría salvo que cambie la referencia asociada al objeto.

let edad = 10;
const provincia = 'Madrid';
const obj = { provincia: 'Madrid' };

console.log(edad); // 10
console.log(provincia); // Madrid
console.log(obj); // { provincia: 'Madrid' }

edad = 20;
console.log(edad); // 20
provincia = 'Guadalajara'; // Error en la consola
obj.provincia = 'Guadalajara';
console.log(obj); // { provincia: 'Guadalajara' }

Ahora que sabemos como definir variables y constantes, vamos hacer un pequeño ejemplo.

<script>
   let num1 = 10;
   let num2 = 20;
</script>
<main>
   <h1>La suma de { num1 } y { num2 } es igual a { num1 + num2 }</h1>
</main>
<style>
   main {
      text-align: center;
      padding: 1em;
      max-width: 240px;
      margin: 0 auto;
   }
   h1 {
      color: #ff3e00;
      text-transform: uppercase;
      font-size: 4em;
      font-weight: 100;
   }
   @media (min-width: 640px) {
      main {
         max-width: none;
      }
   }
</style>

Como hemos podido observar el enlace de datos (data binding) se consigue usando los caracteres ‘{ }’.

Si has trabajado con otros framewoks de Javascript veras ciertas similitudes, de hecho si añadiésemos por ejemplo un elemento html podríamos cambiar su valor, usando la palabra reservada bind:.

<script>
    let num1 = 10;
    let num2 = 20;
</script>

<main>
    <h1>La suma de { num1 } y { num2 } es igual a { num1 + num2 }</h1>
    <div>
        Número 1<input type="range" min="0" max="100" bind:value={num1} />
    </div>
    <div>
        Número 2<input type="range" min="0" max="100" bind:value={num2} />
    </div>
</main>

Manejo de eventos en Svelte

Svelte ofrece un sistema para el manejo de eventos muy simple, añadiendo la palabra reservada on: seguido del evento.

Vamos a realizar unos ejemplos para comprobar su uso.

<script>
    let num1 = 10;
    let num2 = 20;
    let cont = 0;
    let aviso = '';
    const contador = () => {
        cont ++;
    };
    const over = () => {
        aviso = 'Dentro de la caja';
    };
    const out = () => {
        aviso = 'Fuera de la caja';
    };
</script>

<main>
    <div class="card">
        <h1>La suma de { num1 } y { num2 } es igual a { num1 + num2 }</h1>
        <div>
            Número 1<input type="range" min="0" max="100" bind:value={num1} />
        </div>
        <div>
            Número 2<input type="range" min="0" max="100" bind:value={num2} />
        </div>
    </div>
    <div class="card">
        <p>
            Contador { cont }
        </p>
        <button on:click={contador}>Suma</button>
        <p>
            El cursor esta... { aviso }
        </p>
        <div class="box" on:mouseover={over} on:mouseout={out}></div>
    </div>
</main>

<style>
    main {
        text-align: center;
        padding: 1em;
        max-width: 240px;
        margin: 0 auto;
    }

    h1 {
        color: #ff3e00;
        text-transform: uppercase;
        font-size: 4em;
        font-weight: 100;
    }

    @media (min-width: 640px) {
        main {
            max-width: none;
        }
    }

    .card {
                box-shadow: 0 0 10px 3px rgba(0,0,0,0.1);
        padding: 10px;
        margin: 15px;
    }

    .box {
                width: 50%;
                height: 100px;
                margin: 0 auto;
        background: #eeeeee;
        cursor: pointer;
    }
</style>

«Propiedades computadas» en Svelte

Si miramos la documentación de Svelte no veremos por ningún sitio propiedad computada, pero en uno de sus apartados nos indica declaración reactiva.

Viendo el funcionamiento de una declaración reactiva o mejor dicho variable reactiva, podemos observar que se comporta como una propiedad computada, de hecho se ejecuta antes de que el componente se actualice y si su contenido cambia de valor entonces esta se ejecutara.

Para declarar nuestra variable reactiva usaremos el carácter ‘$:’ seguido del nombre de nuestra variable, y a continuación le asignaremos el código que necesitemos ejecutar.

<script>
    let num1 = 10;
    let num2 = 20;
    let cont = 0;
    let aviso = '';
    let name = '';
    let age = 0;
    const contador = () => {
        cont ++;
    };
    const over = () => {
        aviso = 'Dentro de la caja';
    };
    const out = () => {
        aviso = 'Fuera de la caja';
    };
    $: saludo = `Tu nombre es ${name} y tienes ${age || 0} años`;
    $: acceso = age < 18 ? 'No autorizado' : 'Adelante';
    $: {
        if (age === 0) {
            saludo = `Bienvenido!!!, ha nacido ${name} y tiene ${age || 0} años`;
        }
    }
</script>

<main>
    <div class="card">
        <h1>La suma de { num1 } y { num2 } es igual a { num1 + num2 }</h1>
        <div>
            Número 1<input type="range" min="0" max="100" bind:value={num1} />
        </div>
        <div>
            Número 2<input type="range" min="0" max="100" bind:value={num2} />
        </div>
    </div>
    <div class="card">
        <p>
            Contador { cont }
        </p>
        <button on:click={contador}>Suma</button>
        <p>
            El cursor esta... { aviso }
        </p>
        <div class="box" on:mouseover={over} on:mouseout={out}></div>
    </div>
    <div class="card">
        <p>{ saludo } --- { acceso }</p>
        Nombre: <input type="text" bind:value={name} placeholder="Indica un nombre" />
        Edad: <input type="number" bind:value={age} placeholder="Indica una edad" min="0" />
    </div>
</main>

Ampliando nuestro HTML mediante bloques

Como ocurre con en frameworks(Vuejs, Angular, etc..) y lenguajes de programación (PHP, Java, etc..) , estos nos ofrecen la posibilidad de añadir código del propio lenguaje y framework dentro de nuestro HTML, obteniendo un HMTL bastante dinámico.

Pues en Svelte ocurre lo mismo, tiene ciertas sentencias que nos permite construir un HTML mas dinámico.

A continuación algunos ejemplos:

<script>
    let num1 = 10;
    let num2 = 20;
    let cont = 0;
    let aviso = '';
    let name = '';
    let age = 0;
    let aleatorio = 0;
    const contador = () => {
        cont ++;
    };
    const over = () => {
        aviso = 'Dentro de la caja';
    };
    const out = () => {
        aviso = 'Fuera de la caja';
    };
    const rand = () => {
        aleatorio = Math.floor((Math.random() * 100) + 1);
    };
    $: saludo = `Tu nombre es ${name} y tienes ${age || 0} años`;
    $: acceso = age < 18 ? 'No autorizado' : 'Adelante';
    $: {
        if (age === 0) {
            saludo = `Bienvenido!!!, ha nacido ${name} y tiene ${age || 0} años`;
        }
    }
</script>

<main>
    <div class="card">
        <h1>La suma de { num1 } y { num2 } es igual a { num1 + num2 }</h1>
        <div>
            Número 1<input type="range" min="0" max="100" bind:value={num1} />
        </div>
        <div>
            Número 2<input type="range" min="0" max="100" bind:value={num2} />
        </div>
    </div>
    <div class="card">
        <p>
            Contador { cont }
        </p>
        <button on:click={contador}>Suma</button>
        <p>
            El cursor esta... { aviso }
        </p>
        <div class="box" on:mouseover={over} on:mouseout={out}></div>
    </div>
    <div class="card">
        <p>{ saludo } --- { acceso }</p>
        Nombre: <input type="text" bind:value={name} placeholder="Indica un nombre" />
        Edad: <input type="number" bind:value={age} placeholder="Indica una edad" min="0" />
    </div>
    <div class="card">
        { #if aleatorio <= 50 }
            <h1>[{ aleatorio }] Menor que o igual 50</h1>
        { :else }
            <h1>[{ aleatorio }] Mayor que 50</h1>
        { /if }
        <button on:click={rand}>Aleatorio</button>
    </div>
</main>

En el ejemplo anterior hemos visto el uso de un bloque llamado if dentro del HTML, que se comporta como un if de Javascript o cualquier otro lenguaje de programación.

El siguiente ejemplo es mas interesante, ya que vamos a iterar gracias al bloque each dentro de nuestro HTML.

<script>
    let num1 = 10;
    let num2 = 20;
    let cont = 0;
    let aviso = '';
    let name = '';
    let age = 0;
    let aleatorio = 0;
    const provincias = ['Alava','Albacete','Alicante','Almería','Asturias','Avila','Badajoz','Barcelona','Burgos','Cáceres',
                                            'Cádiz','Cantabria','Castellón','Ciudad Real','Córdoba','La Coruña','Cuenca','Gerona','Granada','Guadalajara',
                                            'Guipúzcoa','Huelva','Huesca','Islas Baleares','Jaén','León','Lérida','Lugo','Madrid','Málaga','Murcia','Navarra',
                                            'Orense','Palencia','Las Palmas','Pontevedra','La Rioja','Salamanca','Segovia','Sevilla','Soria','Tarragona',
                                            'Santa Cruz de Tenerife','Teruel','Toledo','Valencia','Valladolid','Vizcaya','Zamora','Zaragoza'];
    const contador = () => {
        cont ++;
    };
    const over = () => {
        aviso = 'Dentro de la caja';
    };
    const out = () => {
        aviso = 'Fuera de la caja';
    };
    const rand = () => {
        aleatorio = Math.floor((Math.random() * 100) + 1);
    };
    $: saludo = `Tu nombre es ${name} y tienes ${age || 0} años`;
    $: acceso = age < 18 ? 'No autorizado' : 'Adelante';
    $: {
        if (age === 0) {
            saludo = `Bienvenido!!!, ha nacido ${name} y tiene ${age || 0} años`;
        }
    }
</script>

<main>
    <div class="card">
        <h1>La suma de { num1 } y { num2 } es igual a { num1 + num2 }</h1>
        <div>
            Número 1<input type="range" min="0" max="100" bind:value={num1} />
        </div>
        <div>
            Número 2<input type="range" min="0" max="100" bind:value={num2} />
        </div>
    </div>
    <div class="card">
        <p>
            Contador { cont }
        </p>
        <button on:click={contador}>Suma</button>
        <p>
            El cursor esta... { aviso }
        </p>
        <div class="box" on:mouseover={over} on:mouseout={out}></div>
    </div>
    <div class="card">
        <p>{ saludo } --- { acceso }</p>
        Nombre: <input type="text" bind:value={name} placeholder="Indica un nombre" />
        Edad: <input type="number" bind:value={age} placeholder="Indica una edad" min="0" />
    </div>
    <div class="card">
        { #if aleatorio <= 50 }
            <h1>[{ aleatorio }] Menor que o igual 50</h1>
        { :else }
            <h1>[{ aleatorio }] Mayor que 50</h1>
        { /if }
        <button on:click={rand}>Aleatorio</button>
    </div>
    <div class="card">
    Provincias
        <select name="provincia" id="provincia" placeholder="Selecciona tu provincia">
            {#each provincias as item, index}
                <option value={index}>{ item }</option>
            {/each}
        </select>
    </div>
</main>

Con un par de lineas de código y el uso del bloque each hemos conseguido hacer un poco de magia en nuestro HTML.

Ahora veremos un ejemplo un poquito mas completo usando variables reactivas, else dentro del bloque each y la manipulación de un Array.

<script>
    let num1 = 10;
    let num2 = 20;
    let cont = 0;
    let aviso = '';
    let name = '';
    let age = 0;
    let aleatorio = 0;
    const provincias = ['Alava','Albacete','Alicante','Almería','Asturias','Avila','Badajoz','Barcelona','Burgos','Cáceres',
                                            'Cádiz','Cantabria','Castellón','Ciudad Real','Córdoba','La Coruña','Cuenca','Gerona','Granada','Guadalajara',
                                            'Guipúzcoa','Huelva','Huesca','Islas Baleares','Jaén','León','Lérida','Lugo','Madrid','Málaga','Murcia','Navarra',
                                            'Orense','Palencia','Las Palmas','Pontevedra','La Rioja','Salamanca','Segovia','Sevilla','Soria','Tarragona',
                                            'Santa Cruz de Tenerife','Teruel','Toledo','Valencia','Valladolid','Vizcaya','Zamora','Zaragoza'];
    let tareas = [
        {
            id: 1,
            tarea: 'Xxxxxx',
            descripcion: 'zxc zc x zxclñkñkl klñlsdfñlskdfk sdf'
        },
        {
            id: 2,
            tarea: 'Yyyyyy',
            descripcion: 'zxc zc x zxclñkñkl klñlsdfñlskdfk sdf'
        },
        {
            id: 3,
            tarea: 'Aaaaaa',
            descripcion: 'zxc zc x zxclñkñkl klñlsdfñlskdfk sdf'
        },
        {
            id: 4,
            tarea: 'Bbbbbb',
            descripcion: 'zxc zc x zxclñkñkl klñlsdfñlskdfk sdf'
        },
        {
            id: 5,
            tarea: 'Cccccc',
            descripcion: 'zxc zc x zxclñkñkl klñlsdfñlskdfk sdf'
        },
    ];
    const removeTask = () => {
        /* 
            Clonamos cada vez que pulsamos para que cambie la referencia y la "variable reactive (propiedad computada)" se entere que ha modificado, 
            y pueda volver a "renderizar"	el contenido en el listado
        */
        tareas = [...tareas];
        tareas.shift();
    };
    const contador = () => {
        cont ++;
    };
    const over = () => {
        aviso = 'Dentro de la caja';
    };
    const out = () => {
        aviso = 'Fuera de la caja';
    };
    const rand = () => {
        aleatorio = Math.floor((Math.random() * 100) + 1);
    };
    $: saludo = `Tu nombre es ${name} y tienes ${age || 0} años`;
    $: acceso = age < 18 ? 'No autorizado' : 'Adelante';
    $: {
        if (age === 0) {
            saludo = `Bienvenido!!!, ha nacido ${name} y tiene ${age || 0} años`;
        }
    }
    $: taskList = tareas;
</script>

<main>
    <div class="card">
        <h1>La suma de { num1 } y { num2 } es igual a { num1 + num2 }</h1>
        <div>
            Número 1<input type="range" min="0" max="100" bind:value={num1} />
        </div>
        <div>
            Número 2<input type="range" min="0" max="100" bind:value={num2} />
        </div>
    </div>
    <div class="card">
        <p>
            Contador { cont }
        </p>
        <button on:click={contador}>Suma</button>
        <p>
            El cursor esta... { aviso }
        </p>
        <div class="box" on:mouseover={over} on:mouseout={out}></div>
    </div>
    <div class="card">
        <p>{ saludo } --- { acceso }</p>
        Nombre: <input type="text" bind:value={name} placeholder="Indica un nombre" />
        Edad: <input type="number" bind:value={age} placeholder="Indica una edad" min="0" />
    </div>
    <div class="card">
        { #if aleatorio <= 50 }
            <h1>[{ aleatorio }] Menor que o igual 50</h1>
        { :else }
            <h1>[{ aleatorio }] Mayor que 50</h1>
        { /if }
        <button on:click={rand}>Aleatorio</button>
    </div>
    <div class="card">
        Provincias
        <select name="provincia" id="provincia" placeholder="Selecciona tu provincia">
            {#each provincias as item, index}
                <option value={index}>{ item }</option>
            {/each}
        </select>
        <div class="task-container">
            Listado de tareas<button on:click={removeTask} title="Eliminar una tarea">-</button>
            {#each taskList as item}
                <div class="task">
                    <ul>
                        <li><b>ID:</b> { item.id }</li>
                        <li><b>Tarea:</b> { item.tarea }</li>
                        <li><b>Descripcion:</b> {item.descripcion }</li>
                    </ul>
                </div>
            {:else}
                <h1>No tienes ninguna tarea pendiente</h1>
            {/each}
        </div>
    </div>
</main>

Por último y para cerrar esta primera parte de Svelte, vamos a ver el uso del bloque await.

Este bloque nos permite dentro del propio HTML realizar operaciones asíncronas para tener HTML dinámico en base a una promesa y sus diferentes estados (pendiente, terminada o rechazada).

A continuación un pequeño ejemplo usando un API Rest de Star Wars:

<script>
    let num1 = 10;
    let num2 = 20;
    let cont = 0;
    let aviso = '';
    let name = '';
    let age = 0;
    let aleatorio = 0;
    const provincias = ['Alava','Albacete','Alicante','Almería','Asturias','Avila','Badajoz','Barcelona','Burgos','Cáceres',
                                            'Cádiz','Cantabria','Castellón','Ciudad Real','Córdoba','La Coruña','Cuenca','Gerona','Granada','Guadalajara',
                                            'Guipúzcoa','Huelva','Huesca','Islas Baleares','Jaén','León','Lérida','Lugo','Madrid','Málaga','Murcia','Navarra',
                                            'Orense','Palencia','Las Palmas','Pontevedra','La Rioja','Salamanca','Segovia','Sevilla','Soria','Tarragona',
                                            'Santa Cruz de Tenerife','Teruel','Toledo','Valencia','Valladolid','Vizcaya','Zamora','Zaragoza'];
    let tareas = [
        {
            id: 1,
            tarea: 'Xxxxxx',
            descripcion: 'zxc zc x zxclñkñkl klñlsdfñlskdfk sdf'
        },
        {
            id: 2,
            tarea: 'Yyyyyy',
            descripcion: 'zxc zc x zxclñkñkl klñlsdfñlskdfk sdf'
        },
        {
            id: 3,
            tarea: 'Aaaaaa',
            descripcion: 'zxc zc x zxclñkñkl klñlsdfñlskdfk sdf'
        },
        {
            id: 4,
            tarea: 'Bbbbbb',
            descripcion: 'zxc zc x zxclñkñkl klñlsdfñlskdfk sdf'
        },
        {
            id: 5,
            tarea: 'Cccccc',
            descripcion: 'zxc zc x zxclñkñkl klñlsdfñlskdfk sdf'
        },
    ];
    const removeTask = () => {
        /* 
            Clonamos cada vez que pulsamos para que cambie la referencia y la "variable reactive (propiedad computada)" se entere que ha modificado, 
            y pueda volver a "renderizar"	el contenido en el listado
        */
        tareas = [...tareas];
        tareas.shift();
    };
    const contador = () => {
        cont ++;
    };
    const over = () => {
        aviso = 'Dentro de la caja';
    };
    const out = () => {
        aviso = 'Fuera de la caja';
    };
    const rand = () => {
        aleatorio = Math.floor((Math.random() * 100) + 1);
    };
    $: saludo = `Tu nombre es ${name} y tienes ${age || 0} años`;
    $: acceso = age < 18 ? 'No autorizado' : 'Adelante';
    $: {
        if (age === 0) {
            saludo = `Bienvenido!!!, ha nacido ${name} y tiene ${age || 0} años`;
        }
    }
    $: taskList = tareas;
    const starWars = () => {
        return new Promise(async(resolve, reject) => {
            const url = 'https://swapi.dev/api/people/';
            const result = await fetch(url);
            const data = await result.json();
            setTimeout(() => {
                if (result.ok) {
                    resolve(data.results);
                } else {
                    reject('conectado con la API');
                }
            }, 3 * 1000); // Simulando una pequeña pausa
        })
    }
</script>

<main>
    <div class="card">
        <h1>La suma de { num1 } y { num2 } es igual a { num1 + num2 }</h1>
        <div>
            Número 1<input type="range" min="0" max="100" bind:value={num1} />
        </div>
        <div>
            Número 2<input type="range" min="0" max="100" bind:value={num2} />
        </div>
    </div>
    <div class="card">
        <p>
            Contador { cont }
        </p>
        <button on:click={contador}>Suma</button>
        <p>
            El cursor esta... { aviso }
        </p>
        <div class="box" on:mouseover={over} on:mouseout={out}></div>
    </div>
    <div class="card">
        <p>{ saludo } --- { acceso }</p>
        Nombre: <input type="text" bind:value={name} placeholder="Indica un nombre" />
        Edad: <input type="number" bind:value={age} placeholder="Indica una edad" min="0" />
    </div>
    <div class="card">
        { #if aleatorio <= 50 }
            <h1>[{ aleatorio }] Menor que o igual 50</h1>
        { :else }
            <h1>[{ aleatorio }] Mayor que 50</h1>
        { /if }
        <button on:click={rand}>Aleatorio</button>
    </div>
    <div class="card">
        Provincias
        <select name="provincia" id="provincia" placeholder="Selecciona tu provincia">
            {#each provincias as item, index}
                <option value={index}>{ item }</option>
            {/each}
        </select>
        <div class="task-container">
            Listado de tareas<button on:click={removeTask} title="Eliminar una tarea">-</button>
            {#each taskList as item}
                <div class="task">
                    <ul>
                        <li><b>ID:</b> { item.id }</li>
                        <li><b>Tarea:</b> { item.tarea }</li>
                        <li><b>Descripcion:</b> {item.descripcion }</li>
                    </ul>
                </div>
            {:else}
                <h1>No tienes ninguna tarea pendiente</h1>
            {/each}
        </div>
    </div>
    <div class="card">
        <h1>Conectando a la API de Star Wars</h1>
        <div>
            { #await starWars() }
         		<div class="lds-ripple"><div></div><div></div></div>
         	{ :then result }
         		{#each result as item}
                <div class="task">
                    <ul>
                        <li><b>Cumpleaños:</b> { item.birth_year }</li>
                        <li><b>Color de ojos:</b> { item.eye_color }</li>
                        <li><b>Total peliculas:</b> { item.films.length }</li>
                        <li><b>Sexo:</b> { item.gender }</li>
                        <li><b>Color de pelo:</b> { item.hair_color }</li>
                        <li><b>Altura:</b> { item.height } cm</li>
                        <li><b>Peso:</b> { item.mass } kg</li>
                        <li><b>Nombre:</b> { item.name }</li>
                        <li><b>Color de piel:</b> { item.skin_color }</li>
                    </ul>
                </div>
            {:else}
                <h1>No existe ningún registro mas</h1>
            {/each}
         	{ :catch err }
         		<div>{ err }</div>
         	{ /await }
        </div>
    </div>
</main>

Bien, con esta primera entrada hemos tocado la base de Svelte para hacernos una idea de lo fácil y potente que puede llegar a ser.

No obstante si queréis ampliar y mejorar el conocimiento sobre este nuevo framework podéis acceder a su documentación y al repositorio del proyecto en git.

Nueva aplicación de datos abiertos usando JS Vanilla

Ministerio de Salud y AMPS

Buscando por internet nuevas fuentes de datos abiertos, encontré esta página.

La página en cuestión es la sede electrónica del Ministerio de Salud y la Agencia de medicamentos y productos sanitarios.

Bien, pues resulta que desde esta web podemos acceder a dos ficheros PDF con toda la documentación necesaria para realizar consultas a sus API´s:

Básicamente REEC es el registro español de estudios clínicos y CIMA es para la consulta de medicamentos y presentaciones de la Agencia Española de Medicamentos y Productos Sanitarios.

Como tenia bastante curiosidad por saber la cantidad de información disponible, me puse manos a la obra y días después termine esta pequeña aplicación usando JS Vanilla, Web Components nativos y CSS Grid, y ahora puedes consultar cualquier medicamento y/o presentación.

Desde aquí puedes ver la aplicación funcionando y desde aquí puedes acceder al repositorio de Github.

Aplicación de Harry Potter con Web Components usando Javascript nativo

En esta ocasión he creado una pequeña aplicación sin usar ningún framework y/o librería, mediante web components nativos.

He aprovechado la potencia que ofrece Javascript para hacer todo el desarrollo, creando web components nativos y toda la gestión sin ningún framework Javascript o css.

Posiblemente el código se pueda mejorar, pero he intentado seguir las pautas y las guías que están en la web de developer mozilla  para la creación de web components.

Puedes obtener todo el código fuente desde el repositorio en github y también puedes probar la aplicación en el siguiente enlace.