3

I have a contenteditable div, something like:

<div id="text" contenteditable="true"></div>

What I would like is to place another div (or span, non-editable) at the start of #text, where the user cannot insert text before it, or even move the cursor before it.

I've tried variations of:

<div id="text" contenteditable="true">
    <span class="lead" contenteditable="false">This is some leader</span>
</div>

You can see a running version of this setup here.

One thing you'll notice is that I've inserted a blank character &#8203; after the </span>. Without it, the cursor (carrot) does not display properly - either in the wrong spot, or not at all.

I'm not sure why, but in the fiddle I've provided, you cannot delete (backspace) the span content. When I have this identical html running in my browser, from an HTML file, I can delete it. I would like the user to be able to delete it, if desired, but to not allow anything to be inserted in front of it. It should delete all at once - it's either there as a group (including the &#8203;), or not at all. Hence the contenteditable="false".

EDIT 1

It seems the non-deleting issue is purely the way the HTML is written. I've created a new post here commenting on it. That problem is solved, but the larger issue here is still ripe for the taking!

/EDIT 1

The closest I've gotten, using JS, is something like:

$('#testing').on('keyup', function(e){
    if($(this).html().indexOf(... leader ...) == 3){ // Needs to be 3, because the actual content of an editable div has markup in it

    }else{
        var splits = $(this).html().split(... leader ...);

        $(this).html(... leader ...);

        for(var i = 0; i < splits.length; i++){
            $(this).append(splits[i]);
        }
    }
});

which you can see running here.

What it does is checks to see if the desired bit is at the front. If not, it splits the content, and rearranges it so that the desired bit is back at the lead.

It's super buggy, has a lag after keyup, and prints the character before moving it around. It also doesn't work for the desired <span> element previously discussed, and also keeps the cursor at the start of the input field if it began there.

Sorry if I've presented it confusingly. I'm trying to think of a service that offers the same functionality, but can't think of any off the top of my head. FaceBook has something similar, when you type a friend's name into a comment box, but the content is editable and can be anywhere in the input. Shown below:

FB leading

FB middle

Thoughts? Anything to get me in the right direction is welcome!

Community
  • 1
  • 1
Birrel
  • 4,754
  • 6
  • 38
  • 74

2 Answers2

1

Check my question here: HTML contenteditable with non-editable islands

There are two possible solutions. One of it is to replace this:

<span class="lead" contenteditable="false">This is some leader</span>

by this

<button class="lead" disabled>This is some leader</button>
Community
  • 1
  • 1
c-smile
  • 26,734
  • 7
  • 59
  • 86
  • Not what I was looking for, but thanks for your suggestion. I played around with it for a while, but ended up going back to what I had. See my answer to this question for the full solution. – Birrel May 10 '16 at 07:39
1

SOLUTION!

Tada! <- Link to Fiddle (if too subtle)

This gal does EXACTLY what I'd like her do to, no questions asked.

First off, let us thank this post and this post for getting us on the right track.

Now, how it works:

HTML

Pretty easy setup here, almost identical to what I had originally, but all one line now.

<div id="testing" contenteditable="true"><span contenteditable="false" class="lead">Something something</span>&#8203;</div>

NOTE!! If you want to be able to delete the preceding <span>, you MUST put everything on a single line. Refer to EDIT 1.

CSS

Nothing special about this stuff, would work with or without it. Just how I've set it up, for the time being. Modify as you please.

#testing {
    padding: 5px;
    width: 90%;
    -moz-box-sizing: border-box;
    -webkit-box-sizing: border-box;
    box-sizing: border-box;
    font-size: 13px;
    color: #3B3B3B;
    margin: 0px 0px 15px 0px;
    resize: none;
    overflow: hidden;
    background-color: #FAFAFA;
    line-height: 15px;
    min-height: 42px;
    max-height: 160px;
    outline: none;
}
.lead {
    color: #000;
    background: #ccd;
    padding: 1px 2px 1px 2px;
    font-size: 12px;
    margin-right: 4px;
    border: none;
    -webkit-touch-callout: none; /* iOS Safari */
    -webkit-user-select: none;   /* Chrome/Safari/Opera */
    -khtml-user-select: none;    /* Konqueror */
    -moz-user-select: none;      /* Firefox */
    -ms-user-select: none;       /* Internet Explorer/Edge */
    user-select: none;    
}

JS

This is the meat and potatoes of the whole thing! I'm not going to cover the snippets of code from the two sources I mentioned, so deal with that yourself.

Variables

var somethingVisible = true;
var somethingsomething = '<span contenteditable="false" class="lead">Something something</span>&#8203;';
var spanContLen = $('.lead').html().length + 1;

somethingVisible (sV, henceforth) is to keep track of whether the <span> element is a child of the contenteditable div.

somethingsomething (ss, henceforth) is the <span> element, in entirety.

spanContLen (scl, henceforth) is the length of the text within the <span> element, plus 1 for the blank character. This is used for comparing the position of the cursor/caret.

And then the rest...

$('#testing').on('keydown', function(e){
    if(somethingVisible){
        var pos = getCaretPosition(this);
        if(pos < spanContLen){
            setPos(this);
        }

        if(pos <= spanContLen && e.keyCode == 8){
            $('.lead').remove();
            somethingVisible = false;
        }
    }
}).on('keyup', function(e){
    if(somethingVisible){
        var pos = getCaretPosition(this);
        if(pos < spanContLen){
            setPos(this);
        }
    }
}).on('click', function(e){
    if(somethingVisible){
        var pos = getCaretPosition(this);
        if(pos < spanContLen){
            setPos(this);
        }
    }
});

Not too bad, huh!?

Basically, it works like this:

  • If the user types, or clicks, in a position less than sol, move the cursor to the first spot after the <span>.
  • The keydown is particularly great at preventing the user from being a ninja, where they press a character key almost simultaneously as the up-arrow (this has the effect of placing a character before the <span> before the cursor has been programatically moved back)
  • If the cursor is in the scl position, or lower, and the user presses the backspace key (keyCode == 8), then remove the <span> element, and set sV to false.

It isn't perfect, I'm sure, but it works how I'd like it to. A user could be a giant, gaping butt, and disable their JS to get around the system. That's where you need to sanitize your input (duh!) to make sure your user isn't trying to wreck your stuff.

In the fiddle linked above, the "Add Back" button is there simply to insert a new <span> element, should you choose to delete the stock one.

I reckon you could add a button or something inside of the <span>, what for removing it with a click. Or other things. But that's all homework for you.

Community
  • 1
  • 1
Birrel
  • 4,754
  • 6
  • 38
  • 74