Jump to content

jquery help - selecting cursor position


nobodyk

Recommended Posts

I'm having issues with this particular lines:

 

	previousValue = currentValue;
	currentValue = lastWord(currentValue);	

	var TodaysV = $("#vB_Editor_QR_textarea").val();
	var cursor_Loc = $("#vB_Editor_QR_textarea").selection().start - currentValue.length;

	if ((currentValue.length >= options.minChars) && (TodaysV.substring(cursor_Loc, cursor_Loc + 1) == "@")) 
	{
		$input.addClass(options.loadingClass);
		if (!options.matchCase)
			currentValue = currentValue.toLowerCase();
		request(currentValue, receiveData, hideResultsNow);
	} else {
		stopLoading();
		select.hide();
	}
};

 

The problem is that this part is never true, when it's suppose to be.

TodaysV.substring(cursor_Loc, cursor_Loc + 1) == "@"

 

Here's what I'm trying to do,

 

A textarea(vB_Editor_QR_textarea) with the content "random text @hello"

 

if the cursor position is after "hello" that means that its value is 17. currentValue always equals the string after "@", so the currentValue. length is 5. cursor_Loc equals 17 minus 5, which is 12.

 

So in conclusion:

TodaysV.substring(cursor_Loc, cursor_Loc + 1)

is actually...

TodaysV.substring(12, 12 + 1)

and since TodaysV equals "random text @hello", the value of

TodaysV.substring(12, 12 + 1)

should be "@"

 

BUT it's not picking it up, if I remove that particular condition everything works ok, so I know it's that line of code. Any ideas what it could be?

 

I included the jquery api, so that's not the issue, thought I mention it. I appreciate any help!

 

Link to comment
Share on other sites

Here's the full code:

 

/*
* jQuery Autocomplete plugin 1.1
*
* Copyright (c) 2009 Jörn Zaefferer
*
* Dual licensed under the MIT and GPL licenses:
*   http://www.opensource.org/licenses/mit-license.php
*   http://www.gnu.org/licenses/gpl.html
*
* Revision: $Id: jquery.autocomplete.js 15 2009-08-22 10:30:27Z joern.zaefferer $
*/

