I'm trying to capture the user keystroke using RxJS, and for each stroke, generate a result object which contains the key, the duration of the stroke (time between keyup and keydown events), and the interval between previous strokes (using timeInterval).
See the image bellow for an overview.
So far, my code is working : Hello outputs ShiftLeftHELLO.
But when I writing faster (as usual I mean), everything breaks up and World outputs ShiftLeftShiftLeftOLD.
Do you have any suggestion on implementing backpressure, buffering or something else in my code to prevent this behavior ?
(function (document) {
var textarea = document.querySelector("#input");
var keyUpStream = Rx.DOM.keyup(textarea);
var keyDownStream = Rx.DOM.keydown(textarea);
var keyStrokeStream = Rx.Observable.merge(keyDownStream, keyUpStream);
var keystroke = keyStrokeStream.filter((function() {
var keysPressed = {};
return function(e) {
var k = e.which;
if (e.type == 'keyup') {
delete keysPressed[k];
return true;
} else if (e.type == 'keydown') {
if (keysPressed[k]) {
return false;
} else {
keysPressed[k] = true;
return true;
}
}
};
})())
.distinctUntilChanged(function (e){
return e.type + e.which;
})
.timeInterval()
.bufferWithCount(2)
.zip(function (evts){
return {
"ts" : Date.now(),
"key": evts[0].value.code,
"evts" : evts,
"duration" : evts.reduce(function(a, b){
return b.value.timeStamp - a.value.timeStamp;
})
};
}).subscribe(function (e){
console.log(e);
document.querySelector("#output").textContent += e.key.replace("Key", '');
document.querySelector("#console").textContent += JSON.stringify(e);
});
})(document);
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/4.1.0/rx.all.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs-dom/7.0.3/rx.dom.js"></script>
<h1>KeyStroke</h1>
<textarea id="input" rows="5" cols="50"></textarea>
<div id="output"></div>
<div id="console"></div>
Disclaimer: Rxjs noob here.
The problem is that your code expect a keyup from a key after a keydown from that same key. When you type fast, you have a race condition on keystrokes, resulting in a buffer with several keydowns or keyups. Not what you expect in your buffer.
I've modified the code to take advantage of your filter function. That function returns only keyup events and it saves the corresponding keydown stroke. This way, you calculate the interval within the filter function. I'm pretty sure that there's a more elegant solution using only RxJS functions but since you already used state in the filter function, I've modified it a little.
The snippet now shows a proper output. I think it solves your problem but it's not a good way of using RxJS (as I said we're storing state in the filter function).
(function (document) {
var textarea = document.querySelector("#input");
var keyUpStream = Rx.DOM.keyup(textarea);
var keyDownStream = Rx.DOM.keydown(textarea);
var keyStrokeStream = Rx.Observable.merge(keyDownStream, keyUpStream);
var keystroke = keyStrokeStream.filter((function() {
var keysPressed = {};
return function(e) {
var key = e.which;
var result;
if (e.type == 'keyup' && keysPressed.hasOwnProperty(key)) {
e.strokeInterval = Date.now() - keysPressed[key];
delete keysPressed[key];
return true;
} else if (e.type == 'keydown') {
if (!keysPressed.hasOwnProperty(key)) {
keysPressed[key] = Date.now();
}
return false;
}
return false;
};
})())
.map(function (evt){
return {
"ts" : Date.now(),
"key": evt.code,
"duration" : evt.strokeInterval
};
})
.subscribe(function (e){
console.log(e);
document.querySelector("#output").textContent += e.key.replace("Key", '');
document.querySelector("#console").textContent += JSON.stringify(e);
});
})(document);
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/4.1.0/rx.all.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs-dom/7.0.3/rx.dom.js"></script>
<h1>KeyStroke</h1>
<textarea id="input" rows="5" cols="50"></textarea>
<div id="output"></div>
<div id="console"></div>
Related
I am developing a small xterm.js application (just getting started), and I am wondering how to get the text from the current line when the user presses enter. Here is the program:
var term = new Terminal();
term.open(document.getElementById('terminal'));
term.prompt = () => {
term.write('\r\n$ ');
};
term.writeln('This is a shell emulator.');
term.prompt();
term.on('key', function(key, ev) {
const printable = !ev.altKey && !ev.altGraphKey && !ev.ctrlKey && !ev.metaKey;
if (ev.keyCode === 13) {
term.prompt();
console.log(curr_line);
var curr_line = ""
} else if (ev.keyCode === 8) {
// Do not delete the prompt
if (term.x > 2) {
curr_line = curr_line.slice(0, -1);
term.write('\b \b');
}
} else if (printable) {
curr_line += ev.key;
console.log(curr_line, ev.key)
term.write(key);
}
});
term.on('paste', function(data) {
term.write(data);
});
Example taken from the xterm.js homepage (and modified)
As you can see, my attempt involves adding to a line of text every time I get a key event (or removing on backspace). However, this does not work because it is inside an asynchronous function.
Does xterm.js ship with another function that allows you to get the current line content, or is there another workaround for it? My Google searches have been to no avail.
Not the most elegant solution, but by moving "curr_line" into the global scope, we can keep it persistent between "on key" events.
var term = new Terminal();
term.open(document.getElementById('terminal'));
term.prompt = () => {
term.write('\r\n$ ');
};
term.writeln('This is a shell emulator.');
term.prompt();
// Move curr_line outside of async scope.
var curr_line = '';
term.on('key', function(key, ev) {
const printable = !ev.altKey && !ev.altGraphKey && !ev.ctrlKey && !ev.metaKey;
if (ev.keyCode === 13) {
term.prompt();
console.log(curr_line);
curr_line = '';
} else if (ev.keyCode === 8) {
// Do not delete the prompt
if (term.x > 2) {
curr_line = curr_line.slice(0, -1);
term.write('\b \b');
}
} else if (printable) {
curr_line += ev.key;
console.log(curr_line, ev.key)
term.write(key);
}
});
term.on('paste', function(data) {
term.write(data);
});
Your question came up in my search for a similar solution, so thank you for submitting it! : )
Situation
browser
Google Chtome 69
html
<textarea id="message" name="message">
// input some messsage
</textarea>
js(jQuery)
$(function () {
$("#message").on("keydown keyup keypress change", function () {
//this part runs correctly
})
$('#message').on('keydown', function (e) {
if ((e.wich && e.wich === 13) || (e.keyCode && e.keyCode === 13)) {
var $textarea = $(this);
var sentence = $textarea.val();
var position = $textarea.selectionStart;
var length = sentence.length;
var before = sentence.substr(0, position);
var after = sentence.substr(position, length);
sentence = before + "\n" + after;
}
});
});
When I input something in the #message textarea and push Enter key in the area, nothing would happen. According to Chrome developer tool, selectionStart method seems to return Undefined.
Needs
Enter key has been made disabled in this form page in order to avoid submitting the data mitakenly.
js
function fnCancelEnter()
{
if (gCssUA.indexOf("WIN") != -1 && gCssUA.indexOf("MSIE") != -1) {
if (window.event.keyCode == 13)
{
return false;
}
}
return true;
}
However, in this textarea, I want to enable user to add a break line by pressing Enter key.
I'm sorry but I don't have much knowledge about jQuery and javascript. Please tell me how to do.
Please replace $textarea.selectionStart by $textarea.get(0).selectionStart. Then try again.
I'm using the following code to detect multiple keys on a keypress event:
var down = [];
$(document).keydown(function (e) {
down[e.keyCode] = true;
}).keyup(function (e) {
if (down[17] && down[32]) {
// Do something
}
down[e.keyCode] = false;
});
However, this hotkey (CTRL + SPACE) is meant to be used while an input field has focus. So whenever I press the key combination, it also adds a space to the input field.
How can I prevent this from happening? I've looked at ways to disable spaces in input (like this), but I can't figure out how to make it work inside my keypress event only.
You may try this. I hope it helps.
var down = [];
$(document).keydown(function (e) {
down[e.keyCode] = true;
}).keypress(function (e) {
if (down[17] && down[32]) {
var $sampleTextBox = $("input#sampleTextBox");
$sampleTextBox.val($sampleTextBox.val().replace(/\s/g, ''));
alert($sampleTextBox.val().length)
alert("Ctrl + Space Pressed!");
}
down[e.keyCode] = false;
}).keyup(function (e) {
if (down[17] && down[32]) {
var $sampleTextBox = $("input#sampleTextBox");
$sampleTextBox.val($sampleTextBox.val().replace(/\s/g, ''));
alert($sampleTextBox.val().length)
alert("Ctrl + Space Pressed!");
}
down[e.keyCode] = false;
});
--
Thanks,
SuperCoder
I ended up using a different approach, as MelanciaUK suggested.
On the keyup event, it removes the last character in the input field.
var down = [];
$(document).keydown(function (e) {
down[e.keyCode] = true;
}).keyup(function (e) {
if (down[17] && down[32]) {
// Do something
input = $(':focus');
input.val(function (index, value) {
return value.substr(0, value.length - 1);
});
}
down[e.keyCode] = false;
});
While it doesn't prevent the space from being added, it removes it immediately.
I am stuck on problem where I try to utilies the addEventListener.
I did try to find solutions on the web but I think my knowledge is to limited to pick the suitable answer.
What I tried is to invoke a function "addFile()" when a key is pressed in this example enter(13) unfortunatly nothing happens. I could add the onkeypress attribute to the input "add-file" with a slightly edited addFileOnKeyEvent(event) but I'm trying to understand what is wrong with my eventListener.
I hope you could follow my explanation, as this is my first question. :)
function addFile() {
var x = document.getElementById("add-file").value;
x = x + ".xml";
var lbl = document.createElement("label");
var node = document.createTextNode(x);
lbl.appendChild(node);
var element = document.getElementById("appendable");
element.appendChild(lbl);
}
function addFileOnKeyEvent(event) {
var evt = event.keyCode;
var textField = document.getElementById("add-file").addEventListener("keypress", function() {
if (evt == 13) {
addFile();
}
});
}
<label>Dateien</label>
<input id="add-file" type="text" onclick="this.select();">
<button type="submit" onclick="addFile()">Hinzufügen</button>
<div class="data-display">
<span id="appendable"></span>
</div>
At first, addFileOnKeyEvent() is never called before anywhere. So you must call it when you try to add file. Or you must bind the event to the text field by default.
Also need not pass event object to addFileOnKeyEvent(). The event must be captured in the addEventListener callback function.
function addFile() {
var x = document.getElementById("add-file").value;
x = x + ".xml";
var lbl = document.createElement("label");
var node = document.createTextNode(x);
lbl.appendChild(node);
var element = document.getElementById("appendable");
element.appendChild(lbl);
}
function addFileOnKeyEvent() {
document.getElementById("add-file").addEventListener("keypress", function(event) {
var evt = event.keyCode;
if (evt == 13) {
addFile();
}
});
}
// call the function here
addFileOnKeyEvent();
// else just put the event handler part alone. The function is unnecessary here.
<label>Dateien</label>
<input id="add-file" type="text" onclick="this.select();">
<button type="submit" onclick="addFile()">Hinzufügen</button>
<div class="data-display">
<span id="appendable"></span>
</div>
That's not how events work. Try this...
document.getElementById("add-file").addEventListener(
"keypress",
function(event) {
if (event.keyCode == 13) {
addFile();
}
});
Instead of...
function addFileOnKeyEvent(event) {
var evt = event.keyCode;
var textField = document.getElementById("add-file").addEventListener("keypress", function() {
if (evt == 13) {
addFile();
}
});
}
I've been trying to program a pingpong game without jQuery (challenge from Software Design teacher), and am planning on using onkeypress to move the paddles. However, I'm not sure how to attach a specific key to the function specified in the event handler.
It's not terribly relevant, but here's my code:
HTML:
<div id="Paddle1" class="paddle" onkeypress="PaddleMovement1(event)"></div>
<div id="Paddle2" class="paddle" onkeypress="PaddleMovement3(event)"></div>
JavaScript:
var PaddleMovement1 = function(){
document.getElementById('Paddle1Up').style.animationPlayState="running";
setTimeout(Paddle1Stop1, 25)
var Paddle1Stop1 = function(){
document.getElementById('Paddle1Up').style.animationPlayState="paused";
};
};
var PaddleMovement2 = function(){
document.getElementById('Paddle1Down').style.animationPlayState="running";
setTimeout(Paddle1Stop2, 25)
var Paddle1Stop2 = function(){
document.getElementById('Paddle1Down').style.animationPlayState="paused";
};
};
var PaddleMovement3 = function(){
document.getElementById('Paddle2Up').style.animationPlayState="running";
setTimeout(Paddle2Stop1, 25)
var Paddle2Stop1 = function(){
document.getElementById('Paddle2Up').style.animationPlayState="paused";
};
};
var PaddleMovement4 = function(){
document.getElementById('Paddle2Down').style.animationPlayState="running";
setTimeout(Paddle2Stop2, 25)
var Paddle2Stop2 = function(){
document.getElementById('Paddle2Down').style.animationPlayState="paused";
};
};
Finally, the complete thing can be found in this jsfiddle:
http://jsfiddle.net/2RfzF/2/
keypress is only fired for keypresses that result in typeable characters, not other keys. To detect other keys, use keydown and keyup. This should be fairly clear from the specification:
A user agent must dispatch this event when a key is pressed down, if and only if that key normally produces a character value.
This page is a handy guide to the madness that is keyboard events in JavaScript across browsers...
Separately, for your purposes I'd probably trap the events on document rather than on a specific element (keydown and keyup bubble, so that works).
For example:
(function() {
if (document.addEventListener) {
document.addEventListener("keydown", keyDownHandler, false);
document.addEventListener("keydown", keyUpHandler, false);
}
else if (document.attachEvent) {
document.attachEvent("onkeydown", function() {
keyDownHandler(window.event);
});
document.attachEvent("onkeydown", function() {
keyUpHandler(window.event);
});
}
else {
// If you want to support TRULY antiquated browsers
document.onkeydown = function(event) {
keyDownHandler(event || window.event);
};
document.onkeyup = function(event) {
keyUpHandler(event || window.event);
};
}
function keyDownHandler(e) {
var key = e.which || e.keyCode;
display("keydown: " + key);
}
function keyDownHandler(e) {
var key = e.which || e.keyCode;
display("keyup: " + key);
}
function display(msg) {
var p = document.createElement('p');
p.innerHTML = String(msg);
document.body.appendChild(p);
}
})();
Live Copy | Source