Front-End Architecture Case Study: Blog Article Composition User Interface
In a blog admin interface, all the user wants to do is type some text in a form and see a preview of the article. The user can use asterisks around words or phrases to indicate the text should be bold. This is a simplified version of a markup language like textile for the sake of this discussion. When the article looks good in the preview the user clicks the "publish" button so the whole world can read his wonderful thoughts.
Even designing the front-end architecture for something as this simple as a blog article composition user interface is difficult and requires many decisions. An acceptable balance of user experience, development time, performance, and degradation must be found. This article examines three possible solutions: classic no-JavaScript form submission, Rails-style RJS Ajax updates, and a full client-side application.
Solution: Classic
1. initial page request
The user makes a GET /article/new
request for the new article page. The page contains the HTML for the form and a linked css file. The download is relatively fast and this page will work in old browsers and new browsers with certain features disabled (e.g. cookies, JavaScript, CSS, images).
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
"http://www.w3.org/TR/html4/strict.dtd">
<html lang="en">
<head>
<title>New Article</title>
<link href="css/blog.css" rel="stylesheet" type="text/css">
</head>
<body>
<h1>Compose Your Article</h1>
<form action="/article/new">
<p><input type="hidden" name="sessionId" value="1234"></p>
<dl>
<dt>Title</dt>
<dd><input type="text" name="title"></dd>
<dt>Body</dt>
<dd><textarea name="body" rows="10" cols="40"></textarea></dd>
</dl>
<p>
<input type="submit" name="button" value="preview">
</p>
</form>
</body>
</html>
2. user types
As the user composes the article there is no preview. The user knows that asterisks around characters will make them bold. The user must work carefully to make sure asterisks are in pairs and uses imagination to visualize the final output.
3. user clicks "preview"
When the article is finished (or a large chunk is complete) the user clicks the "preview" button to see the same page with a preview at the top. This requires a full page reload and the user waits patiently...Oops! The use of asterisks looks good but the user forgot to include a title.
4. user fixes errors
The user adds a title in the form.
5. user clicks "preview"
Again the user clicks the "preview" button and waits for the full page reload. When the page is reloaded there are no errors and a "publish" button has magically appeared.
The user clicks the "publish" button, the server validates and accepts the blog article. The server response is a HTTP redirect to.
http://blogfest.com/article/123
This solution has strengths. It is easy to program, is fast on initial download, works with old and disabled clients and is DRY.
This solution also has weaknesses. It is painfully slow for the user especially if the user wants to tweak the use of asterisks and requires multiple previews. As users, we expect more these days. For this interface we expect a snappy Ajax page with live preview updates.
Solution: RJS Ajax Updates
In response to a client's Ajax request, a Ruby on Rails server-side application can use RJS templates to generate JavaScript and return it to the client. The concept isn't at all restricted to Rails. A Python, Perl, or PHP server-side application could return a snip of JavaScript. When this response is received by the client, the client knows to use the JavaScript eval()
function to execute the servers response. This gives the server a way to make remote procedure calls on the client. The JavaScript functions the server is calling were loaded into the browser on the initial page load. The response usually contains JavaScript to make DOM manipulations and updates what the users sees. For example, in an online store, if the server wants to add an item at the bottom of a shopping cart list on a page the response could be the following. (Using the Fork JavaScript library.)
FORK.Mutate.insertBottom('myList', '<li>one medium green sweater</li>');
Now back to the blog composition user interface. In the classic solution, to see a preview, the user had to stop and submit the form the server. The server generated a new page with both the preview and the composition form and returned it to the client. After living in the responsive web we have been for the past couple of years, having to stop and wait for this full page refresh seems almost as painful as bamboo under a fingernail. Of course, as I think we all know now, using Ajax we can submit the form contents and have the server send back a response to update just the preview. The user can keep on typing while this occurs in the background. We can repeat this updating every three seconds or so while the user is typing and give slightly delayed preview.
1. initial page request
The user makes a GET /article/new
requests the new article page. The page contains the HTML for the form. Also in the page are place holder divs for the preview and any error messages. The page contains a link to some JavaScript libraries that can make the Ajax requests and define the functions the server will call in it's responses. The blogApp.js
file attaches event handlers to some of the elements in the page and generates Ajax requests at the appropriate times. This file is reasonably small because it only contains enough JavaScript to handle events, run communications and make generic DOM manipulations. The browser can be considered dumb in this solution because it doesn't contain any high-level logic like what those asterisks actually mean or that the article must have a title and a body. (I'm also assuming a cookie-based session from now on.)
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
"http://www.w3.org/TR/html4/strict.dtd">
<html lang="en">
<head>
<title>New Article</title>
<link href="/css/blogUi.css" rel="stylesheet" type="text/css">
<script src="/js/fork/ajax.js" type="text/javascript"></script>
<script src="/js/fork/mutate.js" type="text/javascript"></script>
<script src="/js/blogApp.js" type="text/javascript"></script>
</head>
<body>
<h1>Compose Your Article</h1>
<div id="error"></div>
<div id="preview">
</div>
<form action="/article/new">
<dl>
<dt>Title</dt>
<dd><input type="text" name="title"></dd>
<dt>Body</dt>
<dd><textarea name="body" rows="10" cols="40"></textarea></dd>
</dl>
<p>
<input type="submit" name="button" value="publish">
</p>
</form>
</body>
</html>
2. user types
The user types some text for the body of the article. After a few seconds an Ajax POST /article/preview
request is made with the contents of the form. The server responds with JavaScript to update the page.
FORK.Mutate.update('preview', 'The user must preview the article');
During this request, the user continued typing and when the update is finally made the preview is a couple words behind.
3. user types some more
The user types some more and uses some asterisks to bold some text. The browser continues making Ajax calls and updating the preview by executing the servers JavaScript response. These communications happen very frequently. For a semi-live preview maybe there is an Ajax request each three seconds while the user is actively editing. Note the request and response are getting longer as the article does.
FORK.Mutate.update('preview', 'The user must preview the
article first and then can submit when there are no validation
errors. On a whim I may use bold text <b>here</b> a');
In the screenshot below note the preview is still a couple words behind the user.
The logic for converting the asterisks into bold tags is on the server and the code to make that conversion does not have to be sent to the browser. This is DRY and makes agile developers smile with the feeling of success. If the code to make this conversion is long then this is good. Sending the logic to the browser with the initial page load would be expensive; however, with even moderately sized blog articles, it wouldn't take many Ajax preview updates before sending this conversion code to the browser is less expensive than these RJS updates. Having the logic in the browser would save many client-server connections as well and hosting expense. The DRY principle is about saving money but it isn't always the only or last word in that department.
Because the logic for the markup conversion is on the server, it doesn't have to be written in two languages: for example, Ruby and JavaScript. It is time consuming and difficult to ensure code written in two languages to do the same task is always equivalent. This argument doesn't hold too much weight because, even though it is not currently popular, there is no reason why the server can't use JavaScript!
4. user clicks "publish"
The user waits for the preview to catch up to the text written in the form and then reviews the body of the text. When satisfied the user clicks "publish". The form content is submitted to the browser with POST /article/create
. The user waits. The browser rejects the request because the user forgot the article title. The server responds with
FORK.Mutate.update('error', 'The article must have a title.')
5. user corrects errors
The user adds a title for the article. The browser is still dutifully making Ajax requests and updating the preview when the form content has changed. The user waits for the preview to update.
When the preview looks good the user presses "publish" again. The browser makes another Ajax POST /article/create
request and the server responds with the following JavaScript redirection to view the article as seen by the thousands of blog readers.
window.location="http://blogfest.com/article/123";
This RJS system has been a big improvement for user experience compared to the classic solution. The slightly lagging preview updating in the background is a nice feature. The initial download of the page is reasonably light with only a small set of JavaScript libraries. The server band-width use is very high with the article being sent to and from the server with each update. It is no wonder I've heard people refer to Rails as a "box hungry beast." This solution is DRY and allows the developer to use a language other than JavaScript on the server to contain the logic of converting asterisks to bold tags.
Solution: Client-Side App
1. initial page request
Like both previous solutions, the user makes an initial GET /article/new
request for the new article page. Like the RJS solution this page loads some JavaScript libraries that do low-level tasks that normalize browser differences and bugs. However this time there is and extra library. The markup.js
library includes the logic to convert asterisks to bold tags. This time the blogApp.js
file contains the logic to run a more involved app on the client-side. It reads the form contents, calls the markup library and generates the preview without making any Ajax calls. It also validates the form contents to ensure there is both a title and a body when the user clicks preview and knows how to generate and show an error message. When the article is valid it generates the Ajax request to publish the article. The initial page load is longer but the client is self-sufficient during the entire composition phase up until the publish request is made.
Even though the client-side is validating the form contents, the server will have to repeat this process to guard against hackers and also as part of a graceful degradation plan for clients without JavaScript or enough JavaScript to run the client-side app. Many web pages are not concerned with degradation but graceful degradation is important for others (more thoughts on degradation tradeoffs.)
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
"http://www.w3.org/TR/html4/strict.dtd">
<html lang="en">
<head>
<title>New Article</title>
<link href="/css/blogUi.css" rel="stylesheet" type="text/css">
<script src="/js/fork/ajax.js" type="text/javascript"></script>
<script src="/js/fork/mutate.js" type="text/javascript"></script>
<script src="/js/markup.js" type="text/javascript"></script>
<script src="/js/blogApp.js" type="text/javascript"></script>
</head>
<body>
<h1>Compose Your Article</h1>
<div id="error"></div>
<div id="preview">
</div>
<form action="/article/new">
<p><input type="hidden" name="sessionId" value="1234"></p>
<dl>
<dt>Title</dt>
<dd><input type="text" name="title"></dd>
<dt>Body</dt>
<dd><textarea name="body" rows="10" cols="40"></textarea></dd>
</dl>
<p>
<input type="submit" name="button" value="publish">
</p>
</form>
</body>
</html>
2. user types
The user types and with each key press the preview is updated almost instantly. Because the code to do this is local to the client there are no Ajax requests. A blog article would need to be quite long before this system would bog down to the point where the preview lags the user input noticeably.
3. user types some more
The user types some more and uses asterisks to bold some text. As the user does this the preview updates instantly.
4. user clicks "publish"
The user decides that the article is finished and presses "publish". Oops. The browser instantly shows the validation error without an Ajax request.
5. user corrects errors
The user ads a title and the preview instantly updates. When the first character is entered for the title the error message could be removed.
After the user reviews the article once again he presses "publish". The client-side validation passes and an Ajax POST request sends the form data to the server. The request could also send the generated HTML in the preview and then the conversion code in markup.js
would not have to live on the server at all (depending on your degradation plan.) The server's response has a status code of 200 OK
and the response body is just a URL
http://blogfest.com/article/123
The client-side app knows that with this status code the body will be a URL and the JavaScript redirects the browser to that page.
This solution takes a different set of tradeoff compared to the RJS solution. The responsiveness of the application is dramatically improved and the server does not have to process many reasonably large Ajax requests. The downside is the initial page load is longer and the application may not be DRY. Each of these downsides needs consideration. The initial page load is longer but cacheable. If the headers are set properly on the JavaScript libraries they a user only has to download these files once until the browser cache is cleared. The entire client-server code base could be DRY if the sever uses JavaScript at least for validation and HTML templates. I have thought many times that since we are locked into JavaScript in the browser it is the natural language of the web. It makes sense to use it on both sides. In fact, it may be a disservice to a contractor's paying clients to use anything other than JavaScript on the server-side. These two downsides may not carry that much weight at all for this case study.
Discussion
I have not considered degradation paths in details because I have not included the code in blogApp.js
. Some users can cause real grief. For example, a user with Internet Explorer 6, JavaScript enabled but ActiveX disabled (and hence no Ajax) really throws a wrench in the works. The client-side app solution fairs much better in this situation then the RJS app.
You could argue that a solution like the second one presented, where all logic is on the server, should not return JavaScript but rather just data. This could be returned in the form of just the HTML to be inserted or XML or JSON. The client-side would know what to do with this data. I think that for the sake of this investigation the tradeoffs are the same for returning JavaScript remote procedure calls compared to HTML, XML or JSON data because the size of the requests and responses would be almost equal.
You could argue the user interface should be WYSIWYG like the FCKEditor. A WYSIWYG editor would fall in the full client-side app category. It is virtually the same for this discussion as the third solution presented above. The WYSIWYG editor would submit the HTML with bold tags that will be used for the blog article rather than form data with asterisks. As mentioned earlier, the client-side app presented in this article could also submit HTML generated for the preview rather than the form data.
Each of the three solutions presented is still appropriate today in particular situations. I am planning a big project that may use a full client-side app solution. There would still be times where I use a classic solution for a web page that isn't commonly used and doesn't need to be responsive. It all depends. What I have learned from this investigation is that RJS and similar solutions are probably too expensive for live previews. If the DRY principle is to be implemented and the logic must be on both the client and the server then some JavaScript should be used on the server.
Dave Thomas of the Pragmatic Programmers describes the above exercise of creating different solutions to a common problem as a Code Kata. Two other code katas lead me to this one: a to-do list similar to but simpiler than the to-do lists in Basecamp and a simple math calculator app from a book on building apps with the OS X Cocoa framework. I plan on reading more books about application building frameworks as JavaScript apps are still in a phase of "growing up". I hope the to-do list or calculator example results in another article about using the model-view-controller design pattern in JavaScript to implement the full client-side app type of solution described in this article. (See Steve Yen's Trimpath for one developers attempt at MVC in the browser.)
I am very interested to read the experience of others with solutions like the above. Please leave a comment about what tradeoffs swayed you to your choice in a particular situation. (Isn't it ironic that my prefab blogging software doesn't even include a preview. Not even the classic solution.)
Comments
Have something to write? Comment on this article.
We should hope that the browser developers will decide to include one of the powerful javascript frameworks (dojo, etc.) by default and make it the standard.
Thanks
Have something to write? Comment on this article.
nice article! In my experience the initial loading time of an entire Javascript application is still a pain for most users (even after using compression, nice looking spinning wheels and all these tools). Though after the application is cached I personally prefer the client side app since this enables all kinds of other features like resource friendlyness, off-line usage, etc.. We should hope that the browser developers will decide to include one of the powerful javascript frameworks (dojo, etc.) by default and make it the standard. Until then we have to live with different solutions for different purposes..