How do I detect any change to a textarea? - javascript

I currently have bound my textarea to a couple of events which seems to work. However, the problem is that the events overlap and fire several times, which in turn reduces performance by a factor of too much.
What I want to do is pretty much catch any change to the textarea: clicking, paste, keyup, keydown, right click context menu editing (right click, cut/delete/paste), drag and drop, etc. This has to work cross-browser and at least down to IE8. The events have to fire when you move the caret around in the textarea using arrowkeys or similar (I handle changes based on caret position, among other things).
I can't use any major delays. As soon as you do something with the textarea, the events have to fire and execute whatever code I have there immediately.
I am currently using jQuery to bind the event, but I am fine with a pure javascript solution as long as it works cross browser and does what I want.
Here's the code I currently use:
var deadKeycodes = [16, 17, 18, 19, 20,
27, 33, 34, 35, 36,
38, 40, 44, //37 = left arrow and 39 = right arrow removed, it needs to trigger on those
45, 112, 113, 114, 115,
116, 117, 118, 119, 120,
121, 122, 123, 144, 145];
$(original).bind('propertychange keyup keydown input click', function(e) {
if (!Array.prototype.indexOf || deadKeycodes.indexOf(e.keyCode) == -1) { // prevent execution when pressing a 'dead' key
//do stuff here
}
});
If anything is unclear just ask and I'll clarify it for you :)

I don't think you can really "solve" the problem in the sense of stopping multiple events from firing. Keyup and keydown really are different events and happen at different times. If you want to respond to both (which you probably do, since keying down and keying up will both potentially change the textarea), both events need to be included. However, most of the time they will fire almost simultaneously (and many times in a row), which as you point out can pose a performance problem.
Instead, you should probably consider firing a throttled or debounced callback. A throttled callback only will fire once every n milliseconds (good for functions that might get called too much). A debounced callback will only fire after a stream of events is done; after n milliseconds have elapsed since the last callback.
You can easily accomplish this using underscore's debounce and throttle functions.
Something like:
debouncedFn = _.debounce(function(e) {
if (!Array.prototype.indexOf || deadKeycodes.indexOf(e.keyCode) == -1) { // prevent execution when pressing a 'dead' key
//do stuff here
}
}, 100);
$(original).bind('propertychange keyup keydown input click', debouncedFn);

Your original is overkill. All you need are the input and propertychange events.
2016 Update
The originally linked page has now disappeared. Here's a slightly suboptimal snapshot of it:
http://web.archive.org/web/20140810185102/http://whattheheadsaid.com/2010/09/effectively-detecting-user-input-in-javascript
Here's an answer demonstrating using the input event and falling back to propertychange in IE <= 8:
Catch only keypresses that change input?

To prevent the event overlap, you could store the time of the last call. Before you execute an event's callback, you test if enough time has passed to make sense to fire again.
$(original).bind('propertychange keyup keydown input click', (function () {
var lastCallTime = 0;
return function (e) {
if (Date.now() - lastCallTime < 50) { return; } // Too little time has passed--exit
if (!Array.prototype.indexOf || deadKeycodes.indexOf(e.keyCode) == -1) {
lastCallTime = Date.now();
// your code...
}
};
}()));

This seems to solve it in IE7-9 and Chrome, haven't tested the rest. Only one console.log happens per change regardless of what the change was. If there were no changes, nothing is logged: http://jsfiddle.net/SJN6J/2/
var timer;
$("textarea").on("keydown paste cut", function(){
clearTimeout(timer);
var origvalue = this.value, self = this;
timer = setTimeout(function(){
if ( origvalue !== self.value ) {
console.log("it changed!");
// do stuff because content changed
}
},0);
});
Note: my IE7-8 testing was with IE9 changing browser mode, so you may want to do real IE7-8 testing.

Related

How to ignore all except the last one of a series of quick Javascript events?

