什麼項目都有個需求,應開發需求,須要寫一個公式編輯器選擇公式的插件,下面給你們講一下實現過程。(擦汗,強做淡定,咳,開嗓~)javascript
本小菜開發功能前樂於先寫個需求思惟導圖(純屬我的愛好):css
真的只有一點點~html
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>公式編輯器測試</title> <link rel="stylesheet" type="text/css" href="./css/global.css"> <script type="text/javascript" src="./js/jquery-1.8.3.min.js"></script> <script type="text/javascript" src="./js/editTips.js"></script> </head> <body> <textarea id="test" ></textarea> </body> </html>
一塊兒給了吧~早晚要給:前端
/* 編輯器下拉框相關樣式 */ table,tr,th,td{padding:0;margin:0;} ul,li,textarea,input{text-decoration:none;list-style:none;margin:0;padding:0;box-sizing: border-box;} input{ outline:none; } .editTips{ padding:5px 0; border-radius: 0!important; box-shadow: 0 2px 4px rgba(0,0,0,.2); max-height: auto; margin:0; z-index:9999; } .editTips li{ text-align:left; box-sizing:border-box; display:block; width:100%; line-height:1.42857143; margin:1px 0; padding:6px 11px; color:#333; cursor:pointer; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; font-weight:400; line-height:1.42857143; border-bottom:solid 1px #e5e5e5; background:#e5e5e5; } .editTips li:last-child{ border-bottom:none; } .active{ background:#fee188!important; } .editTips li.active{ background:#fee188!important; } textarea{ text-decoration: none; list-style: none; margin: 0; padding: 0; display: block; box-sizing: border-box; width: 500px; height: 300px; margin-top: 100px; margin-left: 100px; }
(function ($) { $.fn.extend({ "editTips": function (options) { var opts = $.extend({}, defaults, options); //使用jQuery.extend 覆蓋插件默認參數 return this.each(function () { //這裏的this 就是 jQuery對象 }); } }); //默認參數 var defaults = { }; })(window.jQuery);
這裏 this 爲輸入框,在 html 中,不須要再去js插入,固然這不是問題,在這裏咱們將 輸入框和下拉框都自定義一下,插入的 ul 中 editTips 類能夠去掉,爲了方便看效果暫時加上java
我我的確定是但願下拉框寬度是能夠自定義的: 'width':opts.dropdownWidth, 這裏咱們定義了第一個參數: 下拉框寬度 node
1 var _this = this; 2 _this.entertext = $(this); 3 _this.dropdown = $('<ul class="editTips" style="display:none;"></ul>'); 5 $("body").after(_this.dropdown); 6 _this.dropdown.css({ 7 'width':opts.dropdownWidth, 8 'position':'absolute', 9 });
ps:既然是公式編輯器,那說明不會一直觸發檢索事件,只有在輸入了指定字符的時候纔會觸發檢索事件,好比:! @ # 之類,這裏咱們以 $ 爲例,從這裏咱們能夠想到,咱們確定jquery
須要一個參數來動態改變檢索事件觸發條件,先記在內心json
其中數組相關方法運用得較多數組
由於其中還涉及到限制請求,方向鍵切換當前項,這裏就不分開說了,直接給出整塊代碼以下:session
// 監聽輸入框 _this.dropdown.parent().on("keyup",this,function(event){ var nowTime = window.sessionStorage.getItem('nowTime'); // 當前項索引 var n = _this.dropdown.find(".active").index(); // li 個數 var n_max = _this.dropdown.find("li").length; // 注意 event在 firefox 中不能兼容,在方法中帶上event參數,以下聲明實現兼容 EVT = event || window.event; if( EVT.keyCode == 38 ){ // 方向鍵控制 li 選項 if(n-1>=0){ _this.dropdown.find('li').eq(n-1).addClass("active").siblings().removeClass("active"); } if( n == 0){ _this.dropdown.find('li').eq(n_max-1).addClass("active").siblings().removeClass("active"); } return false; } // 禁止enter鍵換行 if( EVT.keyCode == 13 ){ return false; } if( EVT.keyCode == 40 ){ // 方向鍵控制 li 選項 if(n<n_max-1){ _this.dropdown.find('li').eq(n+1).addClass("active").siblings().removeClass("active"); } if( n+1 == n_max ){ _this.dropdown.find('li').eq(0).addClass("active").siblings().removeClass("active"); } return false; } // 限制請求,輸入間隔不超過一秒時不觸發檢索事件發送請求 if( nowTime){ var oldTime = Date.now(); var nowTime = window.sessionStorage.getItem('nowTime'); var m = parseInt((oldTime - nowTime)/1000); if( m >= 1){ // 文本內容 var val = _this.entertext.val(); // 以空格切割文本返回數組 var arr_test = val.split(" "); // 獲取數組最後一個元素 var temp = arr_test[arr_test.length-1]; // 切割最後一個元素 單字符元素 返回數組 var temp_type = temp.split(""); // 獲取數字第一個字符 var temp_cha = temp_type[0]; // 最後一個元素長度 var temp_len = temp.length; var temp_dot = temp_type.lastIndexOf("."); // 定義回調函數 callbacktips var callbacktips = function(arr_json){ // 初始化 UL _this.dropdown.find("li").remove(); for( i=0;i<arr_json.length;i++ ){ _this.dropdown.append('<li>'+arr_json[i]+'</li>'); }; _this.dropdown.show(); _this.dropdown.find("li:first-child").addClass("active"); // 自定義樣式 _this.dropdown.find("li").css({ 'width':'100%', }); }; // 最後一個元素爲空值 if( temp_len == 0 ){ _this.dropdown.hide(); _this.dropdown.find('li').remove(); } // 爲特定字符 符合觸發條件 if( temp_cha == opts.triggerCharacter ){ if($.isFunction(opts.keyPressAction)){ opts.keyPressAction(temp, function(arr_json){ // 調用回調函數 callbacktips(arr_json); }); } }else{ _this.dropdown.hide(); _this.dropdown.find('li').remove(); } } } // 初始化第一次時間 window.sessionStorage.setItem('nowTime',Date.now()); });
ps:這裏出現了咱們第二個參數,也是相當重要的一個參數,觸發檢索事件字符: opts.triggerCharacter
下拉菜單顯示以後(這裏咱們先無論下拉菜單顯示的位置),從體驗上來講我但願既能夠用鼠標選取,也能夠用方向鍵選取,方向鍵切換當前項整合在上面的代碼中,這裏鼠標切換當前項:
// 切換當前項 _this.dropdown.on('mouseenter','li',function(){ $(this).addClass("active").siblings().removeClass("active"); });
若是不作處理,咱們在用方向鍵切換當前項的時候,你會看到光標也在上下移動,這如何能忍?怎麼辦?屏蔽它~,在這裏咱們能夠整合enter鍵選取當前項,看代碼:
// 阻止輸入過程當中 方向鍵盤的默認事件 _this.entertext.on("keydown",_this.entertext,function(event){ EVT = event || window.event; if( EVT.keyCode == 38 ){ EVT.preventDefault(); } if( EVT.keyCode == 40 ){ EVT.preventDefault(); } // enter鍵選取對應的 li if( EVT.keyCode == 13 ){ EVT.preventDefault(); var txt = _this.dropdown.find(".active").html(); var test_txt = _this.entertext.val(); var arr_change = test_txt.split(" "); // 以空格切割文本返回數組 var arr_test = test_txt.split(" "); // 獲取數組最後一個元素 var temp = arr_test[arr_test.length-1]; var temp_type = temp.split(""); var temp_dot = temp_type.lastIndexOf("."); var n ; temp_type.splice(temp_dot+1,temp_type.length-1-temp_dot,txt); n = temp_type.join(''); arr_change.splice(arr_change.length-1,1,""+n); _this.entertext.val(arr_change.join(" ")); _this.dropdown.hide(); _this.dropdown.find('li').remove(); } });
要不咱們直接看代碼吧,你可能已經餓了~好的。看代碼:
// 點擊替換 重組val $(document).on("click",'li',function(){ var txt = $(this).html(); var test_txt = _this.entertext.val(); var arr_change = test_txt.split(" "); // 以空格切割文本返回數組 var arr_test = test_txt.split(" "); // 獲取數組最後一個元素 var temp = arr_test[arr_test.length-1];y var temp_type = temp.split(""); var temp_dot = temp_type.lastIndexOf("."); var n ; temp_type.splice(temp_dot+1,temp_type.length-1-temp_dot,txt); n = temp_type.join(''); arr_change.splice(arr_change.length-1,1,""+n); _this.entertext.val(arr_change.join(" ")); _this.dropdown.hide(); _this.dropdown.find('li').remove(); });
到了這裏拋開動態顯示下拉框位置,咱們的公式編輯器應該能夠跑一跑了,這裏咱們給參數設置一下默認值:
//默認參數 var defaults = { triggerCharacter : '$', dropdownWidth:'150px' };
這裏咱們將特定字符設定爲 $,下拉框默認默認寬度爲150px,固然其中還有一些配角參數,不屑一提,看官們有興趣也能夠自行添加一些參數
測試以後發現是能夠正常使用的,那麼接下來就是下拉框顯示位置的問題了,這個想象好像不是秒秒鐘能解決的問題,怎麼辦?(吐槽:什麼怎麼辦?問百度啊~不要問我搜不到怎麼辦,由於我也不知道~)
這種時候不得不安慰本身:身爲菜鳥仍是有好處的,畢竟有無數前輩在前進的道路上閃閃發光~
搜~
掌聲,在這裏附上獲取光標像素左邊的原文連接以示感謝:http://blog.csdn.net/kingwolfofsky/article/details/6586029
剩下的就是整合啦,固然對於整合過程當中遇到的一點小問題沒必要訝異(都坐下,基本操做):
1,測試以後發現下拉框位置是動態出現了,可位置不對,偏移內心的理想位置有八百里。沒辦法,再去安靜的閱讀一下前輩的代碼,看完代碼,發現上文中 下拉框的位置是絕對定位的座標,這意味着什麼
呢?(我認可是後來才發現的~不測試不知道),這意味着下拉框的顯示位置不是相對於輸入框,而是相對於整個body,因此咱們應該把下拉框插入在body裏面:
$("body").after(_this.dropdown);
2,再測:發現位置大致上是對了,但仍是有偏移,再測測測~原來下拉框顯示位置還受輸入框 margin-left 和 margin-top 的影響,測試得出如下代碼:
// 調用 kingwolfofsky, 獲取光標座標 function show(elem) { var p = kingwolfofsky.getInputPositon(elem); var s = _this.dropdown.get(0); var ttop = parseInt(_this.entertext.css("marginTop")); var tleft = parseInt(_this.entertext.css("marginLeft")) console.log(ttop); s.style.top = p.bottom-ttop+10+'px'; s.style.left = p.left-tleft + 'px'; }
如今咱們須要一次完整的測試,調用:
$("#test").editTips({ triggerCharacter : '$', dropdownWidth:'150px', keyPressAction:function(temp,callbacktips){ var arr_json; if( temp == "$" ){ arr_json = ["$a","$ab","$b","$bb"] } if(temp && temp.indexOf("$a")== 0){ arr_json = ["$a","$ab"]; } else if(temp && temp.indexOf("$b")== 0){ arr_json = ["$b","$bb"]; } callbacktips(arr_json); } });
固然咱們這裏只是模擬返回數組,若是公式庫不是很大,能夠在前端完成,好比說本身建一個 json文件啥的~測試結果如圖:
效果很ok,那麼接下來呢?看官息怒,我知道到了該出所有代碼的時候到了:
/* *****公式編輯器***** * 調用 editTips()方法 * editTips({ * triggerCharacter: 觸發匹配字符 默認爲 "$" * textareaWidth: 輸入框寬度 默認 auto * textareaHeight: 輸入框高度 默認 auto * dropdownWidth: 下拉提示框寬度 默認150px * keyPressAction:function(temp,callbacktips){ * // 參數爲temp 返回 arr_json 數組 調用回調函數 callbacktips(arr_json) * var arr_json; * callbacktips(arr_json); * } * }); * */ (function ($) { $.fn.extend({ "editTips": function (options) { var opts = $.extend({}, defaults, options); //使用jQuery.extend 覆蓋插件默認參數 return this.each(function () { //這裏的this 就是 jQuery對象 // 獲取輸入光標在頁面中的座標 返回left和top,bottom var kingwolfofsky = { getInputPositon: function (elem) { if (document.selection) { //IE Support elem.focus(); var Sel = document.selection.createRange(); return { left: Sel.boundingLeft, top: Sel.boundingTop, bottom: Sel.boundingTop + Sel.boundingHeight }; } else { var that = this; var cloneDiv = '{$clone_div}', cloneLeft = '{$cloneLeft}', cloneFocus = '{$cloneFocus}', cloneRight = '{$cloneRight}'; var none = '<span style="white-space:pre-wrap;"> </span>'; var div = elem[cloneDiv] || document.createElement('div'), focus = elem[cloneFocus] || document.createElement('span'); var text = elem[cloneLeft] || document.createElement('span'); var offset = that._offset(elem), index = this._getFocus(elem), focusOffset = { left: 0, top: 0 }; if (!elem[cloneDiv]) { elem[cloneDiv] = div, elem[cloneFocus] = focus; elem[cloneLeft] = text; div.appendChild(text); div.appendChild(focus); document.body.appendChild(div); focus.innerHTML = '|'; focus.style.cssText = 'display:inline-block;width:0px;overflow:hidden;z-index:-100;word-wrap:break-word;word-break:break-all;'; div.className = this._cloneStyle(elem); div.style.cssText = 'visibility:hidden;display:inline-block;position:absolute;z-index:-100;word-wrap:break-word;word-break:break-all;overflow:hidden;'; }; div.style.left = this._offset(elem).left + "px"; div.style.top = this._offset(elem).top + "px"; var strTmp = elem.value.substring(0, index).replace(/</g, '<').replace(/>/g, '>').replace(/\n/g, '<br/>').replace(/\s/g, none); text.innerHTML = strTmp; focus.style.display = 'inline-block'; try { focusOffset = this._offset(focus); } catch (e) { }; focus.style.display = 'none'; return { left: focusOffset.left, top: focusOffset.top, bottom: focusOffset.bottom }; } }, // 克隆元素樣式並返回類 _cloneStyle: function (elem, cache) { if (!cache && elem['${cloneName}']) return elem['${cloneName}']; var className, name, rstyle = /^(number|string)$/; var rname = /^(content|outline|outlineWidth)$/; //Opera: content; IE8:outline && outlineWidth var cssText = [], sStyle = elem.style; for (name in sStyle) { if (!rname.test(name)) { val = this._getStyle(elem, name); if (val !== '' && rstyle.test(typeof val)) { // Firefox 4 name = name.replace(/([A-Z])/g, "-$1").toLowerCase(); cssText.push(name); cssText.push(':'); cssText.push(val); cssText.push(';'); }; }; }; cssText = cssText.join(''); elem['${cloneName}'] = className = 'clone' + (new Date).getTime(); this._addHeadStyle('.' + className + '{' + cssText + '}'); return className; }, // 向頁頭插入樣式 _addHeadStyle: function (content) { var style = this._style[document]; if (!style) { style = this._style[document] = document.createElement('style'); document.getElementsByTagName('head')[0].appendChild(style); }; style.styleSheet && (style.styleSheet.cssText += content) || style.appendChild(document.createTextNode(content)); }, _style: {}, // 獲取最終樣式 _getStyle: 'getComputedStyle' in window ? function (elem, name) { return getComputedStyle(elem, null)[name]; } : function (elem, name) { return elem.currentStyle[name]; }, // 獲取光標在文本框的位置 _getFocus: function (elem) { var index = 0; if (document.selection) {// IE Support elem.focus(); var Sel = document.selection.createRange(); if (elem.nodeName === 'TEXTAREA') {//textarea var Sel2 = Sel.duplicate(); Sel2.moveToElementText(elem); var index = -1; while (Sel2.inRange(Sel)) { Sel2.moveStart('character'); index++; }; } else if (elem.nodeName === 'INPUT') {// input Sel.moveStart('character', -elem.value.length); index = Sel.text.length; } } else if (elem.selectionStart || elem.selectionStart == '0') { // Firefox support index = elem.selectionStart; } return (index); }, // 獲取元素在頁面中位置 _offset: function (elem) { var box = elem.getBoundingClientRect(), doc = elem.ownerDocument, body = doc.body, docElem = doc.documentElement; var clientTop = docElem.clientTop || body.clientTop || 0, clientLeft = docElem.clientLeft || body.clientLeft || 0; var top = box.top + (self.pageYOffset || docElem.scrollTop) - clientTop, left = box.left + (self.pageXOffset || docElem.scrollLeft) - clientLeft; return { left: left, top: top, right: left + box.width, bottom: top + box.height }; } }; // 文本框監聽事件 var _this = this; _this.entertext = $(this); _this.dropdown = $('<ul class="editTips" style="display:none;"></ul>'); // 獲取到的彈出的下拉框的位置是絕對定位的座標,因此得把彈出的層放到body裏 $("body").after(_this.dropdown); _this.dropdown.css({ 'width':opts.dropdownWidth, 'position':'absolute', }); $(this).css({ 'position': 'relative', }); // 監聽輸入框 _this.dropdown.parent().on("keyup",this,function(event){ var nowTime = window.sessionStorage.getItem('nowTime'); // 當前項索引 var n = _this.dropdown.find(".active").index(); // li 個數 var n_max = _this.dropdown.find("li").length; // 注意 event在 firefox 中不能兼容,在方法中帶上event參數,以下聲明實現兼容 EVT = event || window.event; if( EVT.keyCode == 38 ){ // 方向鍵控制 li 選項 if(n-1>=0){ _this.dropdown.find('li').eq(n-1).addClass("active").siblings().removeClass("active"); } if( n == 0){ _this.dropdown.find('li').eq(n_max-1).addClass("active").siblings().removeClass("active"); } return false; } // 禁止enter鍵換行 if( EVT.keyCode == 13 ){ return false; } if( EVT.keyCode == 40 ){ // 方向鍵控制 li 選項 if(n<n_max-1){ _this.dropdown.find('li').eq(n+1).addClass("active").siblings().removeClass("active"); } if( n+1 == n_max ){ _this.dropdown.find('li').eq(0).addClass("active").siblings().removeClass("active"); } return false; } // 限制請求,輸入間隔不超過一秒時不觸發檢索事件發送請求 if( nowTime){ var oldTime = Date.now(); var nowTime = window.sessionStorage.getItem('nowTime'); var m = parseInt((oldTime - nowTime)/1000); if( m >= 1){ // 文本內容 var val = _this.entertext.val(); // 以空格切割文本返回數組 var arr_test = val.split(" "); // 獲取數組最後一個元素 var temp = arr_test[arr_test.length-1]; // 切割最後一個元素 單字符元素 返回數組 var temp_type = temp.split(""); // 獲取數字第一個字符 var temp_cha = temp_type[0]; // 最後一個元素長度 var temp_len = temp.length; var temp_dot = temp_type.lastIndexOf("."); // 定義回調函數 callbacktips var callbacktips = function(arr_json){ // 初始化 UL _this.dropdown.find("li").remove(); for( i=0;i<arr_json.length;i++ ){ _this.dropdown.append('<li>'+arr_json[i]+'</li>'); }; _this.dropdown.show(); _this.dropdown.find("li:first-child").addClass("active"); // 自定義樣式 _this.dropdown.find("li").css({ 'width':'100%', }); }; // 最後一個元素爲空值 if( temp_len == 0 ){ _this.dropdown.hide(); _this.dropdown.find('li').remove(); } // 爲特定字符 符合觸發條件 if( temp_cha == opts.triggerCharacter ){ if($.isFunction(opts.keyPressAction)){ opts.keyPressAction(temp, function(arr_json){ // 調用回調函數 callbacktips(arr_json); }); } }else{ _this.dropdown.hide(); _this.dropdown.find('li').remove(); } } } // 初始化第一次時間 window.sessionStorage.setItem('nowTime',Date.now()); }); // 切換當前項 _this.dropdown.on('mouseenter','li',function(){ $(this).addClass("active").siblings().removeClass("active"); }); // 阻止輸入過程當中 方向鍵盤的默認事件 _this.entertext.on("keydown",_this.entertext,function(event){ EVT = event || window.event; if( EVT.keyCode == 38 ){ EVT.preventDefault(); } if( EVT.keyCode == 40 ){ EVT.preventDefault(); } // enter鍵選取對應的 li if( EVT.keyCode == 13 ){ EVT.preventDefault(); var txt = _this.dropdown.find(".active").html(); var test_txt = _this.entertext.val(); var arr_change = test_txt.split(" "); // 以空格切割文本返回數組 var arr_test = test_txt.split(" "); // 獲取數組最後一個元素 var temp = arr_test[arr_test.length-1]; var temp_type = temp.split(""); var temp_dot = temp_type.lastIndexOf("."); var n ; temp_type.splice(temp_dot+1,temp_type.length-1-temp_dot,txt); n = temp_type.join(''); arr_change.splice(arr_change.length-1,1,""+n); _this.entertext.val(arr_change.join(" ")); _this.dropdown.hide(); _this.dropdown.find('li').remove(); } }); // 點擊替換 重組val $(document).on("click",'li',function(){ var txt = $(this).html(); var test_txt = _this.entertext.val(); var arr_change = test_txt.split(" "); // 以空格切割文本返回數組 var arr_test = test_txt.split(" "); // 獲取數組最後一個元素 var temp = arr_test[arr_test.length-1]; var temp_type = temp.split(""); var temp_dot = temp_type.lastIndexOf("."); var n ; temp_type.splice(temp_dot+1,temp_type.length-1-temp_dot,txt); n = temp_type.join(''); arr_change.splice(arr_change.length-1,1,""+n); _this.entertext.val(arr_change.join(" ")); _this.dropdown.hide(); _this.dropdown.find('li').remove(); }); // 調用獲取座標方法 show(elem) $(this).keyup(function(){ show(this); }); // 調用 kingwolfofsky, 獲取光標座標 function show(elem) { var p = kingwolfofsky.getInputPositon(elem); var s = _this.dropdown.get(0); var ttop = parseInt(_this.entertext.css("marginTop")); var tleft = parseInt(_this.entertext.css("marginLeft")) console.log(ttop); s.style.top = p.bottom-ttop+10+'px'; s.style.left = p.left-tleft + 'px'; } }); } }); //默認參數 var defaults = { triggerCharacter : '$', dropdownWidth:'150px' }; })(window.jQuery);
對呀,仍是上面的調用,沒問題的吧~
$("#test").editTips({ triggerCharacter : '$', dropdownWidth:'150px', keyPressAction:function(temp,callbacktips){ var arr_json; if( temp == "$" ){ arr_json = ["$a","$ab","$b","$bb"] } if(temp && temp.indexOf("$a")== 0){ arr_json = ["$a","$ab"]; } else if(temp && temp.indexOf("$b")== 0){ arr_json = ["$b","$bb"]; } callbacktips(arr_json); } });
最後,複製粘貼上面的 html ,css ,js ,調用,它就是你的了~
此次分享就到這裏了,歡迎親們品鑑,有問題能夠私信或者留言哦~(偷笑:雖然我不必定看~看了不必定回~回了不必定能解決問題~)
完整代碼:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>公式測試</title> <style> /* 編輯器下拉框相關樣式 */ table,tr,th,td{padding:0;margin:0;} ul,li,textarea,input{text-decoration:none;list-style:none;margin:0;padding:0;box-sizing: border-box;} input{ outline:none; } .editTips{ padding:5px 0; border-radius: 0!important; box-shadow: 0 2px 4px rgba(0,0,0,.2); max-height: auto; margin:0; z-index:9999; } .editTips li{ text-align:left; box-sizing:border-box; display:block; width:100%; line-height:1.42857143; margin:1px 0; padding:6px 11px; color:#333; cursor:pointer; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; font-weight:400; line-height:1.42857143; border-bottom:solid 1px #e5e5e5; background:#e5e5e5; } .editTips li:last-child{ border-bottom:none; } .active{ background:#fee188!important; } .editTips li.active{ background:#fee188!important; } textarea{ text-decoration: none; list-style: none; margin: 0; padding: 0; display: block; box-sizing: border-box; width: 500px; height: 300px; margin-top: 100px; margin-left: 100px; } </style> </head> <body> <textarea id="test" ></textarea> </body> </html> <script type="text/javascript" src="./jquery-1.8.3.min.js"></script> <script type="text/javascript"> /* *****公式編輯器***** * 調用 editTips()方法 * editTips({ * triggerCharacter: 觸發匹配字符 默認爲 "$" * textareaWidth: 輸入框寬度 默認 auto * textareaHeight: 輸入框高度 默認 auto * dropdownWidth: 下拉提示框寬度 默認150px * keyPressAction:function(temp,callbacktips){ * // 參數爲temp 返回 arr_json 數組 調用回調函數 callbacktips(arr_json) * var arr_json; * callbacktips(arr_json); * } * }); * */ (function ($) { $.fn.extend({ "editTips": function (options) { var opts = $.extend({}, defaults, options); //使用jQuery.extend 覆蓋插件默認參數 return this.each(function () { //這裏的this 就是 jQuery對象 // 獲取輸入光標在頁面中的座標 返回left和top,bottom var kingwolfofsky = { getInputPositon: function (elem) { if (document.selection) { //IE Support elem.focus(); var Sel = document.selection.createRange(); return { left: Sel.boundingLeft, top: Sel.boundingTop, bottom: Sel.boundingTop + Sel.boundingHeight }; } else { var that = this; var cloneDiv = '{$clone_div}', cloneLeft = '{$cloneLeft}', cloneFocus = '{$cloneFocus}', cloneRight = '{$cloneRight}'; var none = '<span style="white-space:pre-wrap;"> </span>'; var div = elem[cloneDiv] || document.createElement('div'), focus = elem[cloneFocus] || document.createElement('span'); var text = elem[cloneLeft] || document.createElement('span'); var offset = that._offset(elem), index = this._getFocus(elem), focusOffset = { left: 0, top: 0 }; if (!elem[cloneDiv]) { elem[cloneDiv] = div, elem[cloneFocus] = focus; elem[cloneLeft] = text; div.appendChild(text); div.appendChild(focus); document.body.appendChild(div); focus.innerHTML = '|'; focus.style.cssText = 'display:inline-block;width:0px;overflow:hidden;z-index:-100;word-wrap:break-word;word-break:break-all;'; div.className = this._cloneStyle(elem); div.style.cssText = 'visibility:hidden;display:inline-block;position:absolute;z-index:-100;word-wrap:break-word;word-break:break-all;overflow:hidden;'; }; div.style.left = this._offset(elem).left + "px"; div.style.top = this._offset(elem).top + "px"; var strTmp = elem.value.substring(0, index).replace(/</g, '<').replace(/>/g, '>').replace(/\n/g, '<br/>').replace(/\s/g, none); text.innerHTML = strTmp; focus.style.display = 'inline-block'; try { focusOffset = this._offset(focus); } catch (e) { }; focus.style.display = 'none'; return { left: focusOffset.left, top: focusOffset.top, bottom: focusOffset.bottom }; } }, // 克隆元素樣式並返回類 _cloneStyle: function (elem, cache) { if (!cache && elem['${cloneName}']) return elem['${cloneName}']; var className, name, rstyle = /^(number|string)$/; var rname = /^(content|outline|outlineWidth)$/; //Opera: content; IE8:outline && outlineWidth var cssText = [], sStyle = elem.style; for (name in sStyle) { if (!rname.test(name)) { val = this._getStyle(elem, name); if (val !== '' && rstyle.test(typeof val)) { // Firefox 4 name = name.replace(/([A-Z])/g, "-$1").toLowerCase(); cssText.push(name); cssText.push(':'); cssText.push(val); cssText.push(';'); }; }; }; cssText = cssText.join(''); elem['${cloneName}'] = className = 'clone' + (new Date).getTime(); this._addHeadStyle('.' + className + '{' + cssText + '}'); return className; }, // 向頁頭插入樣式 _addHeadStyle: function (content) { var style = this._style[document]; if (!style) { style = this._style[document] = document.createElement('style'); document.getElementsByTagName('head')[0].appendChild(style); }; style.styleSheet && (style.styleSheet.cssText += content) || style.appendChild(document.createTextNode(content)); }, _style: {}, // 獲取最終樣式 _getStyle: 'getComputedStyle' in window ? function (elem, name) { return getComputedStyle(elem, null)[name]; } : function (elem, name) { return elem.currentStyle[name]; }, // 獲取光標在文本框的位置 _getFocus: function (elem) { var index = 0; if (document.selection) {// IE Support elem.focus(); var Sel = document.selection.createRange(); if (elem.nodeName === 'TEXTAREA') {//textarea var Sel2 = Sel.duplicate(); Sel2.moveToElementText(elem); var index = -1; while (Sel2.inRange(Sel)) { Sel2.moveStart('character'); index++; }; } else if (elem.nodeName === 'INPUT') {// input Sel.moveStart('character', -elem.value.length); index = Sel.text.length; } } else if (elem.selectionStart || elem.selectionStart == '0') { // Firefox support index = elem.selectionStart; } return (index); }, // 獲取元素在頁面中位置 _offset: function (elem) { var box = elem.getBoundingClientRect(), doc = elem.ownerDocument, body = doc.body, docElem = doc.documentElement; var clientTop = docElem.clientTop || body.clientTop || 0, clientLeft = docElem.clientLeft || body.clientLeft || 0; var top = box.top + (self.pageYOffset || docElem.scrollTop) - clientTop, left = box.left + (self.pageXOffset || docElem.scrollLeft) - clientLeft; return { left: left, top: top, right: left + box.width, bottom: top + box.height }; } }; // 文本框監聽事件 var _this = this; _this.entertext = $(this); _this.dropdown = $('<ul class="editTips" style="display:none;"></ul>'); // 獲取到的彈出的下拉框的位置是絕對定位的座標,因此得把彈出的層放到body裏 $("body").after(_this.dropdown); _this.dropdown.css({ 'width':opts.dropdownWidth, 'position':'absolute', }); $(this).css({ 'position': 'relative', }); // 監聽輸入框 _this.dropdown.parent().on("keyup",this,function(event){ var nowTime = window.sessionStorage.getItem('nowTime'); // 當前項索引 var n = _this.dropdown.find(".active").index(); // li 個數 var n_max = _this.dropdown.find("li").length; // 注意 event在 firefox 中不能兼容,在方法中帶上event參數,以下聲明實現兼容 EVT = event || window.event; if( EVT.keyCode == 38 ){ // 方向鍵控制 li 選項 if(n-1>=0){ _this.dropdown.find('li').eq(n-1).addClass("active").siblings().removeClass("active"); } if( n == 0){ _this.dropdown.find('li').eq(n_max-1).addClass("active").siblings().removeClass("active"); } return false; } // 禁止enter鍵換行 if( EVT.keyCode == 13 ){ return false; } if( EVT.keyCode == 40 ){ // 方向鍵控制 li 選項 if(n<n_max-1){ _this.dropdown.find('li').eq(n+1).addClass("active").siblings().removeClass("active"); } if( n+1 == n_max ){ _this.dropdown.find('li').eq(0).addClass("active").siblings().removeClass("active"); } return false; } // 限制請求,輸入間隔不超過一秒時不觸發檢索事件發送請求 if( nowTime){ var oldTime = Date.now(); var nowTime = window.sessionStorage.getItem('nowTime'); var m = parseInt((oldTime - nowTime)/1000); if( m >= 1){ // 文本內容 var val = _this.entertext.val(); // 以空格切割文本返回數組 var arr_test = val.split(" "); // 獲取數組最後一個元素 var temp = arr_test[arr_test.length-1]; // 切割最後一個元素 單字符元素 返回數組 var temp_type = temp.split(""); // 獲取數字第一個字符 var temp_cha = temp_type[0]; // 最後一個元素長度 var temp_len = temp.length; var temp_dot = temp_type.lastIndexOf("."); // 定義回調函數 callbacktips var callbacktips = function(arr_json){ // 初始化 UL _this.dropdown.find("li").remove(); for( i=0;i<arr_json.length;i++ ){ _this.dropdown.append('<li>'+arr_json[i]+'</li>'); }; _this.dropdown.show(); _this.dropdown.find("li:first-child").addClass("active"); // 自定義樣式 _this.dropdown.find("li").css({ 'width':'100%', }); }; // 最後一個元素爲空值 if( temp_len == 0 ){ _this.dropdown.hide(); _this.dropdown.find('li').remove(); } // 爲特定字符 符合觸發條件 if( temp_cha == opts.triggerCharacter ){ if($.isFunction(opts.keyPressAction)){ opts.keyPressAction(temp, function(arr_json){ // 調用回調函數 callbacktips(arr_json); }); } }else{ _this.dropdown.hide(); _this.dropdown.find('li').remove(); } } } // 初始化第一次時間 window.sessionStorage.setItem('nowTime',Date.now()); }); // 切換當前項 _this.dropdown.on('mouseenter','li',function(){ $(this).addClass("active").siblings().removeClass("active"); }); // 阻止輸入過程當中 方向鍵盤的默認事件 _this.entertext.on("keydown",_this.entertext,function(event){ EVT = event || window.event; if( EVT.keyCode == 38 ){ EVT.preventDefault(); } if( EVT.keyCode == 40 ){ EVT.preventDefault(); } // enter鍵選取對應的 li if( EVT.keyCode == 13 ){ EVT.preventDefault(); var txt = _this.dropdown.find(".active").html(); var test_txt = _this.entertext.val(); var arr_change = test_txt.split(" "); // 以空格切割文本返回數組 var arr_test = test_txt.split(" "); // 獲取數組最後一個元素 var temp = arr_test[arr_test.length-1]; var temp_type = temp.split(""); var temp_dot = temp_type.lastIndexOf("."); var n ; temp_type.splice(temp_dot+1,temp_type.length-1-temp_dot,txt); n = temp_type.join(''); arr_change.splice(arr_change.length-1,1,""+n); _this.entertext.val(arr_change.join(" ")); _this.dropdown.hide(); _this.dropdown.find('li').remove(); } }); // 點擊替換 重組val $(document).on("click",'li',function(){ var txt = $(this).html(); var test_txt = _this.entertext.val(); var arr_change = test_txt.split(" "); // 以空格切割文本返回數組 var arr_test = test_txt.split(" "); // 獲取數組最後一個元素 var temp = arr_test[arr_test.length-1]; var temp_type = temp.split(""); var temp_dot = temp_type.lastIndexOf("."); var n ; temp_type.splice(temp_dot+1,temp_type.length-1-temp_dot,txt); n = temp_type.join(''); arr_change.splice(arr_change.length-1,1,""+n); _this.entertext.val(arr_change.join(" ")); _this.dropdown.hide(); _this.dropdown.find('li').remove(); }); // 調用獲取座標方法 show(elem) $(this).keyup(function(){ show(this); }); // 調用 kingwolfofsky, 獲取光標座標 function show(elem) { var p = kingwolfofsky.getInputPositon(elem); var s = _this.dropdown.get(0); var ttop = parseInt(_this.entertext.css("marginTop")); var tleft = parseInt(_this.entertext.css("marginLeft")) console.log(ttop); s.style.top = p.bottom-ttop+10+'px'; s.style.left = p.left-tleft + 'px'; } }); } }); //默認參數 var defaults = { triggerCharacter : '$', dropdownWidth:'150px' }; })(window.jQuery); //調用,仍是上面的調用 $("#test").editTips({ triggerCharacter : '$', dropdownWidth:'150px', keyPressAction:function(temp,callbacktips){ var arr_json; if( temp == "$" ){ arr_json = ["$a","$ab","$b","$bb 副科級"] } if(temp && temp.indexOf("$a")== 0){ arr_json = ["$a","$ab"]; } else if(temp && temp.indexOf("$b")== 0){ arr_json = ["$b","$bb 這個是說明"]; } callbacktips(arr_json); } }); </script>
若是想使用的時候顯得高大上的感受,其中的不足和高級功能須要你本身去補充了。
出處:http://www.cnblogs.com/wbsndbf/p/7976300.html