Dabblet A visual IDE for rapid prototyping
of client-side web development

by Michailia Verou

For the partial fulfillment of B.Sc. Degree in Computer Science

Supervised by Vasilis Vassalos

Associate professor, Athens University of Economics & Business

• Dept. of Informatics & Computer Science, Athens University of Economics & Business

Introduction

Dabblet is a web application that lets users quickly prototype HTML, CSS and JavaScript, save the result online and share it with others. While similar applications existed before Dabblet, Dabblet offered a few significant competitive advantages that enabled it to take a cut of the pie and gain a loyal following of a couple thousand visitors per day. Specifically:

Overview

Below is a screenshot of Dabblet’s initial screen:

The code is written in the bottom half and the visual result can immediately be seen in the top half part of the screen. The only exception is JavaScript, since in many cases, it would be annoying to execute it on every keystroke and might even lock up the browser. JavaScript is executed when a dabblet loads, when the user presses the “Run JS” button (only visible in the JavaScript tab) or uses the keyboard combination (Ctrl + Enter or Cmd + Enter). This layout can change through the settings menu. The tabs at the top right part of the screen can be used to alternate between authoring CSS, HTML and JavaScript, all at once, or a full view of the result.

Architecture

Summary

Dabblet’s architecture is unique in the sense that it has no database of its own and a very minimal backend. Most of the time, the server just serves static files, since almost all the application logic is client-side. For storage and authentication, it uses the Github API, with all requests to it being performed from the client side as well. The user logs in using their Github account and their documents are saved as Github Gists with a specific structure.

This results in improved scalability and minimal costs, since serving static files is not as resource intensive as database transactions or executing server-side code. Another positive side effect of this architecture is that it inspires more confidence, as users generally trust an established web service like Github much more to store their data than a newcomer like Dabblet.

File structure

Saving & Opening dabblets

Dabblets and Gists

Dabblets are saved as Github Gists with a specific structure:

dabblet.css:
The CSS the user has written in dabblet
dabblet.html
The HTML the user has written, which is the part that will be inserted inside the <body> element.
settings.json
A JSON file with various settings about the dabblet, e.g. view mode, selected tab etc

An example of this structure can be seen at gist.github.com/1810274. However, Dabblet is more lenient when attempting to read a gist back and will correctly process any gist with at least one HTML or CSS file.

Saving

The “Save” command

When a user activates the Save command, Dabblet checks the state of the current dabblet before deciding how to proceed. The following pseudocode outlines the algorithm used:


					if ( dabblet is saved ) {
						if ( saved dabblet belongs to current user ) {
							// Update gist
						}
						else {
							// Fork gist
							// Update forked gist
						}
					}
					else {
						// Create new gist
					}

Other actions

Occasionally, the action determined by the algorithm above might not be what the user wants. For example, sometimes the user might want to fork their own dabblet, as they plan to develop two diverging branches from the same starting point. Or, they may want to save anonymously as they either don’t wish to create a Github account or they don’t want to use their account, for privacy reasons. For these cases, two additional options are offered:

  • “Save as new” saves the current dabblet as a new gist belonging to the currently logged in user, regardless of its current ownership.
  • “Save anonymously” saves the current dabblet as a new anonymous gist, regardless of whether the user is authenticated or not. This is the only saving option that users get when they are not logged in. Anoynmous gists cannot be edited.

Process

Saving new gists, forking and updating gists all directly correspond to Github API calls. These calls are performed asynchronously behind the scenes through XMLHttpRequest. Until a response is received, a loading indicator is displayed to the user, to communicate the fact that Dabblet is waiting for something to complete their command.

When a response is received, the Gist ID and the revision hash, are used to update various parts of the UI, as well as the URL in the browser’s address bar. Dabblet uses the HTML5 History API to be able to update the displayed URL without a page reload. Users can then share their dabblet with others or embed it in their webpages by using that URL. URLs have the structure:

http://dabblet.com/gist/GIST_ID/REVISION_HASH

For example:

http://dabblet.com/gist/1901867/5f859c83ede55350bf3ef93183c57ac895a72b88

A revision hash is not required if we want to display the latest revision, which is usually the case. Therefore, URLs are in most cases simpler, like:

http://dabblet.com/gist/1901867

The /gist/ part of the URL was devised for maximum forwards compatibility, i.e. if Dabblet eventually starts supporting other means of storage (its own?), it would be almost impossible to know where to look when opening a dabblet, without additional information beyond the ID.

Opening dabblets

Dabblet uses Apache mod_rewrite to redirect all non-existing URLs to the homepage. This is a very common technique for URL rewriting, employed by popular scripts like Wordpress. It only needs 3 lines in the .htaccess file:

RewriteBase /
				RewriteCond %{REQUEST_FILENAME} !-f
				RewriteCond %{REQUEST_FILENAME} !-d
				RewriteRule . /index.html [L]