One of my script calls a function at some point, due to a 'changeCursor' event (I am using ACE editor). This slows down the movement of the cursor when I press it many times.
I really want this function to be called, but it's fine if it is only called once my cursor stopped moving (i.e. I do not need to see intermediary states).
Is there a standard way to have all but the last event ignored?
The classic way is to use a short timeout:
var cursorTimer;
function changeCursor() {
clearTimeout(cursorTimer);
cursorTimer = setTimeout(function() {
// process the actual cursor change here
}, 500);
}
Your regular code can continue calling changeCursor() every time it changes (just like it does now), but the actual code inside the setTimeout() will only execute when no cursor change events have occurred in the last 500ms. You can adjust that time value as desired.
The only way to know that events have stopped is to wait some short period of time and detect no further movement (which is what this does). It is common to use similar logic with scroll events.
It maybe overkill for this one issue, but check out RxJS: http://reactive-extensions.github.com/RxJS/#What is RxJS?
It adds some pretty powerful methods to "query/manipulate" event streams in JavaScript. https://github.com/Reactive-Extensions/RxJS/wiki/Observable
In this case the "throttle" method is what your after. Here is an example that uses Throttle with the keyup event to create auto-complete of wikipedia matches. https://github.com/Reactive-Extensions/RxJS-Examples/blob/master/autocomplete/autocomplete.js
// Get all distinct key up events from the input and only fire if long enough and distinct
var keyup = Rx.Observable.fromEvent(input, 'keyup').select(function (e) {
return e.target.value; // Project the text from the input
})
.where(function (text) {
return text.length > 2; // Only if the text is longer than 2 characters
})
.throttle(
750 // Pause for 750ms
)
.distinctUntilChanged(); // Only if the value has changed

Overlapping key events

I am working on a little HTML/JavaScript/CSS3 project for fun. I'm basically trying to make a wheel that rolls around in the browser window. To control the wheel I'm using keyup and keydown events for the cursor keys (left and right turn the wheel and up and down roll it forward or backward).
I've got it working pretty well so far, but there are two major glitches. Say I want to roll the wheel forward and without stopping I want to turn it a little to the right, then I would keep the up key pressed and press the right cursor key. When I do this there's a pause in the movement before it registers both events and keeps rolling.
That's one of the problems, the main problem is that, once I've performed the previous action and then wheel is at a desirable angle, if I let go of the right cursor key the browser registers both keys as released and the wheel comes to a stand still. Here is a jsFiddle of what it looks like: http://jsfiddle.net/UKqwu/1/. I know the code is a mess but it's a work in progress/learning experience and I've only been programming for a month or so.
Anyways thanks for any help. It only works in Chrome at the moment as far is I know. Haven't really been bothered fixing compatibility issues at this stage.
So, what is happening is essentially a limitation built in by your operating system, but there is a simple work-around. First I'll explain the limitation, and then the work-around.
If you were to (in a text box) hold down the "j" button, first one "j" would appear, and then after a short delay many "j"s would appear "jjjjjjjjjjjjjjjjjjjj"
This is the same problem your experiencing. The event fires once, waits for a moment, and then fires many more times.
The solution, however is simple. Instead of having your wheel move when the events are fired... have it update constantly, and separately keep track of what keys are up or down.
The Key Handler would look something like this...
function KeyHandler() {
this.left = false;
this.right= false;
...
function onKeyDown(e) {
if (e.keyCode == 37) {
this.left = true;
}
...
}
function onKeyUp(e) {
if (e.keyCode == 37) {
this.left = false;
}
...
}
}
(you'd attach the key handler to the body or whatever element you wish)
Your wheel would have an update function that looked like...
wheel.update = function() {
// the wheel is turning left
if (wheel.keyhandler.left) {
// make the appropriate adjustments
}
}
and then the game would have something like this...
wheel = new Wheel;
setInterval(function() {
wheel.update();
},100);
That way your wheel will always be updating based on the current state of the keys, and you wont have to rely on the limitations of events that are firing. :)
Here's a snippet of a simple game I once wrote
//key codes
var KEY_LEFT = 37;
var KEY_RIGHT = 39;
var KEY_A = 65;
var KEY_D = 68;
var KEY_SPACE = 32;
var keys = {};
function onKeyDown(e)
{
e = e || window.event;
var key = window.event.keyCode || e.which; // ie
keys[key] = true;
}
function onKeyUp(e)
{
var key = window.event.keyCode || e.which; // ie
delete keys[key];
}
This keeps track of all current key states. Then your game "tick" is on a setTimeout() rather than moving on key events and checks for appropriate keys.
function gameTick()
{
// update paddle angles
if (keys[KEY_LEFT])
{
// move left
}
if (keys[KEY_RIGHT])
{
// move right
}
}
Here's the game;
the problem you are facing is because your code is meant to detect single key press while your game needs 2 key press detection.
put this line in loop over size of e. that will set all pressed keys as 1. currently its detecting only one key press and processing for one at a time.
keys[e.keyCode] = 1;
check out this thread and u might get what you need. though its jquery, it might help conceptually. m also working on this with js... if anything new comes up will share...
Detect multiple keys on single keypress event in jQuery
your code and concept is cool if you are really one month old in programming.

