HTML contenteditable with non-editable islands

2019-01-08 17:00发布

问题:

I have sort of browser based WYSIWYG editor where users can edit documents-templates.

Document-template is an ordinary html with some special "merge code placeholders". Such template gets "instantiated" by replacing these placeholders by data coming from DB. This gives final document - an instance of the template.

My current approach looks like this:

<div contenteditable>
  Sample template with <input type=button class="mergecode" value="MergeCode1">.
</div>

(Online sample to play with: http://jsfiddle.net/tFBKN/ )

The input is not editable in such case and behaves as a solid block - exactly what I need. Users can delete such merge codes by clicking DEL or BACKSPACE as any other characters, etc. By having proper CSS for such input.mergecode I am able to achieve look-n-feel I want.

But with such approach I have three different problems in three different UAs:

  • IE - CSS { font:inherit } simply does not work there, so if the input is inside <b> like here <b><input value="test"></b> it does not inherit any font styles.
  • FF - Copy of fragment that contains <input> element removes that input from clipboard content so further paste operation inserts everything but not inputs.
  • GC - Click on {BACKSPACE} immediately after the <input> produces weird results (bug)

So I am looking for other ideas of how to represent non-editable inline-block alike "islands" in HTML.

Other approach that I've tried so far:

  • <span contenteditable="false">MergeCode1</span> - it does not work as most of UAs remove such node from selection. So it is not possible to, say, apply <b> or <i> on top of selection that contains such span.

Any other ideas?

回答1:

I'm a CKEditor developer. We've got some experience with nested readonly elements (i.e. placeholder plugin and the feature we're working on currently #9764) and I don't have good news. You have to handle every behaviour manually if you want to have consistent look&feel. There are no tricks that will fix browsers. And many things (like this with weird things happening around input on GC) seem to be unsolvable.



回答2:

One more idea that looks promising:

To use empty span with ::before { content:"caption"; } that should produce non editable block represented in in DOM as a node having no caret positions inside.

You can try it here http://jsfiddle.net/TwVzt/1/

But this approach is not free of problems (in my case): There is no way to declare ::before inline using style DOM attribute and so the whole set should be declared in CSS upfront. But set of merge codes I have is pretty large, even unknown upfront in full in some use cases. Sigh.

Nevertheless putting this recipe here if someone will have better circumstances (known set of codes).



回答3:

I know its an old thread, which I came across with similar problem. Just needed few non editable spans in editable div. Ended up implementing a HACK-AROUND like...

$('#testDiv').on('keydown', function(e) { 
    var targetNode = getSelectionStart();
    if(targetNode != undefined && targetNode.nodeType === 1 && targetNode.nodeName == 'SPAN')    
    {
      var nodeHtmlString = targetNode.outerHTML;

      if(~nodeHtmlString.indexOf("nonEditable"))
      {
        e.preventDefault();
        e.stopPropagation();
      }
    }
});

function getSelectionStart() {
 var node = document.getSelection().anchorNode;
 return (node.nodeType == 3 ? node.parentNode : node);
}

Complete fiddle at https://jsfiddle.net/fxmb3nL6/20/



回答4:

Solution below -- with a small caveat. But first -- you guys are awesome!
I am not a JS or HTML expert, but I also did not know about jsfiddle.net. So reading your comments plus other online searches, and some testing on jsfiddle I created the following code that worked.

<pre><code>
<div contenteditable="false" class="editor" readonly="true" UNSELECTABLE="ON">
    Sample template with <span class="mergecode test1" />.
    <span contenteditable> editable </span>
    <font color=red><span UNSELECTABLE="ON" contenteditable="false" readonly="true"
     UNSELECTABLE="ON">  uneditable1 </span></font>
    <span contenteditable> editable2 </span>
    <font color=red><span contenteditable=false readonly="true" UNSELECTABLE="ON"> uneditable3</span></font>  
    <span contenteditable> editable4 </span>
    <span contenteditable="false" readonly="true" UNSELECTABLE="ON"> uneditable5 </span>
< /div>
</code></pre>

If you put this into jsfiddle on chrome you see that it works in chrome (and IE and FF but a bit differently), but there seems to be a small Caveat.

The caveat is what your key binding does with Backspace! In my case, when I select an uneditable phrase and press backspace in chrome, it seems to refresh, or do a "go to the last page"! I made the first two undeditbles red (left the last black) so that you can see what happens. I test it on jsfiddle in Chrome and in FF and in IE. All seems to have acted properly, give or take the backspace issue.

And I think here is the logic of how it works. The first Div is the top level parent, and it is contenteditable="false". So Everything under it should be un-editable. EXCEPT the children within it that have span contenteditble="true" they become editable. So you inherit from your parent, but then you can overwrite as a child.

I also put the tags readonly="true" UNSELECTABLE="ON" because I read about them some place. And it took more testing!!! NOTE PLEASE I had to take Font... outside of the span, or it would not work. But in IE it worked perfectly. The uneditables were whatever color I told them to be and no amount of single, dourble, or triple clicking would select the uneditbles in jsfiddle!!! :-) For Chrome single click did nothing, double click selected the uneditable, and with backspace it went a bit nuts! It seemed that would go to the previous page. With FF things were a bit crazier! Double click selected the uneditable and the editable before it! No idea why.

If someone can figure out and write about the issues with Chrome and FF that would be great. It would be nice to have double click or any number of fast clicks not to work in Chrome and FF too. Again I want to emphasize the span... seems to have to be next to the uneditables and font... outside of the span.

Hope this all makes sense.



回答5:

For IE, try this out:

<span contenteditable="true">Uneditable</span>

Note the reverse value of the contenteditable attribute! It defies all common sense, but it works - at least in IE8 and IE9. This uneditable element you can move around and copy/paste. It’s however far from perfect. E.g. if you bold a region that encompasses the uneditable element, this will infect the content of the uneditable element as well. So you’ll probably have to use some scripting to keep the sanity of the element. Also setting contenteditable="true" will decorate the uneditable element with resize handles. You can turn them off again by setting unselectable="on", but this will prevent the object from being dragged around. Again you’ll probably have to rely on a script restoring the width and height.