Engineering
established 11 years ago
Subscribe to this blog RSS Feed

MXMC_ENGINEERING

It really is strange to me how some trivially insignificant visual details can make such a dramatic difference in the impact of a piece of software.

I cut my teeth writing systems software mostly for NASA and the military. Functional and bare was the aesthetic I grew up with and as anyone who knows me it's pretty much what I've stuck with.

So I tend to think in the abstract. I don't like GUI's. I don't like colors. I prefer the command line. I enter a command and I understand the unseen effects that command has in the background. Sometimes I know whether or not it's working correctly based on the sound of the disk drives. (Yea, I haven't fully converted to SSDs yet.)

I remember back when I ran an ISP I used to be able to tell when there was a DOS attack by the patterns of lights blinking on the routers.

Spock may have been a huge influence on me.

So a huge hurdle for me has been to think in terms of how do I make the invisible workings of all this software I've been building accessible and visible for non-technical adventurer travelers? What things have I been neglecting as unimportant that actually really are critical?

It's been a huge learning curve and one I struggle with because it's so alien to how I think and how my mind seems to work.

So for the last several weeks aside from working through a list of 40 issues, I've been trying to find ways of making things more visually compelling. I don't have the funding to contract people with those kinds of skills to help me, so I'm kind of left to my own devices figuring things out by trial and error. I'll make some tweak and then ask people at the bar what they think.

For example, that MapLibre mapping toolkit I think so highly of has this feature where if you have too many markers on the map, you can set it up so that the markers coalesce into circles with numbers. It improves the performance and makes the map less cluttered as you zoom out. It's called their MarkerCluster feature.

But the thing was no one I showed it to seemed to understand you could simply touch the circles to zoom into what they hid. They'd look at it and see the circles with numbers in them along a track of my travels and mostly not react. They didn't see.

That was a huge insight. What they "hid". It was another example of me thinking in the abstract .. not visually.

I had not wanted to do it because the Marker Cluster stuff is hugely complicated, but I had a thought. I could write my own marker cluster implementation and set things up where I could search through all the things in a cluster and if there were any photos, pick the first photo I found and display that with a number next to it ... instead of just a circle. If you touch it it'll bring up that photo and then let you swipe back and forth over the photos and status updates and points of interest along your trip.

It was a huge effort. I didn't even know if it was possible or if I could even get it to work performantly enough. (I had to optimize it to only pull the photos as they were displayed otherwise it'd be too slow.)

As soon as I get it working and showed it to the first person they immediately knew to click on the a photo and were swiping through my last sailing trip completely unaware that they were the first person ever to do that ... and completely unaware they were using software I wrote.

There was an especially powerful moment when Aaron was using it to show some details about his last bike-packing trip to someone else ... which is the whole point. The longest part of any adventure is the time long after the adventure is over when you're telling tall tales at a bar or party somewhere with all your trip details in one spot in hand.

So I'm calling it a day after another marathon development session where I've just gotten offline local photo resizing to work. Now when you take a photo and it gets added to the map it shows you a nice high quality thumbnail ... and if you happen to take more photos in that general area and zoom out it clusters them picking on of the photos as the top of the cluster.

And even I recognize that this is much nicer than the ugly "no photo" placeholders I had before. 

I'm learning.

Slowly.

I haven't written anything here in ages upon ages. For some reason, probably fading illness, I'm feeling verbose tonight. 

For this crazy adventure travel planning/tracking platform and app I've been working on for ages, I use MapLibre for the basemaps. It's a fork of the last open source version of Mapbox before they heinously decided to change their license to something that wasn't workable.

I had pondered maintaining a fork of the last version of Mapbox myself, since without it my project would be dead, but I wasn't looking forward to it since it would be a ton of additional work.

I noticed a small group stepped up, forked it, and created the Maplibre project. I didn't hold out much hope because such open source efforts rarely succeed.

But I have to admit, I have been so pleasantly surprised, actually no, impressed beyond words, at how professionally well managed that project is by it's maintainers. These are some serious people and I could learn a lot from them.

I am grateful. It means it's one less project I have to maintain, and frankly with all the support they've gotten they're doing a level of work I wouldn't be able to touch.

But, because I'm using Ionic/Angular/Typescript (i.e. Javascript) I have to use the web browser based API ... which does not have any support for offline use.

For my project, it's a 100% requirement that the basemaps be downloaded to the phone, along with all other assets, images, data, whatnot and be completely usable off-grid.

Back with their 1.15 version, before they made major changes to the original Mapbox codebase, there was no offline solution, so I grabbed an old proof of concept another developer had released, and with a ton of work created my own "patch" that enables my hybrid phone app to pull base map tiles and assets from the local phone.

And it's worked like a champ ever since.

Unfortunately, Maplibre has moved on and radically changed things for the better meaning that the amount of effort it was going to take to update my "patch" to work with the latest version of Maplibre was going to be non-trivial. The additional downside is that I would have to continue to update this complicated patch every time they come up with a new version to keep in sync. No fun.

I was really hoping to avoid this until later this year, but one of my immediate core requirements is to be able to resize photos on the phone and store them while offline. As it stands right now, when you're off grid and you add some photo to the map, I can't create a high quality thumbnail for stupid technical reasons, so I put a black 'no photo' marker on the map and wait until the user gets back on grid, uploads their trip. I then resize the images, and send the thumbnails back down. Stupid.

