Reputation: 996
I have a script that looks like this:
function autoCorrect(searchString, replaceString) {
$("body").on("keyup", "textarea", function() {
// finds current cursor position
var pos = $(this).prop("selectionStart");
// this turns the textarea in a string
var text = $(this).val();
//only search for strings just typed
var stringToSearch = text.substring(pos - searchString.length, pos);
if (searchString == stringToSearch) {
//if there is a match put the replaceString in the right place
var newText = text.substring(0, pos - searchString.length) + replaceString + text.substring(pos);
$(this).val(newText);
//adjust the cursor position to the new text
var newpos = pos - searchString.length + replaceString.length;
this.setSelectionRange(newpos, newpos);
}
});
}
autoCorrect("=>", '⇒');
autoCorrect("->", "→");
autoCorrect("+-", "±");
autoCorrect("<=", "≤");
autoCorrect(">=", "≥");
Now, I want to change it a little bit, I want to change the order the functions are running. Like this:
$("body").on("keyup", "textarea", function() {
function autoCorrect(searchString, replaceString) {
// finds current cursor position
var pos = $(this).prop("selectionStart");
// this turns the textarea in a string
var text = $(this).val();
//only search for strings just typed
var stringToSearch = text.substring(pos - searchString.length, pos);
if (searchString == stringToSearch) {
//if there is a match put the replaceString in the right place
var newText = text.substring(0, pos - searchString.length) + replaceString + text.substring(pos);
$(this).val(newText);
//adjust the cursor position to the new text
var newpos = pos - searchString.length + replaceString.length;
this.setSelectionRange(newpos, newpos);
}
}
autoCorrect("=>", '⇒');
autoCorrect("->", "→");
autoCorrect("+-", "±");
autoCorrect("<=", "≤");
autoCorrect(">=", "≥");
});
But this the script doesn't work anymore like this. I just don't understand why this breaks my code.
Here is my jsfiddle: http://jsfiddle.net/4SWy6/4/
Upvotes: 0
Views: 64
Reputation: 318342
A new function creates a new scope :
$("body").on("keyup", "textarea", function () {
autoCorrect("=>", '⇒', this);
autoCorrect("->", "→", this);
autoCorrect("+-", "±", this);
autoCorrect("<=", "≤", this);
autoCorrect(">=", "≥", this);
});
function autoCorrect(searchString, replaceString, elem) {
var acList = {
"=>": '⇒',
"->": "→",
"+-": "±",
"<=": "≤",
">=": "≥"
},
pos = elem.selectionStart,
text = elem.value,
stringToSearch = text.substring(pos - searchString.length, pos);
if (searchString == stringToSearch) {
var newpos = pos - searchString.length + replaceString.length;
elem.value = text.substring(0, pos - searchString.length) + replaceString + text.substring(pos);
this.setSelectionRange(newpos, newpos);
}
}
EDIT:
based on the comment asking if you could just pass an object of key / value pairs to be replaced together with the element, I cooked up this, which should work with just about any object you pass it :
$("body").on("keyup", "textarea", function () {
var acList = {
"=>": '⇒',
"->": "→",
"+-": "±",
"<=": "≤",
">=": "≥"
};
autoCorrect(acList, this);
});
function autoCorrect(acList, elem) {
var regstr = Object.keys(acList).join("|").replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$]/g, "\\$&"),
reg = new RegExp('('+ regstr +')', "gi"),
pos = elem.selectionStart,
diff = 0;
elem.value = elem.value.replace(reg, function(x) {
diff += x.length - acList[x].length;
return acList[x];
});
elem.setSelectionRange(pos-diff, pos-diff);
}
As a sidenote, this isn't really cross browser, as selectionStart
and setSelectionRange
doesn't work in all browsers, and neither does Object.keys
, so depending on what browsers you need to support, you might have to polyfill those methods.
Upvotes: 2
Reputation: 11824
In the inner function, $(this)
is not actually the jQuery object you're looking for. That's because this
is set to the window object. Observe:
function outer() {
function inner() {
return this;
}
console.log(this, inner());
}
outer.call({ this: "is an object" });
This will log:
{ this: "is an object" }
[Object Window]
JS's scoping behavior is weird.
this
is very magical and you can only really trust its value right at the place where it's set. Otherwise, it's probably window
.
.call
on a function calls the function with a this
set to the first parameter, in this case { this: "is an object" }
. This is called "binding".
This is why in outer
, this
refers to our object. We say outer
is bound to that object. In inner
, we should distrust this
, because it's not explicitly bound to anything -- so it's probably window
.
Similarly to that example:
$('body').on('click', function clickCallback() {
// `this` refers to the element that jQuery set it to,
// as it called the `clickCallback` function with .call(element)
$(this).css({ 'background-color': '#f00' });
// We now have a bright red background!
function changeColor() {
// `this` refers to `window`!!!
// We didn't bind it :(
$(this).css({ color: '#0f0' });
// Text color will not be changed!
}
changeColor();
});
Internally, jQuery uses the .call
method to call the clickCallback
, to bind the callback's this
to the element that was clicked. So we're facing the exact same situation as with our outer
and inner
functions.
The solution to your problem is to either 1) Bind the inner function to the outer this
or 2) Save the outer this
for use in the inner function.
Usually you want to go for the latter. For the first, you'd need to either .call
your function all the time (autoCorrect.call(this, "=>", '⇒');
), which is ugly, or use .bind
once (autoCorrect = autoCorrect.bind(this);
), but that is not cool either, because not all browsers support the .bind
method on functions, and it looks slower.
In your case, going for the latter option:
$("body").on("keyup", "textarea", function() {
// Store `this` for use in the inner function
var self = this;
function autoCorrect(searchString, replaceString) {
// `self` will now be the element we're looking for!
// finds current cursor position
var pos = $(self).prop("selectionStart");
// this turns the textarea in a string
var text = $(self).val();
// [...]
}
autoCorrect("=>", '⇒');
autoCorrect("->", "→");
autoCorrect("+-", "±");
autoCorrect("<=", "≤");
autoCorrect(">=", "≥");
});
Upvotes: 1