I'm trying to allow users to edit a list (UL). In my attempts, it appears that contenteditable doesn't do anything special (like enforcing behind-the-scenes markup) -- it just gives the user a window into the innerHTML.
This is causing issues in that if there is not already a LI, and the user adds something, it doesn't get LI-ized. Similarly, if there are list items, but the user deletes them, then the LI gets deleted, and any new text is added without LI's. See http://jsfiddle.net/JTWSC/ . I've also found that it's sometimes possible for the cursor to "get outside" of an LI that does exist, but I can't reproduce consistently.
I have to include code, so this is what the "result" looks like:
<ul>whatever the user typed in</ul>
How do I fix this? I started down the path of a $('ul').keyup() handler that checks the html and wraps as necessary, but I was running into a handful of gotchas, like timing, losing focus on the element, having to refocus in the right place, etc. I'm sure it's possible if I work at it, but I'm hoping for an easier solution.
I built the following keyup/down handler in order to make my contenteditable <UL>s idiot proof.*
It does two things:
This is pushing my comfort-level on jquery and DOM manipulation, so there are probably a few things I could do better, but it works pretty well as-is.
//keyup prevented the user from deleting the bullet (by adding one back right after delete), but didn't add in li's on empty ul's, thus keydown added to check
$('ul').on('keyup keydown', function() {
var $this = $(this);
if (! $this.html()) {
var $li = $('<li></li>');
var sel = window.getSelection();
var range = sel.getRangeAt(0);
range.collapse(false);
range.insertNode($li.get(0));
range = range.cloneRange();
range.selectNodeContents($li.get(0));
range.collapse(false);
sel.removeAllRanges();
sel.addRange(range);
} else {
//are there any tags that AREN'T LIs?
//this should only occur on a paste
var $nonLI = $this.find(':not(li, br)');
if ($nonLI.length) {
$this.contents().replaceWith(function() {
//we create a fake div, add the text, then get the html in order to strip out html code. we then clean up a bit by replacing nbsp's with real spaces
return '<li>' + $('<div />').text($(this).text()).html().replace(/ /g, ' ') + '</li>';
});
//we could make this better by putting the caret at the end of the last LI, or something similar
}
}
});
jsfiddle at http://jsfiddle.net/aVuEk/5/
*I respectfully disagree with Diodeus that training is the best / easiest solution in all cases. In my situation, I have several contenteditable <UL>s on a page that are very in-line WYSIWYG (ie, not a lot of room for tinymce-style chrome) and used by casual, first-time, non-advanced users.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With