;(function($) {

$.fn.extend({
autocomplete: function(urlOrData, options) {
	var isUrl = typeof urlOrData == "string";
	options = $.extend({}, $.Autocompleter.defaults, {
		url: isUrl ? urlOrData : null,
		data: isUrl ? null : urlOrData,
		delay: isUrl ? $.Autocompleter.defaults.delay : 10,
		max: options && !options.scroll ? 10 : 150
	}, options);

	// if highlight is set to false, replace it with a do-nothing function
	options.highlight = options.highlight || function(value) { return value; };

	// if the formatMatch option is not specified, then use formatItem for backwards compatibility
	options.formatMatch = options.formatMatch || options.formatItem;

	return this.each(function() {
		new $.Autocompleter(this, options);
	});
},
result: function(handler) {
	return this.bind("result", handler);
},
search: function(handler) {
	return this.trigger("search", [handler]);
},
flushCache: function() {
	return this.trigger("flushCache");
},
setOptions: function(options){
	return this.trigger("setOptions", [options]);
},
unautocomplete: function() {
	return this.trigger("unautocomplete");
}
});

$.Autocompleter = function(input, options) {

var KEY = {
	UP: 38,
	DOWN: 40,
	DEL: 46,
	TAB: 9,
	RETURN: 13,
	ESC: 27,
	COMMA: 188,
	PAGEUP: 33,
	PAGEDOWN: 34,
	BACKSPACE: 8,
	AT: 64
};

// Create $ object for input element
var $input = $(input).attr("autocomplete", "off").addClass(options.inputClass);

var timeout;
var F_AT = 0;
var previousValue = "";
var cache = $.Autocompleter.Cache(options);
var hasFocus = 0;
var lastKeyPressCode;
var config = {
	mouseDownOnSelect: false
};
var select = $.Autocompleter.Select(options, input, selectCurrent, config);

var blockSubmit;

// prevent form submit in opera when selecting with return key
$.browser.opera && $(input.form).bind("submit.autocomplete", function() {
	if (blockSubmit) {
		blockSubmit = false;
		return false;
	}
});

// only opera doesn't trigger keydown multiple times while pressed, others don't work with keypress at all
$input.bind(($.browser.opera ? "keypress" : "keydown") + ".autocomplete", function(event) {
	// a keypress means the input has focus
	// avoids issue where input had focus before the autocomplete was applied
	hasFocus = 1;
	// track last key pressed
	lastKeyPressCode = event.keyCode;
	switch(event.keyCode) {

		case KEY.UP:
			event.preventDefault();
			if ( select.visible() ) {
				select.prev();
			} else {
				onChange(0, true);
			}
			break;

		case KEY.DOWN:
			event.preventDefault();
			if ( select.visible() ) {
				select.next();
			} else {
				onChange(0, true);
			}
			break;

		case KEY.PAGEUP:
			event.preventDefault();
			if ( select.visible() ) {
				select.pageUp();
			} else {
				onChange(0, true);
			}
			break;

		case KEY.PAGEDOWN:
			event.preventDefault();
			if ( select.visible() ) {
				select.pageDown();
			} else {
				onChange(0, true);
			}
			break;

		// matches also semicolon
		case options.multiple && $.trim(options.multipleSeparator) == "," && KEY.COMMA:
		case KEY.TAB:
		case KEY.RETURN:
			if( selectCurrent() ) {
				// stop default to prevent a form submit, Opera needs special handling
				event.preventDefault();
				blockSubmit = true;
				return false;
			}
			break;

		case KEY.ESC:
			select.hide();
			break;

		default:
			clearTimeout(timeout);
			timeout = setTimeout(onChange, options.delay);
			break;
	}
}).focus(function(){
	// track whether the field has focus, we shouldn't process any
	// results if the field no longer has focus
	hasFocus++;
}).blur(function() {
	hasFocus = 0;
	if (!config.mouseDownOnSelect) {
		hideResults();
	}
}).click(function() {
	// show select when clicking in a focused field
	if ( hasFocus++ > 1 && !select.visible() ) {
		onChange(0, true);
	}
}).bind("search", function() {
	// TODO why not just specifying both arguments?
	var fn = (arguments.length > 1) ? arguments[1] : null;
	function findValueCallback(q, data) {
		var result;
		if( data && data.length ) {
			for (var i=0; i < data.length; i++) {
				if( data[i].result.toLowerCase() == q.toLowerCase() ) {
					result = data[i];
					break;
				}
			}
		}
		if( typeof fn == "function" ) fn(result);
		else $input.trigger("result", result && [result.data, result.value]);
	}
	$.each(trimWords($input.val()), function(i, value) {
		request(value, findValueCallback, findValueCallback);
	});
}).bind("flushCache", function() {
	cache.flush();
}).bind("setOptions", function() {
	$.extend(options, arguments[1]);
	// if we've updated the data, repopulate
	if ( "data" in arguments[1] )
		cache.populate();
}).bind("unautocomplete", function() {
	select.unbind();
	$input.unbind();
	$(input.form).unbind(".autocomplete");
});


function selectCurrent() {
	var selected = select.selected();
	if( !selected )
		return false;

	var v = selected.result;
	previousValue = v;

	if ( options.multiple ) {
		var words = trimWords($input.val());
		if ( words.length > 1 ) {
			var seperator = options.multipleSeparator.length;
			var cursorAt = $(input).selection().start;
			var wordAt, progress = 0;
			$.each(words, function(i, word) {
				progress += word.length;
				if (cursorAt <= progress) {
					wordAt = i;
					return false;
				}
				progress += seperator;
			});
			words[wordAt] = v;
			// TODO this should set the cursor to the right position, but it gets overriden somewhere
			//$.Autocompleter.Selection(input, progress + seperator, progress + seperator);
			v = words.join( options.multipleSeparator );
		}
		v += "";
	}

	$input.val(v);
	hideResultsNow();
	$input.trigger("result", [selected.data, selected.value]);
	return true;
}

function onChange(crap, skipPrevCheck) {
	if( lastKeyPressCode == KEY.DEL ) {
		select.hide();
		return;
	}

	var currentValue = $input.val();

	if ( !skipPrevCheck && currentValue == previousValue )
		return;

	function caret(node) 
	{
		//node.focus(); 
		if(node.selectionStart) return node.selectionStart;
		else if(!document.selection) return 0;
		var c		= "\001";
		var sel	= document.selection.createRange();
		var dul	= sel.duplicate();
		var len	= 0;
		dul.moveToElementText(node);
		sel.text	= c;
		len		= (dul.text.indexOf(c));
		sel.moveStart('character',-1);
		sel.text	= "";
		return len;
	}

	previousValue = currentValue;
	currentValue = lastWord(currentValue);	

	var TodaysV = $("#vB_Editor_QR_textarea").val();
	var cursor_Loc = $("#vB_Editor_QR_textarea").selection().start - currentValue.length;

	if ((currentValue.length >= options.minChars) && (TodaysV.substring(cursor_Loc, cursor_Loc + 1) == "@")) 
	{
		$input.addClass(options.loadingClass);
		if (!options.matchCase)
			currentValue = currentValue.toLowerCase();
		request(currentValue, receiveData, hideResultsNow);
	} else {
		stopLoading();
		select.hide();
	}
};

function trimWords(value) {
	if (!value)
		return [""];
	if (!options.multiple)
		return [$.trim(value)];
	return $.map(value.split(options.multipleSeparator), function(word) {
		return $.trim(value).length ? $.trim(word) : null;
	});
}

function lastWord(value) {
	if ( !options.multiple )
		return value;
	var words = trimWords(value);
	if (words.length == 1) 
		return words[0];
	var cursorAt = $(input).selection().start;
	if (cursorAt == value.length) {
		words = trimWords(value)
	} else {
		words = trimWords(value.replace(value.substring(cursorAt), ""));
	}
	return words[words.length - 1];
}

// fills in the input box w/the first match (assumed to be the best match)
// q: the term entered
// sValue: the first matching result
function autoFill(q, sValue){
	// if the last user key pressed was backspace, don't autofill
	// autofill in the complete box w/the first match as long as the user hasn't entered in more data
	if( options.autoFill && (lastWord($input.val()).toLowerCase() == q.toLowerCase()) && lastKeyPressCode != KEY.BACKSPACE ) {
		// fill in the value (keep the case the user has typed)
		$input.val($input.val() + sValue.substring(lastWord(previousValue).length));
		// select the portion of the value not typed by the user (so the next character will erase)
		$(input).selection(previousValue.length, previousValue.length + sValue.length);
	}
};

function hideResults() {
	clearTimeout(timeout);
	timeout = setTimeout(hideResultsNow, 200);
};

function hideResultsNow() {
	var wasVisible = select.visible();
	select.hide();
	clearTimeout(timeout);
	stopLoading();
	if (options.mustMatch) {
		// call search and run callback
		$input.search(
			function (result){
				// if no value found, clear the input box
				if( !result ) {
					if (options.multiple) {
						var words = trimWords($input.val()).slice(0, -1);
						$input.val( words.join(options.multipleSeparator) + (words.length ? options.multipleSeparator : "") );
					}
					else {
						$input.val( "" );
						$input.trigger("result", null);
					}
				}
			}
		);
	}
};

function receiveData(q, data) {
	if ( data && data.length && hasFocus ) {
		stopLoading();
		select.display(data, q);
		autoFill(q, data[0].value);
		select.show();
	} else {
		hideResultsNow();
	}
};

function request(term, success, failure) {
	if (!options.matchCase)
		term = term.toLowerCase();
	var data = cache.load(term);
	// recieve the cached data
	if (data && data.length) {
		success(term, data);
	// if an AJAX url has been supplied, try loading the data now
	} else if( (typeof options.url == "string") && (options.url.length > 0) ){

		var extraParams = {
			timestamp: +new Date()
		};
		$.each(options.extraParams, function(key, param) {
			extraParams[key] = typeof param == "function" ? param() : param;
		});

		$.ajax({
			// try to leverage ajaxQueue plugin to abort previous requests
			mode: "abort",
			// limit abortion to this input
			port: "autocomplete" + input.name,
			dataType: options.dataType,
			url: options.url,
			data: $.extend({
				q: lastWord(term),
				limit: options.max
			}, extraParams),
			success: function(data) {
				var parsed = options.parse && options.parse(data) || parse(data);
				cache.add(term, parsed);
				success(term, parsed);
			}
		});
	} else {
		// if we have a failure, we need to empty the list -- this prevents the the [TAB] key from selecting the last successful match
		select.emptyList();
		failure(term);
	}
};

function parse(data) {
	var parsed = [];
	var rows = data.split("\n");
	for (var i=0; i < rows.length; i++) {
		var row = $.trim(rows[i]);
		if (row) {
			row = row.split("|");
			parsed[parsed.length] = {
				data: row,
				value: row[0],
				result: options.formatResult && options.formatResult(row, row[0]) || row[0]
			};
		}
	}
	return parsed;
};

function stopLoading() {
	$input.removeClass(options.loadingClass);
};

};

$.Autocompleter.defaults = {
inputClass: "ac_input",
resultsClass: "ac_results",
loadingClass: "ac_loading",
minChars: 1,
delay: 400,
matchCase: false,
matchSubset: true,
matchContains: false,
cacheLength: 10,
max: 100,
mustMatch: false,
extraParams: {},
selectFirst: true,
formatItem: function(row) { return row[0]; },
formatMatch: null,
autoFill: false,
width: 0,
multiple: false,
multipleSeparator: ", ",
highlight: function(value, term) {
	return value.replace(new RegExp("(?![^&;]+(?!<[^<>]*)(" + term.replace(/([\^\$\(\)\[\]\{\}\*\.\+\?\|\\])/gi, "\\$1") + ")(?![^<>]*>)(?![^&;]+", "gi"), "<strong>$1</strong>");
},
    scroll: true,
    scrollHeight: 180
};

$.Autocompleter.Cache = function(options) {

var data = {};
var length = 0;

function matchSubset(s, sub) {
	if (!options.matchCase) 
		s = s.toLowerCase();
	var i = s.indexOf(sub);
	if (options.matchContains == "word"){
		i = s.toLowerCase().search("\\b" + sub.toLowerCase());
	}
	if (i == -1) return false;
	return i == 0 || options.matchContains;
};

function add(q, value) {
	if (length > options.cacheLength){
		flush();
	}
	if (!data[q]){ 
		length++;
	}
	data[q] = value;
}

function populate(){
	if( !options.data ) return false;
	// track the matches
	var stMatchSets = {},
		nullData = 0;

	// no url was specified, we need to adjust the cache length to make sure it fits the local data store
	if( !options.url ) options.cacheLength = 1;

	// track all options for minChars = 0
	stMatchSets[""] = [];

	// loop through the array and create a lookup structure
	for ( var i = 0, ol = options.data.length; i < ol; i++ ) {
		var rawValue = options.data[i];
		// if rawValue is a string, make an array otherwise just reference the array
		rawValue = (typeof rawValue == "string") ? [rawValue] : rawValue;

		var value = options.formatMatch(rawValue, i+1, options.data.length);
		if ( value === false )
			continue;

		var firstChar = value.charAt(0).toLowerCase();
		// if no lookup array for this character exists, look it up now
		if( !stMatchSets[firstChar] ) 
			stMatchSets[firstChar] = [];

		// if the match is a string
		var row = {
			value: value,
			data: rawValue,
			result: options.formatResult && options.formatResult(rawValue) || value
		};

		// push the current match into the set list
		stMatchSets[firstChar].push(row);

		// keep track of minChars zero items
		if ( nullData++ < options.max ) {
			stMatchSets[""].push(row);
		}
	};

	// add the data items to the cache
	$.each(stMatchSets, function(i, value) {
		// increase the cache size
		options.cacheLength++;
		// add to the cache
		add(i, value);
	});
}

// populate any existing data
setTimeout(populate, 25);

function flush(){
	data = {};
	length = 0;
}

return {
	flush: flush,
	add: add,
	populate: populate,
	load: function(q) {
		if (!options.cacheLength || !length)
			return null;
		/* 
		 * if dealing w/local data and matchContains than we must make sure
		 * to loop through all the data collections looking for matches
		 */
		if( !options.url && options.matchContains ){
			// track all matches
			var csub = [];
			// loop through all the data grids for matches
			for( var k in data ){
				// don't search through the stMatchSets[""] (minChars: 0) cache
				// this prevents duplicates
				if( k.length > 0 ){
					var c = data[k];
					$.each(c, function(i, x) {
						// if we've got a match, add it to the array
						if (matchSubset(x.value, q)) {
							csub.push(x);
						}
					});
				}
			}				
			return csub;
		} else 
		// if the exact item exists, use it
		if (data[q]){
			return data[q];
		} else
		if (options.matchSubset) {
			for (var i = q.length - 1; i >= options.minChars; i--) {
				var c = data[q.substr(0, i)];
				if (c) {
					var csub = [];
					$.each(c, function(i, x) {
						if (matchSubset(x.value, q)) {
							csub[csub.length] = x;
						}
					});
					return csub;
				}
			}
		}
		return null;
	}
};
};

$.Autocompleter.Select = function (options, input, select, config) {
var CLASSES = {
	ACTIVE: "ac_over"
};

var listItems,
	active = -1,
	data,
	term = "",
	needsInit = true,
	element,
	list;

// Create results
function init() {
	if (!needsInit)
		return;
	element = $("<div/>")
	.hide()
	.addClass(options.resultsClass)
	.css("position", "absolute")
	.appendTo(document.body);

	list = $("<ul/>").appendTo(element).mouseover( function(event) {
		if(target(event).nodeName && target(event).nodeName.toUpperCase() == 'LI') {
            active = $("li", list).removeClass(CLASSES.ACTIVE).index(target(event));
		    $(target(event)).addClass(CLASSES.ACTIVE);            
        }
	}).click(function(event) {
		$(target(event)).addClass(CLASSES.ACTIVE);
		select();
		// TODO provide option to avoid setting focus again after selection? useful for cleanup-on-focus
		input.focus();
		return false;
	}).mousedown(function() {
		config.mouseDownOnSelect = true;
	}).mouseup(function() {
		config.mouseDownOnSelect = false;
	});

	if( options.width > 0 )
		element.css("width", options.width);

	needsInit = false;
} 

function target(event) {
	var element = event.target;
	while(element && element.tagName != "LI")
		element = element.parentNode;
	// more fun with IE, sometimes event.target is empty, just ignore it then
	if(!element)
		return [];
	return element;
}

function moveSelect(step) {
	listItems.slice(active, active + 1).removeClass(CLASSES.ACTIVE);
	movePosition(step);
        var activeItem = listItems.slice(active, active + 1).addClass(CLASSES.ACTIVE);
        if(options.scroll) {
            var offset = 0;
            listItems.slice(0, active).each(function() {
			offset += this.offsetHeight;
		});
            if((offset + activeItem[0].offsetHeight - list.scrollTop()) > list[0].clientHeight) {
                list.scrollTop(offset + activeItem[0].offsetHeight - list.innerHeight());
            } else if(offset < list.scrollTop()) {
                list.scrollTop(offset);
            }
        }
};

function movePosition(step) {
	active += step;
	if (active < 0) {
		active = listItems.size() - 1;
	} else if (active >= listItems.size()) {
		active = 0;
	}
}

function limitNumberOfItems(available) {
	return options.max && options.max < available
		? options.max
		: available;
}

function fillList() {
	list.empty();
	var max = limitNumberOfItems(data.length);
	for (var i=0; i < max; i++) {
		if (!data[i])
			continue;
		var formatted = options.formatItem(data[i].data, i+1, max, data[i].value, term);
		if ( formatted === false )
			continue;
		var li = $("<li/>").html( options.highlight(formatted, term) ).addClass(i%2 == 0 ? "ac_even" : "ac_odd").appendTo(list)[0];
		$.data(li, "ac_data", data[i]);
	}
	listItems = list.find("li");
	if ( options.selectFirst ) {
		listItems.slice(0, 1).addClass(CLASSES.ACTIVE);
		active = 0;
	}
	// apply bgiframe if available
	if ( $.fn.bgiframe )
		list.bgiframe();
}

return {
	display: function(d, q) {
		init();
		data = d;
		term = q;
		fillList();
	},
	next: function() {
		moveSelect(1);
	},
	prev: function() {
		moveSelect(-1);
	},
	pageUp: function() {
		if (active != 0 && active - 8 < 0) {
			moveSelect( -active );
		} else {
			moveSelect(-;
		}
	},
	pageDown: function() {
		if (active != listItems.size() - 1 && active + 8 > listItems.size()) {
			moveSelect( listItems.size() - 1 - active );
		} else {
			moveSelect(;
		}
	},
	hide: function() {
		element && element.hide();
		listItems && listItems.removeClass(CLASSES.ACTIVE);
		active = -1;
	},
	visible : function() {
		return element && element.is(":visible");
	},
	current: function() {
		return this.visible() && (listItems.filter("." + CLASSES.ACTIVE)[0] || options.selectFirst && listItems[0]);
	},
	show: function() {
		var offset = $(input).offset();
		element.css({
			width: typeof options.width == "string" || options.width > 0 ? options.width : $(input).width(),
			top: offset.top + input.offsetHeight,
			left: offset.left
		}).show();
            if(options.scroll) {
                list.scrollTop(0);
                list.css({
				maxHeight: options.scrollHeight,
				overflow: 'auto'
			});

                if($.browser.msie && typeof document.body.style.maxHeight === "undefined") {
				var listHeight = 0;
				listItems.each(function() {
					listHeight += this.offsetHeight;
				});
				var scrollbarsVisible = listHeight > options.scrollHeight;
                    list.css('height', scrollbarsVisible ? options.scrollHeight : listHeight );
				if (!scrollbarsVisible) {
					// IE doesn't recalculate width when scrollbar disappears
					listItems.width( list.width() - parseInt(listItems.css("padding-left")) - parseInt(listItems.css("padding-right")) );
				}
                }
                
            }
	},
	selected: function() {
		var selected = listItems && listItems.filter("." + CLASSES.ACTIVE).removeClass(CLASSES.ACTIVE);
		return selected && selected.length && $.data(selected[0], "ac_data");
	},
	emptyList: function (){
		list && list.empty();
	},
	unbind: function() {
		element && element.remove();
	}
};
};

$.fn.selection = function(start, end) {
if (start !== undefined) {
	return this.each(function() {
		if( this.createTextRange ){
			var selRange = this.createTextRange();
			if (end === undefined || start == end) {
				selRange.move("character", start);
				selRange.select();
			} else {
				selRange.collapse(true);
				selRange.moveStart("character", start);
				selRange.moveEnd("character", end);
				selRange.select();
			}
		} else if( this.setSelectionRange ){
			this.setSelectionRange(start, end);
		} else if( this.selectionStart ){
			this.selectionStart = start;
			this.selectionEnd = end;
		}
	});
}
var field = this[0];
if ( field.createTextRange ) {
	var range = document.selection.createRange(),
		orig = field.value,
		teststring = "<->",
		textLength = range.text.length;
	range.text = teststring;
	var caretAt = field.value.indexOf(teststring);
	field.value = orig;
	this.selection(caretAt, caretAt + textLength);
	return {
		start: caretAt,
		end: caretAt + textLength
	}
} else if( field.selectionStart !== undefined ){
	return {
		start: field.selectionStart,
		end: field.selectionEnd
	}
}
};

})(jQuery);

Link to comment
Share on other sites

This thread is more than a year old. Please don't revive it unless you have something important to add.

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...

Important Information

We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.