Since the redirect is internal, the browser still displays the original URL and not index.html. Therefore, JavaScript can parse that, extract the Gist ID and revision hash (if present) and make the respective Github API call to fetch the relevant Gist, like so (simplified):


				if (path) {
					var parts = path.match(/\bgist\/([\da-f]+)(?:\/([\da-f]+)?/i);
					
					if(parts) {
						gist.id = parts[1];
						gist.rev = parts[2];
						
						// Remove the placeholder code from the editors, 
						// so the user doesn’t see an abrupt change
						css.textContent = 
						html.textContent = 
						javascript.textContent = '';
						
						gist.load();
					}
				}
			

After the Gist is fetched, the CSS and HTML code needs to be found. If it follows the structure described in 2.3.1, things are straightforward. However, when there is no dabblet.css, Dabblet has to go through all the Gist files to check if a CSS file is there at all. The same applies to HTML files. Unfortunately, Github does not provide MIME type information, so the only way to check is the file extension. When no settings.json is detected or when it’s missing some settings, the current settings are used.

After the code is found, the code for every language replaces the current (placeholder) in the respective editors. Afterwards, Dabblet simulates a keyboard event, which triggers syntax highlighting and previewer display, among other things. All these are discussed in detail in section 4. The code looks like this (slightly simplified):


				var cssFile = files['dabblet.css'],
				    htmlFile = files['dabblet.html'],
				    jsFile = files['dabblet.js'],
				    settings = files['settings.json'];
	
				if (!cssFile || !htmlFile || !jsFile) {
					for (var filename in files) {
						var ext = filename.slice(filename.lastIndexOf('.'));
						
						if (!cssFile && ext == '.css') {
							cssFile = files[filename];
						}
	
						if (!htmlFile && ext == '.html') {
							htmlFile = files[filename];
						}
						
						if (!jsFile && ext == '.js') {
							jsFile = files[filename];
						}
						
						if (cssFile && htmlFile && jsFile) {
							// If everything is found, exit the loop
							break;
						}
					}
				}
				
				if (htmlFile) {
					html.textContent = htmlFile.content;
					html.onkeyup();
				}
				
				if (cssFile) {
					css.textContent = cssFile.content;
					css.onkeyup();
				}
				
				if (jsFile) {
					javascript.textContent = jsFile.content;
					javascript.onkeyup();
					
					if (!Dabblet.embedded) {
						// We don’t want JS to run immediately if embedded
						Dabblet.update.JavaScript();
					}
				}
				
				defaultSettings = merge(
				                  	Dabblet.settings.current(), 
				                  	JSON.parse(localStorage.settings)
				                  );
				
				settings = settings? JSON.parse(settings.content) : {};
				
				Dabblet.settings.apply(merge(defaultSettings, settings));
			

Opening dabblets without the Dabblet UI

Sometimes, users want to have access to their dabblets without the Dabblet editing UI around them. This is useful for a number of use cases. For example, sometimes they want to use Dabblet to make a small webpage that they can share with others, without an editing UI to clutter its content. It can also be useful for browser bug testcases, since the browser developers won't have to dig into Dabblet’s heaps of code, but just the few lines of the testcase. This is even useful to debug Dabblet itself, in case a demo created through it is not working as expected. For these cases, there is the “View full page result” functionality, under the cloud menu.

This takes place on a different subdomain, result.dabblet.com (for security reasons), and the gist is retrieved and displayed server-side through PHP. The PHP code required is very minimal, just a call to file_get_contents() to retrieve the gist, then json_decode to parse the JSON and then print out the extracted CSS, HTML and JavaScript in the appropriate places in the markup.

Saving and opening dabblets without Github

Another way to save dabblets without requiring Github as storage, is to save the code in an HTML form with the fields css with the CSS code, html with the HTML markup and javascript with the JavaScript code. For this to work, the form needs to be submitted to dabblet.com and use the POST HTTP method.

An example can be found at dabblet.com/post-test.html, with the form:


				<form action="/" method="post">
					<textarea name="css">div { background: green; } </textarea>
					<textarea name="html">&lt;div>foo&lt;/div>&lt;/textarea>
					<textarea name="javascript">alert('it works!');</textarea>
					<button type="submit">Submit</button>
				</form>
			

When dabblet.com is loaded, the .htaccess file detects whether the request is using POST (with RewriteCond %{REQUEST_METHOD} POST) and in those cases, it uses index-php.php instead of index.html. The only difference between the two files is that the latter includes placeholder code when there is no code to load, whereas the former prints out the POST variables in their respective fields, through PHP calls (like <?= $_POST['css'] ?>). This way, Dabblet avoids unnecessary PHP overhead in every pageload, since most dabblet.com requests are using GET.

This way, dabblets can also be stored in any HTML document. This will also work for any POST request with the necessary fields, it doesn’t even have to originate from an HTML form.

Authentication

Github supports the OAuth 2 protocol for authentication, which enables third-party applications to obtain CRUD access to user data without requiring the user to hand in their password to them.

In a nutshell, Github’s implementation of OAuth consists of the following workflow:

  1. Dabblet sends a request to Github providing its client API key (which can be public)
  2. Github replies by sending a GET request to Dabblet (specifically to dabblet.com/oauth.php, which is the callback URL defined in the app settings on Github) with a temporary code
  3. Dabblet sends a POST request to Github, providing the temporary code as well as its secret API key (which shouldn't be shared)

After all attempts to a fully client-side OAuth authentication process were proved futile, a small 30-line PHP script was written to take care of the last step. Here’s how it works:

  1. Extract the temporary code from the code GET parameter
  2. Send POST request to https://github.com/login/oauth/access_token with both the Client and Secret API keys, as well as the above temporary code, through cURL.
  3. Parse the response using the regex /access_token=([0-9a-f]+)/ and store it to a variable
  4. Print a bit of JavaScript code which will call the opener window and send the access token to it. The opener window will now store the access token in both a global variable ACCESS_TOKEN, as well as in localStorage, so it can be used when the user accesses the app again in the future.
  5. Close the popup.

Even though Github allows CORS in every part of its API, the third step of the OAuth process cannot be performed client-side as there is no way to obscure the secret API key since JavaScript code cannot be hidden from clients (it can be obsfucated, but it’s still possible to reverse engineer that). Therefore, CORS is not enabled in that, in order to encourage server-to-server interaction.

The access token is stored locally in localStorage, and subsequent page loads check for its existence and display information about the user if it's found, in order to communicate to them that they have previously logged in and don’t need to do so again.

It should be noted that authentication is not required for using Dabblet, opening or saving dabblets (anonymously).

Applying the code

After every keystroke, the code needs to be sent to the result iframe in order for the result to appear. The code editor monitors keyup events in the editable regions and detects whether the code was altered by checking which key was pressed. It also monitors paste and cut events, since these can also modify the code.

The HTML in the iframe already includes a skeleton for what’s needed:


			<!DOCTYPE html>
			<html>
			<head>
			<meta charset="utf-8" />
			<title>Dabblet result</title>
			<style></style>
			<script src="render.js"></script>
			</head>
			<body></body>
			</html>
		

Both this HTML file and render.js reside in a separate subdomain (preview.dabblet.com), for security reasons detailed below. Since they are separate subdomains, communication between the main Dabblet window and the iframe cannot happen through simple DOM injection, due to JavaScript’s cross-origin policy. Instead, HTML Web Messaging is used.

Every time the code on each pane is modified, a JSON-encoded message is sent to the iframe, with two properties:

action
One of "css", "html", "javascript" or "title". The title is extracted from the first comment in the CSS code, therefore when the CSS code is edited, two messages are sent.
code
The new code

render.js is then responsible for applying the code, in the following ways:

css
Injects it as the content of the <style> element.
html
Injects it as the content of the <body> element.
javascript
Evaluates the code through eval(). Catches any errors and sends a message to the parent frame so that they can be displayed.
title
Injects it as the content of the <title> element.

In older versions of Dabblet, there was no render.js file and the skeleton HTML resided in a root-level file called result.html. The entire logic of applying the code was handled by the parent frame (the Dabblet UI). However, this was insecure, as there was the chance of somebody writing malicious JavaScript which would steal the current user’s Github Access Token.

Dabblet’s server-side architecture

As made clear in the previous sections, most of Dabblet’s functionality is implemented client-side. The only server-side code (written in PHP) is used to fill in gaps, for things that simply aren’t possible to do with javaScript today. These are:

Code editor

Why reinvent the wheel?

Dabblet’s code editor was written from scratch, although there are some open source solutions available such as Codemirror and Ace. This was not an easy decision, but there was a multitude of reasons pointing to it, mostly related to existing editors having too much complexity for things that were not Dabblet’s goals. For example:

Using these scripts would have been a bit like using a chainsaw when only a knife is needed: A chainsaw might be able to fulfill the same purpose, but a knife weighs much less. Similarly, all that superfluous functionality adds a considerable weight to the page. Loading as fast as possible is a goal that Dabblet takes very seriously.

Also, these scripts are quite limiting regarding the editor UI, making radical changes hard. Dabblet has received a lot of praise for its interface and this might not have been possible with such a script (or, at the very least, it would have required the same amount of effort to change the editor as it did to build one from scratch).

Syntax highlighting - Prism

In web applications, syntax highlighting is the process of wrapping the crucial parts of the code with inline HTML elements (usually <span>) with certain formatting (usually applied through CSS classes) in order to make each part easily distinguishable just by skimming through the code. The exact parts that are highlighted differ per language and per application. To accomplish this, a script needs to tokenize the input and wrap certain tokens with <span> elements with appropriate classes. From that point on, the CSS will take care of the rest.

For Dabblet, a new flexible syntax highlighter was written from scratch and released as a separate open source script under the name Prism, since all existing ones were too complicated, buggy and included a lot of cruft for older browsers, which Dabblet doesn’t support. Prism was released publicly as an open source project in July 2012.

Tokenization*

Each supported language is defined in a separate file, as a set of key-value pairs where the key is the token name and the value is a regular expression that matches it. There are a few extensions to this syntax, the main of which will be examined in section 4.2.1.1. For example, here’s the CSS definition which is pretty basic:


				Prism.languages.css = {
					'comment': /\/\*[\w\W]*?\*\//g,
					'atrule': /@[\w-]+?(\s+[^;{]+)?(?=\s*{|\s*;)/gi,
					'url': /url\((["']?).*?\1\)/gi,
					'selector': /[^\{\}\s][^\{\}]*(?=\s*\{)/g,
					'property': /(\b|\B)[a-z-]+(?=\s*:)/ig,
					'string': /("|')(\\?.)*?\1/g,
					'important': /\B!important\b/gi,
					'ignore': /&(lt|gt|amp);/gi,
					'punctuation': /[\{\};:]/g
				};
			

Since neither HTML nor CSS and JavaScript are regular languages, there will be false positives or true negatives in any attempt to parse them with regular expressions. However, in practice, Prism does a pretty good job at keeping them to a minimum and failing gracefully in those rare unfortunate cases. Using a complete, bulletproof parser would increase processing time and given the frequency the highlighting script needs to run, this is much more important than absolute correctness. This is the reason why most syntax highlighters for the Web, as well as many code editors, work in a similar fashion.

By convention, each language definition resides in a separate file. Upon download, Prism’s website provides a build script to concatenate only the selected languages and plugins in one file. The tokenization is performed through a simple iterative algorithm, like the following (in pseudo-JavaScript, simplified):


				Prism.tokenize = function(text, defs) {
					// Initialize the array with just one item: the full code
					var tokenized = new Array(text);
					
					for (var type in defs) {
						var pattern = defs[type];
						
						for (var i=0; i<tokenized.length; i++) {
							var str = tokenized[i];
							
							if (!(str instanceof Token) && pattern matches str) {
								var index = index of match,
								    length = length of match,
								    beforeToken = str.substring(0, index),
								    afterToken = str.substring(index+length+1),
								    token = new Token(matched substring, type);
								
								// Replace string with its 3 pieces
								tokenized[i] = beforeToken;
								tokenized.pushAt(i+1, token);
								tokenized.pushAt(i+2, afterToken);
							}
						}
					}
					
					return tokenized;
				}
			

The algorithm above progressively breaks down the single input string into an array of strings and Token objects. Token objects include a string and the token type, and are the parts of the code which are going to be wrapped in spans. Every iteration of the inner loop either leaves the string intact or it breaks it down in two strings and a token. The strings are then processed further by subsequent iterations, whereas Token instances will not be altered.

The static method Token.stringify is responsible for producing the necessary HTML. It’s initially called on the array and then calls itself for every item, to produce the necessary substrings which are then joined into the final result. Here’s the algorithm in pseudo-JavaScript (simplified):


				Token.stringify = function(o) {
					if (o is string) {
						return o;
					}
					
					if (o is array) {
						return o.map(Token.stringify).join('');
					}
					
					// If we’re here, o is a Token object
					return '<span class="token ' + o.type + '"'>' +
					       Token.stringify(o.content) + '</span>';
				};
			

It may seem slow, but in practice this algorithm has proved quite fast. To further improve performance, DOM interaction is kept to a minimum of once per script run. The entire highlighting operates on a string, which is split into multiple array chunks with each iteration. Then, only at the end, the array is joined into a string which is injected inside the editor’s <pre> element.

* Note: It’s important to point out that Prism’s “tokens” do not necessarily correspond to parser tokens, nor to tokens as defined in Compiler theory.

Nesting “tokens”

The way the above algorithm works is simple and easy to understand, but its not without its limitations. A token cannot have other tokens inside it, since once its wrapped in a Token object, it’s not processed further. This is mitigated by explicitly allowing tokens to be “nested”. So far, we’ve shown the following simple notation to define a token:


					type: /regex/
				

However, tokens can also be defined in the following, “extended” syntax:


					type: {
						pattern: /regex/
					}
				

The two examples above are functionally equivalent, but the latter allows for more options, the most powerful of which is the inside property, which accepts another object literal, with the exact same syntax. This means it may have its own inner tokens as well, a feature used by the built-in Markup definition, with up to two levels deep nesting. As an example, suppose we wanted to highlight URL-looking strings in CSS comment tokens. Then, we could modify the comment token from the CSS definition like so:


					'comment': {
						pattern: /\/\*[\w\W]*?\*\//g,
						inside: {
							'url': /\b([a-z]{3,7}:\/\/|tel:)[\w-+%~/.]+/
						}
					},
				

Of course, this means the tokenize() algorithm has to become recursive to accommodate this feature. This is done by replacing the new Token() line with:


					var subDefs = defs[type].inside;
					var token = new Token(subDefs !== undefined?
					            	  Prism.tokenize(matched substring, subDefs)
					            	: matched substring,
					            type);
				

Even though nesting tokens is allowed, it should be avoided, as it can cause a performance hit: When tokens are not nested, the code inside every token is not processed further, decreasing the code that needs to be examined with every step. Contrary, when child tokens are allowed, every such token invokes a recursive call of the highlighting function, albeit with much fewer definitions. However, when the parent tokens are large, nesting can actually aid performance, due to nested tokens being processed against a much smaller token dictionary which usually consists of simpler regular expressions.

Extensibility

One of Prism’s goals was extensibility, not only in defining new languages, but also in extending its core functionality. This is achieved by having “hooks” at several key points throughout its code. Plugins can then assign functions that need to run in those hooks by calling Prism.hooks.add() with the hook name and a function with the code to run. The Prism core takes care of calling any such functions, and passing them the key parameters as properties of an env object.

Plugins can even used in language definition files, to enhance functionality. For example, the markup definition includes the following small plugin, to show the symbol that corresponds to HTML entities, as a tooltip:


					Prism.hooks.add('wrap', function(env) {
					
						if (env.type === 'entity') {
							env.attributes['title'] = env.content.replace(/&/, '&');
						}
					});
				

Several Prism plugins already exist:

  • A plugin to make URLs and emails clickable and parse Markdown links
  • A plugin to highlight specific lines and line ranges
  • A plugin to show invisible characters like tabs, spaces etc

Formatting

Each token is wrapped with a <span> element with two classes: One of them is always .token, the other one identifies the kind of token (for example .comment or .property. Those classes can be then used by CSS to style the tokens and thus, produce the syntax highlighting effect.

Undo and Redo

Since the code is overwritten with practically every keystroke, the built-in undo and redo system of the browser will not work. Therefore, an undo and redo manager had to be implemented from scratch. Dabblet’s code editor has a private UndoManager class to manage this functionality. Every Editor instance stores an UndoManager instance. Whereas some editors store the entire code for every state, this was deemed inefficient and wasteful. Thus, Dabblet only stores incremental changes. There are three categories of changes:

These actions may be combined, for example typing over selected text is both an “add” and a “delete” action and commenting is two additions: One for the opening comment delimiter and one for the closing one. The “action” change is currently only used for multiline indenting, and may eventually be replaced by a series of additions.

Every UndoManager contains two stacks: The undoStack and the redoStack. These stacks store objects with up to four predefined properties:

add
The text to add
del
The text to delete
start
The start index of the operation
action
The id of the custom action, if it’s one

Not all of the four properties are required. If it’s a custom action, the object may contain any number of custom properties as well.

When the user presses an undo shortcut (Ctrl+Z on Windows, Cmd+Z on OSX), the last state is popped from the undoStack, it’s reversed and then pushed onto the redoStack. Similarly, when the user hits a redo shortcut (Ctrl+Y on Windows, Cmd+Shift+Z on OSX), the last state is popped from the redoStack, applied and then pushed onto the undoStack.

Previewers

One of the most innovative features of Dabblet is the concept of value previewers. When authors are writing CSS or (to a lesser extent) HTML code, they are practically trying to visualize the result in their minds, occasionally switching to a browser to observe how everything works together. Dabblet already helps in that by offering instant preview with every keystroke, but sometimes this is not enough. It’s hard to visualize a specific value by observing the result of the entire CSS code on the page.

A prime example of this is CSS colors. Over the years, authors have been trained to somewhat visualize a CSS color from its numerical components, however their estimation is usually very crude, even for the most experienced of professionals. Previewing the entire result helps, but in most cases, the color is not visible enough to be evaluated. For example, it’s obscured by a background image or displayed in a very small area, such as a border or text.

To help code understanding, Dabblet includes small inline popups for various kinds of tokens, which display the visual result of a value, isolated from any other styling. It currently supports previewers for the following kinds of values:

The user can see the visual result of each value as they type, with no additional action required on their behalf. In many cases, this alleviates the need for external tools such as color pickers or gradient generators and expedites CSS development.

In the future, some of these previewers could become read-write instead of read-only. For example, dragging the ruler from any of its vertical edges will allow horizontal resizing and will alter the value of the length token it represents.

Implementation

Every previewer is an instance of a Previewer object. The constructor is called with two parameters: The type of token the previewer applies to and a function which applies the appropriate style to the previewer and returns true if it should be shown or false if it shouldn’t (e.g. if the value is invalid). The function is called with the active token element as its context and is passed its contents (element.textContent) as a parameter. For example, here is the code for the simplest previewer, the one for color:


				new Previewer('color', function(code) {
					var style = this.style;
										
					style.backgroundColor = '';
					style.backgroundColor = code;
					
					return !!style.backgroundColor;
				});
			

The Previewer “class” takes care of the rest, for example positioning the previewer popup accordingly. Of course, there should be appropriate markup for every previewer. Every previewer has a class of previewer and an id with the same name as the token type the previewer is about. For example, here is the markup for the color previewer:


				<div id="color" class="previewer"></div>
			

This is a very simple previewer, since it only shows a color. Other previewers can be more complex. For example, the previewer for cubic bezier curves includes an SVG whose parameters are tweaked by the script to match the curve it’s supposed to be showing:


				<div id="easing" class="previewer">
					<svg width="100" height="100" viewBox="-20 -20 140 140">
						<defs>
							<marker id="marker" viewBox="0 0 4 4" refX="2" refY="2" markerUnits="strokeWidth">
								<circle cx="2" cy="2" r="1.5" />
							</marker>
						</defs>
						<path d="M0,100 C20,50, 40,30, 100,0" />
						<line x1="0" y1="100" x2="20" y2="50" />
						<line x1="100" y1="0" x2="40" y2="30" />
					</svg>
				</div>
			

The keyup event of the editors checks whether the cursor is on a token that is registered as having a previewer. If so, it assigns the span element with that token in the token property of the corresponding Previewer object. That property has a setter assigned which takes care of running the appropriate code when the property changes. If the cursor is not on a token that has a previewer, all previewers are hidden. Similar code runs on the mouseover event, to enable showing previewers with the mouse as well.

-prefix-free

Originally, Dabblet was created for making CSS-heavy examples and demos. A common problem when using CSS features from specifications that have not yet reached Candidate Recommendation status is that browsers are forced to implement them with a vendor prefix. Every rendering engine has its own prefix, for example:

-webkit-
Chrome, Safari and other WebKit-based browsers
-moz-
Gecko-based browsers such as Firefox
-ms-
Trident (Internet Explorer)
-o-
Presto (Opera)

Quite commonly, these experimental implementations converge, since they are based on the same draft specification. Therefore, authors end up having to repeat every prefixed declaration, CSS rule or @rule multiple times, sometimes as many as five. For example, to apply a rotate transformation to an element, the CSS code that was required until recently looked like this:


			-webkit-transform: rotate(45deg);
			-moz-transform: rotate(45deg);
			-ms-transform: rotate(45deg);
			-o-transform: rotate(45deg);
			transform: rotate(45deg);
		

Done manually, this repetition is error-prone, hard to maintain and tedious. The most common solution to this was using a CSS preprocessor and adding the prefixes through mixins. However, this approach bloats the CSS code that gets downloaded and forces authors to forever depend on proprietary syntax — or at least until they refactor.

This is the reason -prefix-free was written, and released two months before Dabblet under a permissive MIT license. -prefix-free operates client-side and depends on feature detection to determine which prefixes are truly needed for which features. It operates on regular CSS code that is written without any prefixes (prefixed code can be included along as well, -prefix-free just won’t touch it) and only adds the prefixes that are necessary for the current environment. The ultimate goal is that the author will eventually be able to remove -prefix-free from their page in the future, and their code will still work, i.e. they are not depending on -prefix-free, but are only using it until all browsers transition to unprefixing the features they’ve used.

A crucial Dabblet feature is including -prefix-free by default (it can be turned off through the Settings pane), which greatly simplifies the code of experimental demos and examples written through it (which is one of the most common use cases for such tools). This feature influenced more recent competitors of Dabblet, such as CodePen.IO.

Settings

The Settings pane can be seen below:

Dabblet’s general UX philosophy is having sensible defaults, rather than lots of settings. Therefore, its Settings pane is very minimal. There are only four settings:

These settings are saved in the settings.json file of each gist, so that dabblets can be viewed with the same settings they were created. Defaults for them can be set by tweaking them while not viewing an existing dabblet. These defaults are saved client-side, in localStorage.

User Profiles

Like the rest of Dabblet, its user profiles are built on top of the Github API and are basically wrappers for Github profiles. User profiles were added to Dabblet several months after its release, and this is when the Github API started showing its limitations. Here is a typical user profile:

The initial state of the user profile requires two requests to the Github API: One to get the user information and one for the gists. One more request is made for each of the “Following” and “Followers” tabs.

A main limitation of the Github API was that there was no way to only fetch gists that are also dabblets. Since the user might also be using Github Gist outside dabblet, the results need to be filtered. There are many heuristics to check if a gist was created through Dabblet. In this case, Dabblet checks the filenames of the CSS and HTML files. If both a “dabblet.css” and a “dabblet.html” file are included, it assumes that the gist is also a dabblet. This approach is not perfect: In cases when some of the gists are not dabblets, there will be fewer dabblets per page than there should be (30) — or even none, in rare cases.

Another challenge was related to previewing the listed dabblets. The open web platform does not yet offer an API for capturing a web page, which means there is no way to save a screenshot of the result. Therefore, the only way to show a preview was to embed iframes of the dabblets themselves. To avoid having the small dimensions of the viewport distort the examples, the iframe is rendered at a size of 800×600 and a scale transform of 50% makes it appear at a smaller size.

Workflow

To sum up and illustrate the way Dabblet works, below is what happens behind the scenes for a typical workflow, like the following example sequence of steps:

  1. User loads dabblet.com
  2. Types some HTML
  3. Types some CSS
  4. Edits the CSS code to change something
  5. Writes some JS
  6. Runs the JS
  7. Edits the JS to fix a mistake
  8. Saves the dabblet

User loads dabblet.com

  1. .htaccess checks if it’s a POST request. It isn’t, so index.html is loaded
  2. localStorage['access_token'] is read, to see if the user has previously logged in
  3. If the user has logged in before, their info is fetched through a Github API call to api.github.com/user and saved to window.user. The DOM is updated with the user info, where needed.
  4. The URL path is parsed, to see if an existing gist needs to be fetched from Github. Since the user has loaded the homepage, no action needs to be taken for that.
  5. localStorage is checked for the dabblet.css, dabblet.html and/or dabblet.js keys. If any of those is set, the corresponding <pre> elements are populated with the stored code.
  6. The default settings are merged with localStorage.settings and applied.
  7. An Editor object is created for every <pre> element that matches .editor.page > pre. An UndoManager object is created by the Editor constructor. Several events are bound to the <pre> element and its container.
  8. Several Previewer objects are created
  9. Once the load event fires in the result iframe, the keyup event is triggered on the three <pre> elements for the code editors.
  10. Since the keyup event is simulated, the main thing that happens is that the caret position is stored, Prism.highlightElement() is called for each of those <pre> elements and then the caret position is restored.
  11. Dabblet is now ready to be used.

Types some HTML

  1. The keydown event is fired on the <pre id="html"> element.
  2. Since the pressed key is neither Backspace, nor Tab or an Undo/Redo shortcut or a commenting shortcut, nothing happens. If it’s the Enter key, a newline character is added plus the same level of indentation that the previous line had.
  3. The keypress event is fired on the <pre> element.
  4. UndoManager#action is called which pushes the added characters in the undo stack and chains together multiple additions (or deletions) so that undoing is more natural and quicker than doing it character-by-character.
  5. The keyup event is fired on the <pre> element.
  6. If the text was changed, a custom contentchange event is fired
  7. If the key pressed results in the caret changing position (for example, one of the arrow keys), a custom caretmove event is fired
  8. If the caretmove event is fired and the caret is on a different line, a handler registered on it takes care of moving the current line highlight appropriately
  9. There is a callback for contentchange events on dabblet.js which calls Dabblet.update.HTML() and sets Gist.saved to false
  10. Dabblet.update.HTML() sends a message to the result window with the code typed
  11. The result window gets that message through the message event handler and sets the innerHTML of the <body> element to the code sent.
  12. The caret position is stored, Prism.highlightElement() is called for the <pre> element with id "html" and then the caret position is restored.
  13. Previewer.get() is called for the token under the caret. If a previewer corresponds to tokens of that type, Previewer.active will be assigned that element and a previewer will be shown. Otherwise, Previewer.hideAll() will be called, to hide any previewers that might be already shown.

Types some CSS

  1. The keydown event is fired on the <pre id="css"> element.
  2. Since the pressed key is neither Backspace, nor Tab or an Undo/Redo shortcut or a commenting shortcut, nothing happens. If it’s the Enter key, a newline character is added plus the same level of indentation that the previous line had.
  3. The keypress event is fired on the <pre> element.
  4. UndoManager#action is called which pushes the added characters in the undo stack and chains together multiple additions (or deletions) so that undoing is more natural and quicker than doing it character-by-character.
  5. The keyup event is fired on the <pre> element.
  6. If the text was changed, a custom contentchange event is fired
  7. If the key pressed results in the caret changing position (for example, one of the arrow keys), a custom caretmove event is fired
  8. If the caretmove event is fired and the caret is on a different line, a handler registered on it takes care of moving the current line highlight appropriately
  9. There is a callback for contentchange events on dabblet.js which calls Dabblet.update.CSS()
  10. Dabblet.update.CSS() sends a message to the result window with the code typed
  11. The result window gets that message through the message event handler and sets the textContent of the <style> element to the code sent.
  12. The caret position is stored, Prism.highlightElement() is called for the <pre> element with id "html" and then the caret position is restored.
  13. Previewer.get() is called for the token under the caret. If a previewer corresponds to tokens of that type, Previewer.active will be assigned that element and a previewer will be shown. Currently, CSS has a lot more previewers than HTML, so this is far more likely in this case. Otherwise, Previewer.hideAll() will be called, to hide any previewers that might be already shown.

Edits the CSS code to change something

Same as above. If an undoing shortcut is pressed:

  1. The keydown handler on the <pre> element takes care of calling undo() on the UndoManager instance of the Editor instance.
  2. UndoManager#undo pops the last state off the undoStack
  3. If there is no state to be popped (i.e. the stack is empty) it returns there
  4. The popped action is pushed on the redoStack
  5. UndoManager#applyInverse() is called with the action as a parameter. This takes care of applying the action in reverse, i.e. added text is deleted, deleted text is added etc and then the caret is moved accordingly.
  6. A keyup event is simulated on the < element, for cleaning up (so that syntax highlighting is reapplied etc)

Writes some JS

  1. The keydown event is fired on the <pre id="javascript"> element.
  2. Since the pressed key is neither Backspace, nor Tab or an Undo/Redo shortcut or a commenting shortcut, nothing happens. If it’s the Enter key, a newline character is added plus the same level of indentation that the previous line had.
  3. The keypress event is fired on the <pre> element.
  4. UndoManager#action is called which pushes the added characters in the undo stack and chains together multiple additions (or deletions) so that undoing is more natural and quicker than doing it character-by-character.
  5. The keyup event is fired on the <pre> element.
  6. If the text was changed, a custom contentchange event is fired
  7. If the key pressed results in the caret changing position (for example, one of the arrow keys), a custom caretmove event is fired
  8. If the caretmove event is fired and the caret is on a different line, a handler registered on it takes care of moving the current line highlight appropriately.
  9. Unlike HTML and CSS, JS is not executed on every keystroke.
  10. The caret position is stored, Prism.highlightElement() is called for the <pre> element with id "html" and then the caret position is restored.
  11. There are currently no previewers for JavaScript.

Runs the JS

  1. The click event handler on the “Run JS” button (or the keydown event on the Ctrl+Enter shortcut) calls Dabblet.update.JavaScript()
  2. A message is sent to the result iframe, which contains a serialized JSON object with two keys: action with the value "javascript" and code with the JavaScript code.
  3. The result window gets that message through the message event handler and checks if the DOM has loaded. If not, it defers execution until the DOMContentLoaded event fires.
  4. When the DOM is ready, it first reapplies the HTML (as an attempt to clear the effect from previous executions of the JS) and then calls eval() with the JavaScript code passed.
  5. The previous step happens in a try...catch block, so that any errors are caught and displayed to the user. If there is no error, the process ends here. Otherwise, the result iframe sends a message to the parent window with a JSON-serialized object of the following structure:
    
    					{
    						"action": 'jserror',
    						"data": {
    							"name": /* The exception name, e.g. ReferenceError */, 
    							"message": // The error message */,
    							"lineNumber": // The line it occured, if available */
    						}
    					}
    				
  6. A new <div> is created with id="jserror", unless it already exists and the error information is added in its innerHTML.
  7. It gets a class of active which displays it.

Edits the JS to fix a mistake

Same as in section 7.5, except that when the contentchange event fires, the JS error is hidden (the active class is removed).

Saves the dabblet

  1. The click event handler on the “Save” button or the keydown handler for the Ctrl+S shortcut calls Gist.save()
  2. Gist.request(), a helper method for sending requests to the Github API, is called with a JSON object of the following structure:
    
    					{
    						"description": /* The title */,
    						"public": true,
    						"files": {
    							"dabblet.css": {
    								"content": /* CSS code */
    							},
    							"dabblet.html": {
    								"content": /* HTML markup */
    							},
    							"dabblet.js": {
    								"content": /* JS code */
    							},
    							"settings.json": {
    								"content": /* The current settings, JSON-serialized */
    							}
    						}
    					}
    				
    and a few other parameters, such as a callback for when Github responds, the HTTP method to use, the Github URL, headers needed etc.
  3. Gist.request() sends a POST request to https://api.github.com/gists with the JSON object above in its body.
  4. When Github responds, its responseText is parsed as JSON and passed to the callback as its first parameter. If there is no responseText or there is an HTTP error, it’s presented to the user through a simple alert().
  5. The callback provided by Gist.save() checks if the object returned has an id property, and if so, calls Gist.update(), which is responsible for updating the URL and Dabblet UI with the gist information.
  6. Gist.update calls history.pushState() (History API) to change the displayed URL without a page reload.
  7. Gist.user stores the returned info for the user who saved it. In this case, this happens to be the current user, but the same function is called when loading a gist by a different user too.
  8. The cloud menu is updated with links to the full page result, the gist on Github and the user information (see screenshot below for before and after).
  9. Gist.saved is set to true.
The cloud menu, before and after saving a dabblet

Community adoption & publicity

Dabblet launched in December 16th, 2011. Dabblet’s release received enthusiastic feedback from the community, and was publicized by popular industry media like Wired.com’s WebMonkey, CSS-Tricks, Smashing Magazine and others.

According to Google Analytics, it received more than 65,000 unique visitors in its first month, 15,000 of which during its first two days. Ever since it steadily gets an average of 2,000 visitors per day. 9,000 users have logged in to it through Github (statistic reported by Github’s API) which saved a total of almost 17,000 gists with it (measured through searching Github Gist for “dabblet.css”). On Twitter, the tweet announcing Dabblet got 271 retweets and 241 favorites. The @dabblet twitter account currently has 1,300 followers.

A customized version of Dabblet has been adopted in WebPlatform.org, W3C’s documentation effort together with Adobe, Facebook, Google, HP, Intel, Microsoft, Nokia, Mozilla and Opera Software, and resides under code.webplatform.org.

Prism

As discussed in section 4.2, Prism is a syntax highlighting script that emerged from Dabblet as a separate open-source project. Prism has received significant popularity in its niche. It got 23,000 unique visitors on its first day (measured by Google Analytics) and WebMonkey devoted an entire new post to it.

It’s being used by the creator of JavaScript, Brendan Eich in his personal blog, the popular web designer online magazine A List Apart, Smashing Magazine, some of the examples in WebPlatform.org and many other websites.

Several people have contributed to Prism since its launch in July 2012 by sending a total of 45 pull requests on Github.

References