hashchange polyfill for IE7 that can track more than one event per tick

I need to fire a hash change event more than once per tick.
My current polyfill looks like this.
//If the hashchange event is missing implement it
hashchangeSupported || (function() {
//save the current hash for reference next cycle
var lastHash = location.hash;
//check the hash for changes every tick
setInterval(function() {
//if the hash is different since the last tick then
// fire a hash change event.
if(lastHash !== location.hash) {
trigger('hashchange', window);
lastHash = location.hash;
}
}, 1);
});
The problem is that if the hash is updated more than once per tick it still only fires a single hash change event. I'm looking for a way to check for changes more than once per tick.
I know this is asking a lot and I doubt its possible without getters and setters but I'm aware there are better programmers that I on Stack Overflow and I want some second opinions.
It isn't possible without proxies.

Can I query/detect the double click speed for a webpage user?

It's OS/user dependant. Not the browser, not the website, but the OS decides how fast and slow a double click must be.
I'd like to use that number in my app. Is there a way to get that number with JS?
Simple question. Might not be possible.
Thanks
Simple answer: no, sorry.
The best you could do would be something like this (example uses jQuery simply because it was quicker to write, the principle holds if jQuery is unavailable. Also note that this could well be simplified, this is just what came to mind first):
var timer,
num = 0;
$("#example").click(function() {
/*This condition is required because 2 click events are fired for each
dblclick but we only want to record the time of the first click*/
if(num % 2 === 0) {
timer = (new Date()).getTime();
}
num++;
}).dblclick(function() {
var time2 = (new Date()).getTime(),
dblClickTime = time2 - timer;
});
Unfortunately, that's probably not very helpful. You may be able to record the dblClickTime values and check for the longest, but that still is very unlikely to be the actual value you're after. That sort of thing is just not available through JavaScript.
Answer 2021 - as far as I know - still not. There is a reason: we should not care.
In principle dblclick is somehow obsolete …
We have the not well known detail property. Maybe because of the name.
From MDN:
The MouseEvent object passed into the event handler for click has its detail property set to the number of times the target was clicked. In other words, detail will be 2 for a double-click, 3 for triple-click, and so forth. This counter resets after a short interval without any clicks occurring; the specifics of how long that interval is may vary from browser to browser and across platforms. The interval is also likely to be affected by user preferences; for example, accessibility options may extend this interval to make it easier to perform multiple clicks with adaptive interfaces.
With detail ie. click_count it is possible to stop propagation of CLICK when detail != 1
So pseudcode:
if evt.detail==1
do_click()
if evt.detail==2
do_dblclick()
...
if evt.detail!=1
evt.stopPropagation()
If someone really needs to distinguish between click, double-click, triple-click, … like an 'XOR', they should really rethink the design.
The DblClickTime can be very long, that means the app feels like not responding, if the user just wants the click-action.
The other problem is, that it is possible, that users intention is a double-click, but is to slow - then there are two click-actions, they should not be to different to dblclick.
I'd like to use that number in my app. Is there a way to get that number with JS?
Definitely not - stuff like this is outside JavaScript's scope.
You may be able to find out values that work for a double click by asking the user to double-click, listen to the click events and see whether the dblclick event is fired - I'm nnot sure whether event handling works that way, though. But even if that works, it is still a long way from actually finding out the actual value.
This is my 2015 solution, would like to see a pure js version tho.
var start;
var click = null;
$(document).click(function() {
var now = performance.now();
start = click ? click : now;
click = now;
}).dblclick(function() {
alert(performance.now()-start)
});
EDIT
Pure JS
var start;
var click = null;
var getStart = function() {
var now = performance.now();
start = click ? click : now;
click = now;
}
var getStop = function() {
alert(performance.now()-start)
}
if (window.addEventListener) {
window.addEventListener('click', getStart , false);
} else {
window.attachEvent('onclick', function() {
return(getStart.call(window, window.event));
});
}
if (window.addEventListener) {
window.addEventListener('dblclick', getStop , false);
} else {
window.attachEvent('ondblclick', function() {
return(getStop.call(window, window.event));
});
}
Adding on to James Allardice's answer:
Depending on your implementation and where you are looking for double clicks you may want to also check the users mouse location (or I guess tap location). This is to avoid a double click firing when the user is clicking things on different parts of your page (again depends on your event listener implementation -- if it is just on one button for example this probably isn't an issue).
When a click event fires the event listener in my example below has two variables e.clientX and e.clientY. This will give you the location of the mouse. You might want to check to see if the user has moved their mouse significantly since the first click (adapt accordingly to your code).
document.addEventListener("click", function(e){ console.log("Mouse X: " + e.clientX + ": Mouse Y: " + e.clientY); });
You don't want to have it be too tight or else a user may never be able to fire a double click, and you don't want it to be too loose so that double clicks fire seemingly randomly for the user. Maybe start with a 25px or so box around the first click (again this depends on your application). This is something you can test and adjust based on your user interface.
I am assuming you don't have jQuery or aren't using it, because I believe jQuery might already do this calculation to fire dblclick

Smooth keypress event handeling in Javascript?

I know its possible but I cant seem to figure it out. In its basic form I have a canvas circle moving 5 pixels every key event however this seems to be rather slow at firing and really jumpy, plus it stalls when changing key or changing from rapid key press to holding down.
So can someone enlighten me of a way ofwhich this could work without it stuttering?
Thanks.
What you want to do is set variables on keydown and keyup to true or false that you check every tick. Something like this:
var tickRate = 30,
keyDown = {},
keyMap = {
37: 'left',
38: 'up',
39: 'right',
40: 'down'
};
$('body').keydown(function(e){ keyDown[keyMap[e.which]] = true; });
$('body').keyup(function(e){ keyDown[keyMap[e.which]] = false; });
var tick = function() {
if (keyDown['up']) {
// up code
} else if (keyDown['down']) {
// down code
} else if (keyDown['left']) {
// left code
} else if (keyDown['right']) {
// right code
}
// other code
setTimeout(tick, tickRate);
};
tick();
The main problem you have here is that you are at the mercy of the "key repeat" functionality of the Operating System. Usually if you hold a key down, a keyPress event will be generated once, then (after a delay) on a repeat. But if you want to control this delay/repeat, then that's not good enough.
Instead I'd have your own polling function, set to run every few milliseconds, checking whether each key you're interested in is down or up. Then you know that, for every poll whilst the key is down, some event can be fired regardless of the OS's settings.
You can then apply your own algorithms and multipliers to smooth the movement out.

Categories