Skip to main content
50 of 60
edited body
Mori
  • 197
  • 1
  • 5
  • 20

Main updates:

  1. Indented code with two spaces rather than four
  2. Changed the download attribute value, myFile.html, to something meaningful: template.html
  3. Combined the preview and createURL functions in a new function
  4. Removed the fileChooser.onclick function: this function is done by the Reset button now.
  5. Added downloader.download = file.name; to the fileChooser.onchange function so the downloader download attribute has the same value as the imported file name. Now there's a logical relationship between the text field content, fileChooser value, and the downloader download attribute value.
  6. In the resize function, changed flex to flexGrow as flexShrink and flexBasis never change
  7. Defined a new task for the Reset button so it not only resets the text field, but also the fileChooser and downloader download attribute values
  8. Added a confirmation message on page exit if the text field is modified
  9. Added a new option: Dark theme; made some code improvements
  10. Added flex-wrap: wrap; to the header and footer so the flex items wrap if necessary
  11. Added a run-stop toggle switch:
  • Sometimes you shouldn't run your code until you finish coding or else it will crash your browser. For example, when you're writing loops it can cause an infinite loop.
  • You might want to re-run your code and re-see the result when working with CSS animations, for example. You can achieve it by double-clicking the Run checkbox.
  1. Now Edge supports outputObject.value — changed indicator.textContent to indicator.value.

Credit:

Special thanks to Schism for his detailed pointers!

Final source code:

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <meta name="description" content="Edit your HTML, CSS, and JavaScript code and monitor the instant live preview.">
  <title>HTML Editor: online HTML editor with real-time preview</title>
  <link rel="icon" href="favicon.ico">
  <base target="_blank">
  <style>
    html,
    body {
      margin: 0;
      padding: 0;
      height: 100%;
    }

    body {
      display: flex;
      flex-direction: column;
    }

    header,
    footer.shown {
      display: flex;
      flex-wrap: wrap;
      align-items: center;
      padding: 5px;
    }

    header {
      background: linear-gradient(#FFF, #CCC);
    }

    label,
    #downloader,
    [type="button"],
    #fileChooser,
    output {
      font: bold 11px Arial;
      color: #333;
    }

    [type="checkbox"] {
      margin: 0 10px 0 5px;
    }

    #resetter,
    #resizer {
      margin: 0 5px;
    }

    #selector {
      margin: 0;
    }

    #fileChooser {
      margin: 0 auto 0 5px;
    }

    #resizer,
    iframe {
      padding: 0;
    }

    output {
      width: 3.5ch;
      margin-right: 10px;
    }

    #more {
      margin: 0;
      border: 0;
      padding: 0;
      background: transparent;
    }

    #more.on {
      border-bottom: 2px solid;
      margin-bottom: -2px;
    }

    main {
      flex: 1;
      display: flex;
    }

    main.horizontal {
      flex-direction: column;
    }

    div {
      flex-basis: 0;
      position: relative;
    }

    #viewerWrapper {
      border-left: 5px solid #CCC;
    }

    main.horizontal #viewerWrapper {
      border-left: 0;
      border-top: 5px solid #CCC;
    }

    div * {
      position: absolute;
      width: 100%;
      height: 100%;
      margin: 0;
      border: 0;
      background: #FFF;
    }

    textarea {
      box-sizing: border-box;
      padding: 5px;
      outline: 0;
      resize: none;
      color: #333;
    }

    textarea.dark {
      background: #333;
      color: #FFF;
    }

    footer.shown {
      column-gap: 5px;
      background: linear-gradient(#CCC, #FFF);
    }

    img {
      display: block;
      width: 18px;
      height: 18px;
    }

    address {
      margin-left: auto;
      font-size: 16px;
      font-family: 'Times New Roman';
      color: #333;
    }

    address a {
      color: inherit;
    }
  </style>
</head>

<body>
  <header>
    <label for="runner">Run</label>
    <input type="checkbox" checked id="runner">
    <a href="" download="template.html" title="Download the HTML document" id="downloader">Download</a>
    <input type="button" value="Reset" id="resetter">
    <input type="button" value="Select" id="selector">
    <input type="file" accept="text/html" id="fileChooser">
    <label for="resizer">Editor size</label>
    <input type="range" id="resizer">
    <output for="resizer" id="indicator"></output>
    <label for="viewsToggler">Horizontal view</label>
    <input type="checkbox" id="viewsToggler">
    <label for="themesToggler">Dark theme</label>
    <input type="checkbox" id="themesToggler">
    <input type="button" value="•••" title="More" id="more">
  </header>
  <main id="main">
    <div id="editorWrapper">
      <textarea spellcheck="false" id="editor"><!DOCTYPE html>
<html lang="en">
  <head>
    <title>HTML Document Template</title>
  </head>
  <body>
    <p>Hello, world!</p>
  </body>
</html></textarea>
    </div>
    <div id="viewerWrapper">
      <iframe id="viewer"></iframe>
    </div>
  </main>
  <footer hidden id="footer">
    <a href="https://www.linkedin.com/sharing/share-offsite/?url=http%3A%2F%2Fhtmleditor.gitlab.io%2F" title="Share on LinkedIn">
      <img src="images/linkedin.png" alt="LinkedIn">
    </a>
    <a href="https://twitter.com/share?text=HTML%20Editor%3A%20online%20HTML%20editor%20with%20real-time%20preview&amp;url=http%3A%2F%2Fhtmleditor.gitlab.io%2F" title="Share on Twitter">
      <img src="images/twitter.png" alt="Twitter">
    </a>
    <a href="https://www.facebook.com/dialog/share?app_id=664554287087112&amp;href=http%3A%2F%2Fhtmleditor.gitlab.io%2F" title="Share on Facebook">
      <img src="images/facebook.png" alt="Facebook">
    </a>
    <address><a href="https://codereview.stackexchange.com/questions/56106/html-editor-online-html-editor-with-real-time-preview" title="Code Review Stack Exchange">Feedback</a> | Created by <a href="https://mori.pages.dev" title="Mori" rel="author">Mori</a></address>
  </footer>
  <script>
    var runner = document.getElementById('runner'),
      editor = document.getElementById('editor'),
      downloader = document.getElementById('downloader'),
      fileChooser = document.getElementById('fileChooser'),
      resizer = document.getElementById('resizer'),
      viewsToggler = document.getElementById('viewsToggler'),
      themesToggler = document.getElementById('themesToggler');

    function preview() {
      if (runner.checked) {
        var viewer = document.getElementById('viewer');
        try {
          var viewerDoc = viewer.contentDocument;
          viewerDoc.open();
          viewerDoc.write(editor.value);
          viewerDoc.close();
        } catch (e) { // in case of iframe redirection to a different origin
          viewer.src = 'about:blank';
          setTimeout(preview, 4); // minimum delay
        }
      }
    }
    editor.addEventListener('input', preview);
    runner.addEventListener('change', preview);

    function createURL() {
      var blob = new Blob([editor.value], {
        type: 'text/html'
      });
      downloader.href = window.URL.createObjectURL(blob);
    }
    editor.addEventListener('change', createURL);

    function previewAndCreateURL() {
      preview();
      createURL();
    }

    document.getElementById('resetter').addEventListener('click', function() {
      if (!editor.value || editor.value != editor.defaultValue && confirm('Your changes will be lost.\nAre you sure you want to reset?')) {
        downloader.download = 'template.html';
        fileChooser.value = '';
        editor.value = editor.defaultValue;
        previewAndCreateURL();
      } else if (editor.value == editor.defaultValue) {
        downloader.download = 'template.html';
        fileChooser.value = '';
      }
    });

    document.getElementById('selector').addEventListener('click', function() {
      editor.select();
    });

    fileChooser.addEventListener('change', function() {
      var file = this.files[0],
        reader = new FileReader();
      if (file) { // to ensure that there's a file to read so Chrome, for example, doesn't run this function when you cancel choosing a new file
        downloader.download = file.name;
        reader.readAsText(file);
        reader.addEventListener('load', function() {
          editor.value = this.result;
          previewAndCreateURL();
        });
      }
    });

    function resize() {
      var resizerVal = resizer.value;
      document.getElementById('editorWrapper').style.flexGrow = resizerVal;
      document.getElementById('viewerWrapper').style.flexGrow = 100 - resizerVal;
      document.getElementById('indicator').value = (resizerVal / 100).toFixed(2);
    }
    resizer.addEventListener('input', resize);

    function toggleViews() {
      var main = document.getElementById('main');
      if (viewsToggler.checked) {
        main.className = 'horizontal';
      } else {
        main.className = '';
      }
    }
    viewsToggler.addEventListener('change', toggleViews);

    function toggleThemes() {
      if (themesToggler.checked) {
        editor.className = 'dark';
      } else {
        editor.className = '';
      }
    }
    themesToggler.addEventListener('change', toggleThemes);

    document.getElementById('more').addEventListener('click', function() {
      if (this.title == 'More') {
        this.title = 'Less';
      } else {
        this.title = 'More';
      }
      this.classList.toggle('on');
      document.getElementById('footer').classList.toggle('shown');
    });

    window.addEventListener('beforeunload', function(event) {
      if (editor.value && editor.value != editor.defaultValue) {
        event.returnValue = 'Your changes may be lost.';
      }
    });

    resize();
    toggleViews();
    toggleThemes();
    previewAndCreateURL();
  </script>
</body>

</html>
Mori
  • 197
  • 1
  • 5
  • 20