jQuery keypress handler under certain conditions - javascript

I have a webpage that has lots of 'windows', which aren't wrapped in iframes or anything silly. Each 'window' is its own 'App'.
One of these Apps has a ton DOM elements in it. I want to bind a listener to that App, which is encased in a DIV, and want to capture some hotkey presses such as the left and right arrows.
However, if some element that actually rightfully deserves focus (such as an input inside of this app) gets a left or right keypress, I want to ignore it.
Problems:
The above means I only want to capture keypress on DIVs, basically. I could add a tabindex, however the div isn't going to focus itself if someone clicks within that app, so i'm not sure what to do about that:
<div class='app-wrapper' tabindex='1'><!-- behold ye content --></div>
$('.app-wrapper').on('keypress', function(){
// ... I dont think this will work.
});
I am not going to tell every single valid input / textarea in my app to stop propagating on a keypress.
Is there any good solution to achieving the above without a major hack job?

You could look at e.target in your listener function:
$('.app-wrapper').on('keypress', function(e){
if(e.target.tagName === 'DIV'){ //Or, more jQuery-esque: $(e.target).is('div')
console.log("Wooo, I'm totally a div. Let's do stuff");
}
});
In that way you can ignore keypresses fired inside "valid" elements.

Okay, I managed to figure it out.
My first complaint actually did end up working, and then coupled with $(element)[0].nodeName, it handled the latter complaint:
HTML
<div class='app-wrapper'><!-- behold ye content --></div>
JS
$('.app-wrapper').attr('tabindex', 1);
$('.app-wrapper').on('keypress', function(e){
var nodeName = $(e.target)[0].nodeName;
if (nodeName == 'INPUT' || nodeName == 'TEXTAREA') {
// bug off...
return;
}
// handle key press now.
});

Related

Detecting typing outside of an input in jQuery

Okay, so for our app we have a sidebar with a long list of all the different pages available to that current user. At the top of the sidebar is an input which, using Javascript, only shows options on the sidebar that contains the word(s) they searched for.
Is there any way of achieving this without actually having an input, or using a hidden input?
I want it so when the user types, as long as they aren't focused onto another input/textarea/whatever, it refines the sidebar. I'm not sure if you've seen the new MySpace but that's exactly what I'm going for.
Here is a quick example to point you in the right direction. Every time a key is pressed the event is checked to see if it was activated in an input. If not the character code is logged to the console.
$('body').on('keypress', function(e) {
if ($(e.target).is('input')) {
return;
}
console.log(e.which);
});
Listen to the keypress event on document. Then get the target from the event to determine if it's an input or similar.
$(document).keypress(function(event) {
if ($(event.target).is('input')) {
return;
}
event.preventDefault();
});
Take a look at the jQuery Hotkeys plugin.

HTML browser "text" selection: select whole words (like in iBooks)

