0

I have an in-house built WYSIWYG editor that is able to select content and convert it to a list (it does other things as well) using the JavaScript range and selection objects. This is similar to moving the caret to the end of a line when a BR tag is inserted (I used Tim Down's example, provided here) to implement that, however I need to keep the cursor inside the initial node so that further nodes can be inserted. The initial node would be an OL | UL tag and I need to be able to insert list items with the text from the selection.

The code that I'm using to handle this:

OverrideListCreation: function (commandName, showDefaultUI, commandValue, context) {
        var html;
        var contextHtml = context.range.htmlText;
        var insertSuccess;
        var element;

        console.log("ContextHtml: " + contextHtml);

        if (commandName == "insertorderedlist") {
            element = this.designModeDocument.createElement("ol");
        } else {
            element = this.designModeDocument.createElement("ul");
        }

        element.setAttribute("class", "content");
        element.setAttribute("style", "margin: 1em;");

        insertSuccess = this.InsertNodeAtCursor(context, element, false);      

        element = this.designModeDocument.createElement("li");
        element.setAttribute("class", "content");
        element.setAttribute("style", "margin: 0em;");

        if (contextHtml.search(/<br>/gi) > -1) {
            console.log("ContextHtml Length: " + contextHtml.length);
            console.log("Multiline Check - Passed");
            var lines = contextHtml.split(/<br>/gi);

            for (var i = 0; i < lines.length; i++) {
                element.innerHTML = lines[i];

                insertSuccess = this.InsertNodeAtCursor(context, element, false);

                if (insertSuccess === false) {
                    return;
                }
            }
        } else if (contextHtml.length > 1 && contextHtml.search(/<br>/gi) === -1) {
            console.log("Single Line Check w/o BR - Passed");
            element.innerHTML = contextHtml;

            insertSuccess = this.InsertNodeAtCursor(context, element, false);

            if (insertSuccess === false) {
                return;
            }
        } else {
            console.log("ContextHtml is empty, insert an empty list element");
            insertSuccess = this.InsertNodeAtCursor(context, element, false);

            if (insertSuccess === false) {
                return;
            }
        }

        return;

For Reference: The Context Object Code:

GetDesignModeContext: function () {
        var context = new Object();

        try {
            if (this.designModeDocument.selection) {
                context.selection = this.designModeDocument.selection;
                context.range = context.selection.createRange();
                context.selectedText = context.range.text;

                switch (context.selection.type) {
                    case "None":
                    case "Text":
                        context.parentElement = context.range.parentElement();
                        break;
                    case "Control":
                        context.parentElement = context.range.item(0);
                        break;
                    default:
                        context.parentElement = this.designModeDocument.body;
                        break;
                }
            }
            else if (this.designModeDocument.getSelection || this.designEditor.getSelection) {
                context.selection = this.designEditor.getSelection();
                context.selectedText = context.selection.toString();

                try {
                    context.range = context.selection.getRangeAt(0);
                }
                catch (e) {
                    context.range = this.designModeDocument.createRange();
                }

                function IsSelectedTextNode(container, offset, start) {
                    if (container.nodeType != 3) return false;
                    var startIndex = start ? offset : 0;
                    var endIndex = start ? container.nodeValue.length : offset + 1;
                    var text = container.nodeValue.substring(startIndex, endIndex);

                    return (context.selectedText == text);
                }

                var r = context.range;
                var p = null;

                if (r.startContainer == r.endContainer) {
                    if (r.collapsed) {
                        p = r.startContainer;
                    }
                    else if (r.startOffset - r.endOffset <= 1 &&
                             r.startContainer.hasChildNodes()) {
                        p = r.startContainer.childNodes[r.startOffset];
                    }
                }
                else if (IsSelectedTextNode(r.startContainer, r.startOffset, true)) {
                    p = r.startContainer;
                }
                else if (IsSelectedTextNode(r.endContainer, r.endOffset, false)) {
                    p = r.endContainer;
                }

                if (!p) p = r.commonAncestorContainer;

                while (p.nodeType == 3) p = p.parentNode;

                context.parentElement = p;
            }

            if (context.parentElement == null) return null;
            if (context.parentElement.nodeType != 1) return null;
            if (context.parentElement.ownerDocument != this.designModeDocument) return null;
        }
        catch (e) {
            return null;
        }


        return context;
    },

The Insert Node at Cursor Code:

InsertNodeAtCursor: function (context, domElement, positionEnd) {
        var selection, range;

        if (typeof this.designModeDocument.getSelection != "undefined" && context.selection) {
            //console.log("getSelection is supported by this browser && Context.Selection was not undefined.");
            selection = this.designModeDocument.getSelection() || context.selection;

            if ((selection.getRangeAt && selection.rangeCount) && context.range) {
                //console.log("getRangeAt & rangeCount are supported by this browser && Context.Range is not undefined");

                range = selection.getRangeAt(0) || context.range;

                range.deleteContents();

                //console.log("Inserting BR Element Node");
                range.insertNode(domElement);

                //console.log("Setting Position of Cursor");
                if (positionEnd) {
                    range.setEndAfter(domElement);
                    range.setStartAfter(domElement);
                } else {

                }



                //console.log("Selection Clean-up");
                selection.removeAllRanges();
                selection.addRange(range);

                return true;
            }
        } else if (typeof this.designModeDocument.selection != "undefined" && context.selection) {
            //console.log("Using alternate selection method.");

            if (typeof selection.createRange && context.range) {
                //console.log("Using alternate range method.");
                range = selection.createRange || context.range;

                var tmpElement = this.designModeDocument.createElement("div");
                tmpElement.appendChild(domElement);

                this.PasteHtml(range, tmpElement.innerHTML);

                range.select();

                this.designModeDocument.removeChild(tmpElement);

                return true;
            }
        } else {
            return false;
        }
    },

My initial thought were to change the setStartAfter and setEndAfter functions to not move the cursor however that didn't work, which is why there is an empty else block following those functions.

EDIT: I did have an older version of the list creation code that would take the selected text and build list items around it by replacing the break lines with start and close list tags, after which it would paste them to the editable document (The original target was for IE. The original code was written in as an included script file, which is why it has a different structure).

function doCommandIERangeList(strCommand, bShowDefaultUI, strOptional) {
    // Get a text range for the selection
    var strHTML;
    var strHTMLFromControl;
    var tr = getIFrameDocument(_strIFrame).selection.createRange();

    strHTMLFromControl = tr.htmlText;

    //if there are break lines present create a new list
    //otherwise we just want to switch between list types
    if (strHTMLFromControl.search(/<BR>/gi) > -1) {

        if (strCommand == "InsertOrderedList") {
            strHTML = "<ol>";
        } else {
            strHTML = "<ul>";
        }


        strHTML += "<li>" + strHTMLFromControl.replace(/<BR>/gi, "</li><li>") + "</li>";


        tr = getIFrameDocument(_strIFrame).selection.createRange();

        if (strCommand == "InsertOrderedList") {
            strHTML += "</ol>";
        } else {
            strHTML += "</ul>";
        }

        //IE Version Check to remove somewhat randomly placed <br> tags when trying to format a set of text instructions as a List Range
        if (IsIE9()) {
            strHTML = strHTML.replace(/[\r\n]/g, "");
        }

        if (IsIE10()) {
            strHTML = strHTML.replace(/[\r\n]/g, "");
        }

        tr.pasteHTML(strHTML);

    } else {
        //needed to switch between ul and ol
        tr.execCommand(strCommand, bShowDefaultUI, strOptional);
    }

    // Reselect and give the focus back to the editor
    tr.select();
    frames.editor.focus();
}

UPDATE: I figured out how to get the HTML to build (I will post the code below) and insert into the document, however the list items are not rendering correctly in the editor window. Any thoughts?

OverrideListCreation: function (commandName, showDefaultUI, commandValue, context) {
    var html;
    var contextHtml = context.range.htmlText;
    var insertSuccess;
    var parentElement,element;

    console.log("ContextHtml: " + contextHtml);

    if (commandName == "insertorderedlist") {
        parentElement = this.designModeDocument.createElement("ol");
        parentElement.setAttribute("type", "1");
    } else {
        parentElement = this.designModeDocument.createElement("ul");
        parentElement.setAttribute("type", "disc");
    }

    parentElement.setAttribute("class", "content");
    parentElement.setAttribute("style", "margin: 1em;");



    if (contextHtml.search(/<br>/gi) > -1) {
        console.log("ContextHtml Length: " + contextHtml.length);
        console.log("Multiline Check - Passed");
        var lines = contextHtml.split(/<br>/gi);

        for (var i = 0; i < lines.length; i++) {
            console.log("Lines @ " + i + ": " + lines[i]);

            element = this.designModeDocument.createElement("li");
            element.setAttribute("class", "content");
            element.setAttribute("style", "margin: 0em;");

            if (typeof element.innerHTML != "undefined") {
                element.innerHTML = lines[i]
            } else {
                element.textContent = lines[i];
            }

            parentElement.appendChild(element);

        }
    } else if (contextHtml.length > 1 && contextHtml.search(/<br>/gi) === -1) {
        console.log("Single Line Check w/o BR - Passed");

        element = this.designModeDocument.createElement("li");
        element.setAttribute("class", "content");
        element.setAttribute("style", "margin: 0em;");

        if (typeof element.innerHTML != "undefined") {
            element.innerHTML = contextHtml
        } else {
            element.textContent = contextHtml
        }

        parentElement.appendChild(element);

    } else {
        console.log("ContextHtml is empty, insert an empty list element");

        element = this.designModeDocument.createElement("li");
        element.setAttribute("class", "content");
        element.setAttribute("style", "margin: 0em;");

        parentElement.appendChild(element);
    }

    insertSuccess = this.InsertNodeAtCursor(context, parentElement, false);

    return;

Specifying the type attributes (not HTML 5 friendly) did not solve my issue, so they are unused.

Community
  • 1
  • 1
JGx714791
  • 51
  • 7

1 Answers1

0

I solved this by updating the code the creates the context object to get me the selected HTML from the document. From there, I was able to manipulate the DOM via the HTML string the was provided and re-post to the DOM after the function had completed. (Also, I realize that my original question was incredibly vague, but I hope my code snippets prove useful to somebody someday)

Updated Context Function:

GetDesignModeContext: function () {
    var context = new Object();

    try {
        //console.log("Check Function Support: designModeDocument.getSelection && designEditor.getSelection")
        if (typeof this.designModeDocument.getSelection != "undefined" && typeof this.designEditor.getSelection != "undefined") {

            var editor = this;
            //console.log("Get: Selection Object")
            context.selection = this.designEditor.getSelection();
            //console.log("Get: Selection Text");
            context.selectedText = context.selection.toString();
            //console.log("Call Function: GetSelectedContextHtml");
            context.selectedHtml = GetSelectedContextHtml(editor);

            try {
                //console.log("Get: Range Object");
                context.range = context.selection.getRangeAt(0);
                //context.parentElement = context.range.commonAncestorContainer;
            } catch (e) {
                //console.log("Get: Range Object [LEGACY]");
                context.range = this.designModeDocument.createRange();
                //context.parentElement = context.range.parentElement();
            }

            function GetSelectedContextHtml(editor) {
                //console.log("Start Function: GetSelectedContextHTML");
                var html;

                //console.log("Check Function Support: getSelection");
                if (typeof editor.designEditor.getSelection != "undefined") {

                    var sel = editor.designEditor.getSelection();

                    if (sel.rangeCount) {
                        //console.log("Check Property Support: rangeCount");
                        var container = editor.designModeDocument.createElement("div");

                        for (var i = 0, len = sel.rangeCount; i < len; i++) {
                            //console.log("Get: Range Object HTML @ Index[" + i + "]");
                            container.appendChild(sel.getRangeAt(i).cloneContents());
                        }

                        //console.log("Set: HTML = Container.InnerHTML");
                        html = container.innerHTML;
                    }
                } else if (typeof editor.designModeDocument.selection != "undefined") {
                    //console.log("Check Function Support: Selection");
                    if (editor.designModeDocument.selection.type == "Text") {
                        //console.log("Check Selection Type: Text");
                        html = editor.designModeDocument.selection.createRange().htmlText;
                    }
                }
                //console.log("End Function: GetSelectedContextHTML");
                return html;
            }

            function IsSelectedTextNode(container, offset, start) {
                if (container.nodeType != 3) return false;

                var startIndex = start ? offset : 0;
                var endIndex = start ? container.nodeValue.length : offset + 1;
                var text = container.nodeValue.substring(startIndex, endIndex);

                return (context.selectedText == text)
            }



            var r = context.range;
            var p = null;


            if (r.startContainer == r.endContainer) {
                console.log("Check: Range.StartContainer == Range.EndContainer");
                if (r.collapsed) {
                    console.log("Check: Range.Collapsed");
                    console.log("Set: Parent = Range.StartContainer");
                    p = r.startContainer;
                } else if (r.startOffset - r.endOffset <= 1 && r.startContainer.hasChildNodes()) {
                    console.log("Check: Difference Between Offsets <= 1 && Range.StartContainer.HasChildNodes");
                    console.log("Set: Parent = Range.StartContainer.ChildNodes @ Index[" + r.startOffset + "]");
                    p = r.startContainer.childNodes[r.startOffset];
                }
            } else if (IsSelectedTextNode(r.startContainer, r.startOffset, true)) {
                console.log("Check Function: IsSelectedTextNode(range.startContainer, range.startOffset, start = true)");
                console.log("Set: Parent = Range.StartContainer");
                p = r.startContainer;
            } else if (IsSelectedTextNode(r.endContainer, r.endOffset, false)) {
                console.log("Check Function: IsSelectedTextNode(range.endContainer, range.endOffset, start = false)");
                console.log("Set: Parent = Range.EndContainer");
                p = r.endContainer;
            }

            if (!p) {
                console.log("Check: Parent != Nothing");
                console.log("Set: Parent = Range.CommonAncestor");
                p = r.commonAncestorContainer;
            }

            console.log("Loop: Parent.NodeType = 3");
            while (p.nodeType == 3) p = p.parentNode;

            console.log("Set: Context.ParentElement = Parent");
            context.parentElement = p;

            //console.log("Context.Selection: " + context.selection);
            //console.log("Context.SelectedText: " + context.selectedText);
            //console.log("Context.SelectedHtml: " + context.selectedHtml);
            //console.log("Context.ParentElement: " + context.parentElement);
            //console.log("Context.Range: " + context.range);

        } else if (typeof this.designModeDocument.selection != "undefined") {
            context.selection = this.designModeDocument.selection;
            context.range = context.selection.createRange();
            context.selectedText = context.range.text;
            context.selectedHtml = context.range.htmlText;

            switch (context.selection.type) {
                case "None":
                case "Text":
                    context.parentElement = context.range.parentElement();
                    break;
                case "Control":
                    context.parentElement = context.range.item(0);
                    break;
                default:
                    context.parentElement = this.designModeDocument.body;
                    break;
            }
        }

        if (context.parentElement == null) return null;
        if (context.parentElement.nodeType != 1) return null
        if (context.parentElement.ownerDocument != this.designModeDocument) return null;

    } catch (e) {
        return null;
    }

    return context;
},
JGx714791
  • 51
  • 7