I have a problem when I want to replace the last match one expresión regular
by one textarea
, I explain:
If I write
@
intextarea
it, a menu opens where there are user names when selecting it, it auto-completes with the selected name.But if I write again
@
and select another user,replace
it is done in the first@
.If I write
@a
and select a user, and I write again@a
and select another user, it is correctly replaced in the last match.
How can I solve the second point?
var arrobaGato = "";
var $textarea = $('textarea');
var $btndropdown = $('.dropdown button');
/*Detectamos @, # o espacio*/
$textarea.on('keypress', function (evt) {
if (evt.which == 64 || evt.which == 35) {
arrobaGato = String.fromCharCode(evt.which);
$btndropdown.dropdown('toggle');
} else if (evt.which == 32) {
arrobaGato = "";
$('.dropdown.open').removeClass('open');
}else if(arrobaGato != "" && !$('.dropdown').hasClass('open')){
$btndropdown.dropdown('toggle');
}
});
/*obtenemos el nombre seleccionado del menu*/
$('.dropdown li a').on('click', function(evt){
evt.preventDefault();
let txt = $textarea.val();
let usr = $(this).text();
let idusr = this.id;
switch(arrobaGato){
case "@":
regex1 = /[\@][\w\.\-_]*/g
matchs1 = txt.match(regex1);
//console.log(matchs1)
//console.log(matchs1[matchs1.length - 1])
//console.log(matchs1.length)
txt = txt.replace(matchs1[matchs1.length-1], "@" + usr + "~" + idusr + " ")
break;
case "#":
regex2 = /[\#][\w\.\-_]*/g
matchs2 = txt.match(regex2);
txt = txt.replace(matchs2[matchs2.length-1], "#" + usr + "~" + idusr + " ")
break;
}
match = [];
arrobaGato = "";
$textarea.val(txt);
});
textarea{
border: 1px solid gray;
height: 100px;
min-width: 100%;
max-width: 100%;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<!-- Latest compiled and minified CSS -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
<!-- Optional theme -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap-theme.min.css" integrity="sha384-rHyoN1iRsVXV4nD0JutlnGaslCJuC7uwjduW9SVrLvRYooPp2bWYgmgJQIXwl/Sp" crossorigin="anonymous">
<!-- Latest compiled and minified JavaScript -->
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
<!--<div contenteditable="true"></div>-->
<br/><br/><br/>
<textarea autofocus="true"></textarea>
<div class="dropdown">
<button id="dLabel" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" style="display: none;">
Dropdown trigger
<span class="caret"></span>
</button>
<ul class="dropdown-menu" aria-labelledby="dropdownMenu1">
<li><a href="#" id='1'>User1</a></li>
<li><a href="#" id='2'>User2</a></li>
<li><a href="#" id='3'>User3</a></li>
<li><a href="#" id='4'>User4</a></li>
</ul>
</div>
The problem
You are having a very common error , which is:
find what text matches a searched text
replace that text found in the string
This will always replace the first occurrence of the text it matches, even when the regex matched text at another position.
To explain it easier, let's see a simplified example of this:
First correction (answering the question)
To make it always match the last occurrence of
@xx..
or#yy...
, we use the following regular expression ? :^
- Start of text(
..)
- Group 1 - saves the text from the beginning to the last[@#]
, since it matches:[\s\S]*
- Any number of characters. It is the same as.*
(but, unlike this, it also matches\n
). The quantifier*
causes the last occurrence of what follows in the pattern to be searched for.[@#]
- It is a character class , which matches 1 character (either of the two: at sign or numeral).\S*
- Matches all non-whitespace characters .Alternatively, it could be used
[^\s@#]*
to only replace the first part of@uno#dos
when they are not separated by spaces.?
- Optionally a space at the end (since in the replacement we will add 1 space).That is, we are having the regex engine consume all possible characters from the start of the text to the last
[@#]
, and we are recording the text that was matched in this group 1 to use the backreference of this match when replacing with$1
. For example, it matches like this:So at replace time it
$1
will contain@uno #dos tres @
.Code:
This is all it takes to replace the last occurrence in the
click
menu event:Background issue: replace at current position
Is the solution really to replace the last occurrence? What if the user moved the cursor and is typing in the middle of the text?
Get the last occurrence before the current position:
The position of the cursor is obtained with .selectionStart ( IE9+ ) .
And if there was selected text, when replacing we delete from that position to .selectionEnd .
We are going to use the same regular expression, modified with these values. For example, if the cursor is at position 10 , we would have to get the last character
[#@]
within the first 10 characters, that is, up to 9 characters followed by an at sign or a numeral:Which in code, using the RegExp() constructor , would be:
What's more, now we can use this strategy to see when to show the menu and when to hide it...
Show menu based on cursor position:
First we get the text up to the cursor position:
And we should show the menu only when there is a
[@#]
followed by non-space characters up to that position. That is, when it matches/[@#]\S*$/
(at the end oftxtAntes
).end code
I've refactored your code a bit and I think I've solved your problems.
What I have done is first check if
textarea
it is empty, if it is empty simply insert the selected user.If it is not empty, I pass it first in a variable with all the text that is in it
textarea
, I do a cleaning of the@
and I convert all of itstring
into aarray
and I also put the new selected user in thearray
.Then it only remains to remove it from
array
and convert it with the prefix@
o#
and print it again in thetextarea
.The code can be improved for sure, for example if you have selected all the users with
@
and then select the next one with#
they change all to#
...But as I already said the problems you had at the beginning are eliminated.
A lot of blah blah... better to see the code: