As Ive been bemoaning onFacebook and Twitter for the last few weeks, after spending quite a bit of time looking far and wide for an adequate pre-existing solution and finding none, I have decided to roll up my sleeves, get my hands dirty, and build a replacement for the venerable TEXTAREA input box.
This has turned out to be such a vastly more difficult and annoying task than I could have imagined. As has always been the case with relatively low level browser based development, it has been a frustrating exercise in identifying and working around a seemingly endless series of interrelated implementation differences and browser bugs. Ive been at this for 17 days now. Of the entire Miles By Motorcycle refactoring effort, this one component has proven to be the most challenging. It was also the one Ive dreaded the most since the beginning. I simply hate doing this kind of work.
So, Yermo, why are you spending all this time on a simple text entry box?
In the good old days, the TEXTAREA was good enough. It allows you to enter text into a box which can then be submitted to a server. All forum posts and comment boxes on the current version of my Miles By Motorcycle site using these simple text entry boxes. Most sites still use them.
For some years now, my users have been able to post photos to the forum as in this example. (Personally, I cant wait to see that bike in person.) When entering the post, my users get a link that brings up a popup dialog where they can upload a photo and when they press POST this ugly marker gets added to the textarea box to indicate where the photo will go:
Of course, this sucks. If Im adding a bunch of photos to a post I sometimes forget what the names are and I lose my place and upload the same photo twice. For non-technical users, the marker can just be confusing and they often muck the markers up meaning I have to go into the posts and fix them. Really, you shouldnt even be able to click inside the marker at all.
I guess maybe Ive been influenced by the world after all. Left to my own devices Id leave it ugly. Pretty hurts. Pretty takes so much time and as Ive said so often I cant do sex-appeal to save my life.
But, unduly influenced by the world and trying to step up my game, what I really want is to have a thumbnail of the uploaded photo show up right there instead of the [img] marker. And I want it to be treated as a single thing. If I BACKSPACE over it or press DELETE on front of it, I want it treated as a single entity and have the whole thing deleted even if I decide to get fancy and add some decorations to the thumbnail like maybe a title or whatnot.
Ialso want to add @mentions as you see on Facebook, because they are so useful in making people aware of cool things on the site. There are so many times that Ive wanted to type @buffalo or @rshaug to let those guys know of some cool post or photo on the site, but I cant, so thats something while Im here I definitely want to build. I also want to tag things using #tags like you see on Twitter and other sites. Its just another one of those really useful things so I can find things later. When these items get added, I want decorate them differently in the content so they stand out and treat them as single object again, so that if I backspace over them they get deleted in one keystroke.
I also want to be able to add any kind of other thing in the future that I might want to add, of particular importance being maps. But I can also imagine formatted amazon links, emoticons, videos, etc. So clearly, whatever I build has to have an extension system.
But, you cant add anything like this stuff to a simple TEXTAREA. TEXTAREAs only do text; no images or any other objects. Theres also no mechanism for styling the text or changing the background color of the font.
But Facebook does it!, you might mention.
I looked at what Facebook does, and as you all know, when you add an @mention it shows up in blue. If you use the developer tools and look, you will clearly see a TEXTAREA. What Facebook is doing is layering a SPAN over the TEXTAREA and updating the SPAN as you enter text and using it to do some basic colorations which are layered on top of the text. As far as I can tell, the text in the SPAN has to align perfectly with the text in the TEXTAREA. Its a complete hack and prevents them from doing any of the fancy inline stuff that I want to do.
Because I knew what this was going to mean, I even tried doing it Facebooks way, and implemented a TEXTAREA with autocompletions. I had this intention of the user entering text into the TEXTAREA and updating some HTML SPAN with some content. But that approach quickly became more problematic than I wanted to deal with. Moreover, I knew it was an ugly hack the user would see and it wouldnt feel right. One thing I really dont want to do during this project is too many ugly hacks if I can avoid them.
So there is another way, but its a path fraught with peril and pain. All relatively modern browsers have a feature whereby you can declare part of a webpage as contenteditable which means the users is allowed to go in and muck with the page. You can click places that are editable and just start typing. What makes contenteditable sections difficult to deal with is the fact that there is no standard on how they should be implemented in addition to the fact that as the users types the browser is inserting and/or modifying the underlying HTML of the page. Each browser modifies the HTML in a different way. Ugh.
As a programmer, you can intercepts the users actions in the contenteditable section and do additional modifications to the underlying HTML. For instance, if the user enters I might notice the spaces around and decide to remove that text and replace it with the smiley face emoticon. I can also intercept any @mentions, display the jQuery autocomplete menu, and then insert an appropriately formatted block of HTML at that character position to represent the user I just mentioned. (Personally, I want to insert the users avatar thumbnail as well since usernames, like on Facebook, are not unique on Miles By Motorcycle. I think the thumbnail inline would be really cool.)
Actually, its this contenteditable feature of the browsers that make all those WYSIWYG in page editors possible. To type in this blog post, Im using the one that comes with WordPress for instance. Unfortunately, a WYSIWYG editor is not what I want. I really want something that does inserted object management better than such an editor.
It really seemed simple enough. Just text with inserted objects interspersed. It cant be that bad. Its not like Im writing a word processor. But since I want to insert my own objects that I want to treat as single things there are a few cases I need to consider:
- If the caret is on the left of an object and the user presses the RIGHT arrow key, the cursor should jump over the object. Same in reverse
- If the caret is on the right side of an object and the user presses BACKSPACE, the object should get deleted.
- If the caret is on the left side of an object and the users presses DELETE, the object should get deleted.
- If the cursor is next to an object, either left or right, the object should get highlighted somehow to indicate to the user they are next to the object.
- If the user clicks the mouse on an object, the cursor should move to the beginning of the object and it should get highlighted.
- If the caret is on the line above or below an object and the user presses the UP and DOWN arrow keys into the object, the cursor should jump to the beginning of the object and it should get highlighted.
- Selecting a range using the mouse should work as expected.
- Inserting and deleting lines should work as expected.
- Merging lines together should work as expected.
- Breaking lines should work as expected.
- Moving the caret in front of or behind objects should work as expected.
- The user should be prevented from getting inside an object and mucking with it, because that will just confuse them.
- The code needs to distinguish between an @mention and using @ in some other context. Same of the # mark.
- I should be able to programmatically insert objects in any location of the editable area and have it do the right thing.
As of this writing, I have a solution that is ugly but works more or less. It still fails in a bunch of edge cases I dont yet understand. I never would have imagined that getting the above list of requirements working would be as challenging, but there are some really nasty bugs in the Four Horsemen of the Apocalypse, namely Safari, Chrome, FireFox, and MSIE. I never would have guessed that it would be Chrome and Safari that would make this task so much more difficult than I ever imagined. (Foreshadowing, you cannot move the cursor in front of or behind an object in Chrome and Safari.)
It had been my intention to write a very long article outlining in detail all the problems I have run into and the workarounds I have attempted. After banging my head against a wall for so long and feeling just this gawdawful pressure to get this done, Im pausing to re-evaluate and make certain I understand exactly whats happening. Having one problem is usually not difficult. Two is also not a problem But when you are dealing with a dozen or more conflicting, confounding and related problems simultaneously, teasing out the interactions can be ridiculously time consuming. Fix problem one after a long slog, then fix problem two only to realize that that breaks your solution to problem one. Then do this for different problems in all browsers. Then realize the developer tools are lying to you, and maybe making things worse. You get the picture. Its like tuning a guitar. Tune the low E string and you have to go back and tune all the others. Once you do that you need to re-tune E. And so on.
So, Ive now laid the groundwork for what Ive been building. My intention now is to write a series of short articles on each individual problem Ive encountered, mostly to prove to myself I actually understand whats happening in enough detail to explain it. My hope is in explaining it to the ether, the problems that Im facing will become clearer and Ill be able to fix the last remaining problems before calling this phase of my many months long project done.
Man, I tell you what. I cant wait to get M-BY-MC done, not because I have any delusions of it ever being successful, but because I really want to do use it