However, I decided to bite the bullet and solve that problem and after a few hours of scouring, I found what looks to be a high quality in browser image resizer that doesn't generate pixelated images. Awesome.

Then came the bad news. It relies on an API called FileReader. Ok. It's standard API that every browser support but I couldn't for the life of me figure out why it was crashing with 'no such method' errors inside this apparently high quality solution.

Hours of digging and on the verge of punting for a lesser quality solution I came across a github issue where one of the plugins I rely on for the "patch" mentioned above, overrides the FileReader API ... and does so incorrectly.

(Seriously, if you're going to be override the FileReader API, you could at least do it correctly.)

As it is all those plugins that my patch rely on are legacy and unmaintained at this point, so they all need to go.

Time to bite the bullet.

And then I got stupid sick.

Aaron's bicycle trip is in two weeks. I have been working to get a new build put together for him for that trip that would address all the issues we uncovered during the camping trip several weeks ago. But it's been a huge effort. 40 issues total. 20 issues done. 20 to go.

Then this.

I had noticed some time ago that the Maplibre folks had added an API where one could add custom map tile handlers in a clean way. If I could get that to work it would mean I would no longer have to maintain my own offline base map patch and I could just keep up with whatever their release schedule was without hopefully too much issue. (Famous last words as i just spent half a week upgrading another component, Ionic from Ionic 6 to 7.)

But I only have two weeks. I was not at all convinced I'd be able to get a new offline solution developed and working with their new API, it's just a hook after all, in anything under a week. 

But as of as long ago as I've been writing, I actually have a first working version of Maplibre 3.1 with an updated offline map tiles solution that seems to work like a champ. 

It only took about 18 hours. 

18 hours to make three weeks or so of work from ages ago completely obsolete. 

But that's how it goes. 

It's working on Android. Tomorrow i'll get it working iOS, fingers crossed. and then it's back to client side photo resizing and the other 19 items on my list. 

The plan, once I finish up the Twitter Bootstrap conversion of the site, is to build a mobile app. I'm on the fence as to whether I want to try Appcelerator Titanium to re-use at least some portion of the code between platforms or whether I just want to go Native and target Android exclusively first. (I know, I know, you're supposed to bite the bullet, deal with Objective-C and target the iPhone first.)

Regardless, I need to get the Google SDK installed and the emulator working which proved to be far more challenging than I had initially expected due to a confluence of problems.

The Android emulator would hang when loading and would display the following error message:

Failed to load libGL.so
error libGL.so: cannot open shared object file: No such file or directory

This is easiest to see if you launch the emulator from the command line:

/path/to/sdk/tools/emulator -avd <name of avd>

At first I thought it was a missing 32 bit library issue as I'm on Ubuntu 64, but after some searching I came across this stackoverflow thread which indicated that symlinking libGL.so.1 to the sdk/tools/lib directory would resolve it:

ln -s/usr/lib/x86_64-linux-gnu/mesa/libGL.so.1 /path/to/sdk/tools/lib/libGL.so

After this, in my case, the emulator would just hang with the error:

FB::flushWindowSurfaceColorBuffer: window handle 0x4 not found
FB: closeColorBuffer cb handle 0x3 not found

I found this stackoverflow thread that indicated "host GPU support" is only available for API levels 15 and higher. Because I'm experimenting with Titanium I needed to use API level 14. The solution is to uncheck Host GPU support.

So now I have a functioning android emulator under Ubuntu 14.04 LTS.

The fine folks building HHVM released version 3.2.0 which I installed on my development box to try out only to find that suddenly I was getting nothing but 404's.

I downgraded to 3.1.0 and once again everything worked.

It took some crawling through the FastCGI code to realize that in 3.2.0 SCRIPT_FILENAME was wrong.

Checking through the changes to the FastCGI code on github I came across this commit:

https://github.com/facebook/hhvm/commit/80638d7e97db40753f7b374010dd78716c717e87

It turns out that in my nginx configuration I was inadvertently pulling in the standard fastcgi_params include file after I set my own values. If NGINX replaced a header if it's redefined or threw some kind of warning, I would have caught this some time ago. Why NGINX doubles up the headers, I don't understand. So I had SCRIPT_FILENAME defined twice and it was getting sent to HHVM twice. In 3.1.0, the first definition was used. In 3.2.0, the second one was used.

Oops.

The current site with it's ugly markup and abysmal CSS has outlived it's usefulness. More and more people are trying, unsuccessfully, to use the site from mobile devices. So, before I embark on adding any new features, I've decided it's time to move the site over to something that will work equally well on mobile devices as on desktop PCs.

"Dammit, Jim, I'm a programmer, not a designer."

I started looking at a few so called Responsive CSS Frameworks. I chose Twitter Bootstrap v.3.1 because it seems to already address the vast majority of patterns I use on the site. I initially thought, as so many people seem to, that it's going to make the site look too "generic" since all sites that use bootstrap seem to have a similar appearance. But after taking a closer look, I realized that it was going to, once I finished the conversion, save me so much time and make developing the UI portion of any new features so much easier.

