Skip to content

Chat con Firebase 9

⭐ Videos Premium ⭐

Esta sección es parte del curso en Udemy. Si quieres acceder a ella, puedes comprar el curso en Udemy: Bootstrap 5 by bluuweb.

HTML

html
<!DOCTYPE html>
<html lang="es">
  <head>
    <meta charset="UTF-8" />
    <meta
      http-equiv="X-UA-Compatible"
      content="IE=edge"
    />
    <meta
      name="viewport"
      content="width=device-width, initial-scale=1.0"
    />
    <title>Chat</title>

    <link
      href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha3/dist/css/bootstrap.min.css"
      rel="stylesheet"
      integrity="sha384-KK94CHFLLe+nY2dmCWGMq91rCGa5gtU4mk92HdvYe+M/SXH301p5ILy+dN9+nJOZ"
      crossorigin="anonymous"
    />

    <link
      rel="stylesheet"
      href="style.css"
    />
  </head>
  <body>
    <nav class="navbar navbar-dark bg-dark">
      <div class="container">
        <a
          href="#"
          class="navbar-brand"
          >Chat</a
        >

        <div>
          <button
            class="btn btn-primary d-none"
            id="acceder"
          >
            Acceder
          </button>
          <button
            class="btn btn-danger d-none"
            id="salir"
          >
            Salir
          </button>
        </div>
      </div>
    </nav>

    <p
      class="container my-5 lead text-center d-none"
      id="mensajeLogOut"
    >
      Aún no estás conectado, debes iniciar sesión para ver los chats
    </p>

    <main
      class="container py-3 container-chat d-none"
      id="chat"
    >
      <template id="templateChat">
        <div class="text-start">
          <span class="badge">Text</span>
        </div>
        <!-- <div class="text-end">
          <span class="badge bg-success">Text</span>
        </div> -->
      </template>
    </main>

    <form
      class="container fixed-bottom mb-2 d-none"
      id="formulario"
    >
      <input
        type="text"
        class="form-control"
        placeholder="..."
        name="msg"
      />
      <button
        class="btn btn-dark w-100 mt-2"
        type="submit"
        id="btnEnviar"
      >
        Enviar
      </button>
    </form>

    <script
      src="app.js"
      type="module"
    ></script>
  </body>
</html>
css
.container-chat {
  overflow-y: scroll;
  height: calc(100vh - 160px);
}

JS

js
const acceder = document.querySelector("#acceder");
const salir = document.querySelector("#salir");
const formulario = document.querySelector("#formulario");
const templateChat = document.querySelector("#templateChat");
const chat = document.querySelector("#chat");
const btnEnviar = document.querySelector("#btnEnviar");
const mensajeLogOut = document.querySelector("#mensajeLogOut");

import { initializeApp } from "https://www.gstatic.com/firebasejs/9.19.1/firebase-app.js";
import {
  getAuth,
  onAuthStateChanged,
  GoogleAuthProvider,
  signInWithPopup,
  signOut,
} from "https://www.gstatic.com/firebasejs/9.19.1/firebase-auth.js";
import {
  getFirestore,
  collection,
  addDoc,
  query,
  onSnapshot,
  orderBy,
} from "https://www.gstatic.com/firebasejs/9.19.1/firebase-firestore.js";

const firebaseConfig = {
  apiKey: "AIzaSyDsZh66q4NqcgU2ohElmKGwt4Hn2Lo1ZHw",
  authDomain: "test-65695.firebaseapp.com",
  projectId: "test-65695",
  storageBucket: "test-65695.appspot.com",
  messagingSenderId: "638364455400",
  appId: "1:638364455400:web:524de0f880c1bc4e55145d",
};

const app = initializeApp(firebaseConfig);
const auth = getAuth(app);
const db = getFirestore(app);

const mostrarElemento = (elemento) => {
  elemento.classList.remove("d-none");
};

const ocultarElemento = (elemento) => {
  elemento.classList.add("d-none");
};

