Is there a way to focus element from querySelectorAll()? - javascript

I want to focus my dropdown elements from keyboard (up and down keys).
My code:
var matches = document.querySelectorAll("div.dropdown-content > a");
var i = 0;
var focused = matches[i];
document.onkeydown = function(e) {
switch (e.keyCode) {
case 37:
i++;
focused.focus();
break;
case 38:
i--;
focused.focus();
break;
}
};
But it's actually not working. Console does not report any errors.

Arrow key up is 38, down is 40, also check i range between 0, nodes.length
const matches = document.querySelectorAll('div.dropdown-content > a');
let i = 0;
let focused;
if (matches.length > 0) {
matches[i].focus();
focused = matches[i];
document.onkeydown = function(e) {
e.preventDefault();
switch (e.keyCode) {
case 40:
if (i < matches.length - 1) {
i++;
}
matches[i].focus();
focused = matches[i];
break;
case 38:
if (i > 0 ) {
i--;
}
matches[i].focus();
focused = matches[i];
break;
}
};
}
a { display: block; }
<div class="dropdown-content">
Link
Link
Link
Link
Link
</div>

You keep focusing the same element, focused is not updated when i changes.
Also, the keycode for down arrow is 40, not 37.
Finally, you should check that i is always between 0 and matches.length - 1.
var matches = document.querySelectorAll("div.dropdown-content > a");
var i = 0;
document.onkeydown = function(e) {
switch (e.keyCode) {
case 40:
if(i < matches.length - 1) i++;
matches[i].focus();
break;
case 38:
if(i > 0) i--;
matches[i].focus();
break;
}
};
You can also focus the first item when you reached the last one and vice versa :
document.onkeydown = function(e) {
switch (e.keyCode) {
case 40:
i = (i + 1) % matches.length;
matches[i].focus();
break;
case 38:
i = Math.abs(i - 1 + matches.length) % matches.length;
matches[i].focus();
break;
}
};

Related

Accessibility: How can I set the position of the cursor in the input when navigating arrow keys?

I have developed an arrow-keys navigation within a table. Navigation is possible with arrow keys up, down, left and right. How can the cursor always be set to the right in the input during navigation?
My Stackblitz: https://stackblitz.com/edit/stackoverflow-72056135-ube6hj?file=app%2Ftable-basic-example.ts
My code:
// HTML
<input class="edit-input || focus-cell" type="text"[formControl]="rowControl.get(column.attribute)" appArrowKeyNav>
// TS
/**
* Use arrowKeys
* #param object any
*/
move(object) {
const inputToArray = this.inputs.toArray();
let index = inputToArray.findIndex((x) => x.element === object.element);
switch (object.action) {
case 'UP':
index -= this.columns.length;
break;
case 'DOWN':
index += this.columns.length;
break;
case 'LEFT':
index--;
break;
case 'RIGTH':
index++;
break;
}
if (index >= 0 && index < this.inputs.length) {
inputToArray[index].element.nativeElement.focus();
}
}
I have now solved the problem considering setSelectionRange(). I have optimized my code as follows:
/**
* Use arrowKeys
* #param object any
*/
move(object) {
const inputToArray = this.inputs.toArray();
let index = inputToArray.findIndex((x) => x.element === object.element);
switch (object.action) {
case 'UP':
index -= this.columns.length;
break;
case 'DOWN':
index += this.columns.length;
break;
case 'LEFT':
index--;
break;
case 'RIGTH':
index++;
break;
}
if (index >= 0 && index < this.inputs.length) {
// Set focus
inputToArray[index].element.nativeElement.focus();
// Set cursor at the end of the text in the input
setTimeout(() => {
if (inputToArray[index].element.nativeElement.value?.length > 0) {
const pos = inputToArray[index].element.nativeElement.value.length;
inputToArray[index].element.nativeElement.setSelectionRange(pos, pos);
}
}, 1);
}
}

how i get out from the loop if switch case implemented(there is a switch inside the loop)

