HTML Pencil: an online HTML editor with real-time preview

HTML Pencil is an online HTML editor created for modern browsers.

Please review the source code and provide feedback:

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

<head>
    <meta charset="UTF-8">
    <meta name="description" content="An online HTML editor with real-time preview">
    <title>HTML Pencil</title>
    <link rel="icon" href="favicon.ico" type="image/x-icon">
    <base target="_blank">
    <style>
        html,
        body {
            margin: 0;
            padding: 0;
            height: 100%;
        }
        body {
            display: -webkit-flex;
            /* WebKit prefixes are added to support Safari. */
            display: flex;
            -webkit-flex-direction: column;
            flex-direction: column;
        }
        header,
        .shown {
            display: -webkit-flex;
            display: flex;
            -webkit-align-items: center;
            align-items: center;
            padding: 5px;
        }
        header {
            background: linear-gradient(#FFF, #CCC);
        }
        #fileSaver,
        [type="button"],
        #fileChooser,
        label,
        span {
            font: bold 11px arial;
            color: #333;
        }
        #selector,
        #resizer,
        #viewsToggle,
        [title$="Twitter"] {
            margin-right: 5px;
            margin-left: 5px;
        }
        #fileSaver {
            margin-right: 5px;
        }
        #fileChooser,
        [title$="Facebook"] {
            margin-right: auto;
        }
        #resizer {
            margin-top: 0;
            margin-bottom: 0;
            padding: 0;
        }
        /* to remove the extra margins and padding in some browsers, e.g. IE11 */
        span {
            width: 35px;
        }
        #footerToggle {
            margin-right: 0;
            margin-left: 5px;
            border: 0;
            padding: 0;
            background: transparent;
        }
        main {
            display: -webkit-flex;
            display: flex;
            -webkit-flex: 1;
            flex: 1;
        }
        .horizontal {
            -webkit-flex-direction: column;
            flex-direction: column;
        }
        main * {
            margin: 0;
            -webkit-flex: 50;
            flex: 50;
            background: #FFF;
            min-height: 100%;
            /* to ensure that the flex items are stretched to use available space; IE11, for example, doesn't stretch the iframe. */
        }
        .horizontal * {
            min-width: 100%;
            min-height: 0;
            /* to get back to the initial value */
        }
        textarea {
            box-sizing: border-box;
            border: 0;
            outline: 0;
            padding: 5px;
            resize: none;
            overflow: auto;
            /* to remove the default scrollbar in IE11 */
        }
        .minSize {
            padding: 0;
        }
        iframe {
            border: solid #CCC;
            border-width: 0 0 0 5px;
            padding: 0;
        }
        .horizontal iframe {
            border-width: 5px 0 0;
        }
        .shown {
            background: linear-gradient(#CCC, #FFF);
        }
        img {
            display: block;
            width: 20px;
            height: 20px;
        }
        address,
        address a {
            color: #333;
        }
    </style>
</head>

<body>
    <header>
        <a download="myFile.html" title="Save as..." id="fileSaver">Save as...</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">Text field size</label>
        <input type="range" id="resizer">
        <span id="indicator">50%</span>
        <!-- The semantic element to use instead of span is output. But it's not supported in IE11. -->
        <label for="viewsToggle">Horizontal view</label>
        <input type="checkbox" id="viewsToggle">
        <input type="button" value="&#9650;" title="Show footer" id="footerToggle">
    </header>
    <main id="main">
        <textarea spellcheck="false" id="editor"><!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>HTML Document Template</title>
  </head>
  <body>
    <p>Hello, world!</p>
  </body>
</html></textarea>
        <iframe id="viewer"></iframe>
    </main>
    <footer hidden id="footer">
        <a href="https://plus.google.com/share?url=http%3A%2F%2Fhtmlpencil.appspot.com%2F" title="Share on Google+">
            <img src="images/google+.png" alt="Google+">
        </a>
        <a href="https://twitter.com/share?text=HTML%20Pencil&amp;url=http%3A%2F%2Fhtmlpencil.appspot.com%2F" title="Share on Twitter">
            <img src="images/twitter.png" alt="Twitter">
        </a>
        <a href="https://www.facebook.com/sharer.php?u=http%3A%2F%2Fhtmlpencil.appspot.com%2F" title="Share on Facebook">
            <img src="images/facebook.png" alt="Facebook">
        </a>
        <address><a href="feedback.html" title="Feedback">Feedback</a> / Created by <a href="https://plus.google.com/+MortezaMirmojarabian?rel=author" title="Google+ profile" rel="author">Mori</a></address>
    </footer>
    <script>
        var editor = document.getElementById('editor'),
            viewer = document.getElementById('viewer'),
            fileChooser = document.getElementById('fileChooser'),
            resizer = document.getElementById('resizer');

        function preview() {
            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
            }
        }
        preview();
        editor.oninput = preview;

        function createURL() {
            var blob = new Blob([editor.value], {
                type: 'text/html'
            });
            document.getElementById('fileSaver').href = window.URL.createObjectURL(blob);
        }
        createURL();
        editor.onchange = createURL;
        fileChooser.onclick = function () { // to empty the fileList so you can rechoose the same file
            this.value = '';
        };
        fileChooser.onchange = function () {
            var file = this.files[0],
                reader = new FileReader();
            if (file) { // to ensure that there's a file to read so IE11 doesn't run this function on clicking fileChooser before you choose a file
                reader.readAsText(file);
                reader.onload = function () {
                    editor.value = this.result;
                    preview();
                    createURL();
                };
            }
        };
        document.getElementById('viewsToggle').onchange = function () {
            document.getElementById('main').classList.toggle('horizontal');
        };
        resizer.oninput = resizer.onchange = function () { // The onchange property is added to support IE11.
            var resizerVal = this.value;
            editor.style.webkitFlex = resizerVal;
            editor.style.flex = resizerVal;
            viewer.style.webkitFlex = 100 - resizerVal;
            viewer.style.flex = 100 - resizerVal;
            document.getElementById('indicator').textContent = resizerVal + '%';
            if (resizerVal == 0) {
                editor.className = 'minSize';
            } else {
                editor.className = '';
            }
        };
        document.getElementById('selector').onclick = function () {
            editor.select();
        };
        document.getElementById('resetter').onclick = function () {
            if (!editor.value || editor.value != editor.defaultValue && confirm('Are you sure?')) {
                editor.value = editor.defaultValue;
                preview();
                createURL();
            }
        };
        document.getElementById('footerToggle').onclick = function () {
            var footerClasses = document.getElementById('footer').classList;
            footerClasses.toggle('shown');
            if (footerClasses.length) {
                this.value = '&#9660;';
                this.title = 'Hide footer';
            } else {
                this.value = '&#9650;';
                this.title = 'Show footer';
            }
        };
    </script>