let unsubscribe; // Variable global para la función de cancelación de la suscripción.
onAuthStateChanged(auth, (user) => {
  if (user) {
    mostrarElemento(chat);
    mostrarElemento(formulario);
    mostrarElemento(salir);
    ocultarElemento(acceder);
    ocultarElemento(mensajeLogOut);

    console.log("usuario logueado", user);
    chat.innerHTML = "";
    const q = query(collection(db, "chat"), orderBy("fecha"));
    unsubscribe = onSnapshot(q, (snapshot) => {
      snapshot.docChanges().forEach((change) => {
        if (change.type === "added") {
          console.log("New chat: ", change.doc.data());
          pintarChat(change.doc.data());
        }
        chat.scrollTop = chat.scrollHeight;
      });
    });
  } else {
    ocultarElemento(chat);
    ocultarElemento(formulario);
    ocultarElemento(salir);
    mostrarElemento(acceder);
    mostrarElemento(mensajeLogOut);

    // Si el usuario cierra la sesión, llama a la función de cancelación de la suscripción.
    if (unsubscribe) {
      unsubscribe();
    }
  }
});

acceder.addEventListener("click", async () => {
  try {
    const provider = new GoogleAuthProvider();
    await signInWithPopup(auth, provider);
    console.log("acceso correcto");
  } catch (error) {
    console.log(error);
  }
});

salir.addEventListener("click", async () => {
  try {
    await signOut(auth);
    console.log("cerrando sesion");
  } catch (error) {
    console.log(error);
  }
});

formulario.addEventListener("submit", async (e) => {
  e.preventDefault();
  //   console.log(formulario.msg.value);

  if (!auth.currentUser) return console.log("no hay usuario logueado");
  if (!formulario.msg.value.trim()) {
    formulario.msg.focus();
    formulario.msg.value = "";
    return console.log("no hay mensaje");
  }

  try {
    btnEnviar.disabled = true;
    const docRef = await addDoc(collection(db, "chat"), {
      msg: formulario.msg.value.trim(),
      fecha: Date.now(),
      uid: auth.currentUser.uid,
    });
    console.log("Document written with ID: ", docRef.id);
    formulario.msg.value = "";
  } catch (error) {
    console.log(error);
  } finally {
    btnEnviar.disabled = false;
  }
});

const pintarChat = ({ msg, uid }) => {
  const clone = templateChat.content.cloneNode(true);
  if (uid === auth.currentUser.uid) {
    clone.querySelector("div").classList.add("text-end");
    clone.querySelector("span").classList.add("bg-success");
  } else {
    clone.querySelector("div").classList.add("text-start");
    clone.querySelector("span").classList.add("bg-secondary");
  }
  clone.querySelector("span").textContent = msg;
  chat.append(clone);
};

Se utiliza el método content para clonar el elemento templateChat. El método content retorna el contenido del elemento templateChat como un fragmento de documento.

Se utiliza el método append para agregar el elemento clonado al elemento chat. El método append es más eficiente y puede agregar múltiples nodos al mismo tiempo.

DocumentFragment

Un fragmento de documento (en inglés DocumentFragment) es un objeto del DOM que representa un fragmento de un documento. A diferencia de los nodos regulares del DOM, un fragmento de documento no está conectado al árbol DOM principal del documento.

Los fragmentos de documento se utilizan a menudo para realizar operaciones de manipulación del DOM de manera más eficiente. Cuando se realizan múltiples manipulaciones en el DOM, cada vez que se realiza una operación se vuelve a calcular el diseño del documento, lo que puede ser costoso en términos de rendimiento. En lugar de manipular el DOM directamente, se pueden realizar las operaciones en un fragmento de documento y luego agregar el fragmento completo al árbol DOM una vez que todas las operaciones hayan finalizado.

De esta manera, el uso de fragmentos de documento puede reducir el número de cálculos de diseño necesarios y mejorar el rendimiento de la aplicación. Además, el uso de fragmentos de documento también puede hacer que el código sea más legible y fácil de mantener.

Reglas

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /chat/{doc} {
      allow read: if request.auth != null;
      allow write: if request.auth != null;
    }
  }
}