I'm working in a mentioning directive, basically when the user is typing in the input field ( a div with contentEditable=true in this case ), is gonna display a list of user for then insert the name of the user in a specific format, now the list is gonna displayed after the user press #, for chrome and firefox work just great but for EDGE and IE ( unfortunately i need to support ) doesn't work because in this case the # apparently doesn't exist.
now for the key press I'm using the #HostListener('keydown', ['$event'])
HostListener
#HostListener('keydown', ['$event']) keyHandler(event: any, nativeElement: HTMLInputElement = this._element.nativeElement) {
let val: string = getValue(nativeElement);
let pos = getCaretPosition(nativeElement, this.iframe);
let charPressed = this.keyCodeSpecified ? event.keyCode : event.key;
if (!charPressed) {
let charCode = event.which || event.keyCode;
if (!event.shiftKey && (charCode >= 65 && charCode <= 90)) {
charPressed = String.fromCharCode(charCode + 32);
} else if (event.shiftKey && charCode === KEY_2) {
charPressed = this.triggerChar;
} else {
charPressed = String.fromCharCode(event.which || event.keyCode);
}
}
if (event.keyCode == KEY_ENTER && event.wasClick && pos < this.startPos) {
// put caret back in position prior to contenteditable menu click
pos = this.startNode.length;
setCaretPosition(this.startNode, pos, this.iframe);
}
// console.log('=== keyHandler', this.startPos, pos, val, charPressed, event);
this.triggerList(event, charPressed, nativeElement, val, pos);
}
Now as you can see I'm using event.keycode and event.key to get the key from the event keydown, I pass does values to the method this.triggerList()
that basically is gonna display the list of mentions options if and only if the user press # that is the trigger char ( this.triggerChar ).
TriggerList Method
private triggerList(event, charPressed, nativeElement, val, pos): any {
if (charPressed == this.triggerChar) {
this.startPos = pos;
this.startNode = (this.iframe ? this.iframe.contentWindow.getSelection() : window.getSelection()).anchorNode;
// console.log('=== HERE CHAR', this.startNode, this.startPos);
// check if mentioning is allowed based on the text before the mention start char
if (!this.configService.appConfig.platform.EDGE) {
let position = this.getHtmlCaretPosition(nativeElement);
const charBefore = val[position - 1];
if (charBefore == undefined || charBefore.trim() == '' || charBefore == ':') {
this.log.trace('Start mentioning');
this.stopSearch = false;
this.searchString = null;
this.showSearchList(nativeElement);
this.updateSearchList();
}
} else {
this.stopSearch = false;
this.searchString = null;
this.showSearchList(nativeElement);
this.updateSearchList();
}
} else if (this.startPos >= 0 && !this.stopSearch) {
if (pos <= this.startPos) {
this.searchList.hidden = true;
}
// ignore shift when pressed alone, but not when used with another key
else if (event.keyCode !== KEY_SHIFT && !event.metaKey && !event.altKey && !event.ctrlKey && pos > this.startPos) {
if (event.keyCode === KEY_SPACE) {
this.startPos = -1;
} else if (event.keyCode === KEY_BACKSPACE && pos > 0) {
pos--;
if (pos == 0) {
this.stopSearch = true;
}
this.searchList.hidden = this.stopSearch;
} else if (!this.searchList.hidden) {
if (event.keyCode === KEY_TAB || event.keyCode === KEY_ENTER) {
this.stopEvent(event);
this.searchList.hidden = true;
// value is inserted without a trailing space for consistency
// between element types (div and iframe do not preserve the space)
let textValue = this.mentionSelect(this.searchList.activeItem);
insertValue(nativeElement, this.startPos, pos, textValue, this.iframe);
this.emitSelection(nativeElement);
if (this.htmlStyling) {
let quillElement = document.querySelector('.ql-editor');
let innerHtml = quillElement.innerHTML;
let strings = innerHtml.split(textValue);
if (strings.length === 2) {
innerHtml = `${strings[0]}<span id="mention${textValue.substring(1)}${strings.length - 1}" style="color: #0065FF; background: rgba(0,101,255,.2)">${textValue}</span> ${strings[1]}`;
} else {
let openSpan = false;
innerHtml = strings.reduce((total, current, currentIndex) => {
if (current.indexOf(`mention${textValue.substring(1)}`) > 0) {
return `${total}${openSpan ? '</span> ' : ''}${current}${textValue}`;
} else if (openSpan) {
return `${total}</span> ${currentIndex < strings.length - 1 ? current + textValue : current}`;
} else {
openSpan = true;
return `${total}${current}<span id="mention${textValue.substring(1)}${strings.length - 1}" style="color: #0065FF; background: rgba(0,101,255,.2)">${textValue}`;
}
}, '');
}
quillElement.innerHTML = innerHtml;
// tslint:disable-next-line:no-angle-bracket-type-assertion
let mentionElement: HTMLInputElement = document.getElementById(`mention${textValue.substring(1)}${strings.length - 1}`) as HTMLInputElement;
// tslint:disable-next-line:no-angle-bracket-type-assertion
setCaretPosition(mentionElement.nextSibling as HTMLInputElement, 1);
}
// fire input event so angular bindings are updated
if ('createEvent' in document) {
let evt = document.createEvent('HTMLEvents');
evt.initEvent('input', false, true);
nativeElement.dispatchEvent(evt);
}
this.startPos = -1;
return false;
} else if (event.keyCode === KEY_ESCAPE) {
this.stopEvent(event);
this.searchList.hidden = true;
this.stopSearch = true;
return false;
} else if (event.keyCode === KEY_DOWN) {
this.stopEvent(event);
this.searchList.activateNextItem();
return false;
} else if (event.keyCode === KEY_UP) {
this.stopEvent(event);
this.searchList.activatePreviousItem();
return false;
}
}
if (event.keyCode === KEY_LEFT || event.keyCode === KEY_RIGHT) {
this.stopEvent(event);
return false;
} else {
let mention = val.substring(this.startPos + 1, pos);
if (event.keyCode !== KEY_BACKSPACE) {
mention += charPressed;
}
this.searchString = mention;
this.searchTerm.emit(this.searchString);
this.updateSearchList();
}
}
}
}
now the issue here is that if the user to insert the char # need to use the combination of ALT + Q EDGE only detect ALT and then Q, compare to firefox and chrome that with the combination ALT + Q detect # for this reason the list is not display because the char never match.
First i replace the event keydown for keypress, then where i save the char in the variable charPress i create a condition to check if the browser is EDGE or IE, and get char code using event.charCode and convert it in to string using String.fromCharCode(event.charCode) at the end looks like this.
#HostListener('keypress', ['$event']) keyHandler(event: any, nativeElement: HTMLInputElement = this._element.nativeElement) {
let val: string = getValue(nativeElement);
let pos = getCaretPosition(nativeElement, this.iframe);
let charPressed = this.keyCodeSpecified ? event.keyCode : event.key;
if (this.configService.appConfig.platform.EDGE) {
charPressed = String.fromCharCode(event.charCode);
}
......
Related
I have a question about an input field which should only accept numbers with two digits after the dot. For this scenario I created a fully functional Fiddle example - where you can see where I am stuck right now.
My issue: I can't change the value when it's completely selected/marked.
Fiddle Example of my issue
The scripts check for:
only positive numbers
only numbers between 0 - 100
only two digits after .
only one . (dot) possible
copy&paste check implementation
// Check if keypressed and only number & .
$('#inpPercent').keypress(function(event) {
var inpVal = $(this).val();
if ((inpVal.indexOf('.') != -1) && (inpVal.substring(inpVal.indexOf('.')).length > 2)) {
event.preventDefault();
return false;
}
if (isNumberRegChecked(event, this) == false) {
event.preventDefault();
return false;
}
return isNumber(event, this)
});
// CHANGE VALID FEEDBACK OF INPUT FIELD
$('#inpPercent').on('input', function(e) {
var inpVal = $(this).val();
if (inpVal > 100 || inpVal < 0 || !inpVal) {
$("#inpPercent").removeClass("is-valid");
$("#inpPercent").addClass("is-invalid");
return false;
} else {
$("#inpPercent").removeClass("is-invalid");
$("#inpPercent").addClass("is-valid");
return true;
}
});
// PREVENT COPY PASTE
$('#inpPercent').bind("paste", function(e) {
var text = e.originalEvent.clipboardData.getData('Text');
if ($.isNumeric(text)) {
if (text > 100 || text < 0) {
$("#inpPercent").addClass("is-invalid");
} else {
$("#inpPercent").addClass("is-valid");
}
if ((text.substring(text.indexOf('.')).length > 3) && (text.indexOf('.') > -1)) {
e.preventDefault();
$(this).val(text.substring(0, text.indexOf('.') + 3));
}
} else {
e.preventDefault();
}
});
function isNumberRegChecked(evt, element) {
var inpVal = $(element).val();
var checkValue = /^(\d{0,2}(\.\d{0,2})?)$/.test(inpVal);
if (checkValue == false) return false;
return true;
};
// THE SCRIPT THAT CHECKS IF THE KEY PRESSED IS A NUMERIC OR DECIMAL VALUE.
function isNumber(evt, element) {
var charCode = evt.which ? evt.which : event.keyCode;
if (
(charCode != 46 ||
$(element)
.val()
.indexOf('.') != -1) && // “.” CHECK DOT, AND ONLY ONE.
(charCode < 48 || charCode > 57)
)
return false;
return true;
};
How can I detect the difference between a single CTRL key and a CTRL + 1 key combination?
Currently I have the following:
window.addEventListener('keydown', function(e) {
if(e.keycode === 17){
alert('crtl only')
}
else if (e.ctrlKey && e.keyCode == 49) {
alert('crtl + 1');
}
}
When the key(s) are hit, make a note of the time. Then compare it with the time you noted the last time they key(s) were hit.
If the difference is within your threshold, consider it a double. Otherwise, don't.
If the difference is within your threshold, consider it a double. Otherwise, don't.
Rough example:
var delta = 500;
var lastKeypressTime = 0;
function KeyHandler(event)
{
if ( event.ctrlKey
&& String.fromCharCode(event.charCode).toUpperCase()) == 'T' )
{
var thisKeypressTime = new Date();
if ( thisKeypressTime - lastKeypressTime <= delta )
{
doDoubleKeypress();
// optional - if we'd rather not detect a triple-press
// as a second double-press, reset the timestamp
thisKeypressTime = 0;
}
lastKeypressTime = thisKeypressTime;
}
}
This should work:
window.addEventListener('keydown', function (e) {
if (e.ctrlKey && e.keyCode === 49){
alert('combo hit!');
}
}
Use debounce with keydown
function KeyPress(e) {
var evtobj = window.event ? event : e;
debouce(function(evtobj) {
if (evtobj.keyCode == 65 && evtobj.ctrlKey) {
console.log("Ctrl+a");
evtobj.preventDefault();
} else if (evtobj.ctrlKey) {
console.log("Only ctrl");
}
}, evtobj, 200);
}
Demo
function KeyPress(e) {
var evtobj = window.event ? event : e;
debouce(function(evtobj) {
if (evtobj.keyCode == 65 && evtobj.ctrlKey) {
console.log("Ctrl+a");
evtobj.preventDefault();
} else if (evtobj.ctrlKey) {
console.log("Only ctrl");
}
}, evtobj, 200);
}
function debouce(method, eventObj, debounceTime) {
if (this.timeoutId)
clearTimeout(this.timeoutId);
this.timeoutId = setTimeout(function() {
method(eventObj);
}, debounceTime);
}
document.onkeydown = KeyPress;
I am trying to format the phone number field based on the country .The logic works fine when the user fill in the details but does not work when the country is changed and filled in again.
Ex:
Filled in the form for Germany and then without reloading the page if try change county to US and fill in phone "+" is being added even though format for it is different.
$(document).ready(function($){
$('#country').on('change', function() {
if ( this.value == 'US' || this.value == 'CA')
{
$('#C_BusPhone')
.keydown(function (e) {
var key = e.charCode || e.keyCode || 0;
$phone = $(this);
if (key !== 8 && key !== 9) {
if ($phone.val().length === 4) {
$phone.val($phone.val() + ')');
}
if ($phone.val().length === 5) {
$phone.val($phone.val() + ' ');
}
if ($phone.val().length === 9) {
$phone.val($phone.val() + '-');
}
}
return (key == 8 ||
key == 9 ||
key == 46 ||
(key >= 48 && key <= 57) ||
(key >= 96 && key <= 105));
})
.bind('focus click', function () {
$phone = $(this);
if ($phone.val().length === 0) {
$phone.val('(');
}
else {
var val = $phone.val();
$phone.val('').val(val);
}
})
.blur(function () {
$phone = $(this);
if ($phone.val() === '(') {
$phone.val('');
}
});
}
else
{
$('#C_BusPhone')
.keydown(function (e) {
if ($(this).val().indexOf("+") === -1) {
$(this).val("+" + $this.val());
}
})
}
});
});
Not sure if this was the cause for your error, but this line needs to get corrected from
$(this).val("+" + $this.val());
to this
$(this).val("+" + $(this).val());
It was a fast catch, just use your browser javascript console for debugging.
See the updated fiddle here. You might want to add the starting parenthesis to your international prefix as well.
I'm trying to implement a customised input that can use left or right arrow key to select the digit and use up/down arrow key to increment/decrement the digit. Here's the code in jsfiddle: http://jsfiddle.net/uk5t3z4d/48/. However, I have two problems:
I cannot add digit using the number pad, the input always stays at X.XX
When I use another function I wrote (parseLocalFloat which is commented out), the output stops displaying anything, and I cannot use the left and right key to select the digit etc.
How can I overcome these two issues? Please shed a light on me, thanks!
HTML
<div class="display" id="out"></div>
<div class="form-group">
<label for="comment">value:</label>
<input class="form-control" type="text" value="0.00" id="in"></input>
</div>
JavaScript
function createSelection(field, start, end) {
if( field.createTextRange ) {
var selRange = field.createTextRange();
selRange.collapse(true);
selRange.moveStart('character', start);
selRange.moveEnd('character', end);
selRange.select();
} else if( field.setSelectionRange ) {
field.setSelectionRange(start, end);
} else if( field.selectionStart ) {
field.selectionStart = start;
field.selectionEnd = end;
}
}
function getLocalDecimalSeparator() {
var n = 1.1;
return n.toLocaleString().substring(1,2);
}
function parseLocalFloat(num) {
return +(num.replace(getLocalDecimalSeparator(), '.'));
}
var inputBox = document.getElementById('in');
//var inputBox = parseLocalFloat(document.getElementByID('in').value);
inputBox.onkeyup = function(){
document.getElementById('out').innerHTML = inputBox.value;
}
$('#in').on("keydown", function(e){
var gotCode = false;
var curPos = this.selectionStart;
var endPos = this.selectionEnd;
if(curPos !== endPos) {
createSelection(this, curPos, curPos+1);
}
// get the position
if(e.keyCode == 37){
curPos--;
gotCode=true;
}
if(e.keyCode == 39){
curPos++;
gotCode=true;
}
var before = $(this).val().substring(0,curPos);
var after = $(this).val().substring(curPos+1);
var cur = Number($(this).val().substring(curPos, curPos+1));
// avoid adding extra stuff
if(curPos < $(this).val().length) {
if(e.keyCode == 38) {
cur++;
if(cur > 9) cur = 0;
$(this).val(before + '' + cur + '' + after);
gotCode=true;
}
if(e.keyCode == 40) {
cur--;
if(cur < 0) cur = 9;
$(this).val(before + '' + cur + '' + after);
gotCode=true;
}
}
if(!gotCode) {
e.preventDefault();
return false;
}
var field = this;
window.setTimeout(function(){
createSelection(field, curPos, curPos+1);
}, 10);
});
as for the "get number keys to work":
as stated you need to add the keys you want to support:
if(e.keyCode >= 48 && e.keyCode <= 57) {
var num = e.keyCode - 48; // 0=48; 9=59
$(this).val(before + '' + num + '' + after);
gotCode = true;
e.preventDefault(); // otherwise a new number is added as well
}
(this needs to come before the if (!gotCode) ... )
as for the customFloat: the the response from Moishe
For #1:
if(!gotCode) {
e.preventDefault();
return false;
}
ensures that if gotCode is false the default event (which in this case is the default keydown event) will not occur.
gotCode only seems to be true if keyCode is equal to 37, 38, 39, or 40 (the arrow keys). You are essentially preventing the other keys (like number keys) from having any effect on the textBox.
You probably would like to enable the number keys (when shift or caps aren't on) and number pad keys.
Additionally, you may want to check that the cur is a number (and not .) before attempting to increment or decrement its value.
You could do:
var isNumberKey = (
( e.keyCode >= 48 //is more than or equal to 0 key
&& e.keyCode <= 57 //is less than or equal to 9 key
&& !e.shiftKey) //shift key or cap key not on
|| ( e.keyCode >= 96 //more than or equal to 0 key in number pad
&& e.keyCode <= 105)); //less than or equal to 9 key in number pad
if(!gotCode && !isNumberKey) { //not arrow key or number key
console.log(e);
e.preventDefault();
return false;
}
For #2:
var inputBox = parseLocalFloat(document.getElementByID('in').value);
is setting inputBox to whatever parseLocalFloat returns which happens to be a number.
This is problematic because you then attempt to attach a keyUp event to that number instead of the inputBox:
inputBox.onkeyup = function(){
document.getElementById('out').innerHTML = inputBox.value;
}
You may want to instead call parseLocalFloat on the number and set the out textBox's value to that:
var inputBox = document.getElementById('in');
inputBox.onkeyup = function(){
document.getElementById('out').innerHTML = parseLocalFloat(inputBox.value);
}
function createSelection(field, start, end) {
if( field.createTextRange ) {
var selRange = field.createTextRange();
selRange.collapse(true);
selRange.moveStart('character', start);
selRange.moveEnd('character', end);
selRange.select();
} else if( field.setSelectionRange ) {
field.setSelectionRange(start, end);
} else if( field.selectionStart ) {
field.selectionStart = start;
field.selectionEnd = end;
}
}
function getLocalDecimalSeparator() {
var n = 1.1;
return n.toLocaleString().substring(1,2);
}
function parseLocalFloat(num) {
return +(num.replace(getLocalDecimalSeparator(), '.'));
}
var inputBox = document.getElementById('in');
// var inputBox = parseLocalFloat(document.getElementByID('in').value);
inputBox.onkeyup = function(){
document.getElementById('out').innerHTML = parseLocalFloat(inputBox.value);
}
$('#in').on("keydown", function(e){
var gotCode = false;
var curPos = this.selectionStart;
var endPos = this.selectionEnd;
if(curPos !== endPos) {
createSelection(this, curPos, curPos+1);
}
// get the position
if(e.keyCode == 37){
curPos--;
gotCode=true;
}
if(e.keyCode == 39){
curPos++;
gotCode=true;
}
var $thisVal = $(this).val();
var before = $thisVal.substring(0,curPos);
var after = $thisVal.substring(curPos+1);
var cur = Number($thisVal.substring(curPos, curPos+1));
// avoid adding extra stuff
if(curPos < $thisVal.length && !isNaN(cur)) {
if(e.keyCode == 38) {
cur++;
if(cur > 9) cur = 0;
$(this).val(before + '' + cur + '' + after);
gotCode=true;
}
if(e.keyCode == 40) {
cur--;
if(cur < 0) cur = 9;
$(this).val(before + '' + cur + '' + after);
gotCode=true;
}
}
var isNumberKey = ((e.keyCode >= 48 && e.keyCode <= 57 && [16, 20].indexOf(e.keyCode) == -1 && !e.shiftKey) || (e.keyCode >= 96 && e.keyCode <= 105));
if(!gotCode && !isNumberKey) {
console.log(e);
e.preventDefault();
return false;
}
var field = this;
window.setTimeout(function(){
createSelection(field, curPos, curPos+1);
}, 10);
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="display" id="out"></div>
<div class="form-group">
<label for="comment">value:</label>
<input class="form-control" type="text" value="0.00" id="in"></input>
</div>
I have a searchable textbox which populates a div with the search results. The div is scrollable. What I am trying to achieve, is to navigate through the result items with page up and down (keycode 38 & 40). But as soon as I try this, the whole div scrolls, and the result item itself does not take on the new selected css class.
Below is some of my code
this.TagNavigation = function (event) {
var div = $("#TagSearchResults");
var anchors = $("#TagSearchResults a");
var selectedAnchor = $("#TagSearchResults a.selected");
var position = anchors.index(selectedAnchor);
if (event.keyCode == "13" && anchors.length > 0) {
FRAMEWORK.AddUpdateInterventionTags(selectedAnchor.attr("id").split("-")[1] + "|" + selectedAnchor.text(), "add");
}
if (event.keyCode == "13" && anchors.length == 0 && $("#txtTagSearch").val() != "Start typing to search Tags") {
FRAMEWORK.AddNewTag($("#txtTagSearch").val());
}
else if (event.keyCode == "38") {
if (position > 0) {
canClose = false;
selectedAnchor.removeClass("selected");
var newSelectedAnchor = $(anchors.get(position - 1));
newSelectedAnchor.addClass("selected");
$("#txtTagSearch").val(newSelectedAnchor.text());
}
}
else if (event.keyCode == "40") {
if (position <= anchors.length) {
canClose = false;
selectedAnchor.removeClass("selected");
var newSelectedAnchor = $(anchors.get(position + 1));
newSelectedAnchor.addClass("selected");
$("#txtTagSearch").val(newSelectedAnchor.text());
//newSelectedAnchor.focus();
}
}
};
this.AjaxSearch = function (text) {
var div = $("#TagSearchResults");
var anchors = $("#TagSearchResults a");
var selectedAnchor = $("#TagSearchResults a.selected");
var position = anchors.index(selectedAnchor);
if (event.keyCode == "13") {
FRAMEWORK.TagNavigation(event);
}
else if (event.keyCode == "38") {
FRAMEWORK.TagNavigation(event);
}
else if (event.keyCode == "40") {
FRAMEWORK.TagNavigation(event);
}
else if (text.length >= 3) {
FRAMEWORK.RenderSearchResults(text);
}
else {
$("#TagSearchResults").html("");
$("#TagSearchResults").hide();
}
};
As you can see in the TagNavigation function (keycode 40), I tried to set the focus on the active element, but still no success.
Any help please.
You need to check weather the newly selected element has a higher Y value that the bottom of the containing div. If so, then scroll the div by the height of the new element. Change your 'if (event.keyCode == "40")' statement to the following:
this.TagNavigation = function (event) {
var div = $("#TagSearchResults");
var anchors = $("#TagSearchResults a");
var selectedAnchor = $("#TagSearchResults a.selected");
var position = anchors.index(selectedAnchor);
if (event.keyCode == "13" && anchors.length > 0) {
FRAMEWORK.AddUpdateInterventionTags(selectedAnchor.attr("id").split("-")[1] + "|" + selectedAnchor.text(), "add");
}
if (event.keyCode == "13" && anchors.length == 0 && $("#txtTagSearch").val() != "Start typing to search Tags") {
FRAMEWORK.AddNewTag($("#txtTagSearch").val());
}
else if (event.keyCode == "38") {
if (position > 0) {
canClose = false;
selectedAnchor.removeClass("selected");
var newSelectedAnchor = $(anchors.get(position - 1));
newSelectedAnchor.addClass("selected");
$("#txtTagSearch").val(newSelectedAnchor.text());
var newSelectedAnchorPosistion = newSelectedAnchor.offset();
var divPosition = div.offset();
divPosition = divPosition.top;
if (newSelectedAnchorPosistion.top + 1 > divPosition) {
var newPos = div.scrollTop() - newSelectedAnchor.outerHeight();
div.scrollTop(newPos);
}
}
}
else if (event.keyCode == "40") {
if (position < anchors.length - 1) {
canClose = false;
selectedAnchor.removeClass("selected");
var newSelectedAnchor = $(anchors.get(position + 1));
newSelectedAnchor.addClass("selected");
$("#txtTagSearch").val(newSelectedAnchor.text());
var newSelectedAnchorPosistion = newSelectedAnchor.offset();
var divPosition = div.offset();
divPosition = divPosition.top + div.outerHeight();
if (newSelectedAnchorPosistion.top + 1 >= divPosition) {
var newPos = div.scrollTop() + newSelectedAnchor.outerHeight();
div.scrollTop(newPos);
}
}
}
};