</body>

</html>

Acknowledgement

Many thanks to Paul O’B who’s always been a great help!

Why would anyone use that over something more robust and well known like code pen, jsfiddle, and google code playground :confused:

Other than an exercise this really doesn’t compare to well known solutions out there.

I like the real time preview.

I added a <style> tag for my own use since I usually start in document then move the css to an external sheet later.

Nice job…

Is this the result of some of the questions you’ve been asking over the last few months? :slight_smile:

Looks quite accomplished to me but as Oddz said you may need to explain why you are re-inventing the wheel a bit and what makes your’s different from the others.

If you are looking for an actual ‘flat out’ review then this may need to go in the review forum.

Yes! It’s something I’ve been working on in my free time.

Looks quite accomplished to me but as Oddz said you may need to explain why you are re-inventing the wheel a bit and what makes your’s different from the others.

Good question: CodePen, JSFiddle, etc. have different features that makes one handier than the others based on your requirements. Similarly, HTML Pencil has its own features. Just for instance it allows you to interact with your file system and import/export HTML files.

If you are looking for an actual ‘flat out’ review then this may need to go in the review forum.

I didn’t know about Website Design & Content - Reviews & Critiques forum. I try to move the thread there. Thanks for letting me know and thanks for your kind comment! :slight_smile:

Update:
It seems that I cannot move the thread(?) I wonder if you could do it as a moderator.

Update:
It seems that I cannot move the thread(?) I wonder if you could do it as a moderator.