Unfortunately, this did mean I would have to rework quite a few interactive features. I make heavy use of jQuery UI dialogs which would have to be rebuilt as Bootstrap Modals. You can argue that it's not a good UX pattern, but I do in places do stacked dialogs. To my chagrin the bootstrap documentation has this to say:

Overlapping modals not supported

Be sure not to open a modal while another is still visible. Showing more than one modal at a time requires custom code.

There were a few questions about this topic on Stackoverflow such as this one. Hacking bootstrap, or any third party project, is generally a bad idea unless you can get them to accept a patch. I really didn't feel like going through another round of getting patches approved so I embarked on trying to come up with a way to implement stacking modals external to Bootstrap.

Thanks to answers in that Stackoverflow thread, the solution is pretty straight forward. Using the hidden.bs.modal and shown.bs.modal event callbacks we can keep track of the number of modals opened at a time. Given these are modals and not dialogs, only one modal should be active at a time. The rest should be disabled.

I create a counter on the body tag for lack of a better place. Each time a modal is shown I increment the counter and then decrement it again when the modal is closed.

Using this counter, I can quickly calculate a z-index for the modal and it's corresponding backdrop that will be higher than any other modal and backdrop on the page. This satisfies the 'disable everything else' requirement.

Setting the z-index of the modal is no problem, but finding the corresponding backdrop div requires doing a search for tags with class .modal-backdrop. To make sure I'm setting the z-index only on the most recent one, I set the z-index and also add a .fv-modal-stack class to it. Then when subsequent modals are opened, I look for all backdrops that do not have a class of fv-modal-stack.

The advantage of doing it this way is that I don't have to patch Bootstrap each time I upgrade. I suspect eventually they will add support for stacked modals and this hack can be avoided.

Here's a working demo. View the source to see the code.

Chrome and FireFox behave very differently in terms of what happens when you attempt to select the position before a DIV usingrange.setStartBefore() when the DIV is preceded by a textnode.

Consider the HTML:

<div contenteditable="true" id="edit">foo<div id="test">bar</div></div>

I want to select the end of the textnode foo.. You use a range to accomplish this as in:

// for FireFox make sure the editable div is focused so we can see the cursordocument.getElementById( 'test' ).focus();// select the spot before the DIV.var sel = document.getSelection();var range = document.createRange();var dom_node = document.getElementById( 'test' );// this is the critical methodrange.setStartBefore( dom_node );range.collapse( true );sel.removeAllRanges();sel.addRange( range );// ask the browser what is selectedconsole.log( "selection is :", document.getSelection().getRangeAt(0) );

In Chrome, the resulting range has a startContainer of the textnode and a startOffset of 3, which conveniently points to the just past the o in foo. The cursor moves after foo.

However, in FireFox, the resulting range has a startContainer of the <div> and a startOffset of 1 which means its pointing at the textnode but doesnt give you any indication of where in the textnode its pointing. Also, the cursor moves before the b in bar.

It gets more confusing if the HTML is changed to:

<div contenteditable="true"><div>foo</div><div id="test">bar</div></div>

If the same experiment is run again, FireFox behaves as before and returns the editable <div> with an offset of 1 pointing at the element before <div id=test>, which is the previous DIV. That makes sense The cursor again is placed before the b in bar, which doesnt make sense to me.

In this situation, Chome does is completely unexpected.

The range returned by Chrome
has a startContainer with a textnode with length of 0
andnextSibling or prevSibling that are both null
but a parentNode that is set to the <div id=test>
angry-desk-flip-l.png

at least if Chrome Developer Tools are to be believed. I think its time for anotherStackOverflow question. You can see an example of this here.

This is a technical article covering how browsers muck up the underlying markup in an editable area as a user edits content. It turns out the problems center around opening up new lines using the ENTER key and merging them back together again using the BACKSPACE or DELETE keys.

If you dont go look you just wont know.

When you attempt to create an editor in a contenteditable div, the browser is in control and youre just along for the ride. Each browser has different ideas of what should be done to the underlying markup when a user presses ENTER to open up a new line or DELETE/BACKSPACE to merge lines back together. This conspires to make writing an editor that behaves as you would expect somewhat challenging.

In trying to develop this code, I realized that I didnt fully understand how the various browsers were interfering with the underlying markup as the user typed. I kept getting surprising results. So I decided to go look and carefully document what Ive found. Im putting this up here in the hopes that it might save someone else a bit of annoying work.

Exploring what happens on ENTER

I first explored all the cases related to the ENTER key that I could think of such as:

  • ENTER at the beginning of a line.
  • ENTER at the end.
  • ENTER in the middle.
  • ENTER in a line with pre-existing BR tags.
  • ENTER in a line with SPAN tags.
  • etc.

What I found was, in many cases, the browsers did not behave at all as I would have expected. To my surprise, MSIE (10) behaved more in line with my expectations than the other browsers. WebKit was the worst but FireFox likes to muck up the works with the best of them.

The markup is what was displayed using the developer tools. In MSIE, I used FireBug lite. In Chrome I used the Google Developer Tools and in FireFox I used FireBug proper.

Here are the results upon pressing ENTER in various circumstances:

as the first character in an empty editable div

