I am trying to move cursor one more position to the right in input.
var start = this.selectionStart,
end = this.selectionEnd;
this.setSelectionRange(start, end);
This works for keeping position in the same place after input value changed, but iam trying to add one more position to it, because my format code add "-" character after 4 chars.
$("input[name=login-access-token]").keypress(function(event){
var start = this.selectionStart,
end = this.selectionEnd;
console.log(start);
var input = $(this).val();
input = input.replace(/[\W\s\._\-]+/g, '');
var split = 4;
var chunk = [];
for (var i = 0, len = input.length; i < len; i += split) {
split = 4;
chunk.push( input.substr( i, split ) );
}
var test = [];
chunk.forEach(element => {
test.push(element.match(/.{1,4}/g));
});
test.forEach(element => {
if(element.length!=4){
$(this).val(function() {
var out = chunk.join("-").toUpperCase();
if(out.length == 4 && event.keyCode != 8){
out += "-";
}
if(out.length == 9 && event.keyCode != 8 ){
out += "-";
}
return out;
});
}
});
this.setSelectionRange(start, end);
var selection = window.getSelection();
selection.addRange(1);
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<input type="text" name="login-access-token" placeholder="Code" />
var selection = window.getSelection();
selection.addRange(1); //not working :/
I solved it! Just add +1 to start and end which is values what we get from selectionStart.
if(out.length == 4 && event.keyCode != 8){
out += "-";
start = start+1;
end = end +1;
}
if(out.length == 9 && event.keyCode != 8 ){
out += "-";
start = start+1;
end = end +1;
}
Related
I'm new to HTML/JS and I'm trying to make a text editor as a small project.
Please forgive me if I'm not explaining my thoughts clearly.
Let's say I have the following text in my contenteditable div environment, and let | represent the cursor (as it would look in most editors):
hi this is some text
here is some mor|e text
hello, world!
How would I be able to return the text "here is some more text"?
I'm using jQuery and I was thinking I want to use the onClick handler, but that doesn't respond to the arrow keys being used to navigate. What kind of event handler would I need? So far, I've parsed the text to replace the div separators, but I'm a bit lost on how to proceed.
What would you suggest doing? (General links/advice also work, I'm trying to learn more through this project)
Edit, here's my html:
<div id="editor" class="editor" contenteditable="true"></div>
here's the JS:
$(document).on('keydown', '.editor', function(e){
//detect 'tab' key
if(e.keyCode == 9){
//add tab
document.execCommand('insertHTML', false, '	');
//prevent focusing on next element
e.preventDefault()
}
var text = $("#editor").html();
console.log("MYLITERAL:" + text);
// parse the string :)
// for the div tags, replacing them with \n
var tmp = text.replace(/<div>/g, "");
tmp = tmp.replace(/<\/div>/g, "");
tmp = tmp.replace(/<br>/g, "\n");
console.log(tmp);
document.getElementById("demo").innerHTML = tmp;
});
You can try the following:
var strO, endO, lstEle;
function selectRange(start, end, this_){
var el = this_,sPos = start,ePos = end;
var charIndex = 0, range = document.createRange();
range.setStart(el, 0);
range.collapse(true);
var nodeStack = [el], node, foundStart = false, stop = false;
while (!stop && (node = nodeStack.pop())) {
if (node.nodeType == 3) {
var nextCharIndex = charIndex + node.length;
if (!foundStart && sPos >= charIndex && sPos <= nextCharIndex) {
range.setStart(node, sPos - charIndex);
foundStart = true;
}
if (foundStart && ePos >= charIndex && ePos <= nextCharIndex) {
range.setEnd(node, ePos - charIndex);
stop = true;
}
charIndex = nextCharIndex;
} else {
var i = node.childNodes.length;
while (i--) {
nodeStack.push(node.childNodes[i]);
}
}
}
var s = (window.getSelection) ? window.getSelection() : document.selection;
if(window.getSelection) {
s.removeAllRanges();
s.addRange(range);
} else {
range.select();
}
}
// node_walk: walk the element tree, stop when func(node) returns false
function node_walk(node, func) {
var result = func(node);
for(node = node.firstChild; result !== false && node; node = node.nextSibling){
result = node_walk(node, func);
lstEle = node;
}
return result;
};
// getCaretPosition: return [start, end] as offsets to elem.textContent that
// correspond to the selected portion of text
// (if start == end, caret is at given position and no text is selected)
function getCaretPosition(elem) {
strO = 0, endO = 0, lstEle = elem;
var sel = window.getSelection();
var cum_length = [0, 0];
if(sel.anchorNode == elem)
cum_length = [sel.anchorOffset, sel.extentOffset];
else {
var nodes_to_find = [sel.anchorNode, sel.extentNode];
if(!elem.contains(sel.anchorNode) || !elem.contains(sel.extentNode))
return undefined;
else {
var found = [0,0];
var i;
node_walk(elem, function(node) {
for(i = 0; i < 2; i++) {
if(node == nodes_to_find[i]) {
found[i] = true;
if(found[i == 0 ? 1 : 0])
return false; // all done
}
}
if(node.textContent && !node.firstChild) {
for(i = 0; i < 2; i++) {
if(!found[i])
cum_length[i] += node.textContent.length;
}
}
});
strO = cum_length[0];
endO = strO + lstEle.textContent.length;
cum_length[0] += sel.anchorOffset;
cum_length[1] += sel.extentOffset;
}
}
if(cum_length[0] <= cum_length[1])
return cum_length;
return [cum_length[1], cum_length[0]];
}
var update = function() {
$('#test').html(getCaretPosition(this)+' '+strO+' '+endO);
selectRange(strO, endO, this);
$('#test').append('<br>'+window.getSelection().toString());
};
$('#editor').on("mouseup keydown keyup", update);
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.2.4/jquery.min.js"></script>
<div id="editor" class="editor" contenteditable="true">hi this is some text<br>here is some more text<br>hello, world!</div>
<div id="test">test</div>
I'm working on a "autocomplete usernames when TAB is pressed" feature.
I've been able to detect # and to search in a username list.
How to perform the autocompletion now, with a different matching username each time we press TAB ?
var userlist = ['bonjour', 'bleuet', 'bonobo', 'coucou'];
function getCaretPosition(ctrl) {
var start, end;
if (ctrl.setSelectionRange) {
start = ctrl.selectionStart;
end = ctrl.selectionEnd;
} else if (document.selection && document.selection.createRange) {
var range = document.selection.createRange();
start = 0 - range.duplicate().moveStart('character', -100000);
end = start + range.text.length;
}
return {
start: start,
end: end
}
}
$('#writing').keydown(function(e) {
if (e.keyCode == 9) {
var caret = getCaretPosition(this);
var word = /\S+$/.exec(this.value.slice(0, this.value.indexOf(' ',caret.end)));
word = word ? word[0] : null;
if (word.charAt(0) === '#')
alert(userlist.filter((x) => x.indexOf(word.slice(1)) === 0));
e.preventDefault();
return false;
}
});
#writing { width: 500px; }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<textarea id="writing">Hello #b what's up? hello #you as well... Type #b and then "TAB" key it should autocomplete usernames
</textarea>
Here is the solution for your issue:
I used .on() to bind keydown to textbox, rather than keypress.
var userlist = ['bonjour', 'bleuet', 'bonobo', 'coucou'];
function getCaretPosition(ctrl) {
var start, end;
if (ctrl.setSelectionRange) {
start = ctrl.selectionStart;
end = ctrl.selectionEnd;
} else if (document.selection && document.selection.createRange) {
var range = document.selection.createRange();
start = 0 - range.duplicate().moveStart('character', -100000);
end = start + range.text.length;
}
return {
start: start,
end: end
}
}
$('#writing').keydown(function(e) {
if (e.keyCode == 9) {
var caret = getCaretPosition(this);
var word = /\S+$/.exec(this.value.slice(0, this.value.indexOf(' ',caret.end)));
word = word ? word[0] : null;
if (word.charAt(0) === '#')
//alert(userlist.filter((x) => x.indexOf(word.slice(1)) === 0));
var stringParts = $(this).val().split('#');
var nameToInsert = userlist.filter((x) => x.indexOf(word.slice(1)) === 0)[0];
var completeString = stringParts[0] + '#' + nameToInsert;
$(this).val(completeString);
e.preventDefault();
return false;
}
});
#writing { width: 500px; }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<textarea id="writing">Hello #b what's up? hello #you as well... Type #b and then "TAB" key it should autocomplete usernames
</textarea>
Now it completes the name. But I would work on improving on the algorithm predicting the name.
Here's a very basic example that works in modern browsers (Chrome, Firefox, Edge):
const users = ['asdasd', 'fgsfds', 'Foobar']
const input = document.getElementById('input')
const patt = /\S+$/
input.addEventListener('keydown', e => {
if (e.key !== 'Tab') {
return
}
e.preventDefault()
const start = input.selectionStart
const seg = input.value.slice(0, start)
const match = (seg.match(patt) || [])[0]
if (!match) {
return
}
const idx = users.findIndex(x => x.startsWith(match))
if (idx < 0) {
return
}
const replace = users[users[idx] === match ? (idx + 1) % users.length : idx]
const newSeg = seg.replace(patt, replace)
input.value = newSeg + input.value.slice(start)
input.setSelectionRange(newSeg.length, newSeg.length)
})
<input type="text" id="input" size="50" value="bla asd bla fgs">
It cycles through the names and works at any point in the line. Support for older browsers can be added with Babel and es6-shim.
Replace keypress with keydown. Keypress doesn't fire when tab is pressed, I guess because tab gets input out of focus.
EDIT:
Not perfect, gets undefined sometimes, but you can work from here
EDIT:
ok don't mind this, it's half done and #slikts solution is obviously better, made from scratch and all.
var userlist = ['bonjour', 'bleuet', 'bonobo', 'coucou'];
function getCaretPosition(ctrl) {
var start, end;
if (ctrl.setSelectionRange) {
start = ctrl.selectionStart;
end = ctrl.selectionEnd;
} else if (document.selection && document.selection.createRange) {
var range = document.selection.createRange();
start = 0 - range.duplicate().moveStart('character', -100000);
end = start + range.text.length;
}
return {
start: start,
end: end
}
}
function setCaretPosition(elem, caretPos, caretPosEnd) {
caretPosEnd = caretPosEnd || caretPos;
if(elem != null) {
if(elem.createTextRange) {
var range = elem.createTextRange();
range.move('character', caretPos);
range.select();
}
else {
if(elem.selectionStart) {
elem.focus();
elem.setSelectionRange(caretPos, caretPosEnd);
}
else
elem.focus();
}
}
}
function getIndexCloserTo(str, char, ref) {
if(str.indexOf(char) == -1) return false;
//flip string and find char beggining from reference point
var nstr = str.split("").reverse().join("");
return str.length - 1 - nstr.indexOf(char, str.length - 1 - ref);
}
var lastWordToMatch = "";
var lastAddedWord = "";
var lastIndexUsed = 0;
$(document).ready( function() {
$('#writing').keydown(function(e) {
if (e.keyCode == 9) {
var caret = getCaretPosition(this);
//Get username input part, from the "#" closer to the cursor
//to the position of the cursor
var beginning = getIndexCloserTo(this.value, '#', caret.start);
if( beginning !== false){
var word = this.value.slice( beginning , caret.start);
word = word ? word[0] : null;
if (word.charAt(0) === '#'){
//Get array of names that match what is written after the #
var usermatches = userlist.filter((x) => x.indexOf(word.slice(1)) === 0);
//Check if any matches were found
if( usermatches.length > 0 ){
//If the word is the same as last time, use the next match
if( word == lastWordToMatch ){
//use mod to circle back to beginning of array
index = (lastIndexUsed + 1 ) % usermatches.length;
lastIndexUsed = index;
} else {
//otherwise get first match
index = 0;
lastWordToMatch = word;
}
var text = this.value;
//Remove #
word = word.slice(1);
//replace the portion of the word written by the user plus the
//word added by autocompletion, with the match
$(this).val(text.replace(word+lastAddedWord, usermatches[index]) );
//save the replacement for the previous step, without the user input
//just what autocompetion added
lastAddedWord = usermatches[index].replace(word, '');
//put cursor back where it was
setCaretPosition(this,caret.start);
}
}
}
e.preventDefault();
return false;
}
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<textarea id="writing">Hello #b what's up? hello #you as well... Type #b and then "TAB" key it should autocomplete usernames
</textarea>
I want to enter a "/" when user enters MM(2 digit) so it will be like MM/YYYY.
I have done similar for credit card number input which insert a space after 4 digit on keypress.
let ccNumber = e.target.value.split(" ").join("");
if (ccNumber.length > 0) {
ccNumber = ccNumber.match(new RegExp('.{1,4}', 'g')).join(" ");
}
e.target.value = ccNumber;
Fiddle
This works with
Regular keyboard input
Copy/Cut/Paste
Selected text
Adding the /
Because you're programmatically adding the / character, you have to update the cursor position whenever that affects the new input value. This can be more than one character if the user is pasting something. Most of the code complexity revolves around this issue.
There are a lot of comments in the code explaining the various situations that come up because of the /.
Full Code
var date = document.getElementById('date');
date.addEventListener('keypress', updateInput);
date.addEventListener('change', updateInput);
date.addEventListener('paste', updateInput);
date.addEventListener('keydown', removeText);
date.addEventListener('cut', removeText);
function updateInput(event) {
event.preventDefault();
var string = getString(event);
var selectionStart = this.selectionStart;
var selectionEnd = this.selectionEnd;
var selectionLength = selectionEnd - selectionStart;
var sanitizedString = string.replace(/[^0-9]+/g, '');
// Do nothing if nothing is added after sanitization
if (sanitizedString.length === 0) {
return;
}
// Only paste numbers that will fit
var valLength = date.value.replace(/[^0-9]+/g, '').length;
var availableSpace = 6 - valLength + selectionLength;
// If `/` is selected it should not count as available space
if (selectionStart <= 2 && selectionEnd >= 3) {
availableSpace -= 1;
}
// Remove numbers that don't fit
if (sanitizedString.length > availableSpace) {
sanitizedString = sanitizedString.substring(0, availableSpace);
}
var newCursorPosition = selectionEnd + sanitizedString.length - selectionLength;
// Add one to cursor position if a `/` gets inserted
if (selectionStart <= 2 && newCursorPosition >= 2) {
newCursorPosition += 1;
}
// Previous input value before current cursor position
var valueStart = date.value.substring(0, this.selectionStart);
// Previous input value after current cursor position
var valueEnd = date.value.substring(this.selectionEnd, date.value.length);
var proposedValue = valueStart + sanitizedString + valueEnd;
// Remove anything that's not a number
var sanitized = proposedValue.replace(/[^0-9]+/g, '');
format(sanitized);
this.setSelectionRange(newCursorPosition, newCursorPosition);
}
function removeText(event) {
if (event.key === 'Backspace' || event.type === 'cut') {
event.preventDefault();
var selectionStart = this.selectionStart;
var selectionEnd = this.selectionEnd;
var selectionLength = selectionEnd - selectionStart;
// If pressing backspace with no selected text
if (selectionLength === 0 && event.type !== 'cut') {
selectionStart -= 1;
// Remove number from before `/` if attempting to delete `/`
if (selectionStart === 2) {
selectionStart -= 1;
}
}
var valueStart = date.value.substring(0, selectionStart);
var valueEnd = date.value.substring(selectionEnd, date.value.length);
// Account for added `/`
if (selectionStart === 2) {
selectionStart += 1;
}
var proposedValue = valueStart + valueEnd;
var sanitized = proposedValue.replace(/[^0-9]+/g, '');
format(sanitized);
this.setSelectionRange(selectionStart, selectionStart);
}
}
function getString(event) {
if (event.type === 'paste') {
var clipboardData = event.clipboardData || window.clipboardData;
return clipboardData.getData('Text');
} else {
return String.fromCharCode(event.which);
}
}
function format(sanitized) {
var newValue;
var month = sanitized.substring(0, 2);
if (sanitized.length < 2) {
newValue = month;
} else {
var year = sanitized.substring(2, 6);
newValue = month + '/' + year;
}
date.value = newValue;
}
<input id="date" type="text" maxlength="7">
Try:
var date = document.getElementById('date');
date.addEventListener('keypress', function (event) {
var char = String.fromCharCode(event.which),
offset = date.selectionStart;
console.log(offset)
if (/\d/.test(char) && offset < 7) {
if (offset === 2) {
offset += 1;
}
date.value = date.value.substr(0, offset) + char + date.value.substr(offset + 1);
date.selectionStart = date.selectionEnd = offset + 1;
}
if (!event.keyCode) {
event.preventDefault();
}
});
<input id="date" type="text" value="mm/yyyy" maxlength="6" size="6">
function keypress(elem) { // get Input
if (typeof elem == 'string') {
if (document.getElementById(elem)) elem = document.getElementById(elem);
if (typeof elem == 'string') elem = document.getElementsByName(elem).item(0);
}
const el = elem; //handle error if not found input
el.maxLength = 19;
el.addEventListener('keypress', function (e) {
const t = e.keyCode || e.which
if (t == 8 || (t > 47 && t < 58)) { // limit numeric characters and backspace
if (t != 8) {
if (el.value.length == 2) el.value += '/';
if (el.value.length == 5) el.value += '/';
if (el.value.length == 10) el.value += ' ';
if (el.value.length == 13) el.value += ':';
if (el.value.length == 16) el.value += ':';
}
} else {
e.preventDefault();
}
});}
I have a form that I'm using to calculate some numbers, and the final 3 input fields on the form are disabled because they show the results of the calculator.
I'm using the following javascript/jquery to add commas to the user editable fields which works great but I can't seem to find a way to add commas to the "results" fields:
$('input.seperator').change(function(event){
// skip for arrow keys
if(event.which >= 37 && event.which <= 40){
event.preventDefault();
}
var $this = $(this);
var num = $this.val().replace(/,/gi, "").split("").reverse().join("");
var num2 = RemoveRougeChar(num.replace(/(.{3})/g,"$1,").split("").reverse().join(""));
// the following line has been simplified. Revision history contains original.
$this.val(num2);
});
function RemoveRougeChar(convertString){
if(convertString.substring(0,1) == ","){
return convertString.substring(1, convertString.length)
}
return convertString;
}
This is what I'm using the populate the fields, basically the fields show the results in dollars, so I'm trying to add a comma every 3 numbers:
$('#incorrect-payment').val(fieldK);
$('#correcting-payment').val(fieldL);
$('#total-cost').val(fieldM);
I think you'd want to use a function like this:
function FormatCurrency(amount, showDecimals) {
if (showDecimals == null)
showDecimals = true;
var i = parseFloat(amount);
if (isNaN(i)) { i = 0.00; }
var minus = false;
if (i < 0) { minus = true; }
i = Math.abs(i);
i = parseInt((i + .005) * 100);
i = i / 100;
s = new String(i);
if (showDecimals) {
if (s.indexOf('.') < 0) { s += '.00'; }
if (s.indexOf('.') == (s.length - 2)) { s += '0'; }
}
//s = minus + s;
s = '$' + FormatCommas(s, showDecimals);
if (minus)
s = "(" + s + ")";
return s;
}
function FormatCommas(amount, showDecimals) {
if (showDecimals == null)
showDecimals = true;
var delimiter = ","; // replace comma if desired
var a = amount.split('.', 2)
var d = a[1];
var i = parseInt(a[0]);
if (isNaN(i)) { return ''; }
var minus = '';
if (i < 0) { minus = '-'; }
i = Math.abs(i);
var n = new String(i);
var a = [];
while (n.length > 3) {
var nn = n.substr(n.length - 3);
a.unshift(nn);
n = n.substr(0, n.length - 3);
}
if (n.length > 0) { a.unshift(n); }
n = a.join(delimiter);
if (!showDecimals) {
amount = n;
}
else {
if (d.length < 1) { amount = n; }
else { amount = n + '.' + d; }
}
amount = minus + amount;
return amount;
}
May be you might want to trigger change event manually through javascript for your three read-only input fields. Using jquery trigger . I am not sure but it seems like a bad idea to have a read-only input field if no user can change these values. Usually having read-only input fields is good if a user with some security can edit those and some cannot.
in in Input field, if the user presses Backspace or Delete key, is there a way to get the deleted character.
I need to check it against a RegExp.
Assuming the input box has an id 'input'. Here is how with least amount of code you can find out the last character from the input box.
document.getElementById("input").onkeydown = function(evt) {
const t = evt.target;
if (evt.keyCode === 8) { // for backspace key
console.log(t.value[t.selectionStart - 1]);
} else if (evt.keyCode === 46) { // for delete key
console.log(t.value[t.selectionStart]);
}
};
<input id="input" />
The following will work in all major browsers for text <input> elements. It shouldn't be used for <textarea> elements because the getInputSelection function doesn't account for line breaks correctly in IE. See this answer for a (longer) function that will do this.
function getInputSelection(input) {
var start = 0, end = 0;
input.focus();
if ( typeof input.selectionStart == "number" &&
typeof input.selectionEnd == "number") {
start = input.selectionStart;
end = input.selectionEnd;
} else if (document.selection && document.selection.createRange) {
var range = document.selection.createRange();
if (range) {
var inputRange = input.createTextRange();
var workingRange = inputRange.duplicate();
var bookmark = range.getBookmark();
inputRange.moveToBookmark(bookmark);
workingRange.setEndPoint("EndToEnd", inputRange);
end = workingRange.text.length;
workingRange.setEndPoint("EndToStart", inputRange);
start = workingRange.text.length;
}
}
return {
start: start,
end: end,
length: end - start
};
}
document.getElementById("aTextBox").onkeydown = function(evt) {
evt = evt || window.event;
var keyCode = evt.keyCode;
var deleteKey = (keyCode == 46), backspaceKey = (keyCode == 8);
var sel, deletedText, val;
if (deleteKey || backspaceKey) {
val = this.value;
sel = getInputSelection(this);
if (sel.length) {
deletedText = val.slice(sel.start, sel.end);
} else {
deletedText = val.charAt(deleteKey ? sel.start : sel.start - 1);
}
alert("About to be deleted: " + deletedText);
}
};
No, there is no variable that stores the deleted char. Unless you have a history for Undo/Redo, but it would be difficult to get the information out of that component.
Easiest would be to compare the contents of the input field before and after delete/backspace have been pressed.
You could try something with the caret position:
function getCaretPosition(control){
var position = {};
if (control.selectionStart && control.selectionEnd){
position.start = control.selectionStart;
position.end = control.selectionEnd;
} else {
var range = document.selection.createRange();
position.start = (range.offsetLeft - 1) / 7;
position.end = position.start + (range.text.length);
}
position.length = position.end - position.start;
return position;
}
document.getElementById('test').onkeydown = function(e){
var selection = getCaretPosition(this);
var val = this.value;
if((e.keyCode==8 || e.keyCode==46) && selection.start!==selection.end){
alert(val.substr(selection.start, selection.length));
} else if(e.keyCode==8){
alert(val.substr(selection.start-1, 1));
} else if(e.keyCode==46){
alert(val.substr(selection.start, 1));
}
}
Tested on Chrome 6. See jsFiddle for an example