• 另一种场景下的js @提到好友


    转载请注明: TheViper http://www.cnblogs.com/TheViper 

    在输入框中@好友这个功能很常见,具体效果大概有两种:

    1.像js实现@提到好友,在输入框中输入@时,根据@后面的字符,弹出相应好友菜单。

    2.增加一个按钮,点击后出现包含所有好友的弹出层。

    本文就介绍本屌在实现第二种@时走过的一些坑。为了简单,其中的什么弹出层,拉数据就不说了。

    先说下要求:

    1.添加@好友时,无论编辑器有没有焦点,@好友都会被添加到上一个焦点位置,效果和更简单的 编辑器从光标处插入图片(失去焦点后仍然可以在原位置插入)中的一样。

    2.添加到上一个焦点位置后,无论编辑器有没有焦点,光标自动出现在@好友之后。

    3.如果要删除@,必须是@+好友一块删除,就像关于qq空间评论回复的一点研究中删除回复好友一样。图为qq空间评论框

    4.同2类似,删除整块@好友后,光标停留到@好友前面的位置。

    5.兼容firefox,chrome,ie6 7 8.

    第一点和第二点用更简单的 编辑器从光标处插入图片(失去焦点后仍然可以在原位置插入)就很容易实现,只不过这里传入的不是img src地址,而是html.至于传入的html,还是firefox传入<img>标签.

    html="<img alt='@"+name+"' onclick='return false;' contenteditable='false' class='mention'>&nbsp;";

    ie和chrome传入<button>标签。

    html="<button onclick='return false;' class='mention' contentEditable='false' >@"+name+"</button>&nbsp;";

    另外对于editor对象的插入方法需要像上一篇,做点修改,使得在原来光标位置插入html后,光标紧跟其后。

    对于第三点,如果不写其他代码看下效果有多坑爹。

    firefox

    可以看到当光标移到@好友后面时,按backspace,怎么都删除不了。

    chrome

    可以看到光标不光会跳到@好友里面,甚至可以直接鼠标点击到@好友里面。

    ie8

    可以看到ie8居然神奇的满足所有要求,ie6,ie7结果也一样,就不贴出来了。

    首先解决第三点,@好友必须是整块删除。

    本屌想到的是对编辑器添加keydown事件,原因参见js实现@提到好友。当按backspace时,判断此时光标前的html的lastChild是不是相应的包裹@好友的标签(firefox <img>,ie chrome <button>),如果是,就把标签删除了。具体的

    获取光标前的html,这个在js实现@提到好友中出现过,这里因为后面要用到它的range,所以把range也返回了。

            function getTextBeforeCursor(containerEl) {
                var precedingChar = "", sel, range, precedingRange;
                if (window.getSelection) {
                    sel = window.getSelection();
                    if (sel.rangeCount > 0) {
                        range = sel.getRangeAt(0).cloneRange();
                        range.collapse(true);
                        range.setStart(containerEl, 0);
                        precedingChar = range.cloneContents();
                    }
                } else if ( (sel = document.selection)) {
                    range = sel.createRange();
                    precedingRange = range.duplicate();
                    precedingRange.moveToElementText(containerEl);
                    precedingRange.setEndPoint("EndToStart", range);
                    precedingChar = precedingRange.htmlText;
                }
                return [precedingChar,range];
            }

    在现代浏览器中,这个函数返回的是FragmentDocument.然后把FragmentDocument中的lastChild,也就是相应的包裹@好友的标签删掉,再放回光标后的node前面。

                check_key:function(e){
                    var htmlBeforeCursor=getTextBeforeCursor($('editor')),frag=htmlBeforeCursor[0],range=htmlBeforeCursor[1],
                    last_node=frag.lastChild;
                    if(last_node!=null&&last_node.nodeType==3&&last_node.nodeValue=="")
                        last_node=last_node.previousSibling;
                    if(last_node!=null&&last_node.className=='mention'){
                        if(last_node.nodeName=='IMG'||last_node.nodeName=='BUTTON'){
                            frag.removeChild(last_node)
                            range.deleteContents();
                            $('editor').insertBefore(frag,$('editor').childNodes[0])
                            e.preventDefault();
                        }
                    }
                }

    第一个if是排除在删除过程中,可能会多出值为""的TextNode.

    当然也可以通过其他方法,找出相应的包裹@好友的标签,然后removeChild()删除。

    效果

    firefox

    可以看到相应的包裹@好友的标签是可以整块删除了,不过删除后光标跑到最前面了。

    chrome

    和firefox一样,可以成功整块删除,但是这里删除后,编辑器没有焦点了。

    第三点貌似是解决了,下面解决第四点,删除整块后,光标跳到删除块之前。

    注意到上一篇中document.execCommand("insertHtml",false,html);,可以在插入后,让光标紧随其后。

            this.insertImage=function(html){
                this.restoreSelection();
                if(document.selection)
                    currentRange.pasteHTML(html); 
                else{
                    container.focus();
                    document.execCommand("insertHtml",false,html);
                    currentRange.collapse();
                }
                saveSelection();
            };

    这里对删除完包裹标签的FragmentDocument使用它,而不是之前的$('editor').insertBefore(frag,$('editor').childNodes[0]).具体的,

                check_key:function(e){
                    var htmlBeforeCursor=getTextBeforeCursor($('editor')),frag=htmlBeforeCursor[0],range=htmlBeforeCursor[1],
                    last_node=frag.lastChild;
                    if(last_node!=null&&last_node.nodeType==3&&last_node.nodeValue=="")
                        last_node=last_node.previousSibling;
                    if(last_node!=null&&last_node.className=='mention'){
                        if(last_node.nodeName=='IMG'||last_node.nodeName=='BUTTON'){
                            frag.removeChild(last_node)
                            range.deleteContents();
                            var div=document.createElement('div');
                            div.appendChild(frag);
                            document.execCommand("insertHtml",false,div.innerHTML);
                            div=null;
                            e.preventDefault();
                        }
                    }
                }

    创建一个div element,把FragmentDocument添加到里面,然后通过div element的innerHTML得到删除完包裹标签的html,最后document.execCommand("insertHtml",false,html);

    效果

    firefox

    可以看到firefox是达到要求了。

    chrome

    可以看到删除是成功了,光标位置也没问题,就是多了很多无意义的空包裹标签。这是因为chrome在document.execCommand("insertHtml",false,html);时,很坑爹的把<button>标签的contenteditable属性自动删掉了,即便后面再去setAttribute添加contenteditable属性也没用。而在firefox下就不存在这个问题,<img>标签仍然有contenteditable属性,使得光标不会跳到标签里面。

    怎么解决?只有对chrome不用document.execCommand("insertHtml",false,html);.但是在光标处插入@好友的方法中也使用了document.execCommand("insertHtml",false,html);。没办法,只有重写chrome在光标处插入@好友的部分,这里要用到一直在监听编辑器而随时在变化的editor封装里的currentRange.

                    if(isChrome){
                        html="<button onclick='return false;' class='mention' contentEditable='false' >@"+name+"</button>&nbsp;";
                        $('editor').focus();
                        var sel=window.getSelection(),range=at_editor.getRange(),node=avalon.parseHTML(html);
                        range.insertNode(node);
                        range.collapse();
                        sel.removeAllRanges();
                        sel.addRange(range);
                        // at_editor.insertImage(html);
                    }

    为此还要在editor封装里暴露currentRange。

            this.getRange=function(){
                return currentRange;
            }

    这下就可以保证,在添加的@好友标签中存在contenteditable=“false”了。另外,也不用在删除时判断FragmentDocument的lastChild是不是<button>标签了。因为<button>标签里有contenteditable=“false”,删除后光标会自动跳到@好友标签前面位置。

    效果

    最后附上例子下载

    更新:

    firefox下,按<-左箭头移动光标时,如果光标左边是@好友标签,就会删除标签,所以删除标签时需要判断按键是不是backspace键。

                    if(e.keyCode==8&&last_node!=null&&last_node.className=='mention'){
                      ....
                    }
  • 相关阅读:
    HDU 2157 How many ways?【矩阵快速幂】
    CodeForces 3 D.Least Cost Bracket Sequence【贪心+优先队列】
    【差分】Tallest Cow
    P2220 [HAOI2012]容易题【快速幂】
    无题II HDU
    PHP编译常见错误
    MySQL编译安装
    Tomcat 单(多)实例部署使用
    lvs+keepalived 高可用及负载均衡
    MySQL操作命令梳理(2)
  • 原文地址:https://www.cnblogs.com/TheViper/p/4633745.html
Copyright © 2020-2023  润新知