WebKit:

<DIV><BR></DIV><DIV><BR></DIV>

FireFox:

<BR _moz_dirty=""><BR _moz_dirty="">

MSIE:

<P/><P/>

NOTE: Because of the <P>s MSIE lines appear double spaced.

after opening a new line, going back to the first and entering a character

WebKit:

<DIV>t</DIV><DIV><BR></DIV>

NOTE: THE BR DISAPPEARS

FireFox:

t<BR _moz_dirty=""><BR _moz_dirty="">

NOTE: pressing ENTER once inserts a single BR but does not open a new line.

MSIE:

<P>t</P><P/>

after opening a new line and entering a character on the second line

WebKit:

<DIV><BR></DIV> <DIV>t</DIV> -> BR DISAPPEARS

FireFox:

<BR _moz_dirty="">t<BR _moz_dirty="">

MSIE:

<P/><P>t</P>

end of first line with trailing BR
(initial content<div contenteditable=true>test<BR></DIV>)

WebKit:

test1<BR> -> UNEXPECTED<DIV><BR></DIV>

NOTE: INITIAL CONTENT SHOWS ONE LINE INSTEAD OF TWO.

FireFox:

test1<BR _moz_dirty=""><BR> -> UNEXPECTED that both BR's would show up.

NOTE: INITIAL CONTENT SHOWS ONE LINE INSTEAD OF TWO.

MSIE:

<P>test1</P><P><BR></P>

NOTE: INITIAL CONTENT SHOWS TWO LINES.

end of first line with embedded BR between lines
(initial content <DIV>test1<BR>test2</DIV> )

WebKit

test1<DIV><BR>test2</DIV>

FireFox

test1<BR _moz_dirty=""><BR>test2

MSIE

<P>test1</P><P><BR/>test2</P>

adding a character to initial content of <DIV><BR></DIV>

WebKit:

<DIV>t</DIV> -> UNEXPECTED. BR gets removed even though it was not added by WebKit

FireFox:

test1<BR _moz_dirty=""><BR>test2

MSIE:

<P>test1</P><P><BR/>test2</P>

after a line of text

WebKit:

test<DIV><BR></DIV>

FireFox:

test<BR _moz_dirty=""/><BR _moz_dirty="moz"/> -> DAFUQ??

MSIE:

<P>test</P><P/>

before a line of text

WebKit:

<DIV><BR></DIV>test

FireFox:

<BR _moz_dirty="">test

MSIE:

<P/><P>test</p>

in the middle of a line of text

WebKit:

te<DIV>st</DIV> -> UNEXPECTED

FireFox:

te<BR _moz_dirty="">st

MSIE:

<P>TE</P><P>ST</p>

before an image

WebKit:

<DIV><BR></DIV><IMG SRC="...">

FireFox:

<BR _moz_dirty="" /><IMG SRC="...">

MSIE:

<P/><P><IMG SRC="..."</P>

after an image

WebKit:

<img src="..."><DIV><BR></DIV>

FireFox:

<IMG SRC="..."><BR _moz_dirty="" /><BR _moz_dirty="" type="_moz" /> -> DAFUQ??

MSIE:

<P><IMG SRC="..."></P><P/>

between two images

WebKit:

<img src="..."><DIV><img src="..."></DIV> -> UNEXPECTED

FireFox:

<IMG SRC="..."><BR _moz_dirty="" /><IMG SRC="...">

MSIE:

<P><IMG SRC="..."></P><P><IMG SRC="..."></P>

before a span Using <SPAN>test</SPAN> as initial editable content.

WebKit:

<DIV><SPAN><BR></SPAN></DIV> -> UNEXPECTED

NOTE: can be explained by noting the cursor cannot be moved outside of the span in WebKit

FireFox:

<SPAN><BR _moz_dirty="" />test</SPAN> --> UNEXPECTED

MSIE:

<P><SPAN/></P> <P><SPAN>TEST</SPAN></P> --> UNEXPECTED

after a span

WebKit:

<SPAN>test</SPAN<DIV><SPAN><BR></SPAN></DIV> -> UNEXPECTED

NOTE: can be explained by noting the cursor cannot be moved outside of the span in WebKit.

FireFox:

<SPAN>test<BR _mod_dirty=""/><BR _moz_dirty="" type="_moz" /> --> DAFUQ?? UNEXPECTED</SPAN>

MSIE:

<P><SPAN>test</SPAN></P><P><SPAN/></P>

in the middle of a span

WebKit:

<SPAN>te</SPAN><DIV><SPAN>st</SPAN></DIV>

FireFox:

<SPAN>te<BR _moz_dirty=""/>st</SPAN>

MSIE:

<P><SPAN>te</SPAN></P><P><SPAN>st</SPAN></P>

Exploring what happens on DELETE/BACKSPACE

The other problematic case seems to be when lines are merged back together through the use of the DELETE or BACKSPACE keys. (The same situation likely arises in a multi-line selection thats typed over.) In the tests I move the cursor to the end of the first line and press DELETE to merge the lines together.

merging two lines of text separated by a <BR>
(initial content test1<BR>test2)

WebKit:

test1test2 (as two separate text nodes)

FireFox:

test1test2 (as a single text node)

MSIE

