This repository has been archived on 2024-08-27. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
redstoner.com/app/assets/javascripts/autocomplete.js

612 lines
16 KiB
JavaScript

/**
* Plugin Name: Autocomplete for Textarea
* Author: Amir Harel
* Copyright: amir harel (harel.amir1@gmail.com)
* Twitter: @amir_harel
* Version 1.4
* Published at : http://www.amirharel.com/2011/03/07/implementing-autocomplete-jquery-plugin-for-textarea/
*/
(function($){
/**
* @param obj
* @attr wordCount {Number} the number of words the user want to for matching it with the dictionary
* @attr mode {String} set "outter" for using an autocomplete that is being displayed in the outter layout of the textarea, as opposed to inner display
* @attr on {Object} containing the followings:
* @attr query {Function} will be called to query if there is any match for the user input
*/
$.fn.autocomplete = function(obj){
this.each(function(index,element){
if( element.nodeName == 'TEXTAREA' ){
makeAutoComplete(element,obj);
}
});
};
//var browser = {isChrome: $.browser.webkit };
function getTextAreaSelectionEnd(ta) {
var textArea = ta;//document.getElementById('textarea1');
if (document.selection) { //IE
var bm = document.selection.createRange().getBookmark();
var sel = textArea.createTextRange();
sel.moveToBookmark(bm);
var sleft = textArea.createTextRange();
sleft.collapse(true);
sleft.setEndPoint("EndToStart", sel);
return sleft.text.length + sel.text.length;
}
return textArea.selectionEnd; //ff & chrome
}
function getDefaultCharArray(){
return {
'`':0,
'~':0,
'1':0,
'!':0,
'2':0,
'@':0,
'3':0,
'#':0,
'4':0,
'$':0,
'5':0,
'%':0,
'6':0,
'^':0,
'7':0,
'&':0,
'8':0,
'*':0,
'9':0,
'(':0,
'0':0,
')':0,
'-':0,
'_':0,
'=':0,
'+':0,
'q':0,
'Q':0,
'w':0,
'W':0,
'e':0,
'E':0,
'r':0,
'R':0,
't':0,
'T':0,
'y':0,
'Y':0,
'u':0,
'U':0,
'i':0,
'I':0,
'o':0,
'O':0,
'p':0,
'P':0,
'[':0,
'{':0,
']':0,
'}':0,
'a':0,
'A':0,
's':0,
'S':0,
'd':0,
'D':0,
'f':0,
'F':0,
'g':0,
'G':0,
'h':0,
'H':0,
'j':0,
'J':0,
'k':0,
'K':0,
'l':0,
'L':0,
';':0,
':':0,
'\'':0,
'"':0,
'\\':0,
'|':0,
'z':0,
'Z':0,
'x':0,
'X':0,
'c':0,
'C':0,
'v':0,
'V':0,
'b':0,
'B':0,
'n':0,
'N':0,
'm':0,
'M':0,
',':0,
'<':0,
'.':0,
'>':0,
'/':0,
'?':0,
' ':0
};
}
function setCharSize(data){
for( var ch in data.chars ){
if( ch == ' ' ) $(data.clone).html("<span id='test-width_"+data.id+"' style='line-block'>&nbsp;</span>");
else $(data.clone).html("<span id='test-width_"+data.id+"' style='line-block'>"+ch+"</span>");
var testWidth = $("#test-width_"+data.id).width();
data.chars[ch] = testWidth;
}
}
var _data = {};
var _count = 0;
function makeAutoComplete(ta,obj){
_count++;
_data[_count] = {
id:"auto_"+_count,
ta:ta,
wordCount:obj.wordCount,
on:obj.on,
clone:null,
lineHeight:0,
list:null,
charInLines:{},
mode:obj.mode,
chars:getDefaultCharArray()};
var clone = createClone(_count);
// we need to fix the width
window.onresize = function() {
clone.remove();
clone = createClone(_count);
console.debug('window resized!');
};
_data[_count].clone = clone;
setCharSize(_data[_count]);
//_data[_count].lineHeight = $(ta).css("font-size");
_data[_count].list = createList(_data[_count]);
registerEvents(_data[_count]);
}
function createList(data){
var ul = document.createElement("ul");
$(ul).addClass("auto-list");
document.body.appendChild(ul);
return ul;
}
function createClone(id) {
var data = _data[id];
var div = document.createElement("div");
var offset = $(data.ta).offset();
offset.top = offset.top - parseInt($(data.ta).css("margin-top"));
offset.left = offset.left - parseInt($(data.ta).css("margin-left"));
//console.log("createClone: offset.top=",offset.top," offset.left=",offset.left);
$(div).css({
position:"absolute",
top: offset.top,
left: offset.left,
"border-collapse" : $(data.ta).css("border-collapse"),
"border-bottom-style" : $(data.ta).css("border-bottom-style"),
"border-bottom-width" : $(data.ta).css("border-bottom-width"),
"border-left-style" : $(data.ta).css("border-left-style"),
"border-left-width" : $(data.ta).css("border-left-width"),
"border-right-style" : $(data.ta).css("border-right-style"),
"border-right-width" : $(data.ta).css("border-right-width"),
"border-spacing" : $(data.ta).css("border-spacing"),
"border-top-style" : $(data.ta).css("border-top-style"),
"border-top-width" : $(data.ta).css("border-top-width"),
"direction" : $(data.ta).css("direction"),
"font-size-adjust" : $(data.ta).css("font-size-adjust"),
"font-size" : $(data.ta).css("font-size"),
"font-stretch" : $(data.ta).css("font-stretch"),
"font-style" : $(data.ta).css("font-style"),
"font-family" : $(data.ta).css("font-family"),
"font-variant" : $(data.ta).css("font-variant"),
"font-weight" : $(data.ta).css("font-weight"),
"width" : $(data.ta).css("width"),
"height" : $(data.ta).css("height"),
"letter-spacing" : $(data.ta).css("letter-spacing"),
"margin-bottom" : $(data.ta).css("margin-bottom"),
"margin-top" : $(data.ta).css("margin-top"),
"margin-right" : $(data.ta).css("margin-right"),
"margin-left" : $(data.ta).css("margin-left"),
"padding-bottom" : $(data.ta).css("padding-bottom"),
"padding-top" : $(data.ta).css("padding-top"),
"padding-right" : $(data.ta).css("padding-right"),
"padding-left" : $(data.ta).css("padding-left"),
"overflow-x" : "hidden",
"line-height" : $(data.ta).css("line-height"),
"overflow-y" : "hidden",
"z-index" : -10,
"color" : "transparent",
"visibility" : "hidden"
});
//console.log("createClone: ta width=",$(data.ta).css("width")," ta clientWidth=",data.ta.clientWidth, "scrollWidth=",data.ta.scrollWidth," offsetWidth=",data.ta.offsetWidth," jquery.width=",$(data.ta).width());
//i don't know why by chrome adds some pixels to the clientWidth...
data.chromeWidthFix = (data.ta.clientWidth - $(data.ta).width());
data.lineHeight = $(data.ta).css("line-height");
if( isNaN(parseInt(data.lineHeight)) ) data.lineHeight = parseInt($(data.ta).css("font-size"))+2;
document.body.appendChild(div);
return div;
}
/*function breakLongLines(lines,miror){
var ret = [];
for( var i=0; i<lines.length; i++ ){
var lines2 = breakLine(lines[i],miror);
for( var j=0;j< lines2.length; j++ ){
ret.push(lines2[j]);
}
}
return ret;
}
var stats = {getCursorPosition:{start:0,end:0},breakLongLines:{start:0,end:0},
breakLine:{total:0, runCount:0}
};
function breakLine(line,miror){
var _start = new Date().getTime();
stats.breakLine.runCount++;
var ret =[line];
var count=0;
miror.html("<span style='display:inline-block;'>"+line+"</span>");
var span = miror.children("span");
if( span.width()+10 < miror.width()) return [line];
while( span.width() >= miror.width()-10 ){
count++;
var words = line.split(" ");
var left = words.slice(0,words.length-count);
var right = words.slice(words.length-count,words.length);
miror.html("<span style='display:inline-block;'>"+left.join(" ")+"</span>");
var span = miror.children("span");
ret = [left.join(" "),right.join(" ")];
}
var arr = breakLine(ret[1],miror);
var ret2= [ret[0]];
for( var i=0; i<arr.length; i++ ){
ret2.push(arr[i]);
}
stats.breakLine.total += (new Date().getTime() - _start);
return ret2;
}*/
function getWords(data){
var selectionEnd = getTextAreaSelectionEnd(data.ta);//.selectionEnd;
var text = data.ta.value;
text = text.substr(0,selectionEnd);
if( text.charAt(text.length-1) == ' ' || text.charAt(text.length-1) == '\n' ) return "";
var ret = [];
var wordsFound = 0;
var pos = text.length-1;
while( wordsFound < data.wordCount && pos >= 0 && text.charAt(pos) != '\n'){
ret.unshift(text.charAt(pos));
pos--;
if( text.charAt(pos) == ' ' || pos < 0 ){
wordsFound++;
}
}
return ret.join("");
}
function showList(data,list,text){
if( !data.listVisible ){
data.listVisible = true;
var pos = getCursorPosition(data);
$(data.list).css({
left: pos.left+"px",
top: pos.top+"px",
display: "block"
});
}
var html = "";
var regEx = new RegExp("("+text.slice(1)+")", "i");
var taWidth = $(data.ta).width()-5;
var width = data.mode == "outter" ? "style='width:"+taWidth+"px;'" : "";
for( var i=0; i< list.length; i++ ){
//var a = list[i].replace(regEx,"<mark>$1</mark>");
html += "<li data-value='@"+list[i]+"' "+width+">"+list[i].replace(regEx,"<mark>$1</mark>")+"</li>";
}
$(data.list).html(html);
}
function breakLines(text,data){
var lines = [];
var width = $(data.clone).width();
var line1 = "";
var line1Width = 0;
var line2Width = 0;
var line2 = "";
var chSize = data.chars;
var len = text.length;
for( var i=0; i<len; i++){
var ch = text.charAt(i);
line2 += ch.replace(" ","&nbsp;");
var size = (typeof chSize[ch] == 'undefined' ) ? 0 : chSize[ch];
line2Width += size;
if( ch == ' '|| ch == '-' ){
if( line1Width + line2Width < width-1 ){
line1 = line1 + line2;
line1Width = line1Width + line2Width;
line2 = "";
line2Width = 0;
}
else{
lines.push(line1);
line1= line2;
line1Width = line2Width;
line2= "";
line2Width = 0;
}
}
if( ch == '\n'){
if( line1Width + line2Width < width-1 ){
lines.push(line1 + line2);
}
else{
lines.push(line1);
lines.push(line2);
}
line1 = "";
line2 = "";
line1Width = 0;
line2Width = 0;
}
//else{
//line2 += ch;
//}
}
if( line1Width + line2Width < width-1 ){
lines.push(line1 + line2);
}
else{
lines.push(line1);
lines.push(line2);
}
return lines;
}
function getCursorPosition(data){
if( data.mode == "outter" ){
return getOuterPosition(data);
}
//console.log("getCursorPosition: ta width=",$(data.ta).css("width")," ta clientWidth=",data.ta.clientWidth, "scrollWidth=",data.ta.scrollWidth," offsetWidth=",data.ta.offsetWidth," jquery.width=",$(data.ta).width());
/*if( browser.isChrome ){
$(data.clone).width(data.ta.clientWidth-data.chromeWidthFix);
}
else{*/
$(data.clone).width(data.ta.clientWidth);
//}
var ta = data.ta;
var selectionEnd = getTextAreaSelectionEnd(data.ta);
var text = ta.value;//.replace(/ /g,"&nbsp;");
var subText = text.substr(0,selectionEnd);
var restText = text.substr(selectionEnd,text.length);
var lines = breakLines(subText,data);//subText.split("\n");
var miror = $(data.clone);
miror.html("");
for( var i=0; i< lines.length-1; i++){
miror.append("<div style='height:"+(parseInt(data.lineHeight))+"px"+";'>"+lines[i]+"</div>");
}
miror.append("<span id='"+data.id+"' style='display:inline-block;'>"+lines[lines.length-1]+"</span>");
miror.append("<span id='rest' style='max-width:'"+data.ta.clientWidth+"px'>"+restText.replace(/\n/g,"<br/>")+"&nbsp;</span>");
miror.get(0).scrollTop = ta.scrollTop;
var span = miror.children("#"+data.id);
var offset = span.offset();
return {top:offset.top+span.height(),left:offset.left+span.width()};
}
function getOuterPosition(data){
var offset = $(data.ta).offset();
return {top:offset.top+$(data.ta).height()+8,left:offset.left};
}
function hideList(data){
if( data.listVisible ){
$(data.list).css("display","none");
data.listVisible = false;
}
}
function setSelected(dir,data){
var selected = $(data.list).find("[data-selected=true]");
if( selected.length != 1 ){
if( dir > 0 ) $(data.list).find("li:first-child").attr("data-selected","true");
else $(data.list).find("li:last-child").attr("data-selected","true");
return;
}
selected.attr("data-selected","false");
if( dir > 0 ){
selected.next().attr("data-selected","true");
}
else{
selected.prev().attr("data-selected","true");
}
}
function getCurrentSelected(data){
var selected = $(data.list).find("[data-selected=true]");
if( selected.length == 1) return selected.get(0);
return null;
}
function onUserSelected(li,data){
var seletedText = $(li).attr("data-value");
var selectionEnd = getTextAreaSelectionEnd(data.ta);//.selectionEnd;
var text = data.ta.value;
text = text.substr(0,selectionEnd);
//if( text.charAt(text.length-1) == ' ' || text.charAt(text.length-1) == '\n' ) return "";
//var ret = [];
var wordsFound = 0;
var pos = text.length-1;
while( wordsFound < data.wordCount && pos >= 0 && text.charAt(pos) != '\n'){
//ret.unshift(text.charAt(pos));
pos--;
if( text.charAt(pos) == ' ' || pos < 0 ){
wordsFound++;
}
}
var a = data.ta.value.substr(0,pos+1);
var c = data.ta.value.substr(selectionEnd,data.ta.value.length);
var scrollTop = data.ta.scrollTop;
data.ta.value = a+seletedText+c;
data.ta.scrollTop = scrollTop;
data.ta.selectionEnd = pos+1+seletedText.length;
hideList(data);
$(data.ta).focus();
}
function registerEvents(data){
$(data.list).delegate("li","click",function(e){
var li = this;
onUserSelected(li,data);
e.stopPropagation();
e.preventDefault();
return false;
});
$(data.ta).blur(function(e){
setTimeout(function(){
hideList(data);
},400);
});
$(data.ta).click(function(e){
hideList(data);
});
$(data.ta).keydown(function(e){
//console.log("keydown keycode="+e.keyCode);
if( data.listVisible ){
switch(e.keyCode){
case 9:
case 13:
case 40:
case 38:
e.stopImmediatePropagation();
e.preventDefault();
return false;
case 27: //esc
hideList(data);
}
}
});
$(data.ta).keyup(function(e){
if( data.listVisible ){
//console.log("keCode=",e.keyCode);
if( e.keyCode == 40 ){//down key
setSelected(+1,data);
e.stopImmediatePropagation();
e.preventDefault();
return false;
}
if( e.keyCode == 38 ){//up key
setSelected(-1,data);
e.stopImmediatePropagation();
e.preventDefault();
return false;
}
if( e.keyCode == 13 || e.keyCode == 9 ){//enter key or Tab key
var li = getCurrentSelected(data);
if( li ){
e.stopImmediatePropagation();
e.preventDefault();
hideList(data);
onUserSelected(li,data);
return false;
}
hideList(data);
}
if( e.keyCode == 27 ){
e.stopImmediatePropagation();
e.preventDefault();
return false;
}
}
switch( e.keyCode ){
case 27:
return true;
}
var text = getWords(data);
//console.log("getWords return ",text);
if( text != "" ){
data.on.query(text,function(list){
//console.log("got list = ",list);
if( list.length ){
showList(data,list,text);
}
else{
hideList(data);
}
});
}
else{
hideList(data);
}
});
$(data.ta).scroll(function(e){
var ta = e.target;
var miror = $(data.clone);
miror.get(0).scrollTop = ta.scrollTop;
});
}
})(jQuery);