oscarviedma

Funcionalidad JS Formulario Listado Musical

Apr 24th, 2026 (edited)
315
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
HTML 5.00 KB | None | 0 0
  1. <script>
  2. // ── CONFIGURACIÓN ────────────────────────────────────────────
  3. // Reemplaza con tu URL de Apps Script
  4. const WS_URL = "URL_APPS_SCRIPT";
  5.  
  6. let wsSongs = [];
  7. let wsVoted = new Set(JSON.parse(localStorage.getItem('ws_voted') || '[]'));
  8.  
  9. function wsSaveVoted() {
  10.   localStorage.setItem('ws_voted', JSON.stringify([...wsVoted]));
  11. }
  12.  
  13. async function wsGet(params) {
  14.   const url = WS_URL + '?' + new URLSearchParams(params).toString();
  15.   const res  = await fetch(url);
  16.   return res.json();
  17. }
  18.  
  19. async function wsLoad() {
  20.   try {
  21.     const data = await wsGet({ action: 'list' });
  22.     if (data.ok) {
  23.       wsSongs = data.songs;
  24.       wsRender();
  25.     } else {
  26.       wsShowError();
  27.     }
  28.   } catch(e) {
  29.     wsShowError();
  30.   }
  31. }
  32.  
  33. function wsRender() {
  34.   const container = document.getElementById('ws-songs-container');
  35.   const countEl   = document.getElementById('ws-count');
  36.   if (!container) return;
  37.  
  38.   if (!wsSongs.length) {
  39.     countEl && (countEl.textContent = '');
  40.     container.innerHTML = `
  41.       <div class="ws-empty">
  42.         <i class="fas fa-music ws-empty-icon"></i>
  43.         Aún no hay canciones.<br>¡Sé el primero en sugerir una!
  44.       </div>`;
  45.     return;
  46.   }
  47.  
  48.   countEl && (countEl.textContent = wsSongs.length + (wsSongs.length === 1 ? ' canción' : ' canciones'));
  49.  
  50.   const sorted = [...wsSongs].sort((a, b) => b.votes - a.votes);
  51.  
  52.   container.innerHTML = `<div class="ws-songs">` +
  53.     sorted.map((s, i) => {
  54.       const voted = wsVoted.has(s.id.toString());
  55.       const isTop = i === 0 && sorted.length > 1;
  56.       return `
  57.         <div class="ws-song ${isTop ? 'top-song' : ''}" style="animation-delay:${i*0.05}s">
  58.           <div class="ws-rank ${isTop ? 'top' : ''}">${i + 1}</div>
  59.           <div class="ws-song-info">
  60.             <div class="ws-song-name">
  61.               ${wsEsc(s.song)}
  62.               ${isTop ? '<span class="ws-top-badge">favorita</span>' : ''}
  63.             </div>
  64.             <div class="ws-song-artist">${wsEsc(s.artist)}</div>
  65.           </div>
  66.           <button class="ws-vote ${voted ? 'voted' : ''}" onclick="wsVote('${s.id}')">
  67.             <i class="${voted ? 'fas' : 'far'} fa-heart ws-vote-icon"></i>
  68.             <span class="ws-vote-n">${s.votes}</span>
  69.           </button>
  70.         </div>`;
  71.     }).join('') +
  72.   `</div>`;
  73. }
  74.  
  75. async function wsSongAdd() {
  76.   const songEl   = document.getElementById('ws-inp-song');
  77.   const artistEl = document.getElementById('ws-inp-artist');
  78.   const btn      = document.getElementById('ws-btn-add');
  79.  
  80.   const song   = songEl.value.trim();
  81.   const artist = artistEl.value.trim();
  82.  
  83.   if (!song || !artist) {
  84.     wsToast('Completa el nombre de la canción y el artista', true);
  85.     return;
  86.   }
  87.  
  88.   btn.disabled    = true;
  89.   btn.textContent = 'Agregando…';
  90.  
  91.   try {
  92.     const data = await wsGet({ action: 'add', song, artist });
  93.  
  94.     if (data.ok) {
  95.       wsSongs.push({ id: data.id, song, artist, votes: 1 });
  96.       wsVoted.add(data.id.toString());
  97.       wsSaveVoted();
  98.       wsRender();
  99.       songEl.value   = '';
  100.       artistEl.value = '';
  101.       wsToast('¡Canción agregada!');
  102.     } else if (data.error === 'duplicate') {
  103.       wsToast('Esa canción ya está en la lista 😊', true);
  104.     } else {
  105.       wsToast('Ocurrió un error, intenta de nuevo', true);
  106.     }
  107.   } catch(e) {
  108.     wsToast('Sin conexión, intenta más tarde', true);
  109.   } finally {
  110.     btn.disabled  = false;
  111.     btn.innerHTML = '<i class="fas fa-music"></i> Agregar a la lista';
  112.   }
  113. }
  114.  
  115. async function wsVote(id) {
  116.   const idStr = id.toString();
  117.   if (wsVoted.has(idStr)) {
  118.     wsToast('Ya votaste por esta canción 💕');
  119.     return;
  120.   }
  121.  
  122.   const song = wsSongs.find(s => s.id.toString() === idStr);
  123.   if (song) song.votes++;
  124.   wsVoted.add(idStr);
  125.   wsSaveVoted();
  126.   wsRender();
  127.  
  128.   try {
  129.     await wsGet({ action: 'vote', id });
  130.   } catch(e) {
  131.     if (song) song.votes--;
  132.     wsVoted.delete(idStr);
  133.     wsSaveVoted();
  134.     wsRender();
  135.     wsToast('No se pudo registrar el voto', true);
  136.   }
  137. }
  138.  
  139. function wsEsc(str) {
  140.   return str.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;');
  141. }
  142. function wsShowError() {
  143.   const c = document.getElementById('ws-songs-container');
  144.   if (c) c.innerHTML = `<div class="ws-empty">No se pudo cargar la lista.<br>Recarga la página e intenta de nuevo.</div>`;
  145. }
  146.  
  147. let wsToastTimer;
  148. function wsToast(msg, isError = false) {
  149.   const el = document.getElementById('ws-toast');
  150.   if (!el) return;
  151.   el.textContent = msg;
  152.   el.className   = 'ws-toast show' + (isError ? ' error' : '');
  153.   clearTimeout(wsToastTimer);
  154.   wsToastTimer = setTimeout(() => el.className = 'ws-toast', 2800);
  155. }
  156.  
  157. document.addEventListener('DOMContentLoaded', () => {
  158.   ['ws-inp-song', 'ws-inp-artist'].forEach(id => {
  159.     const el = document.getElementById(id);
  160.     if (el) el.addEventListener('keydown', e => { if (e.key === 'Enter') wsSongAdd(); });
  161.   });
  162.   wsLoad();
  163. });
  164. </script>
Advertisement
Add Comment
Please, Sign In to add comment