Friday, August 24, 2012

Getting HTML5 Ready - ContentEditable

Getting HTML5 Ready - ContentEditable:
In the prior editions of this series, we took a look at HTML5 parsing and CORS. This week, we'll take a look at a feature that, by itself, doesn't do anything terribly useful but, when combined with other features, can be used to do some interesting things. That feature is ContentEditable and it can be used to allow you to make any document regions editable, allowing a user to modify the text on the page.

contenteditable and designmode

There is really more than one aspect to contenteditable. While contenteditable can control making a specific DOM element editable, designmode makes the entire document editable, except those regions specifically marked as not editable. The user is free to modify the text or even do things like drag and reposition images in areas that are marked as editable.

On its own, this may not sound terribly useful since any changes you make to the page only last until you leave or refresh the page. Also, using something like designmode to allow editing of nearly any portion of the page can quickly become a mess. However, in conjunction with some JavaScript, you can easily see how this functionality could be used to create interfaces for WYSIWYG type editors and content management systems. In fact, in taking a look at the spec, and even the related editing API's, it seems like that is exactly what this type of functionality was designed to do. So, without further ado, let's walk through the spec a bit.

Examining the Spec

The WHATWG spec for contenteditable is actually very short, but as I mentioned, both the designmode attribute and the editing API's are related and are covered within the same section of the document. Let's take a look at what the spec says. As always, I preface this by saying I am no expert in reading web standards specifications, which can sometimes be a little tough to decipher.

The contenteditable attribute is defined as an enumerable attribute, which simply means that it accepts a set of predefined values, which in this case are "true," "false," or inherit. Obviously true means that, obviously, whatever document element this is applied to becomes editable by the user, while false, it goes without saying, means the opposite. Inherit on the other hand simply means that this elements contenteditable attribute is whatever its parent's contenteditable attribute says. According to the spec, if you use any other than these three values, your browser should throw a syntax error.

The spec also defines an isContentEditable property that, as expected, will tell you whether the element in question is editable or not by returning true or false. This seems pretty self-explanatory.

Designmode, on the other hand, is intended to be applied to the entire document and is set to either "on" or "off." Documents have designmode set to off by default.

The spec briefly mentions the editing API which is somewhat related to contenteditable in that it makes available a whole list of commands that you can execute against editable redions of the document. The spec doesn't go into detail about these (and seems to note in the little sidebar window that support is spotty) but if you are curious, they are explained a bit in this old post from the WHATWG blog. This blog post also links to an very basic example browser-based HTML code editor on Quirksmode that makes use of the execCommand() function.

Finally, the spec discusses a spellcheck attribute that allows you to enable or disable spelling and grammar checking on editable regions.

Browser Support

As usual, let's take a look at some example code before we examine browser support for that code. In this case, I have written two examples.

designmode

The first demonstrates turning on designmode for an entire page, and how you can override making a specific section editable. It also demonstrates something mentioned briefly in the specs, which modifies the white-space setting on editable content to get the behavior you'd likely expect. First, let's look at the code (for the sake of brevity I am removing the lorem ipsum text from the code below).

<html>
<head>
<title>Designmode Example</title>
<style>
div {
   width: 49%;
   padding: 3px;
   float: left;
   border: 1px solid #FDF6E8;
}
.prewrap {
   white-space: pre-wrap;
}
/* can't select on contenteditable attribute though */
div[contenteditable="true"]:hover {
   border: 2px solid #F4DC2C;
   background-color: #FDF6E8;
}
</style>
</head>
<body onload="document.designMode = 'on';">
<div id="editable">
<strong>Go ahead and edit me.</strong> ...
<img src="Samuel_L_Jackson.jpg" width="100">
</div>
<div id="noteditable" contenteditable="false">
<strong>You can't edit me.</strong> ...
</div>
<div id="also-editable" class="prewrap">
<strong>I am also editable but my whitespace is set to pre-wrap. Try adding spaces at the end of the line to see difference.</strong> ...
</div>
</body>
</html>


You can view the page in action here. As you can see in the body onload function, I turn on designmode for the document. As you'll notice if you try the page, the content on the upper left (which indicates, "Go ahead and edit me") is editable as is the text on the bottom right (which says, "I am also editable"). However, the text on the upper right (which says, "You can't edit me") is not editable because the contenteditable attribute on the div is specifically set to false.

The only difference between the editable text on the top left and the editable text on the bottom right is that the text on the bottom right has it's white-space style set to pre-wrap. This comes from the recommendation in the spec. What this does is that instead of replacing multiple spaces entered by the user with non-breaking spaces it uses standard spaces, thereby not causing unusual line breaks. The pre-wrap functionality is more in line with what you'd see in your word processor.