test1test2 (as a single text node)

two spans separated by a BR
(initial content <span>test1</span><BR><span>test2</span>)

WebKit

<span>test1</span><span>test2</span>   -> BR got deleted. inconsistent behavior

This seems like inconsistent behavior because typically you cannot get behind a <SPAN> in webkit.

FireFox

<span>test1</span><span>test2</span>

MSIE

<span>test1</span><span>test2</span>

text followed by a BR and SPAN
(initial content test1<BR><span>test2</span>)

WebKit

test1<span>test2</span>

FireFox

test1<span>test2</span>

MSIE

test1<span>test2</span>

text followed by a div
(initial content test1<div>test2</div>

WebKit

"test1""test2"    as two separate text nodes

FireFox

test1test2   as a single text node

MSIE

test1test2   as a single text node

two divs
(initial content <div>test1</div><div>test2</div>)

WebKit

<div>test1test2</div>    (as two text nodes

FireFox

<div>test1test2</div>

MSIE

<div>test1test2</div>

text followed by <BR> followed by DIV
(initial content test1<br><div>test2</div>)

WebKit

test1test2  as two text nodes --> UNEXPECTED, but makes sense after a bit of thought.

FireFox

test1test2

MSIE

test1test2

divs ending in BR
(initial content <div>test1<br></div><div>test2<br></div>

WebKit

<DIV>test1test2     as two text nodes --> UNEXPECTED. BOTH BRs gone.</DIV>

FireFox:

<DIV>test1test2<BR></DIV>

MSIE First DELETE has no visible effect on screen but consumes the BR:

<div>test1</div><div>test2<br></div>

There are obviously many other test cases that could be explored. Interestingly, as I was formatting this article I noticed that the WordPress WYSIWYG editor suffers from some of the same problems Ive identified above.

Merging lines turned out not to be as unexpected as I had feared however it did provide the interesting insight that MSIE and FireFox apparently merge multiple adjacent text nodes together while WebKit does not. This explains at least one bug Im seeing in my code.

This is a technical article.

I am building a jQuery plugin to as areplacement for the venerable HTML TEXTAREA tag so I can do some fanciness.

In my textarea plugin I want to be able to insert blocks of HTML into a contenteditable DIV that should be treated as single objects. These can be wrapped in a SPAN or a DIV.

One of my requirements is that, I should be able to move the cursor over them. If Im to the left of the object and press the right arrow key, the cursor should jump to the end of the object and visa versa.

So my approach is to hook the onKeyDown event and check whether or not I am next to an object. In the handler I know which key on the keyboard was pressed by inspecting the event.which property. ( I hope to discuss the horrors of event handling in keyboard event handlers in another article.) So I know whether or not the LEFT or RIGHT arrow keys have been pressed.

But I also need to know WHERE the cursor is and I have to be able to select where it should go.

To get the current cursor position you have to use the DOM SELECTION and RANGE features, in modern browsers. MSIE added these features in IE9. Before that it used its own system.

Selections and ranges primarily deal with when you use the mouse (or SHIFT KEY and ARROWS) to select a block of content on a page. However, they are also used when you click the mouse at a given location or, in the case of a contenteditable block, where youve moved the cursor.

To get the current cursor position in the editable div you can use:

var sel = window.getSelection();var range = sel.getRangeAt(0);

The range object includes a bunch of properties. You have to remember that in a contenteditable div the underlying data structures are DOM nodes. Its important to note that there are different kinds of nodes present in a DOM (Document Object Model) structure. For my purposes, I am interested in two kinds of nodes: ELEMENT nodes and TEXTNODES.

ELEMENT nodes represent HTML tags. They can be containers that contain other tags. They may be <BR> tags representing a line break, which cannot contain other nodes. (foreshadowing) TEXTNODES represent text content, the written word.

TEXTNODEs are treated differently from ELEMENT nodes. Its important to know the jQuery seems to almost entirely ignore TEXTNODEs. The .from() and .next() methods in jQuery do not ever return TEXTNODEs.

So we want to get the cursor (a,k.a. caret) position. But we get a range object back. Since its a range, the range may start on one kind of node, say a text node at a given character offset, and then span a bunch of other nodes and end at some other textnode or similar at an offset.

So the range gives you the startContainer, which is the node where the range starts, the startOffset, which is the offset into that node (or character position if its a text node). The same occurs for the end of the range. There is also another property which is the deepest node in the tree that contains the entire selection.

The caret position, startContainer and endContainer will be the same as will be the case for startOffset and endOffset. There a convenience property collapsed that if true lets you know youre dealing with a single point select, i.e. the caret/cursor position.

But a single point range does not always give you a character offset in text.

Read on.

It seems simple enough. If I can move the cursor somewhere, I had assumed there would be a text node there that the cursor would occupy.

thinking_guy.jpg

This assumption cost me quite a bit of time.

Consider the following block of HTML

<div id="div" contenteditable="true">this is some text<span id="span1">TEXT IN A SPAN</span><br><span id="span2">SOME OTHER TEXT IN ANOTHER SPAN</span></div>

And a little bit of jQuery/Javascript:

$( '#button' ).bind( 'click', function()   {   var sel = document.getSelection();   var range = sel.getRangeAt(0);   alert( "container is '" + range.startContainer.nodeName + "'" );   });// now move the cursor AFTER the second span programmatically$( '#button2' ).bind( 'click', function()    {   var span_node = $( '#span2' ).get(0);   var sel = document.getSelection();   var range = document.createRange();   range.setStartAfter( span_node );   range.collapse( true );   sel.removeAllRanges();   sel.addRange( range );   var new_range = document.getSelection().getRangeAt(0);      alert( "New container is '" + range.startContainer.nodeName + "' with offset '" + range.startOffset + "' childNodes.length '" + range.startContainer.childNodes.length );   // for the benefit of FireFox   $( '#div' ).focus();   });

You can see this test here:http://jsfiddle.net/7cYff/5/

If you click on the world ANOTHER then move the cursor to the right as far as you can with the arrow keys and click the first button, which will query the range and print out the name of the node that the caret is currently, we see that we get a TEXTNODE. Ok, that makes sense.

The second button in the example, uses the RANGE selection method setStartAfter() to move the current selection AFTER the span. If you press this button you will notice that we get a startContainer of the parent DIV with an offset of 4.

This confused me for quite some time as I didnt understand what this was supposed to mean.

Each DOM node has a property called childrenwhich I incorrectly believed contained all the children of that node. That array did not have 4 entries in it. It turns out, that

The children DOM Node property does not, in fact, contain the children of a node

It only contains the child ELEMENTS of a node. The childNodes property contains all children of a node including textnodes and the like.

If you inspect the childNodes property, you will notice it only has 3 entries, not 4. So we have gotten an offset OUTSIDE of the range of our childNodes.

I should point out that the select addRange() method has no return code and when you select a node that cant be selected you get no exception:https://developer.mozilla.org/en-US/docs/DOM/Selection/addRange

Now for the icing on the cake, try typing something into the jsFiddle after pressing the second button and right click -> Inspect Element.

The text you just entered was put INSIDE the SPAN.

angry-desk-flip-l.png

In Chrome and Safari, you cannot enter anything after the end if theres an element there. So imagine its a thumbnail or an @mention. You do the @mention and start typing, youll be typing INSIDE the @mention span.

Now repeat the experiment using FIreFox. It seems to behave more or less the same way EXCEPT the contenteditable DIV loses focus which is why I call the .focus() method in the example above.

Now repeat the experiment. Press the second button, start typing and then inspect it using FireBug (youll have to install the FireBug extension). You will notice the text you just entered is AFTER the span.Same code, different behavior.

In FireFox, if you call range.setStartAfter() a container and start typing the text will be entered after the container.

What about the reverse? Lets say we have the following:

<p>Test1:</p><div id="test1" contenteditable="true">The cursor will jump here -><br><span id="span1" contenteditable="false">NOT EDITABLE</span> [click here] Move cursor left using left arrow key. If you are in Chrome of Safari you will not be able to move the cursor to the beginning of this line.</div>

See it here:http://jsfiddle.net/YyTEP/

You can mark elements inside a contenteditable as not editable by adding contenteditable=false. In Chrome, this is nice because itll automatically jump the cursor over the item for you. No having to worry about checking to see if youre inside. It also prevents the user from clicking inside the range of the element. It would have been so nice if this actually worked. (Foreshadowing.)

In Chrome, click the mouse on the [click here] in the first block then use the left cursor to move over the NOT EDITABLE span. To my shock and horror, the cursor will move to the LINE ABOVE! If you move it to the right, itll jump back to the place before the NOT EDITABLE.

If you do the same thing in FireFox, you get the same result using the left arrow key. But if you then use the RIGHT arrow key, Chrome jumps back to the end of the SPAN but Firefox moves the cursor in front of the span.

angry-desk-flip-l.png

(One meme, so appropriate for a wide range of situations.)

It becomes more complicated, if you have something like the following:

<div id="test" contenteditable="true"><div id="div1">FIRST DIV</div><div id="div2">SECOND DIV</div><div>

And you attempt, using code similar to the previous example, to select the node after DIV id=test1?. You can see the example here:http://jsfiddle.net/jdmDj/4/

In Chrome, if you click the button, you get the main DIV as a parent and a startOffset of 1, which is the second div. If you start typing, as in the previous example, the content you enter will go into the div, not between it.

In FireFox, however, the same experiment will cause the content to be inserted between the DIVs and a new line will open up.

For completeness sake, what happens if you try to select the space before the first div?

Using the same markup above and changing the setStartAfter() to setStartBefore(), can we select the spot BEFORE the id=div1? div? See the example here:http://jsfiddle.net/jdmDj/5/

In this situation we get a 0 for startOffset which represents the id=div1? div. We asked for the node in front of the first DIV and we got that DIV back without any error. This works the same in Chrome as in Firefox however as has been the case with the examples above, in Chrome the text you enter in this location will get entered into the beginning of the first DIV while the content entered in FireFox will cause a new line to be opened up in FRONT of the first DIV.

In summary:

Using WebKit browers (Chrome and Safari), you cannot select the locations before the first element, after the last element or between any two elements. Any text you enter will be entered into the beginning or end of one of the adjacent elements depending on circumstance.

What does this mean?, the business guys will always ask. It means that if you insert something at the beginning of a line theres no way to get in front of it with the cursor to insert something before it. If its at the end of a line theres no way to get behind it to continue typing after it.

This sucks.

So I considered adding an element before and after each thing I might want to insert. This sucks because its going to muck up the way the content looks. I thought about adding a textnode with a space in it before and after each element I insert. This would allow me at least to select it in Chrome/Safari, but it would be ugly because the users would get confused why this extra space was getting added.

So I posted a question on StackOverflow:

How to set caret/cursor position in a contenteditable div between two divs.

Interestingly, I got two relatively notable developers to respond. Tim Down who wrote theRangy cross browser range selection library. He clearly understands these range related subjects far better than I do. He said:

Browsers are inconsistent on this. Firefox will let you position the caret in more positions than most browsers but WebKit and IE both have definite ideas about valid caret positions and will amend a range you add to the selection to conform to the nearest valid position. This does make sense: having different document positions and hence behaviours for the same visual caret location is confusing for the user. However, this comes at the cost of inflexibility for the developer.

This is not documented anywhere. Thecurrent Selection specsays nothing about it, principally because no spec existed when browsers implemented their selection APIs and there is no uniform behaviour for the current spec to document.

I also received a response from Reinmar who describes himself as aCKEditor core developer & JavaScript ninja. CKEditor is one of the premier HTML WYSIWYG editors out there and is all about manipulating content in contenteditable areas. They have had such trouble with this very issue that theyve implemented a completely different solution to get in front of things which involves hovering a mouse over the area and clicking on a little bar to open up a newline. Ive played with it and it seems to me to place more of a burden on the user than at least my users are willing to accept.

But it was something that Tim Down said that gave me an idea.

,,, create elements with, say, a zero-width space character for the caret to be placed in and place the caret in one those elements when necessary. As you say, ugly.

Heres a case of being too old school. I grew up in ASCII, and to a much lesser degree EBCIDIC. ASCII does not have an invisible placeholder character that one might use to create an apparently empty text node.

However, UNICODE has a zero width space character, code \u200B.

Immediately a path opened up. It would be challenging because of all the edge cases, but if I could wrap any of my elements in zero width space characters as they are being inserted I would have a way of selecting the space before and after the element regardless of whether or not it was:

  • at the beginning of the editable text
  • at the beginning of a line
  • at the end of a line
  • right next to another element

The downside to this approach is that its a bit ugly. If you go interspersing zero width space characters everywhere and the user wants to use an arrow key or a backspace over them, they will each consume a keystroke without moving the cursor. You can see this in this fiddle:http://jsfiddle.net/BxapL/

Just click on the [click here] and use the arrow keys to move left. Youll press the arrow key at some point and the cursor will not move.

In order to not confuse the users, for all key presses, arrow key moves, and searching whether we are right next to an object or not (for highlighting purposes) any zero width space characters will need to be skipped over so that users does not know they are there.

This means managing every keystroke in onKeyDown and in some cases onKeyUp for all arrow keys. But it also means being careful to catch an ENTER key and fixing up anything on the previous line. Chrome wraps each line of text in a <div> </div>. Internet Explorer uses <p> </p> and FireFox does the worst of the bunch by adding <br _moz_dirty="> for a new line.

Chrome also adds:

<div><br></div>

whenever the ENTER key is pressed to open up a new line. It then unceremoniously deletes the BR as soon as the user enters some text . sometimes. Other times itll leave <BR>s lying around inside the DIVs but I have not yet been able to identify any particular pattern to it.

So, this is the approach I have mostly implemented. And it works, mostly. Unfortunately, it is still relatively easy to get the DOM nodes behind the scenes to be out of whack.

The Relevant Bugs

The above algorithm would have worked so well and solved so many of my issues if it werent for some really nasty browser bugs Ive run into.

The contenteditable=false bug

Unfortunately, setting contenteditable=false to my inserted objects breaks Chrome and Safari. After all that work and getting it to work what I thought was flawlessly, I inserted an object in the middle of a line and tried to merge the line with the one above it by pressing BACKSPACE at the beginning of the line. To my shock, the embedded object and the remaining text on the line disappeared.

angry-desk-flip-l.png

You can see this in action here.http://jsfiddle.net/pmARP/

So this means I cant use the contenteditable=false trick to take care of making sure the cursor jumps over the embedded objects automatically. I have to check each time the cursor moves, an object is inserted, the mouse is used that the cursor hasnt made it into the confines of an embedded object and then I need to move the cursor out of it. Because of all the range challenges described above and the fact that for some reason my zero width space characters are getting deleted, this has proven to be the core of my difficulty.

I could just try to educate my users to press the right arrow key a couple of times and that would work around it, but that would spell doom for my little social network. It has to work and it has to work well.

The display: inline-block bug

I wanted to put a border around my objects when the cursor is close to them or when theyre clicked on. But I also wanted to be able to display my objects inline. An @mention isnt much good if you have to put it on a separate line.

If you use a SPAN tag, you cant set a border because its an inline element.

If you use a DIV tag, you can specify a border but its a block level element and opens up a new line.

Enter the new display: CSS property inline-block that gives you the best of both worlds. This was great. I liked being able to do the outlines. But then I tried to press the UP and DOWN arrow. I spent two days trying to figure out what in my code was causing the up and down arrow not to work. Eventually I posted this question to StackOverflow:

Chrome DIVs with display: inline-block breaks up and down arrows

It turns out to be a bug in WebKit browsers. So Ive had to opt to using SPANs for my embedded objects. No outlines since you cant put an outline on a SPAN.

jQuery AutoComplete Breaks UP and DOWN arrows in Firefox

The autocomplete trigger code is working pretty well. However, I discovered that in FireFox the UP and DOWN arrows would not work in a contenteditable div much in the same way that they didnt in Chrome. Initially, once again, I thought it was my own bug. Another day lost.

It turns out its some detail about how FireFox implements keypress events. There is a bug filed in the jQuery bug tracker about it. In the mean time, Ive hacked jQuery UI to work around it at least for the time being.

Conclusion

After working on this stuff for this long, I felt the need to explain to myself the problems Ive been running into and verify that I really did understand what I was seeing and was able to verify it. Spending to day writing this has highlighted a few things I did misunderstand which may explain some of the remaining problems I have in the implementation.

Ill spend tomorrow trying to update the code, which is now a few thousands lines long, to reflect these understandings to see if that improves things.

If I can get this thing to work it really will be a good little widget, but man I tell you this has been more of a pain in the ass. This article represents only a subset of the problems Ive run into during the development of this jQuery plugin.

If you know someone who fighting the same good fight, maybe you could pass along the article to them. Finding people who know about these issues has proven to be quite challenging.

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

Sorry, this is a bit of technical minutia having to do with using Javascript in modern browser to manipulate content on the fly. Its a bit technical but I do discuss the purpose behind it in terms of M-BY-MC and what youll be able to do on the site. If youd like a taste of the horror that is browser based development, read on

Its always something. Apparently, you cannot position the cursor into the empty space between two block level tags. (e.g. <div>something like an image</div>(cannot move cursor here)<div>like a block of text</div>) unless that spot in between contains at least one character. Thats what my testing here seems to indicate in Chrome. Ill have to try it with FireFox and the other browsers as well.

This endless minutia is annoying. If the method is called setStartBefore() you would think it would set the range BEFORE the node, no? especially when the documentation tells you, and I quote, Sets the start boundary of the range to be immediately before the specified node Its nuts because even with the keyboard typing into the contenteditable div you cant do it.

This is why I detest about this kind of development. First you have to figure out in Browser-ease how to say what you want to say. (e.g. setting a cursor position involves a thing called a selection and then a thing called a range and then collapsing the range so that the startOffset and the endOffset are the same. Walla! You have set the position of the cursor except .) Then you have to figure out how its broken in all kinds of annoying edge cases. (e.g. sorry, theres no way to put the cursor in front of that DIV the way you want to unless theres something there already.)

I figured this one out when I realized I couldnt insert characters between two objects in the @mention/#tag jquery plugin Ive been writing for the last week.

And this is after days of trying to figure out how to handle asynchronous lag in event handling when trying to figure out where the damned cursor is. When I type in @test into the box I want my little event handler, which gets called for each character entered, to check if an @ character has been entered. I need to know where its been entered so I can look at the characters around it to tell if its part of an email address like test@test.com, or whether its at the start of a word like @rob. In the latter case, I want to do the autocomplete. Imagine my shock and horror when I type in @test into the box, I get the character @ in my event handler and ask the browser to tell me what the character in front of the @ is and I randomly get t, e or s. (If it hadnt been for this issue I would have had this done last week.)

As with the rest of this, Ive agonized about the amount of time Ive been spending writing this plugin. Its just that the core social value that the formVista platform brings to the table is the linking of objects (think you have a forum post and in it you have a map and on the map you have a photo. Youre going to be able to view the photo in a gallery and then see every place that photo has been mentioned or shared on the site, given privacy settings, of course. The same is going to be true of every other kind of object.)

If Im typing away at speed adding some post to the forum when I go to insert a photo I want to see the thumbnail, not just some cryptic tag as is the case now. If I insert a map, I want to see a map. If I backspace over the thing I want it to disappear. If I move over it with the arrow keys I want the cursor to jump over the thing regardless if its left or right. (It turns out left is causing me all kinds of problems.). I want to be able to do Facebook style @mentions and Twitter style #hash tags with server side autocomplete, which I actually have working. And I want to have room to add support for any other kind of richtext thing I might want to add in the future.

Its the central feature that brings everything that Ive built so far together and its the thing that users will use more than any other piece of this whole system. As a result, this is where it really needs to be slick and work.

After all the work Ive done so far, it just didnt seem right to continue with the plain text box approach that I had in the previous version.

Honestly, of this entire nearly 6 month long project, it was this piece that I had the most trepidations about. I had hoped I was going to be able to use some of the existing @mention solutions that I had found. Initially they looked good but unfortunately, all the ones Ive been able to find are either too limited or too broken to use.

Once I have this @richmention beast built and tested, I plan to release it under an MIT style license and put it up on github. I also plan on writing up a detailed post about the plugin and the issues I ran into in the hopes that itll be of use to some poor fool who wants to write their own rich text editor widget thing.