How I get out from the loop if switch-case implemented (there is a switch inside the loop).
function playInbestPlace() {
console.log("hello from playInbestPlace ")
findEmptyarea();
for (var i = 0; i < indexOfEmpty.length; i++) {
var elem = indexOfEmpty[i];
switch (elem) {
case 0:
cells[elem].childNodes[0].append("o");
break;
case 2:
cells[elem].childNodes[0].append("o");
break;
case 4:
cells[elem].childNodes[0].append("o");
break;
case 6:
cells[elem].childNodes[0].append("o");
break;
case 8:
cells[elem].childNodes[0].append("o");
break;
}
}
}
I want it to get out if any case valid.
you can add a variable found and break out of the loop if it's true :
function playInbestPlace() {
console.log("hello from playInbestPlace ")
findEmptyarea();
for (var i = 0; i < indexOfEmpty.length; i++) {
var elem = indexOfEmpty[i];
var found = false; // initial found is false
switch (elem) {
case 0:
cells[elem].childNodes[0].append("o");
found = true;
break;
case 2:
cells[elem].childNodes[0].append("o");
found = true;
break;
case 4:
cells[elem].childNodes[0].append("o");
found = true;
break;
case 6:
cells[elem].childNodes[0].append("o");
found = true;
break;
case 8:
cells[elem].childNodes[0].append("o");
found = true;
break;
}
if(found) // break out if it's true
break;
}
}
You could use a flag variable to break from the loop when some condition is verified.
function playInbestPlace() {
console.log("hello from playInbestPlace ");
findEmptyarea();
var keepOnLooping = true;
for (var i = 0; keepOnLooping && i < indexOfEmpty.length; i++) {
if (elem % 2 === 0) {
cells[elem].childNodes[0].append("o");
keepOnLooping = false;
}
}
}
I've also added epascarello optimization in my answer.

change current TextArea value without assign a new one?

When I press "A" to add a character to a value of the TextArea, how does the value change?
does the browser do something like this:
texarea.value+="A";
//same as:
texarea.value=texarea.value+"A";
?
I would like to change that value dynamicaly and notify the textarea about the change without assign a new one, because I think that assingment is not very perfomant if the value takes a lot of memory.
Is there a way to do this?
Ok, I solved it:
at first sight this solution seems also not very performant, but it has some big advatages:
for example you could load only a specific part of a very big file from server and dynamically load it if the user scroll through the page.
you can also update the client version of the file representation (synchronized editing)
document.registerElement('text-area', class NicePanel extends HTMLElement {
createdCallback() {
//this.root = this.createShadowRoot();
this.tabIndex = 1;
this.onkeydown = function(e) {
this.monkeydown(e);
}
this.onkeypress = function(e) {
this.monkeypress(e);
}
this.setValue(this.innerHTML);
}
setValue(value) {
var mutablestring = Array.from(value);
this.innerHTML = "";
var l = mutablestring.length;
for (var i = 0; i < l; i++) {
if (mutablestring[i] == '\n') {
var br = document.createElement('br');
br.onclick = this.characterClick;
this.appendChild(br);
} else {
var span = document.createElement('span');
span.onclick = this.characterClick;
span.innerHTML = mutablestring[i];
this.appendChild(span);
}
}
this.cursor = document.createElement('span');
this.cursor.innerHTML = "|";
this.appendChild(this.cursor);
}
getValue() {
var value = "";
var children = this.childNodes;
var that = this;
children.forEach(function(child) {
if (child.nodeName == "BR") {
value += "\n";
return;
}
if (child === that.cursor) return;
value += child.innerHTML;
});
return value;
}
attachedCallback() {}
monkeydown(e) {
var KeyID = event.keyCode;
switch (KeyID) {
case 8:
this.cursor.parentNode.removeChild(this.cursor.previousSibling);
e.preventDefault();
break;
case 46:
this.cursor.parentNode.removeChild(this.cursor.nextSibling);
e.preventDefault();
break;
case 13:
var br = document.createElement('br');
br.onclick = this.characterClick;
this.cursor.before(br);
e.preventDefault();
break;
case 37: //left
this.cursor.parentNode.insertBefore(this.cursor, this.cursor.previousSibling);
break;
case 38: //up
break;
case 39: //right
this.cursor.parentNode.insertBefore(this.cursor, this.cursor.nextSibling.nextSibling);
break;
case 40: //down
break;
default:
break;
}
}
characterClick(e) {
this.parentNode.cursor.parentNode.insertBefore(this.parentNode.cursor, e.target);
}
monkeypress(e) {
var span = document.createElement('span');
span.onclick = this.characterClick;
span.innerHTML = String.fromCharCode(e.which);
this.cursor.parentNode.insertBefore(span, this.cursor);
}
monclick(e) {
var target = e.target; // Clicked element
while (target && target.parentNode !== this) {
target = target.parentNode; // If the clicked element isn't a direct child
if (!target) {
return;
} // If element doesn't exist
}
if (target.tagName === 'SPAN') {
alert(target.id); // Check if the element is a LI
}
}
})
var myta = document.getElementById('myta');
myta.setValue("huhu\nlala");
console.log(myta.getValue());
text-area {
width: 100%;
background: #fff;
display: block;
}
<text-area id="myta" class="text">Hello World
</text-area>

