After the Introduction, its time to build things! We will basically lay out a basic UI for the playground and learn how to render our code in the sandbox.
Markup and Styling
Woohoo! CSSDeck Embeds helping us learn quick. I have setup some HTML and CSS code already as you can see. On the left there are the HTML, CSS, JS codeboxes with textarea
as editors. On the right there is the output pane where the rendering and displaying is going to happen.
What's the one thing every developer wants? More screens! Enhance your coding experience with an external monitor to increase screen real estate.
We’re using an iframe
as a sandbox as they are great for this exact purpose. Rendering everything in an iframe
means no conflict with the styles or scripts from the main window. What I mean is that, if we use a plain HTML block element like a div
or section
instead of an iframe
and throw all our code from the code editors into that element, then our styles and scripts for that restricted output will conflict with the ones that we define in and for our main window. Hope that makes sense.
Realtime Rendering
The most important bit now. Taking all the code from the code editors as something is typed and injecting that to the iframe.
Step 1
Attaching a handler to the keyup
event of the textarea.
var html_editor = document.querySelector('#html textarea'), css_editor = document.querySelector('#css textarea'), js_editor = document.querySelector('#js textarea'); var editors = [html_editor, css_editor, js_editor]; // Attaching the onkeyup Event editors.forEach(function(editor, i, arr) { editor.addEventListener('keyup', function() { // The function that'll prepare the code and inject // into the iframe. render(); }, false); });
Now, whenever one types into the editors, the event handler is going to fire up, which will execute the render
method. Soon, we’ll define this method that’ll do the real work of sending over the code to the iframe
.
STEP 2
Preparing a function that can take html, css and javascript code (from code editors) and in turn prepare the entire HTML code that we can simply inject into the iframe.
// Base template var base_tpl = "<!doctype html>\n" + "<html>\n\t" + "<head>\n\t\t" + "<meta charset=\"utf-8\">\n\t\t" + "<title>Test</title>\n\n\t\t\n\t" + "</head>\n\t" + "<body>\n\t\n\t" + "</body>\n" + "</html>"; var prepareSource = function() { var html = html_editor.value, css = css_editor.value, js = js_editor.value, src = ''; // HTML src = base_tpl.replace('</body>', html + '</body>'); // CSS css = '<style>' + css + '</style>'; src = src.replace('</head>', css + '</head>'); // Javascript js = '<script>' + js + '</script>'; src = src.replace('</body>', js + '</body>'); return src; };
The function prepareSource()
gets the contents of the editors and then does some basic string replacements on the base template we stored in base_tpl
. This way we form our entire HTML source that we can simply inject into the iframe
.
STEP 3
Writing a function that calls prepareSource()
and injects the returned code into the iframe. Using doc.open()
, doc.write()
, doc.close()
is a really easy way to do that and it just works great.
var render = function() { var source = prepareSource(); var iframe = document.querySelector('#output iframe'), iframe_doc = iframe.contentDocument; iframe_doc.open(); iframe_doc.write(source); iframe_doc.close(); };
Some developers consider it a bad practise to use those 3 document
methods but I really think its just fine in our context. Using those methods we kind of replace the entire document with a new one which means we go through the entire initialization process which is expensive and slow (by about 30-50ms ?). But it doesn’t matter, modern browser engines are pretty fast and smart. Oh, and those methods are also blocking and synchronous.
Anyway, I have been using this approach for months on CSSDeck without any problems and I am sure quite a few other similar sites do the same.
The other way is to inject the HTML code from the editor into the iframe’s body node, CSS code inside a style element in the iframe’s head node and JS code inside a script element at the end of the iframe’s body node. Some code:
// For HTML iframe_doc.body.innerHTML = code_for_body; // or var old_body = iframe_doc.body; var new_body = document.createElement('body'); new_body.innerHTML = html_code; old_body.parentNode.replaceChild(new_body, old_body); // For CSS var style = document.createElement('style'); style.innerHTML = css_code; iframe_doc.head.appendChild(style); // For JS var script = document.createElement('script'); script.innerHTML = js_code; iframe_doc.body.appendChild(script);
This way works fine when you are just dealing with html and css code, but when Javascript comes into action, things might not work well. I remember facing a lot of problems with this approach. For example, G+ and Twitter share buttons wouldn’t load, although FB Like button would load fine. Also faced some problems with Google Map API in the sandbox. Sometimes, there were inconsistent rendering too, that can get a bit frustrating for the end user.
STEP 4
Celebrations! We made it. Now you realize how easy it is ? Final Result.
Extra Tips
- You may want to call the
render
function once on page load just incase you are loading code from some database. - The keyup event gets called on every keypress which is inefficient since we don’t want to render our code on pressing keys like the
arrows
,ctrl
,alt
,shift
, etc. Precisely, we want to render when there is a code change in our textarea.
Theonchange
event is not the proper solution to this problem, because it won’t be triggered until the textarea has lost focus. One solution to this problem is to use theoninput
event that has been standardized in the HTML5 specification. This event gets triggered whenever there is a user input detected. Unfortunately, it is not supported in IE6-IE8 but you can get some polyfills. Also IE9 support is a bit buggy as the event doesn’t get fired when thebackspace
anddelete
keys are pressed or there is a cut operation.
The other working solution is to use a global flag (variable) that holds the value of yourtextarea
fields. Then, in thekeyup
event handler, you can check whether the stored value is same as the editor contents or not. If not, then the code was changed and you need to render and also save the current value into that flag. Here’s a sample code:var code = ''; editor.addEventListener('keyup', function() { // editor contents is not the same as the code // in our flag/storage. This means the code has been // changed. if (code !== this.value) { render(); code = this.value; } }, false);
What’s Next?
In the next part we’ll discuss some of the security problems this method has if we allow others to create on our own platform like CSSDeck does and how to solve those issues.
Hi!
Is there any implications if I wish to only and only use the JavaScript editor for output?
Not wanting to use HTML/CSS that is!
Umm, I don’t think so. But not sure why would you want to just have a JS editor ? Although that’s perfectly fine. It’s like having all 3 but disabling HTML/CSS ones for the user. So seems good to me.