Another note is that because we're using designmode here, I could not determine a way to set specific styles on editable content areas versus others, as you can with the contenteditable attribute (as you'll see below).

Chrome, Firefox

The examples work fine in both the current versions of Chrome and Firefox. One thing you may notice in these browsers is that the editable text with the photo included allows you to drag the image around within the text or even over to the other editable section.

Opera

The demos work fine as well in Opera with the only significant difference being that the image is not draggable.

Internet Explorer

IE9 required an additional line of code to make this demo work and subsequently behaves differently in an important way than the other browsers. You can see the IE specific version working here. First, I went ahead and moved the JavaScript to an onloadHandler() function where you will see the additional line of code.

function onloadHandler() {
   document.designMode = 'on';
   document.body.contentEditable = 'true';
}


For some reason, just setting designmode to on didn't cause anything on the page to be editable, but if you set the document's body to contenteditable, then everything does work. The div that was set with contenteditable turned off is not editable. The big behavior difference here is that now all divs can be resized and dragged around the screen, including the one with contenteditable turned off.

contenteditable

The contenteditable example is a very simple attempt to recreate a Facebook-like commenting system. You can see it in action here. Essentially, it uses a div to display the finished comments and to allow you to enter them, it simply turns the contenteditable property off. I rely on jQuery for doing the DOM manipulation and handling the events. Let's look at the code.

<html>
<head>
<title>ContentEditable Sample</title>
<style>
body {
font-family: Arial;
font-size: 14px;
}
#myPost {
width: 500px;
margin: 5px;
}
.comment {
margin-left: 100px;
margin-top: 2px;
padding: 3px;
width: 400px;
min-height: 40px;
font-size: 12px;
}
.comment[contenteditable="true"] {
border: 2px solid #FDF6E8;
}
.comment[contenteditable="true"]:hover {
border: 2px solid #F4DC2C;
background-color: #FDF6E8;
}
.comment-posted {
background-color: #EDEFF4;
}
.timestamp {
font-size:10px;
color: #CCCCCC;
}
</style>
<script src="jquery-1.7.2.min.js"></script>
<script>
$(function () {
   $("#myComment").keypress(function(event) {
      // watch for enter key pressed
      if (event.which === 13) {
         var timestamp, now = new Date();
         timestamp = "<br><span class='timestamp'>Posted on " + now.toLocaleDateString() + " at " + now.toLocaleTimeString() + "</span>";
         $("#myPost").append($("<div>").addClass("comment comment-posted").html($("#myComment").html() + timestamp));
         $("#myComment").html("");
      }
   });
   $("#myComment").click(function(event) {
      if ($(this).html() === "<em>Leave a comment</em>") {
         $(this).html("");
      }
   });
  
   $("#myComment").focusout(function(event) {
      if ($(this).html() === "") {
         $(this).html("<em>Leave a comment</em>");
      }
   });
});
</script>
</head>
<body>
<div id="myPost">
You think water moves fast? You should see ice...
</div>
<div id="myComment" class="comment" contenteditable="true"><em>Leave a comment</em></div>
</body>
</html>


Once again I took out the filler text in the sample for the sake of brevity. The key piece to this is that the div with the id of "myComment" has its contenteditable property set to true. The jQuery function that runs when the page is ready sets some eventhandlers on the content. It listens for hitting the enter key when modifying text in our editable div. As in Facebook, this will be construed as you are finished entering your comment as we have no submit button. It then appends a div with your edited content as a posted comment with a timestamp beneath the main div. The posted comments are not editable. It also adds some handlers to display and hide the "Leave a comment" text within the editable div.

I also borrowed a trick from Jack Osborne's post about contenteditable on HTML5 Doctor and set some styles and hover styles specifically to the editable content divs.

Chrome, Firefox, Opera

In my tests, the example worked fine on all the current versions of Chrome, Firefox and Opera. According to When Can I Use, contenteditable support in these browsers goes back several versions. In fact, the only current browsers without support are Opera Mini and Opera Mobile.

Internet Explorer

Actually, the contenteditable portion of this example works fine. The CSS selectors to style the editable divs do not work. Also, my code to handle hiding and displaying the "Leave a comment" text doesn't work, which is probably just a matter of my relying on the HTML contents to determine whether to do so (obviously, if this was something you'd be posting to production, you might choose a better method like using a data property perhaps).

What's Next

I suspect this may be more than you ever wanted to know about contenteditable. The good news is that you have pretty much across the board browser support for both the contenteditable attribute and the designmode property on the document. As noted, there are some quirks with behavior of designmode across browsers, but I don't know how useful designmode is anyway. As noted, I didn't cover the editing API, which may open up some interesting possibilities if you choose to explore it further.

Up next in this series of posts is SVG.

DIGITAL JUICE

No comments:

Post a Comment

Thank's!