Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Closing <ul /> tags in contenteditable without inserting superfluous tags

Working with an unordered (or ordered) list in a contenteditable gives me a headache.

Whenever I want to end editing the list by pressing ENTER twice the browser will close the <ul /> but inserts a <p /> (Firefox) or a <div /> (Chrome) tag that contains a <br />.
Example here

My goal is to avoid that superfluous <p /> or <div /> and instead just close the <ul />.
I have tried to modify Tim Down's solution which will prevent the browser to insert <p /> or <div /> when pressing ENTER and instead inserts a clean <br /> tag.
Example here

Unfortunately though, when using that solution the <ul /> is never closed by the browser since only <br /> tags are inserted inside the <li /> item.

So my question is:

How can I actively close the <ul /> by inserting a node or pasting html when pressing enter on the last empty <li />?

Update: In case the question is stll unclear: I am looking for a way to close the <ul /> without inserting <p /> or <div /> tags but just by inserting a good old plain <br /> instead

like image 947
Horen Avatar asked Jan 23 '13 16:01

Horen


People also ask

What does contenteditable do in HTML?

The contenteditable attribute specifies whether the content of an element is editable or not. Note: When the contenteditable attribute is not set on an element, the element will inherit it from its parent.

What does contenteditable mean?

The contenteditable global attribute is an enumerated attribute indicating if the element should be editable by the user. If so, the browser modifies its widget to allow editing.

How to make an HTML tag editable?

You can set the HTML5 contenteditable attribute with the value true (i.e. contentEditable="true" ) to make an element editable in HTML, such as <div> or <p> element.


2 Answers

Similar to your attempt, we modify the contenteditable when the user presses Enter: Demo

if (window.getSelection) { // w3c
    $('div').keypress(function (e) {
        var sel, node, children, br, range;
        if (e.which == 13) {
            sel = window.getSelection();
            node = $(sel.anchorNode);
            children = $(sel.anchorNode.childNodes);

            // if nothing is selected and the caret is in an empty <li>
            // (the browser seems to insert a <br> before we get called)
            if (sel.isCollapsed && node.is('li') && (!children.length ||
                    (children.length == 1 && children.first().is('br')))) {
                e.preventDefault();

                // if the empty <li> is in the middle of the list,
                // move the following <li>'s to a new list
                if (!node.is(':last-child')) {
                    node.parent().clone(false)
                        .empty()
                        .insertAfter(node.parent())
                        .append(node.nextAll());
                }

                // insert <br> after list
                br = $('<br>').insertAfter(node.parent());

                // move caret to after <br>
                range = document.createRange();
                range.setStartAfter(br.get(0));
                range.setEndAfter(br.get(0));
                sel.removeAllRanges();
                sel.addRange(range);

                // remove <li>
                node.remove();
            }
        }
    });

} else if (document.selection) { // internet explorer
    $('div').keypress(function (e) {
        var range, node, children;
        if (e.which == 13) {
            range = document.selection.createRange();
            node = $(range.parentElement());
            children = $(range.parentElement().childNodes);

            // if nothing is selected and the caret is in an empty <li>
            // (the browser seems to insert a <br> before we get called)
            if (!range.htmlText.length && node.is('li') && (!children.length ||
                    (children.length == 1 && children.first().is('br')))) {
                e.preventDefault();

                // if the empty <li> is in the middle of the list,
                // move the following <li>'s to a new list
                if (!node.is(':last-child')) {
                    node.parent().clone(false)
                        .empty()
                        .insertAfter(node.parent())
                        .append(node.nextAll());
                }

                // insert <br> after list
                br = $('<br>').insertAfter(node.parent());

                // move caret to after <br>
                range = document.body.createTextRange();
                range.moveToElementText(br.get(0));
                range.collapse(false);
                range.select();

                // remove <li>
                node.remove();
            }
        }
    });
}

Note that this doesn't handle the case where the user has selected something before pressing Enter. If you want to handle this case, you'll need to figure out if the user has selected the entire contents of the <li> (this doesn't seem like a trivial task), and if so, delete the contents and treat it the same as if the user pressed Enter in an empty <li>.

like image 123
Jeffery To Avatar answered Sep 30 '22 08:09

Jeffery To


I understand your question but it's not clear why such a requirement would exist. It might help to clarify that aspect.

Regardless, here's one idea. Why not replace or remove the empty <div> and <p> tags.

$(document).ready(function(){
    $("div").keyup(function(evt) { 
        $("div, p").filter( function() {
                $this = $(this);
            return !($.trim($(this).text()).length);
        }).replaceWith('<br />');
    });
});

Working Example: http://jsfiddle.net/4xyR2/9/

One issue I notice concerns how contenteditable affects the cursor using the above solution. I think Chrome and Firefox require the <div> and <p> so they can track where the cursor exists in the <div>. This comes from my observations while testing, not from a deep understanding on how the browser's interpret contenteditable.

Chrome (24.0.1312.52 m) appears to dislike the replace. I see weird cursor placement when I test it, but it works. Firefox (17.0.1) handles the replace nicely.

like image 25
JSuar Avatar answered Sep 30 '22 07:09

JSuar