0

So, I'm playing around with the (AWESOME) Rangy text selection package. I'm editing text inside of a span with contenteditable=true, with the goal of having a text input element which allows individual styling of each character. Let me stress that last point -- each character should be in its own span. This works fine if I don't need to preserve the styling from one keystroke to the next -- I just do something like

var newHTML = field.innerText.split('').map(function(c){
    return ('<span class="letter">'+c+'</span>');
}).join('');
$(field).empty().append(newHTML);

and use Rangy's (1.3) saveCharacterRange() function to restore the selection, and it's all good.

The problem comes when I've already styled some of those spans, and I delete one, replacing it with new text. I want newly inserted text to be unstyled, but instead it gets inserted into the preceding (or following, if the selection is at the beginning) span. I've tried to work around this by explicitly collapsing the selection's region after (or before) the focusNode, but it seems to not allow the endContainer (or startContainer) to be anything other than a text node... I've even verified that I can create a text range which collapses where I want it, but the selection object's setSingleRange() seems to collapse back around the text node.

I tried inserting a new node and placing the selection wholly inside it, which does work when the new node has content in it before calling insertNode(), but I obviously can't be inserting extra content between each character...

Here's a fiddle demonstrating what I'm talking about. Thanks for any help!

samson
  • 1,152
  • 11
  • 23

2 Answers2

1

I think you're falling foul of a common issue: browsers (particularly WebKit) have specific ideas of where in the document the selection and caret are allowed to be. Here's a recent answer of mine on this subject:

https://stackoverflow.com/a/21591165/96100

Community
  • 1
  • 1
Tim Down
  • 318,141
  • 75
  • 454
  • 536
  • Yes, that seems to be the same issue. I toyed with this solution, but settled on using jQuery to create a new span and insert it after the focus node's parent element. This has other problems, and I'm starting to think I should go back to the empty span solution... My biggest issue now is finding a solution which plays nicely with inputting ornamented characters, i.e. `é` or Chinese input. – samson Mar 11 '14 at 17:54
  • I've tried to follow your suggestions from that post, and it vastly improved both the terseness of my code and its performance, but I'm still a bit lost. Maybe you can lend a bit of your expertise to figuring out what I'm missing? I've edited my answer below to reflect the current state of my attempts. – samson Mar 12 '14 at 19:42
0

Maybe somebody out there can expand on this, but I think save/restoreCharacterRanges() is the key to fixing this problem. I've got it working (assuming your selection region is collapsed) in this fiddle, though not the way I'd like -- this approach (removing the just added content and moving into a new span) causes a flicker between keyup and keydown. I'd really like to be able to move the cursor (on keydown) into a fresh span, which would appear seamless to the user...

... and here's a fiddle that works with long selections, but not pasting...

Update

I've switched over to the 'insert a <span>&#8024;</span> and move the selection region into it' approach, which seemed to be working great, until I started trying different browsers. The behavior is completely different in each browser! Chrome correctly moves the cursor into a 'blank' span when required, but then removes the accent; Firefox handles accented characters perfectly, but fails to move into the new span after deleting a character which follows a new span; Safari displays the accent mark then moves into the new span, leaving the accent mark orphaned... And all three will display the original bug when deleting an old letter which follows a new one! Also, it seems like the different browsers are reporting different keycodes when modifier keys are pressed. Is this a lost cause? Am I missing something? Here's a fiddle with the new code.

samson
  • 1,152
  • 11
  • 23
  • Welcome to the mess that is `contenteditable` (`ce` from here on). The more you want `ce` to behave in a *certain* way, the more obstacles you're going to encounter. You've discovered that browsers behave differently when it comes to `ce`. For my own [project](https://github.com/mangalam-research/wed) I've had to write lots of code to plaster over the differences (and there are probably cases I'm still missing). Support for input methods (to enter Chinese, or accented characters) adds a whole new level of complication. – Louis Mar 13 '14 at 12:40
  • Yeah, I'm starting to see that. Have you worked with `compositionstart/end/update` at all? – samson Mar 13 '14 at 19:28
  • Yup. Search for the composition event names in [this code](https://github.com/mangalam-research/wed/blob/master/lib/wed/wed.js). The technique I'm using for now is to have an `` element receive the key events I care about (including composition) and then insert the data into the editable document myself. If you are curious about caret movement, look at `nextCaretPosition`, `prevCaretPosition` [here](https://github.com/mangalam-research/wed/blob/master/lib/wed/domutil.js). There's also movement code in the first file I mentioned but it is highly project-specific. – Louis Mar 13 '14 at 19:36