I have a structure:
<span class="word">This</span><span class="word">is</span><span class="word">the</span><span class="word">text</span><span>.</span>
I want user to be able to make a selection of whole words (spans, class="words") (in browser on desktop and on iOS as well - like in iBooks).
And how can I style it with css?
What is the right way to do this? (didn't work with selections before)
Thanks.
I know you didn't mention jQuery - but with something this dynamic I think it is highly advisable to use it.
Wrap that whole deal in a div.
<div id="wordSelector"><spans></div>
And then attach a mousedown event to the div. Use a semaphore to ensure that events are handled only during mousedown. Capture mouseover events on the spans until the mouseup event is registered.
Note: These events may need to be attached to document instead of the div to ensure that a mousedown event outside of the div but entering the div is handled, and with mouseup as well in case the mouseup event is outside of the div.
var spansTouched = [];
var mouseDown = 0;
$("#wordSelector").mousedown( function(){
//track spans touched with a semaphore
mouseDown++;
});
$("#wordSelector").mouseup( function(){
mouseDown = 0;
//handle spansTouched and then reset it to []
});
$(".word").mouseover( function(){
if(mouseDown > 0){
spansTouched.push(this);
}
});
Obviously there is room for improvement here, this is just to highlight a possible approach to take using a semaphore and mouse events.
Not sure how you mean that the user should select a word. If the user should "select" a word by clicking on it, you could use a jQuery plugin like TipTip or any other tooltip-plugin. At least tiptip support click.
Not sure if any of the tooltip-plugins support triggering on highlight of text by default, but using a JavaScript to listen for highlighting of text and the trigger the appropriate tooltip to show manually, and hide it again if the text is deselected.
Dave Welsh has written a small piece of jQuery to sniff for text-selection, that could be utilized.

Detect when mouse actually moves, not just when the page moves

I'm having a pretty big problem trying to create navigation on my page. If the mouse enters an element then it selects it, then if you use arrow keys it will select the elements relative to the selected one. However this is an issue when the arrow keys cause the page to scroll, because (depending on the position of the mouse) it will select the appropriate element then instantly select the item the mouse is now over after the page moved (even if you didn't move the mouse).
Does anyone know how to fix this problem? I tried tinkering with it but none of my solutions seemed to work. Any help is appreciated, thank you.
It sounds like you should bind the "select when mouse enters" event on mousemove and unbind said event on mousestop. mousestop does not exist on its own, so you will have to create it somehow or use a plugin (there are at least a few out there such as https://github.com/richardscarrott/jquery-mousestop-event/ ). I think this would be the simplest solution, but your UI seems a little bizarre (you want the arrow key to scroll the page normally and "select" an element that's possibly larger than the scroll size?)
Not sure I completely understand, but you should be able to use a combination of the mousemove and keypress events:
$("#element").mousemove(function(e){
alert("mouse moved");
});
$("#element").keypress(function(e){
if (e.keyCode == 38 || e.keyCode == 40){ //up & down arrow keys
e.preventDefault();
}
});
Try returning false from the keyboard event handler where you check for arrow keys:
element.onkeypress = function(ev) {
// ...
return false;
}
This will prevent the "default behavior" that the browser has for the event, which is to scroll. This works also for links, for example: if you return false from a click event handler for a link, clicking the link will not automatically follow it.

how to prevent mutiple registration of a single event handler in jquery

I have a <div> box displaying search message and some radio button for recent message. There is link option for slide toggle.
When you click on that link it will show some input field and check box and radio button. And at the same time the text of link change to hide option. If you click on that it will hide all the input and checkbox option.
When I refreash the whole page its working properly but when that paticular box or part is refreashing then the box is hiding and imediately hides. If you refresh that portion n number of times the box is going on toggling continously. I think the problem is in registration of event handler. So please give me some solution.
CODE :
$(document).ready(function() {
$(".SideBar-blockheader1").click(function() {
e.preventDefault();
$(".SideBar-blockcontent1").slideToggle("fast");
});
$(".SideBar-optionheader").click(function() {
$(".SideBar-optioncontent").slideToggle("fast");
$(this).text($(this).text() == $("#hideopt").attr('value') ? $("#showopt").attr('value') : $("#hideopt").attr('value'));
});
$(".SideBar-optionheader").text($("#showopt").attr('value'));
$(".SideBar-optioncontent").hide();
});
jQuery has a method, called data() which can be used to extract the attached handler information of an HTML element. You can see if the element has already a click handler, and if it has, then stop re-attaching another handler to it.
if(typeof $('#id').data('events').click == 'object')
{
// A click handler is already attached
}
else
{
// No click handler; Attach one;
}
Although you haven't provided code, I suspect you are using .click(). For jQuery 1.7+ you should be using .on() in delegate mode (the element you bind to is an ancestor, not the clickable element itself) or .delegate() if pre jQ 1.7.
For example:
$('someAncestor').on('click', 'a.specialLink', function(event) {
event.preventDefault();
// the rest of your code for the click handler
})
"someAncestor" is any valid selector that is an ancestor of your link that will not be destroyed, rebuilt, or otherwise manipulated after the DOM is built. It doesn't have to be the direct ancestor.
[updated below after seeing code sample and comments]
There are a few things going on. First, .on() will only work if you're using jQuery 1.7+. Next, .on() can be invoked a few different ways (I wrote about it here: http://gregpettit.ca/2011/jquery-events-its-on/) and you need to be invoking it while delegating an ancestor listener, not simply as a substitute for click. Next, you haven't provided code for your attempted update, only for the original code; it's hard to tell what "didn't work" about trying to use .on(). Moving along, I'm not actually sure what this line is meant to do:
$(this).text($(this).text() == $("#hideopt")...etc...
I can't think of why you would want to try to treat a jQuery object as a variable. I'm not saying the code is wrong, I'm just saying I don't get it. Also, I hate ternary operators... which is part of the reason I don't get it. I much prefer readable conditionals. ;-)
Next, you're calling preventDefault() on "e" but you're not passing "e" into your functions. You might just be getting a JavaScript error, period. (e is undefined)
Then there's attr("value") which I believe should actually work. But why not use .val() if it is indeed a node that HAS a value attribute?
Finally, there is tonnes of room for caching your objects. Every time you see that an object is being used more than once, you can benefit (to varying degrees of performance and legibility) from caching it. I have not updated the code with any caching, though-- that's really something for a whole other "How can I best cache my objects?" question.
Anyhow... to solve the problem, you first have to choose a valid ancestor. This can be any ancestor that isn't destroyed during the process of loading in your new data. This could be anything, but the closest ancestor is the best. It might be a section wrapper, but if you're truly desperate it could be a page wrapper or even the body tag. If you bind to document, you're reproducing the deprecated .live() function, which I definitely recommend against. I have used a placeholder selector, ".section" but you need to figure out what an appropriate ancestor is on your page.
$(document).ready(function()
{
$(".section").on("click", ".SideBar-blockheader1", function(e)
{
e.preventDefault(); // probably not necessary if there's no default click behaviour
$(".SideBar-blockcontent1").slideToggle("fast");
});
$(".section").on("click", ".SideBar-optionheader", function(e)
{
e.preventDefault(); // probably not necessary if there's no default click behaviour
$(".SideBar-optioncontent").slideToggle("fast");
$(this).text($(this).text() == $("#hideopt").val() ?$("#showopt").val() : $("#hideopt").val());
});
$(".SideBar-optionheader").text($("#showopt").val());
$(".SideBar-optioncontent").hide();
});

jQuery find(':focus') not acting as expected

I'm making a widget that slides in and out of view on hover with showTracker and hideTracker functions. I want to prevent it from sliding out of view if it contains a focussed form element though, so I've got this going:
function hideTracker(){
if($('#tracker').find(':focus').length == 0){
$('#tracker').stop().hide();
}
}
Cool. Now it doesn't hide if the mouse happens to move out if there's a field in focus. Unfortunately, that also means that when the field does lose focus (and it's time for the widget to hide again) it just stays there. The unHover event has been and gone.
So I added this:
$('#tracker *').blur(function(){
hideTracker();
});
And that works too - with one little bug that I need help with!
If the focus moves from one element within the tracker to another which is also within #tracker, the tracker hides. I figured that if($('#tracker').find(':focus').length == 0) would return false, given that the next form element has focus, but I guess it doesn't.
Is it the case that .blur() fires before the next element attains focus?
How can I get around this?
How about something like this?
$('body *').focus(function(){
if(!$(this).is('#tracker *') && $('#tracker:visible').length != 0) hideTracker();
});
Yikes. Tricky. Yes, what's happening is:
mousedown: old form element gets the blur event. $(':focus').length == 0.
mouseup: new form element gets the focus event. $newFormElement.is(':focus') == true.
This is an improvement:
$('#tracker').focusout(function() //basically like $('#tracker, #tracker *').blur(), but "this" is always '#tracker'
{
if(!$(this).is('#tracker:hover')) //for some reason plain old :hover doesn't work, at least on the latest OS X Chrome
hideTracker();
});
But it's not perfect. It only really works if you use the mouse. If you use tab to move between fields (or some other possible mechanism) while your mouse is not hovering over #tracker, it won't work.
Here's another attempt. It's a bit...hackier. The gist is that, instead of handling the blur event, you handle the focus event of the second thing that's focused. But! What if you click something that can't be focused? Blank space on your page? Then no focus event is fired.
Okay. So the trick is: put a tabindex="0" in your root <html> tag. This means that there is always something that can be focused. So there's no way to focus on nothing (at least, I don't think so).
Then you can do this:
$('*').live('focus', function(e)
{
if(!$.contains($('#tracker')[0], this)) //if the new thing you focused on is not a descendant of #tracker
hideTracker();
e.stopPropagation();
});
Eh? So yeah, that's a certified hack. But it's a tough problem, and that's the best I can come up with at this hour.
Thank you all for your answers. Utilising the .focus() event rather than .blur() was a clever way to look at it. Unfortunately, it does raise a couple of browser problems, and I couldn't get any of the above working very robustly.
In the end I decided to use setTimeout(hideTracker, 100); to allow the focus() event to take place before the count of focussed elements within tracker was evaluated. Not ideal, but it's working well and the delay is fairly imperceptible.
Thanks again.

Categories