转载请注明: TheViper http://www.cnblogs.com/TheViper html
在输入框中@好友这个功能很常见,具体效果大概有两种:node
1.像js实现@提到好友,在输入框中输入@时,根据@后面的字符,弹出相应好友菜单。chrome
2.增长一个按钮,点击后出现包含全部好友的弹出层。浏览器
本文就介绍本屌在实现第二种@时走过的一些坑。为了简单,其中的什么弹出层,拉数据就不说了。app
先说下要求:编辑器
1.添加@好友时,不管编辑器有没有焦点,@好友都会被添加到上一个焦点位置,效果和更简单的 编辑器从光标处插入图片(失去焦点后仍然能够在原位置插入)中的同样。函数
2.添加到上一个焦点位置后,不管编辑器有没有焦点,光标自动出如今@好友以后。post
3.若是要删除@,必须是@+好友一块删除,就像关于qq空间评论回复的一点研究中删除回复好友同样。图为qq空间评论框this
4.同2相似,删除整块@好友后,光标停留到@好友前面的位置。url
5.兼容firefox,chrome,ie6 7 8.
第一点和第二点用更简单的 编辑器从光标处插入图片(失去焦点后仍然能够在原位置插入)就很容易实现,只不过这里传入的不是img src地址,而是html.至于传入的html,仍是firefox传入<img>标签.
html="<img alt='@"+name+"' onclick='return false;' contenteditable='false' class='mention'> ";
ie和chrome传入<button>标签。
html="<button onclick='return false;' class='mention' contentEditable='false' >@"+name+"</button> ";
另外对于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> "; $('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'){ .... }