Unfortunately that aspect of the system is hard coded and you have to make three reviews of others in that section first and then that allows you to post your own request for a review. (It’s a give and take kind of thing :))

An impressive exercise :tup:

A slight problem with the toggled footer in Firefox 30

I’m glad you like it! :slight_smile:

That’s weird! Don’t you see the share icons on the footer? I just re-checked them in Firefox 30 with no problem:

It looks a lot better when I put some images at the relative URL :wink:

In the meanwhile I checked them on another computer and could see the images. I’m confused! :confused:

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. Added fileSaver to the defined elements at the top of the global scope
  4. Combined the preview and createURL functions in a new function
  5. Removed the fileChooser.onclick function: this function is done by the Reset button now.
  6. Added fileSaver.download = file.name; to the fileChooser.onchange function so the fileSaver download attribute has the same value as the imported file name. Now there’s a meaningful relationship between the text field content, fileChooser value, and the fileSaver download attribute value.
  7. Combined editor.style.webkitFlex = resizerVal; and editor.style.flex = resizerVal;, and the same change for the viewer style
  8. Changed flex to flexGrow as flexShrink and flexBasis never change
  9. Changed resizerVal == 0 to resizerVal == ‘0’: no more data type conversion, and no more errors in JSHint
  10. Defined a new task for the Reset button so it not only resets the text field, but also the fileChooser and fileSaver download attribute values

Final source code:

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