clearInterval not clearing the interval

Thanks for the response. I have solved my problem. I really did see that its a list of callback functions. After some work i managed to shoot by intervals, but the first shot was after 1 second.
1 - a problem - if I call the function in setInterval imidiatly and then set interval - shoots rapidly.
2 - I fixed the problem by making setTimeout to set a bool value hasShooted to false after 1 second and if that value is false i can shoot. In the function i do that i set it to true.
3 - I realized I need only that last function with set timeout and not setInterval at all.
var PlayerManager = (function(parent){
'use strict';
var bulletPossLeft,
bulletPossTop,
FIRE_SPEED = 1000,
hasShot = false;
PlayerManager.prototype = Object.create(parent.prototype);
function PlayerManager() {
parent.call(this);
this.moveLeft= false;
this.moveRight= false;
this.moveForward= false;
this.moveBack= false;
this.isShooting= false;
this.bulletManager = new BulletManager();
}
PlayerManager.prototype.onGameLoop = function(obj) {
if (this.isShooting) {
bulletPossLeft = obj.positionLeft + Math.floor(obj.planeWidth /2);
bulletPossTop = obj.positionTop - Math.ceil(obj.planeHeight /2);
if(!hasShot){
this.shoot();
hasShot = true;
setTimeout(function(){
hasShot = false;
}, FIRE_SPEED);
}
}
if (this.moveLeft && (obj.positionLeft - obj.speed) > 0) {
obj.positionLeft -= obj.speed;
}
if (this.moveRight && (obj.positionLeft + obj.speed) < Game.getContextValue('width')) {
obj.positionLeft += obj.speed;
}
if (this.moveForward && (obj.positionTop - obj.speed) > 0) {
obj.positionTop -= obj.speed;
}
if (this.moveBack && (obj.positionTop + obj.speed) < Game.getContextValue('height')) {
obj.positionTop += obj.speed;
}
obj.move();
};
PlayerManager.prototype.shoot = function(){
this.bulletManager.spawn(new Bullet(bulletPossLeft, bulletPossTop, 'orange'));
};
PlayerManager.prototype.keyboardListener = function(e) {
var value = e.type == 'keydown';
switch (e.keyCode) {
case 37:
this.moveLeft = value;
break;
case 38:
this.moveForward = value;
break;
case 39:
this.moveRight = value;
break;
case 40:
this.moveBack = value;
break;
case 32:
this.isShooting = value;
break;
default:
break;
}
};
return PlayerManager;
})(Manager);
You are setting a new interval every time onGameLoop is executed and this.isShooting equals to true. Therefore when you use clearInterval, you are clearing only the last interval, not all of them.
I recommend you clearing the variable shootInterval after clearing interval (for example: shootInterval = null;) and in the first condition (if (this.isShooting)) check if shootInterval is not null.
Your code should look like this:
var bulletPossLeft,
bulletPossTop,
fireSpeed = 1000,
shootInterval,
self;
PlayerManager.prototype = Object.create(parent.prototype);
function PlayerManager() {
parent.call(this);
this.moveLeft= false;
this.moveRight= false;
this.moveForward= false;
this.moveBack= false;
this.isShooting= false;
this.bulletManager = new BulletManager();
self = this;
}
PlayerManager.prototype.onGameLoop = function(obj) {
if (this.isShooting && shootInterval == null) {
bulletPossLeft = obj.positionLeft + Math.floor(obj.planeWidth /2);
bulletPossTop = obj.positionTop - Math.ceil(obj.planeHeight /2);
shootInterval = setInterval(function(){
self.shoot();
} , fireSpeed);
}
if(!this.isShooting) {
clearInterval(shootInterval);
shootInterval = null;
}
if (this.moveLeft && (obj.positionLeft - obj.speed) > 0) {
obj.positionLeft -= obj.speed;
debugger;
}
if (this.moveRight && (obj.positionLeft + obj.speed) < Game.getContextValue('width')) {
obj.positionLeft += obj.speed;
}
if (this.moveForward && (obj.positionTop - obj.speed) > 0) {
obj.positionTop -= obj.speed;
}
if (this.moveBack && (obj.positionTop + obj.speed) < Game.getContextValue('height')) {
obj.positionTop += obj.speed;
}
obj.move();
};
PlayerManager.prototype.shoot = function(){
this.bulletManager.spawn(new Bullet(bulletPossLeft, bulletPossTop, 'orange'));
};
PlayerManager.prototype.keyboardListener = function(e) {
var value = e.type == 'keydown';
switch (e.keyCode) {
case 37:
this.moveLeft = value;
break;
case 38:
this.moveForward = value;
break;
case 39:
this.moveRight = value;
break;
case 40:
this.moveBack = value;
break;
case 32:
this.isShooting = true;
break;
default:
break;
}
if(e.type == 'keyup'){
this.isShooting = false;
}
};
return PlayerManager;
})(Manager);

