Вопрос

I'm trying to add very specific functionality into a text editor. My users have no need for any actual text formatting or styling, but the interface is greatly improved by being able to place HTML elements into the text editor.

I started by using jQuery Text Editor on top of the textarea in my form. I stripped out all of the formatting buttons from the header, so it's basically just a textarea that renders html. My users will be typing or pasting multiple lines of text just like a text editor. The tricky part for me comes from inserting HTML into that textarea. I have events elsewhere on the page that, when clicked, try to add a button at the current cursor location within the jqte textarea.

I found a jQuery snippet to help me locate the caret position. Pulling the caret position out of the div lets me insert the first button at the correct location. To maintain my button elements, I have to extract the text field's html(), but I don't know how to get the location of my character within the html, only within the text. How can I achieve this functionality?

I created a jsFiddle to demonstrate what I'm doing. Adding the first button works fine, but once I add the second button, grabbing the substring of html from the text field's text makes the whole thing fall apart.

Any help is appreciated, thanks!

I didn't realize you can't link to jsFiddle without code, so here's my HTML, CSS, and JS:

HTML

<div class='buttons'>
    <button>Item A</button>
    <button>Item B</button>
</div>
<textarea id='text'></textarea>

<div class='btn-group hidden cloneable'>
    <button type='button' class='btn btn-default dropdown-toggle' data-toggle='dropdown'>
        <span class='name'></span> <span class='caret'></span>
    </button>
    <ul class='dropdown-menu' role='menu'>
        <li><a href='#'>Function 1</a></li>
        <li class='divider'></li>
        <li><a href='#'>Function 2</a></li>
    </ul>
</div>

CSS

.jqte_editor{
    height: 300px;
}

JS

$('#text').jqte({
    b: false,
    center: false,
    color: false,
    fsize: false,
    format: false,
    i: false,
    link: false,
    left: false,
    ol: false,
    remove: false,
    right: false,
    rule: false,
    source: false,
    sub: false,
    strike: false,
    sup: false,
    u: false,
    ul: false,
    unlink: false,
    indent: false,
    outdent: false
});

var $jqte = $('div.jqte_editor');

$('.buttons').on('click', 'button', function(e){
    e.preventDefault();
    insertHtmlAtCaret($(this).text());
});

function insertHtmlAtCaret(text){
    var position = getCaretCharacterOffsetWithin($jqte.get(0));
    var front = $jqte.html().substring(0, position);
    var back = $jqte.html().substring(position, $jqte.text().length); 

    var $group = $('div.cloneable').clone();
    $group.find('span.name').text(text);
    $group.removeClass('hidden cloneable');
    $jqte.html(front).append($group).append(back);
}

function getCaretCharacterOffsetWithin(element){
    var caretOffset = 0;
    var doc = element.ownerDocument || element.document;
    var win = doc.defaultView || doc.parentWindow;
    var sel;
    if (typeof win.getSelection != "undefined") {
        var range = win.getSelection().getRangeAt(0);
        var preCaretRange = range.cloneRange();
        preCaretRange.selectNodeContents(element);
        preCaretRange.setEnd(range.endContainer, range.endOffset);
        caretOffset = preCaretRange.toString().length;
    } else if ( (sel = doc.selection) && sel.type != "Control") {
        var textRange = sel.createRange();
        var preCaretTextRange = doc.body.createTextRange();
        preCaretTextRange.moveToElementText(element);
        preCaretTextRange.setEndPoint("EndToEnd", textRange);
        caretOffset = preCaretTextRange.text.length;
    }
    return caretOffset;
}
Это было полезно?

Решение

Took a little time but found a JS example that included enough for me to get the functionality I was looking for. Having no prior knowledge of Ranges, that appeared to be the main portion of the solution:

function insertButtonAtCaret(name) {
    var node = document.createElement('div');
    node.innerHTML = $('div.cloneable').html();

    $(node).addClass('btn-group');
    $(node).find('span.name').text(name);

    var selection = document.getSelection();
    selection.getRangeAt(0).insertNode(node);
    selection.removeAllRanges();
}

There are still some small issues with the entire solution in general (the button names are in the text area and can be changed), but this is definitely enough for me to work with for now.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top