轉載請註明: 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'){ .... }