I'm looking for a way to track the text selection on a web page. I need some code to be executed whenever there is a change in selection. I have successfully done this on all major desktop browsers, but the same does not seem to be working on Firefox for Android.
I tried three approaches (none of them worked!):
Tried catching the "mouseup" event and checking if there is a
non-empty text selection. Problem is "mouseup" isn't getting
triggered if a selection was made during the mousedown-move-up
sequence!
Tried doing the same with the "touchend" event - Same result.
Tried catching the "selectionchange" event. I saw that it isn’t
triggered when the selection changes as it needs the config key
"dom.select_events.enabled" to be set. This is false by default and I obviously can't ask my visitors to tweak browser settings :-)
Also, as expected, the first two events don't get triggered if the selection is extended or reduced by dragging the selection start/end markers.
The only solution I can think of now is a periodic poller (using setInterval) that checks if there is a text selection. This is definitely unclean and anti-performance.
Any alternatives and/or advice will be very helpful.
1) Native Text Selection Events
Currently, polling seems to be the only work-around.
However, the selectionchange event is currently experimentally implemented in Firefox for Android Nightly. It can be enabled by setting the dom.select_events.enabled flag to true (defaults to false).
In Nightly builds, this flag already defaults to true, so there is a chance you can use it normally in a couple of months.
2) Find events that work
(UNTESTED!!)
Even if onselectstart cannot be used productively in Firefox for Android yet, the only viable easy solution is polling.
To improve performance and reduce costs, polling can be started on the window blur event. Because whenever the user is making a selection, the focus should be set off the viewport (untested though).
window.addEventListener('blur', function(){
// start polling for text selections
});
When the focus is given back, polling can be stopped.
window.addEventListener('focus', function(){
// stop polling
});
To check if the browser actually supports text selection events, you can use this function:
var browserSupportsTextSelectionEvents = function(){
return !!('onselectstart' in document.body);
}
3) Hacking
A third idea is to disable text selection for mobile Firefox users via CSS (-moz-user-select: none), implement a custom text selection function based on touch start and end positions and pushing the native selection range back to the browser via HTMLElement.setSelectionRange().
Related
Note: this question is probably too specialized. The solution (if I ever find one) is unlikely to help anyone but myself. Nonetheless, I believe the workaround described below to apply to several borderline Chrome/jQuery focus loss scenarios.
Scenario:
I have an input TEXTAREA to enter some text.
Meanwhile, a timer makes periodical AJAX calls to the server (one per minute).
What happens:
In Firefox, everything is hunky dory and the user can type away to his heart's content.
On Chrome, when the AJAX request fires, the input focus is lost. It goes... nowhere, apparently. window.activeElement returns nothing, and the cursor indeed disappears from the textarea, until the user clicks it again with the mouse.
What I expected:
Well, for the focus to stay there.
Attempts:
One - I have tried setting an event handler on the textarea's [.focusout()][1], only to discover that the event does not fire. It does fire when the user clicks somewhere else, but that doesn't help.
Two - I have then tried a less elegant - say rather, brutal - approach:
var hasFocus = document.activeElement;
if (hasFocus) {
var focusKeeper = window.setInterval(function(){
hasFocus.focus(); // JUST. STAY. THERE.
$(hasFocus).css("background-color", "red");
}, 10);
}
The field goes red, so the handler is firing at least. Except that the focus does not come back. It's just as if Chrome isn't even trying.
Again, everything works as expected in Firefox. I'll try next on Safari to confirm whether this is a Webkit-related thing.
Research and more attempts:
I've found several posts on how to overcome focus loss, or how to set the focus in the first place, even on newly created fields (which mine isn't), but my case seems different enough that they either offered no clue, or just plain didn't work. The documentation states that
element.focus();
is necessary and sufficient, yet sufficient quite clearly it is not. Someone has suggested setting focus using a zero-delay timeout; I tried, but this did not seem to help.
Could this be related to the fact that Chrome maybe runs XHR requests in a different process, so that the "focus" is going to the hidden XHR window? (Haven't tried with the --process-per-site commandline switch, it just occurred to me - I will now give it a try).
Could this be a bug? There was something like it, but bug 27868 was related to Flash objects, not TEXTAREAs - that's a completely different animal AFAIK.
The strange thing is that this behaviour (or one amazingly similar) was noted on Firefox and the bug reporter says explicitly, focus should remain on the same input control like in other browsers, so he did not observe it on Chrome.
JSFiddle - not exhibiting the behaviour, thus supplying a clue
I made a fiddle, and... it works. So the issue seems to be more with the function called in the timer, which is a w2ui grid.reload(). I still do not understand why the focus doesn't come back using focus(), as it should.
Acceptable workaround
Inspired by amphetamachine's comment, I've tried combining several of the tricks in the posts above. I've come up with a combination that works... sort of.
The elements needed (whichever I remove, the trick stops working) are:
re-set the focus manually where it was
do this inside a setInterval timer
blur the focus before re-setting it
unset and reset the focus inside a short, but not zero, setTimeout delay.
// Save focus.
hasFocus = document.activeElement;
w2ui.myGrid.reload(function() { // Callback, called after reloading.
// If there was no focus, we just return.
if (hasFocus) {
// We DON'T do anything directly, but use setTimeout.
window.setTimeout(
function() {
// And before setting the focus, we truly remove it.
hasFocus.blur();
hasFocus.focus();
}, 5); // A timeout of 0 does not work.
}
});
The "con" of this solution is that the cursor visually "shivers", and any key that was pressed during the second part of the grid.reload(), after the unknown event that loses the Chrome focus, will of course get lost.
Obsolete: just update the libraries.
The strange behaviour disappeared by upgrading w2ui to 1.4.2 on the latest Chrome (actually, I did not try on previous Chromes because I didn't think to keep copies of the previous versions).
I'm trying to detect when a user has finished resizing a text selection on iOS, using Javascript.
While I'm aware of the selectionchange even , if the user uses the native controls I'll get a lot of scroll events, and a few selectionchange.
Now, I have no idea when the user is done, however. If he spends 5 minutes with his finger down, without moving the controls nor changing the selection, I don't get a "mouseup equivalent" event.
EDIT: ...my situation is basically this one: https://developer.apple.com/library/ios/DOCUMENTATION/AppleApplications/Reference/SafariWebContent/Art/events_information_bubble.jpg
Any idea how this could be achieved?
Thanks!
I've found that selectionchange event fires multiple times even on a single word selection (with a tap or a long press), and of course it fires when you drag the selection handles ...
Here is a workaround i have created (not a perfect one) to get only the text selection end event.
you can see it here: End of text selection event?
or in a small post on my blog: http://www.dimshik.com/end-of-text-selection-event-on-ios-workaround/
I have an application that is using KnockoutJS and I'm attempting to write some tests that test a form. If you don't know KnockoutJS, the short story for it is that it provides bindings from my view to my data model. This means that when I type a value in the an input field, my underlying object automatically gets updated with that input fields value. This is done through a change event by default.
The problem I am having is that when my WebDriver test is typing into the field, the change event is not firing so my underlying data model does not have the appropriate values. This causes my form validation to fail when it should not.
I've done everything I could find on the internet to make this work. I've:
sent the tab key
clicked away from the form field
send JavaScript code to fire focus and blur events (validation occurs on blur)
clicked the form field before typing
set waits just incase it was a timing issue
changed KnockoutJS to update input field on afterkeydown
None of these have worked for me.
In addition, I have verified that this is not an event bubbling issue as I removed all other events explicitly, leaving just the KnockoutJS change event.
for the solution i'm looking for is one that works for all browser drivers (... at least the main ones e.g. IE, FF, Chrome, Safari) and does not require the use of jQuery.
How do I solve the problem?
Here is the relevant code I'm using to type values into the field:
// find element
WebElement input = this.element.findElement(By.className("controls"))
.findElement(By.tagName("input"));
// to set focus?
input.click();
// erase any existing value (because clear does not send any events
for (int i = 0; i < input.getAttribute("value").length(); i++) {
input.sendKeys(Keys.BACK_SPACE);
}
// type in value
input.sendKeys(text);
// to fire change & blur? (doesnt fire change)
//input.sendKeys(Keys.TAB);
// to fire change & blur? (doesnt fire change)
driver.findElement(By.tagName("body")).click();
So I have found a way to work around this issue for now, but by far do I believe this is the correct solution. This does break my rule about not using jQuery, however I feel this is okay for me as KnockoutJS requires jQuery be loaded. There's probably a plain ol' JavaScript way of doing this. I have tested this with FireFox, Safari, and PhantomJS. I assume it will work just as well in Chrome. I give no promises for Internet Explorer.
I am NOT going to mark this answer as the correct answer. The correct solution should be through WebDriver browsers firing the proper events. Only when I believe this is not possible through WebDriver will I mark this as the correct answer.
// find element in question
WebElement input = this.element.findElement(By.className("controls"))
.findElement(By.tagName("input"));
// click it to fire focus event
input.click();
// erase any existing value
for (int i = 0; i < input.getAttribute("value").length(); i++) {
input.sendKeys(Keys.BACK_SPACE);
}
// enter in desired text
input.sendKeys(text);
// fire on change event (WebDriver SHOULD DO THIS)
((JavascriptExecutor) driver).executeScript(
"$(arguments[0]).change(); return true;"
, input);
// click away to fire blur event
driver.findElement(By.tagName("body")).click();
So I believe I have found out my problem. I will fully admit this was PEBKAC. I had forgotten that WebDriver has issues if the browser window is not in active focus on the machine (which is still weird to me). When I was debugging the problem, I was using my editor to step through the code. By running the code normally and without removing focus from the browser, the events fire as expected using all three solutions (tab, click-away, and javascript).
I have an example project showing all three methods, however I am a major noob with git and github and am having problems delivering the project. Once i figure that out, I will share the project with you all.
EDIT: Got the example code up on GitHub (https://github.com/loesak/knockout.webdriver.change.event.test). You can either start the project as a webapp in a server and run the test manually or you can run the tests via maven (mvn clean install). I didn't put a whole lot of effort into getting this to work robustly so it is assuming you have Firefox installed and port 0808 8080 is open.
EDIT: Fixed stated port number
There are lots of guides on how to get the selected text in UIWebView but I can't seem to find any on how to detect the change in selection. I understand that I could simply detect taps elsewhere and always get the latest selection, but I'd like to create a standalone view that wraps all this functionality.
I currently have a view that holds the UIWebView instance and handles the parsing and passing the content to the web view. As far as I know, detecting taps would require a custom window implementation (instructed here). I'd like to follow separation of concerns and avoid any custom window implementations if possible.
Any ideas how could I achieve this within a view? I already tried javascript but I guess I was
not that competent with it.
You can use document.addEventListener("selectionchange") which fires whenever the selection changes, and forward those changes to Obj-C through a bridge.
Bridges:
WebViewJavascriptBridge
JavaScriptCore
Example Code:
document.addEventListener("selectionchange", function(){
bridge.send("selectionchange"); // WebViewJavaScriptBridge
bridge.selectionChanged(); // JavaScriptCore
}, false);
If I'm not totally mistaken, there's no way to detect if the selection range changes when using UIWebView and javascript combination. My solution was to poll the selection range instead of notifying of its changes. Currently I have no need for real-time updates, so it will be sufficient only to know if there's a selection or not. The text selection is retrieved on demand and for the selection change, I use the UITapGestureRecognizer.
For the clearing of the selection I had to use a small delay. The selection is not cleared instantly after the tap.
- (void)tap:(id)sender
{
[self updateSelection];
// A simple hack to detect when selection is cleared.
[self performSelector:#selector(updateSelection) withObject:self afterDelay:0.5];
}
One of the most recommended ways to listen for a change of a input text field is to bind that field to a key up event. That works fine in most cases. But there are cases where this is not working. In Firefox for example one has the option, when text is already selected, to delete it by using the context menu. And this doesn't fire a key up event. I haven't found any event that is fired for that text field when doing this.
Any suggestions how I can react on this (in pure Javascript or jQuery)?
See the oninput event, and my write up about it here.
oninput fires for all forms of text input - including cut, paste, undo, redo, clear, drag and drop and spelling corrections. It's a HTML 5 event which isn't supported in Internet Explorer 8 and lower (but it is in the latest IE 9 preview). However, Internet Explorer supports a proprietary event on all DOM objects - onpropertychange. This fires whenever the value of an input element changes.
I didn't notice you'd tagged with jquery — since you did, it's probably worth mentioning that I wrote a plugin to implement the oninput event cross browser. You can find it here.
The best way is to store the value on a focus event and recheck the value on a blur event. Listening to key events fires a lot of usually redundant processes. Most of the time, you are only interrested in a field value when the user is done inputting (or deleting) it.
This works cross browser, though delegating focus/blur can be an issue in some browsers. The easiest way is to apply blur/focus listeners to the element directly.
Only exceptions are implementations like autosuggest/complete and even then you might want to debounce key input so it only fires when the user idles for a few hundred miliseconds.