Last Friday

You said

I'd like to develop a browser-side app with pure JavaScript code without CSS and HTML. Below is my design and help me convert HTML and CSS code to JavaScript code. My original JavaScript function will be filled by myself. <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1" /> <title>Apple Podcasts Extractor</title> <!-- FileSaver.js for saving blobs as files --> <script src="https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/2.0.0/FileSaver.min.js"></script> <!-- PicoCSS for simple styling --> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/picocss/1.5.10/pico.min.css"> </head> <!-- Initialize app on page load --> <body onload="initApp()"> <main class="container"> <!-- Podcast search section with add/remove buttons --> <div class="grid"> <div> <label for="podcast_id">Podcast Search Term</label> <input type="text" id="podcast_id" name="podcast_id" value="Nutrition Facts with Dr. Greger"> </div> </div> <!-- CORS proxy configuration --> <label for="cors_proxy">CORS Proxy</label> <input type="text" id="cors_proxy" name="cors_proxy" value="https://cors-aywhere.herokuapp.com/"> <!-- Run button to fetch podcasts --> <button onclick="run_it()">Run</button> <!-- Display section for podcast info --> <p> Podcast Name: <em><span id="podcast_name"></span></em> </p> <!-- Container for episode listings --> <div id="episodes"></div> </main> <script> // Global variables let proxy_api; // Stores the CORS proxy URL let id_download; // Stores the ID of the element being downloaded function initApp() { run_itt(); } async function my_backend(search) { // I will fill the placeholder by myself } async function run_it() { const podcast_id = document.getElementById("podcast_id").value; const episodes = document.getElementById("episodes"); // Clear previous episodes episodes.innerHTML = ""; // Show loading indicator const episode_hint = document.createElement("div"); episodes.appendChild(episode_hint); episode_hint.innerText = "loading..."; // Fetch podcast data const podcast = await my_backend(podcast_id); document.getElementById("podcast_name").innerText = podcast[0].albumName; // Remove loading indicator const episode_tmp = document.querySelector("#episodes div"); episodes.removeChild(episode_tmp); // Create episode list items podcast.slice(1).forEach((episode) => { // Create list item container const episode_li = document.createElement("li"); episode_li.setAttribute("class", "grid"); // Create episode link const episode_anchor = document.createElement("a"); episode_anchor.setAttribute("href", episode.url); episode_anchor.setAttribute("target", "_blank"); episode_anchor.innerText = episode.title; episode_li.appendChild(episode_anchor); // Create date display const episode_span = document.createElement("span"); episode_span.setAttribute("style", "color: #999;"); episode_span.innerText = episode.date; episode_li.appendChild(episode_span); // Create download button const episode_button = document.createElement("button"); episode_button.setAttribute("id", episode.title); episode_button.setAttribute( "style", "width: auto; margin-right: 0; margin-left: auto;" ); // Determine file extension let filename_with_ext = episode.title + ".m4a"; if (episode.url.includes(".mp3")) { filename_with_ext = episode.title + ".mp3"; } // Set download handler episode_button.onclick = function (evt) { downloadUsingFetch( proxy_api + episode.url, filename_with_ext, evt ); }; episode_button.innerText = "download"; episode_li.appendChild(episode_button); episodes.appendChild(episode_li); }); } function progress({ loaded, total }) { const elProgress = document.getElementById(id_download); elProgress.innerHTML = Math.round((loaded / total) * 100) + "%"; } async function downloadUsingFetch(FILE_URL, filename, evt) { // I will fill the placeholder by myself }

Copilot said

Below is one way to convert your HTML/CSS design into plain JavaScript that dynamically builds the DOM. In this example, every element in your original static HTML is created using document.createElement calls. (Your own backend logic in my_backend and download code in downloadUsingFetch remain as placeholders.) You can simply drop this script into an otherwise empty page (or run it via a browser console) and it will assemble the whole UI.

js
// Global variables
let proxy_api; // Stores the CORS proxy URL
let id_download; // Stores the ID of the element being downloaded

// Initialize app when the DOM is ready
document.addEventListener("DOMContentLoaded", initApp);

