Javascript:如何将插入符号移动到插入元素的内部

时间:2014-04-29 15:15:49

标签: javascript html html-lists wysiwyg

我有一个内部构建的WYSIWYG编辑器,它能够使用JavaScript范围和选择对象选择内容并将其转换为列表(它还可以执行其他操作)。这类似于在插入BR标签时将插入符号移动到行尾(我使用Tim Down的示例,提供here)来实现它,但是我需要将光标保持在初始节点,以便可以插入更多节点。初始节点将是OL | UL标签和我需要能够使用选择中的文本插入列表项。

我用来处理此问题的代码:

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;

供参考:上下文对象代码:

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;
    },

光标代码处的插入节点:

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;
        }
    },

我最初的想法是将setStartAfter和setEndAfter函数更改为不移动光标但是没有工作,这就是为什么在这些函数后面有一个空的else块。

编辑:我确实有一个旧版本的列表创建代码,它将通过用开始和关闭列表标签替换断行来获取所选文本并在其周围构建列表项,之后它会将它们粘贴到可编辑文档中(原始目标是针对IE。原始代码是作为包含的脚本文件编写的,这就是它具有不同结构的原因。)

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();
}

更新:我想出了如何构建HTML(我将在下面发布代码)并插入到文档中,但是列表项在编辑器窗口中无法正确呈现。 有什么想法吗?

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;

指定类型属性(不是HTML 5友好)并没有解决我的问题,因此它们未被使用。

1 个答案:

答案 0 :(得分:0)

我通过更新代码来解决这个问题,创建上下文对象以从文档中获取所选的HTML。从那里,我能够通过提供的HTML字符串操作DOM,并在函数完成后重新发布到DOM。 (另外,我意识到我的原始问题非常模糊,但我希望我的代码片段有一天对某人有用)

更新的上下文功能:

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;
},