Jquery: count using setInterval

Im trying to create two counter when I press downKey I want to counter1 start counting and when I press keyLeft I want to stop the first counter and start counter2 .... I know that I need to use clearInterval() function but I dont know where I need to use it, here is a JSFiddle
to see what I mean
html:
<div id="left"></div>
<div id="down"></div>
js:
$('body').keydown(function (e) {
switch (e.which) {
case 39:
clearInterval(down_move);
var i=0;
var right_move = setInterval(function(){
$('#left').html(i);
i++
}, 1000)
break;
case 40:
clearInterval(right_move);
var j = 0;
var down_move = setInterval(function(){
$('#down').html(j)
j++;
}, 1000);
break;
default:
}
e.preventDefault();
});
You need to declare down_move and right_move outside of keydown:
var right_move, down_move;
$('body').keydown(function (e) {
switch (e.which) {
case 39:
clearInterval(down_move);
var i=0;
right_move = setInterval(function(){
$('#left').html(i);
i++
}, 1000)
break;
case 40:
clearInterval(right_move);
var j = 0;
down_move = setInterval(function(){
$('#down').html(j)
j++;
}, 1000);
break;
default:
}
e.preventDefault();
});
it seems like a scope issue, here is how I would make it:
updated fiddle
var Interval = function(intervalFunc, selector){
var interval;
this.start = function(){
interval = setInterval(intervalFunc, 1000);
},
this.stop = function(){
clearInterval(interval);
}
}
var i = 0;
var rightBtnInterval = new Interval(function downInterval(){
$('#right').html(i)
i++;
});
var j = 0;
var downBtnInterval = new Interval(function downInterval(){
$('#down').html(j)
j++;
});
$('body').keydown(function (e) {
switch (e.which) {
case 39:
rightBtnInterval.stop();
downBtnInterval.start();
break;
case 40:
downBtnInterval.stop();
rightBtnInterval.start();
break;
default:
}
e.preventDefault();
});
You have to store outside of function the interval to stop.
Warning the ascii code of left is 37.
Here is a JSFiddle to see my change:
var storeInterval = null;
$('body').keydown(function (e) {
switch (e.which) {
case 37:
clearInterval(storeInterval);
var i=0;
storeInterval = setInterval(function(){
$('#left').html(i);
i++
}, 1000)
break;
case 40:
clearInterval(storeInterval);
var j = 0;
storeInterval = setInterval(function(){
$('#down').html(j)
j++;
}, 1000);
break;
default:
}
e.preventDefault();
});
<div id="left"></div>
<div id="down"></div>

Categories