function initApp() {
  // ----- Setup Head -----
  // Meta charset
  const metaCharset = document.createElement("meta");
  metaCharset.setAttribute("charset", "utf-8");
  document.head.appendChild(metaCharset);

  // Meta viewport
  const metaViewport = document.createElement("meta");
  metaViewport.name = "viewport";
  metaViewport.content = "width=device-width, initial-scale=1";
  document.head.appendChild(metaViewport);

  // Title element
  const titleEl = document.createElement("title");
  titleEl.innerText = "Apple Podcasts Extractor";
  document.head.appendChild(titleEl);

  // Load external FileSaver.js script for saving blobs as files
  const fileSaverScript = document.createElement("script");
  fileSaverScript.src =
    "https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/2.0.0/FileSaver.min.js";
  document.head.appendChild(fileSaverScript);

  // Load PicoCSS for simple styling
  const picoCSS = document.createElement("link");
  picoCSS.rel = "stylesheet";
  picoCSS.href =
    "https://cdnjs.cloudflare.com/ajax/libs/picocss/1.5.10/pico.min.css";
  document.head.appendChild(picoCSS);

  // ----- Build Body Contents -----
  // Create main container
  const main = document.createElement("main");
  main.className = "container";
  document.body.appendChild(main);

  // Podcast search section (grid container)
  const gridDiv = document.createElement("div");
  gridDiv.className = "grid";
  main.appendChild(gridDiv);

  // Podcast Search Term
  const searchDiv = document.createElement("div");
  gridDiv.appendChild(searchDiv);

  const podcastLabel = document.createElement("label");
  podcastLabel.setAttribute("for", "podcast_id");
  podcastLabel.innerText = "Podcast Search Term";
  searchDiv.appendChild(podcastLabel);

  const podcastInput = document.createElement("input");
  podcastInput.type = "text";
  podcastInput.id = "podcast_id";
  podcastInput.name = "podcast_id";
  podcastInput.value = "Nutrition Facts with Dr. Greger";
  searchDiv.appendChild(podcastInput);

  // CORS Proxy configuration
  const corsLabel = document.createElement("label");
  corsLabel.setAttribute("for", "cors_proxy");
  corsLabel.innerText = "CORS Proxy";
  main.appendChild(corsLabel);

  const corsInput = document.createElement("input");
  corsInput.type = "text";
  corsInput.id = "cors_proxy";
  corsInput.name = "cors_proxy";
  corsInput.value = "https://cors-aywhere.herokuapp.com/";
  main.appendChild(corsInput);

  // Run button to fetch podcasts
  const runButton = document.createElement("button");
  runButton.innerText = "Run";
  runButton.addEventListener("click", run_it);
  main.appendChild(runButton);

  // Display section for podcast information
  const podcastInfo = document.createElement("p");
  podcastInfo.innerHTML =
    'Podcast Name: <em><span id="podcast_name"></span></em>';
  main.appendChild(podcastInfo);

  // Container for episode listings
  const episodesDiv = document.createElement("div");
  episodesDiv.id = "episodes";
  main.appendChild(episodesDiv);

  // Set the global proxy_api variable from the CORS input
  proxy_api = corsInput.value;
}

// Placeholder for the backend call (fill in your logic)
async function my_backend(search) {
  // Your function that returns an array of podcast info
  // For example purposes, you might return a fake podcast array:
  // return [{ albumName: "My Podcast Album" }, { title: "Episode 1", url: "http://...", date: "2023-01-01" }];
}

// Run button click event handler to fetch and display podcasts
async function run_it() {
  const podcastId = document.getElementById("podcast_id").value;
  const episodes = document.getElementById("episodes");

  // Clear previous episodes
  episodes.innerHTML = "";

  // Loading indicator
  const loadingDiv = document.createElement("div");
  loadingDiv.innerText = "loading...";
  episodes.appendChild(loadingDiv);

  // Fetch podcast data
  const podcast = await my_backend(podcastId);
  document.getElementById("podcast_name").innerText = podcast[0].albumName;

  // Remove loading indicator
  episodes.removeChild(loadingDiv);

  // Create list items for episodes
  podcast.slice(1).forEach((episode) => {
    // Create container for this episode
    const episodeLI = document.createElement("li");
    episodeLI.className = "grid";

    // Create link for the episode
    const episodeLink = document.createElement("a");
    episodeLink.href = episode.url;
    episodeLink.target = "_blank";
    episodeLink.innerText = episode.title;
    episodeLI.appendChild(episodeLink);

    // Create span for the episode date
    const episodeDate = document.createElement("span");
    episodeDate.style.color = "#999";
    episodeDate.innerText = episode.date;
    episodeLI.appendChild(episodeDate);

    // Create a download button
    const downloadButton = document.createElement("button");
    downloadButton.id = episode.title;
    downloadButton.style.cssText =
      "width: auto; margin-right: 0; margin-left: auto;";

    // Determine file extension based on URL
    let filename_with_ext = episode.title + ".m4a";
    if (episode.url.includes(".mp3")) {
      filename_with_ext = episode.title + ".mp3";
    }

    // Set the download click handler (your own function for fetching and saving the file)
    downloadButton.onclick = function (evt) {
      downloadUsingFetch(proxy_api + episode.url, filename_with_ext, evt);
    };

    downloadButton.innerText = "download";
    episodeLI.appendChild(downloadButton);

    // Append this episode item to the list
    episodes.appendChild(episodeLI);
  });
}

// Function to show download progress; you may use it within your download logic
function progress({ loaded, total }) {
  const elProgress = document.getElementById(id_download);
  elProgress.innerHTML = Math.round((loaded / total) * 100) + "%";
}

// Placeholder for the file download function using fetch (to be filled by yourself)
async function downloadUsingFetch(FILE_URL, filename, evt) {
  // Your download logic goes here.
}

Some Thoughts to Consider

  • Dynamic Resource Loading: Because you’re using external libraries (FileSaver.js and PicoCSS), you create and append the <script> and <link> elements. Remember that styles and scripts load asynchronously, so if your app relies on them immediately you may consider adding load event handlers.

  • Event Binding: Instead of using inline HTML as in your original design (e.g., the body’s onload), here we use DOMContentLoaded to trigger initApp. This keeps your code fully in JavaScript.

  • Future Enhancements: When you implement my_backend and downloadUsingFetch, you might consider adding error handling, progress indicators, and possibly converting the app into modular components.

I hope this helps you build your browser-side app completely within JavaScript. Would you like to explore adding error handling or animations for the loading indicator next?