AngularJS And HTML5 ContentEditable

Getting Angular to Play Well With HTML5 ContentEditable


HTML5 introduced a cool new attribute, which can be attached to any element to make it editable (contenteditable="true"). However, that's just the theory. To make that work, you have to use a data binding library (e.g. AngularJS, BackboneJS) or build your own using JavaScript.

I recently created a todos application using pure JavaScript (with jQuery) with a Node/Mongo backend, i.e. without using any data binding framework. This is a good exercise to ensure that you understand JavaScript well enough before you start leaning on hefty frameworks that obscure a lot of what goes on behind the scenes. If you understand JavaScript, you'll be better able to troubleshoot and fix issues that come up when you're using frameworks.

My pure JavaScript app is deployed on Heroku here. The code is on GitHub here.

Once I was happy enough with the pure JavaScript application, my next goal was to redo it using AngularJS (I had previously done some prototyping with Mustache, Handlebars, Backbone, etc.). I also decided to use Bootstrap in order to explore that in the process.

AngularJS Context


If you've played around a little with AngularJS, you know that one of its coolest features is the ng-repeat built-in directive. This directive can be applied to almost any HTML element to cause the element to be repeated for each member of your model. So, it's great for displaying something like a todo list. For example, a single ng-repeat="todo in todos" nested in a paragraph element would cause several paragraph elements to be added to the DOM, one for each todo in your model.

AngularJS uses the model to provide two-way data binding between your database and your displayed data. The idea is that AngularJS keeps track of your edits via the model and makes it easy to save those edits to a database at appropriate junctures.

Also, AngularJS provides a templating engine that allows you to easily control data formatting. So, I could do something like {{ todo.created_date | date }} in order to format the date using Angular's default date format. I could also specify a custom format.

First, Here Are the Requirements for What I Wanted to Achieve


I wanted to be able to edit my todos inline or in place. So I would be able to click to edit my todo description and have the enter key result in saving the edited todo. Sweet and simple. No forms or popups. Definitely no new pages! (Pressing escape or clicking outside the element being edited would cause it to lose focus, but not lose the edits until the user either returns to complete the edits and presses enter to save or performs some other action that causes the changes to be discarded.)

There's a bunch of directives people have written to get this to work, but none of them performed satisfactorily enough for me despite plenty of tinkering from me.

Along the way, I discovered HTML5's super cool contenteditable feature. Upon this discovery I was hell bent on using it rather than any other fancy approach. As it happens, there's no good documentation on how to get AngularJS (I'm using version 1.4 beta) with contenteditable.

I'll come back to this blog post shortly and provide more detail, but after struggling with this for a while, here's a summary of what I found out.

The Issue and the Resolution


The foundation for my effort was provided by Dmitri Akatov's library.

The odd behavior I saw was that if I used a template on a contenteditable element, as soon as I typed a single key, AngularJS would move the cursor to the beginning of the text. Very frustrating, of course. After trying a number of directives, libraries, etc., the solution that worked for me is rather simple.

If I am going to make an element editable, then I don't use a template. Instead, I provide a model for it, e.g. ng-model="todo.description". If I'm not making an element editable, I provide a template for it, e.g. {{ todo.created_date | date }}.

Careful observers will note that both model and template provide granular information about how the data maps to the model. So, you really should only need to provide one or the other. I learned this the hard way, because there are no good documents or examples on the web showing how to do exactly what I was attempting.

Caveats


Although contenteditable is cool to explore, be aware that support for it is still a bit shaky and will vary across browsers. There's a good discussion on some of the pros and cons here.

Next Steps


Over the next few days, I will test this solution out more thoroughly to look for any gaps. Then I will deploy the application to Heroku and add the code to GitHub. I'll return to this blog post and add links so that you can follow along more easily.

You can help to spread the good word by liking and sharing!

Thanks and cheers.


Update


Folks, as promised, here are the links.

App

Code

Comments

Popular posts from this blog

Milwaukee Appliance Hand Truck

My Experiments with the PICAXE 08M2+

Utility trailer buying guide