Introduction
Should develop the demand, need to write a formula editor plug-in, below to tell you about the implementation process. (wiping sweat, forcing calmness, cough, voice opening ~)
Html
There's really only a little bit of it.~
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Formula Editor Test</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>
css
Let's give it together. Sooner or later, we'll give it:
/* Editor drop-down box related styles */ 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; }
Plug-in template - this is the input box
(function ($) { $.fn.extend({ "editTips": function (options) { var opts = $.extend({}, defaults, options); //Use jQuery.extend Override plug-in default parameters return this.each(function () { //There this Namely jQuery object }); } }); //Default parameters var defaults = { }; })(window.jQuery);
Insert drop-down box
Here this is the input box, in html, no need to go to js insert, of course, this is not a problem, here we will input box and drop-down box are customized, insert the ul editTips class can be removed, in order to see the effect temporarily added.
I personally want the width of the drop-down box to be customizable:'width': opts. dropdown Width, where we define the first parameter: the width of the drop-down box.
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 });
Monitoring text box
ps: Since it's a formula editor, it means that the retrieval event will not always be triggered, but only when the specified character is input, such as:! @ # and so on. Here we take $as an example, from which we can think, we are sure.
A parameter is needed to dynamically change the trigger condition of retrieval events. First, keep it in mind.
Retrieval process: Input box input value - Input specific characters trigger retrieval, such as $- Callback function, we here match keywords for callbacktips - Return partial matching results containing keywords, in the form of array - traversal array added to the drop-down box ul display - Select the current item.
Because it also involves restricting requests and switching the current item by direction keys, the whole code is given as follows:
// Listening Input Box _this.dropdown.parent().on("keyup",this,function(event){ var nowTime = window.sessionStorage.getItem('nowTime'); // Current Item Index var n = _this.dropdown.find(".active").index(); // li Number var n_max = _this.dropdown.find("li").length; // Be careful event stay firefox Incompatible in method event The following statement implements compatibility EVT = event || window.event; if( EVT.keyCode == 38 ){ // Directional Key Control li option 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; } // prohibit enter Key line change if( EVT.keyCode == 13 ){ return false; } if( EVT.keyCode == 40 ){ // Directional Key Control li option 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; } // Restrict requests and do not trigger retrieval events to send requests when the input interval is less than one second if( nowTime){ var oldTime = Date.now(); var nowTime = window.sessionStorage.getItem('nowTime'); var m = parseInt((oldTime - nowTime)/1000); if( m >= 1){ // Text content var val = _this.entertext.val(); // Cut text with spaces to return an array var arr_test = val.split(" "); // Get the last element of the array var temp = arr_test[arr_test.length-1]; // Cut the last single character element to return the array var temp_type = temp.split(""); // Get the first character of the number var temp_cha = temp_type[0]; // Last element length var temp_len = temp.length; var temp_dot = temp_type.lastIndexOf("."); // Define callback function callbacktips var callbacktips = function(arr_json){ // Initialization 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"); // custom style _this.dropdown.find("li").css({ 'width':'100%', }); }; // The last element is null if( temp_len == 0 ){ _this.dropdown.hide(); _this.dropdown.find('li').remove(); } // Meeting trigger conditions for specific characters if( temp_cha == opts.triggerCharacter ){ if($.isFunction(opts.keyPressAction)){ opts.keyPressAction(temp, function(arr_json){ // Callback function callbacktips(arr_json); }); } }else{ _this.dropdown.hide(); _this.dropdown.find('li').remove(); } } } // Initialize the first time window.sessionStorage.setItem('nowTime',Date.now()); });
ps: Here comes our second parameter, which is also a crucial parameter, triggering the retrieval event character: opts.triggerCharacter.
Mouse switch current item
After the drop-down menu is displayed (we don't care where the drop-down menu is displayed first), experientially, I hope that I can use either the mouse to select or the direction key to select. The direction key to switch the current item is integrated into the code above, where the mouse to switch the current item:
// Switch the current item _this.dropdown.on('mouseenter','li',function(){ $(this).addClass("active").siblings().removeClass("active"); });
Prevent default events for keyboard keystrokes - enter key to select the current item
If we don't do this, when we switch the current item with the direction key, you will see that the cursor is moving up and down. How can we tolerate this? What should I do? Shield it ~, here we can integrate the enter key to select the current item, see the code:
// Prevent default events for directional keyboard during input _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 Key selection corresponds to 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(" "); // Cut text with spaces to return an array var arr_test = test_txt.split(" "); // Get the last element of the array 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(); } });
Click on the current item to reorganize val
Otherwise, let's look at the code directly. You may be hungry. Look at the code:
// Click Replacement Reorganization val $(document).on("click",'li',function(){ var txt = $(this).html(); var test_txt = _this.entertext.val(); var arr_change = test_txt.split(" "); // Cut text with spaces to return an array var arr_test = test_txt.split(" "); // Get the last element of the array 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(); });
Default parameters
When we get here, we should be able to run away without dynamically displaying the drop-down box. Here we set the default values for the parameters:
//Default parameters var defaults = { triggerCharacter : '$', dropdownWidth:'150px' };
Here we set the specific character to $, the default width of the drop-down box is 150 px, of course, there are some supporting role parameters, not to mention, it is interesting to see the officials can also add some parameters themselves.
After testing, it is found that it can be used normally. Next, it is the problem that the drop-down box shows the location. This imagination does not seem to be a problem that can be solved in seconds. Ask Baidu ~Don't ask me what I can't find, because I don't know ~)
At this time, I have to comfort myself: as a rookie is still good, after all, there are countless predecessors glittering on the way forward.~
Search ~
Applause. Attach a link to the original text on the left of the cursor pixel to thank you: http://blog.csdn.net/kingwolfofsky/article/details/6586029.
What's left is integration. Of course, it's not surprising that there are a few minor problems in the integration process (all sit down, basic operations):
1. After testing, it is found that the drop-down box position appears dynamically, but the position is not right, and the ideal position of the offset heart is 800 li. No way, go quietly to read the code of predecessors, read the code, found that the position of the drop-down box above is absolutely positioned coordinates, what does that mean?
What about it? (I admit it was only later discovered that ~no test did not know), which means that the display position of the drop-down box is not relative to the input box, but relative to the whole body, so we should insert the drop-down box into the body:
$("body").after(_this.dropdown);
2. Re-test: It is found that the position is generally correct, but there is still a deviation. Re-test ~the original display position of the drop-down box is also affected by the input box margin-left and margin-top. The test results show that the following code:
// call kingwolfofsky, Getting cursor coordinates 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'; }
Now we need a complete test to call:
$("#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); } });
Of course, we just simulate the return array here. If the formula library is not very large, it can be completed in the front end, such as building a json file and so on. ~The test results are as follows:
All js code
The effect is ok, so what happens next? Look at the official's anger, I know it's time to come out with all the code:
/* *****Formula Editor***** * Call the editTips() method * editTips({ * triggerCharacter: The trigger matching character defaults to "$" * textareaWidth: Input box width default auto * textareaHeight: Input box Height default auto * dropdownWidth: The default width of the drop-down prompt box is 150px * keyPressAction:function(temp,callbacktips){ * // The parameter temp returns arr_json array and callback function callbacktips(arr_json) * var arr_json; * callbacktips(arr_json); * } * }); * */ (function ($) { $.fn.extend({ "editTips": function (options) { var opts = $.extend({}, defaults, options); //Use jQuery.extend Override plug-in default parameters return this.each(function () { //There this Namely jQuery object // Get the coordinates of the input cursor in the page to return left and 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 }; } }, // Clone element styles and return classes _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; }, // Insert Styles into Page Headers _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: {}, // Get the final style _getStyle: 'getComputedStyle' in window ? function (elem, name) { return getComputedStyle(elem, null)[name]; } : function (elem, name) { return elem.currentStyle[name]; }, // Gets the position of the cursor in the text box _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); }, // Get the location of elements in the page _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 }; } }; // Textbox listens for events var _this = this; _this.entertext = $(this); _this.dropdown = $('<ul class="editTips" style="display:none;"></ul>'); // The position of the pop-up drop-down box obtained is the absolutely positioned coordinate, so the pop-up layer has to be placed on it. body in $("body").after(_this.dropdown); _this.dropdown.css({ 'width':opts.dropdownWidth, 'position':'absolute', }); $(this).css({ 'position': 'relative', }); // Listening Input Box _this.dropdown.parent().on("keyup",this,function(event){ var nowTime = window.sessionStorage.getItem('nowTime'); // Current Item Index var n = _this.dropdown.find(".active").index(); // li Number var n_max = _this.dropdown.find("li").length; // Be careful event stay firefox Incompatible in method event The following statement implements compatibility EVT = event || window.event; if( EVT.keyCode == 38 ){ // Directional Key Control li option 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; } // prohibit enter Key line change if( EVT.keyCode == 13 ){ return false; } if( EVT.keyCode == 40 ){ // Directional Key Control li option 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; } // Restrict requests and do not trigger retrieval events to send requests when the input interval is less than one second if( nowTime){ var oldTime = Date.now(); var nowTime = window.sessionStorage.getItem('nowTime'); var m = parseInt((oldTime - nowTime)/1000); if( m >= 1){ // Text content var val = _this.entertext.val(); // Cut text with spaces to return an array var arr_test = val.split(" "); // Get the last element of the array var temp = arr_test[arr_test.length-1]; // Cut the last single character element to return the array var temp_type = temp.split(""); // Get the first character of the number var temp_cha = temp_type[0]; // Last element length var temp_len = temp.length; var temp_dot = temp_type.lastIndexOf("."); // Define callback function callbacktips var callbacktips = function(arr_json){ // Initialization 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"); // custom style _this.dropdown.find("li").css({ 'width':'100%', }); }; // The last element is null if( temp_len == 0 ){ _this.dropdown.hide(); _this.dropdown.find('li').remove(); } // Meeting trigger conditions for specific characters if( temp_cha == opts.triggerCharacter ){ if($.isFunction(opts.keyPressAction)){ opts.keyPressAction(temp, function(arr_json){ // Callback function callbacktips(arr_json); }); } }else{ _this.dropdown.hide(); _this.dropdown.find('li').remove(); } } } // Initialize the first time window.sessionStorage.setItem('nowTime',Date.now()); }); // Switch the current item _this.dropdown.on('mouseenter','li',function(){ $(this).addClass("active").siblings().removeClass("active"); }); // Prevent default events for directional keyboard during input _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 Key selection corresponds to 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(" "); // Cut text with spaces to return an array var arr_test = test_txt.split(" "); // Get the last element of the array 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(); } }); // Click Replacement Reorganization val $(document).on("click",'li',function(){ var txt = $(this).html(); var test_txt = _this.entertext.val(); var arr_change = test_txt.split(" "); // Cut text with spaces to return an array var arr_test = test_txt.split(" "); // Get the last element of the array 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(); }); // Call the coordinate acquisition method show(elem) $(this).keyup(function(){ show(this); }); // call kingwolfofsky, Getting cursor coordinates 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'; } }); } }); //Default parameters var defaults = { triggerCharacter : '$', dropdownWidth:'150px' }; })(window.jQuery);
Call plug-in
Yeah, it's still the above call. No problem.~
$("#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); } });
Finally, copy and paste the above html, css, js, call, it's yours.~
This is the end of the sharing. Welcome to our family. If you have any questions, you can send them a private message or leave a message. (Smile secretly: although I don't necessarily look at it, I don't necessarily return it, I don't necessarily solve it.)