<head>
  <meta charset="UTF-8">
  <meta name="description" content="An online HTML editor with real-time preview">
  <title>HTML Pencil</title>
  <link rel="icon" href="favicon.ico" type="image/x-icon">
  <base target="_blank">
  <style>
    html,
    body {
      margin: 0;
      padding: 0;
      height: 100%;
    }
    body {
      display: -webkit-flex;
      /* WebKit prefixes are added to support Safari. */
      display: flex;
      -webkit-flex-direction: column;
      flex-direction: column;
    }
    header,
    .shown {
      display: -webkit-flex;
      display: flex;
      -webkit-align-items: center;
      align-items: center;
      padding: 5px;
    }
    header {
      background: linear-gradient(#FFF, #CCC);
    }
    #fileSaver,
    [type="button"],
    #fileChooser,
    label,
    span {
      font: bold 11px arial;
      color: #333;
    }
    #selector,
    #resizer,
    #viewsToggle,
    [title$="Twitter"] {
      margin-right: 5px;
      margin-left: 5px;
    }
    #fileSaver {
      margin-right: 5px;
    }
    #fileChooser,
    [title$="Facebook"] {
      margin-right: auto;
    }
    #resizer {
      margin-top: 0;
      margin-bottom: 0;
      padding: 0;
    }
    /* to remove the extra margins and padding in some browsers, e.g. IE11 */
    span {
      width: 35px;
    }
    #footerToggle {
      margin-right: 0;
      margin-left: 5px;
      border: 0;
      padding: 0;
      background: transparent;
    }
    main {
      display: -webkit-flex;
      display: flex;
      -webkit-flex: 1;
      flex: 1;
    }
    .horizontal {
      -webkit-flex-direction: column;
      flex-direction: column;
    }
    main * {
      margin: 0;
      -webkit-flex: 50;
      flex: 50;
      background: #FFF;
      min-height: 100%;
      /* to ensure that the flex items are stretched to use available space; IE11, for example, doesn't stretch the iframe. */
    }
    .horizontal * {
      min-width: 100%;
      min-height: 0;
      /* to get back to the initial value */
    }
    textarea {
      box-sizing: border-box;
      border: 0;
      outline: 0;
      padding: 5px;
      resize: none;
      overflow: auto;
      /* to remove the default scrollbar in IE11 */
    }
    .minSize {
      padding: 0;
    }
    iframe {
      border: solid #CCC;
      border-width: 0 0 0 5px;
      padding: 0;
    }
    .horizontal iframe {
      border-width: 5px 0 0;
    }
    .shown {
      background: linear-gradient(#CCC, #FFF);
    }
    img {
      display: block;
      width: 20px;
      height: 20px;
    }
    address,
    address a {
      color: #333;
    }
  </style>
</head>

<body>
  <header>
    <a download="template.html" title="Save as..." id="fileSaver">Save as...</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">Text field size</label>
    <input type="range" id="resizer">
    <span id="indicator">50%</span>
    <!-- The semantic element to use instead of span is output. But it's not supported in IE11. -->
    <label for="viewsToggle">Horizontal view</label>
    <input type="checkbox" id="viewsToggle">
    <input type="button" value="&#9650;" title="Show footer" id="footerToggle">
  </header>
  <main id="main">
    <textarea spellcheck="false" id="editor"><!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>HTML Document Template</title>
  </head>
  <body>
    <p>Hello, world!</p>
  </body>
</html></textarea>
    <iframe id="viewer"></iframe>
  </main>
  <footer hidden id="footer">
    <a href="https://plus.google.com/share?url=http%3A%2F%2Fhtmlpencil.appspot.com%2F" title="Share on Google+">
      <img src="images/google+.png" alt="Google+">
    </a>
    <a href="https://twitter.com/share?text=HTML%20Pencil&amp;url=http%3A%2F%2Fhtmlpencil.appspot.com%2F" title="Share on Twitter">
      <img src="images/twitter.png" alt="Twitter">
    </a>
    <a href="https://www.facebook.com/sharer.php?u=http%3A%2F%2Fhtmlpencil.appspot.com%2F" title="Share on Facebook">
      <img src="images/facebook.png" alt="Facebook">
    </a>
    <address><a href="feedback.html" title="Feedback">Feedback</a> / Created by <a href="https://plus.google.com/+MortezaMirmojarabian?rel=author" title="Google+ profile" rel="author">Mori</a></address>
  </footer>
  <script>
    var editor = document.getElementById('editor'),
      viewer = document.getElementById('viewer'),
      fileChooser = document.getElementById('fileChooser'),
      fileSaver = document.getElementById('fileSaver'),
      resizer = document.getElementById('resizer');

    function preview() {
      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.oninput = preview;

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

    function previewAndCreateURL() {
      preview();
      createURL();
    }
    previewAndCreateURL();
    fileChooser.onchange = function () {
      var file = this.files[0],
        reader = new FileReader();
      if (file) { // to ensure that there's a file to read so Chrome and Opera don't run this function when clicking Cancel on the Open dialog
        fileSaver.download = file.name;
        reader.readAsText(file);
        reader.onload = function () {
          editor.value = this.result;
          previewAndCreateURL();
        };
      }
    };
    document.getElementById('viewsToggle').onchange = function () {
      document.getElementById('main').classList.toggle('horizontal');
    };
    resizer.oninput = resizer.onchange = function () { // The onchange property is added to support IE11.
      var resizerVal = this.value;
      editor.style.webkitFlexGrow = editor.style.flexGrow = resizerVal;
      viewer.style.webkitFlexGrow = viewer.style.flexGrow = 100 - resizerVal;
      document.getElementById('indicator').textContent = resizerVal + '%';
      if (resizerVal == '0') {
        editor.className = 'minSize';
      } else {
        editor.className = '';
      }
    };
    document.getElementById('selector').onclick = function () {
      editor.select();
    };
    document.getElementById('resetter').onclick = function () {
      if (!editor.value || editor.value != editor.defaultValue && confirm('Are you sure?')) {
        editor.value = editor.defaultValue;
        fileChooser.value = '';
        fileSaver.download = 'template.html';
        previewAndCreateURL();
      } else if (editor.value == editor.defaultValue) {
        fileChooser.value = '';
        fileSaver.download = 'template.html';
      }
    };
    document.getElementById('footerToggle').onclick = function () {
      var footerClasses = document.getElementById('footer').classList;
      footerClasses.toggle('shown');
      if (footerClasses.length) {
        this.value = '&#9660;';
        this.title = 'Hide footer';
      } else {
        this.value = '&#9650;';
        this.title = 'Show footer';
      }
    };
  </script>
</body>

</html>

Have you tested it in IE9? I’m seeing essential differences between FF31 and IE9, the widths of the panes are off in the latter, and the latter throws an error: “Blob is not defined.”

HTML Pencil is supported by the latest versions of major browsers – created for modern browsers as mentioned in my first post.