Petr Dušek
Petr Dušek

Reputation: 627

CKEDITOR - identify the selected html

I have discovered some usefull attributes of CKEDITOR selection object (get via editor.getSelection()):

e.getSelection().getRanges()[0].startOffset
e.getSelection().getRanges()[0].endOffset

This returns the start and end position of the selected text - but only in relation to startContainer (element) and endContainer (element). I would like to get the absolute position in relation to the whole document or something else to identify the selected html (some intern id of the start or end element?). I would like to read this attributes on the server side and modify the selected html there. Is there some universal way to move the information about position of the selected text from the client side to the server side?

Thanks a lot.

Upvotes: 0

Views: 1788

Answers (4)

Petr Dušek
Petr Dušek

Reputation: 627

I found a good and elegant solution:

textselector.js (marks selection - inserts bookmars into CKEDITOR)

 function selectText(irtId, startIdSelectionId, endIdSelectionId) {
    var editor = CKEDITOR.instances[irtId];
    if (editor.getSelection().getRanges()[0].collapsed) {
        document.getElementById(startIdSelectionId).value = "";
        document.getElementById(endIdSelectionId).value = "";
    } else {
        var bookmarks = editor.getSelection().createBookmarks(true);
        var startId = bookmarks[0].startNode;
        var endId = bookmarks[0].endNode;
        document.getElementById(startIdSelectionId).value = startId;
        document.getElementById(endIdSelectionId).value = endId;
    }
    return true;
}

Action on the serverside (identifying conflicts - tag-crossing, repairs elements if necessary - divides into 2 parts and insert span):

/**
 * prida span na oznacene html osestreni nezadoucich pripadu: prazdny
 * select, kolize s jinym spanem
 */
public void spanSelectedHtml() {
    // parsovani celeho dokumentu
    Document doc = Jsoup.parse(value);
    // osetreni chybovych stavu
    // - prazdny select
    if (startIdSelection.isEmpty() || endIdSelection.isEmpty()) {
        return;
    }
    // nalezeni znacek
    Element es = doc.getElementById(startIdSelection);
    Element ee = doc.getElementById(endIdSelection);
    // - konflikt s jinou znackou
    // bude doplneno

    // oprava okoli znacek v pripade nutnosti
    repairIfNecessary(es, ee);
    // vytvoreni span tagu s nalezitymi atributy
    Element span = doc.createElement("span");
    span.attr("property", clicked.getUrl());
    span.attr("class", clicked.getStyleClass());
    // nahrazeni prvni znacky span tagem
    es.replaceWith(span);
    // pripojeni vsech nasledujicich uzlu az do koncove znacky
    while (span.nextSibling() != ee) {
        span.appendChild(span.nextSibling());
    }
    // odstraneni koncove znacky
    ee.remove();
    // aktualizace hodnoty textove komponenty
    value = doc.toString();
}

/**
 * oprava okoli elementu v pripade nutnosti - pri zjisteni unikatniho rodice
 *
 * @param e1 prvni element
 * @param e2 druhy element
 */
private void repairIfNecessary(Element e1, Element e2) {
    while (hasUniqueParent(e1, e2)) { // unikatni rodice e1?
        repairElement(e1);
    }
    while (hasUniqueParent(e2, e1)) { // unikatni rodice e2?
        repairElement(e2);
    }
}

/**
 * oprava okoli elementu: rozdeleni na dve casti a vymazani rodice,
 * zachovani atributu
 *
 * @param e element s okolim na opravu
 */
private void repairElement(Element e) {
    // "problemovy rodic", ktereho je treba rozdelit
    Element p = e.parent();
    // 1. cast - pred znackou
    if (e.previousSibling() != null) { // osetreni null
        Element n = p.clone().empty(); // vkladany element musi byt formalne stejny
        p.prependChild(n); // umisteni elementu na zacatek - jako 1. dite
        while (n.nextSibling() != e) { // dokud se nedostanu ke znacce, pridavam uzly
            n.appendChild(n.nextSibling());
        }
    }
    // 2. cast - za znackou
    if (e.nextSibling() != null) { // osetreni null
        Element n = p.clone().empty(); // vkladany element musi byt formalne stejny
        p.appendChild(n); // umisteni elementu na konec - jako posledni dite
        while (n.previousSibling() != e) { // dokud se nedostanu ke znacce, pridavam uzly
            n.prependChild(n.previousSibling());
        }
    }
    p.unwrap(); // vymazani puvodniho "problemoveho rodice"
}

/**
 * ma testovaci element rodice, ktereho kontrolni element nema?
 *
 * @param e testovaci element
 * @param c kontrolni element
 * @return testovaci element ma unikatniho rodice (takoveho, ktery kontrolni
 * element nema)
 */
private boolean hasUniqueParent(Element e, Element c) {
    if (e.parents().isEmpty() || c.parents().isEmpty()) { // test na null
        return false;
    }
    for (Element pe : e.parents()) {
        if (!c.parents().contains(pe)) {
            return true; // unikatni rodic
        }
    }
    return false; // bez unikatniho rodice
}

Upvotes: 0

Petr Dušek
Petr Dušek

Reputation: 627

Now I have to solve this issue: - if someone wants to select (* = selection borders) this: <b>some te*xt </b> aaa <i>bb*b</i>

...the JS puts this bookmarks:

<b>some te
<span id="cke_bm_69S" style="display: none;">&nbsp;</span>
xt</b> 
aaa 
<i>bb
<span id="cke_bm_69S" style="display: none;">&nbsp;</span>
b</i> 

... and I have to do something like this:

<b>some te
</b><span property="..."><b>xt</b>
aaa 
<i>bb</i></span><i>b</i> 

My solution (untested yet): select a Node with id="cke_bm_69S", ensure the tag-crossing problems and if there are some then use before() and after() methods to insert html. But I am afraid, there will be temporary unclosed tags, I dont know, if its ok.

Upvotes: 0

Petr Dušek
Petr Dušek

Reputation: 627

The calculation goes strongly against a principles of CKEDITOR. But I think, I have discovered the best solution - it is almost the same as Nenotlep suggested:

var bookmarks = e.getSelection().createBookmarks(true);
var startId = bookmarks[0].startNode;
var endId = bookmarks[0].endNode;

This inserts to the code invisible bookmarks (span with id startId and endId) and then I can process it on the server side. Now I have to solve the multi-selection issue and tag-crossing issue.

Upvotes: 1

Joel Peltonen
Joel Peltonen

Reputation: 13412

If I understood you correctly, you want to identify a specific node or multiple nodes serverside based on where the cursor was or where selection was during a submit/ajax call. And you already know the incantation to get the nodes for the current selection.

I would suggest that before submitting to server, you tag the element(s) manually. Add a custom attribute or class to the intended victims before submitting and then use that tag to identify the node serverside.

For example [ is the start of the selection and ] is the end and your data is:

<p>foo</p><p>[bar</p><p>baz]</p>

Before submit, get and tag the element with JS so that it looks like

<p>foo</p><p class="chosen">bar</p><p class="chosen">baz</p>

And then you can use whatever XML/HTML weapon you like to find the element tagged chosen (if you are using C# I recommend CSQuery). Then just remove the tag and command the node(s) to do your bidding. For multiple nodes, just tag multiple nodes.

Upvotes: 2

Related Questions