Trigger onBlur on multiple elements as a single unit - javascript

I have two inputs that together form a single semantic unit (think an hours and minutes input together forming a time input). If both inputs lose focus I want to call some Javascript function, but if the user merely jumps between those two, I don't want to trigger anything.
I've tried wrapping these two inputs in a div and adding an onBlur to the div, but it never triggers.
Next I tried adding onBlurs to both inputs and having them check the other's :focus attribute through jQuery, but it seems that when the onBlur triggers the next element hasn't received focus yet.
Any suggestions on how to achieve this?
EDIT: Someone questioned the purpose of this. I'd like to update a few other fields based on the values contained by both these inputs, but ideally I don't want to update the other fields if the user is still in the process of updating the second input (for instance if the user tabs from first to second input).

I made a working example here:
https://jsfiddle.net/bs38V/5/
It uses this:
$('#t1, #t2').blur(function(){
setTimeout(function(){
if(!$('#t1, #t2').is(':focus')){
alert('all good');
}
},10);
});

var focus = 0;
$(inputs).focus(function() { focus++ });
$(inputs).blur(function() {
focus--;
setTimeout(function() {
if (!focus) {
// both lost focus
}
}, 50);
});

An alternative approach is to check the relatedTarget of the blur event. As stated in the MDN documentation this will be the element which is receiving the focus (if there is one). You can handle the blur event and check if the focus has now been put in your other input. I used a data- attribute to identify them, but you could equally well use the id or some other information if it fits your situation better.
My code is from an angular project I've worked on, but the principle should translate to vanilla JS/other frameworks.
<input id="t1" data-customProperty="true" (blur)="onBlur($event)">
<input id="t2" data-customProperty="true" (blur)="onBlur($event)">
onBlur(e: FocusEvent){
const semanticUnitStillHasFocus = (val.relatedTarget as any)?.dataset?.customProperty === "true";
// Do whatever you like with this knowledge
}

What is the purpose of this behavior ?
The blur event triggers when a field looses focus, and only one field can gain focus at a time.
What you could do, in case of validation for instance, is to apply the same function on blur for both the fields and check the values of the fields altogether.
Without a context, it is difficult to help you more.
d.

Related

Best way to blur all inputs on page?

I am trying to blur all the inputs on the page, to show error styling.
I am trying to follow this blur documentation.
My attempt is to grab all the inputs and then apply blur...
document.querySelectorAll('input').blur();
But I simply get the error, blur is not a function.
What is the correct approach?
First, there is no point un bluring more than one input element : only one can have focus at a time, so only one can "blur" at a time.
But for the sake of the example, here is how it could be achieved:
document.querySelectorAll('input') returns an array-like object, so:
Array.from(document.querySelectorAll('input')).forEach(el => el.blur())

How can I detect when a user has selected an option from a datalist?

I want to be able to detect when a user has selected an option from the dropdown box on an input element linked to a datalist so that I may use that action to trigger another.
For some reason, clicking on the datalist dropdown box does not fire a click event. So using that is totally out of the question.
Using the change event doesn't work for two reasons. Chrome's change event is pretty nice, it will fire when someone selects an option, however, in Firefox the event is not fired until the input has lost focus. You may think that you could at least use the change event for Chrome, but alas, there is another major issue—if you type in the full text of what you're selecting and then click an option, the change event is never fired, because nothing changed :(
Finally, we come to the very ugly but seemingly only option: comparing the current value of the input element to the options in the datalist with an input event. This has many issues.
It doesn't work if one option is a prefix of another (e.g. foo, foobar). The code will run prematurely if the user types foo before selecting foobar.
Assuming none of your options are prefixes of each other, your code will still run if the user types out the whole text of something in the datalist, as opposed to waiting for them to select it.
It has to loop through every single option in the datalist every time you
type a character.
You could try to mitigate these issues using setTimeout to detect when someone has stopped typing, but that still wouldn't be able to achieve the desired behaviour.
I can't believe there's not a simple event that deals with this, but I know there must be a way.
Indeed, there is a way! Our hero is the Invisible Separator (U+2063). It is a character that adds no visible effect to a string, it merely exists. Copy and paste this f⁣oo and check its length. You will see it equals 4!
We can append this character to the end of every option in the datalist. Then we can set up a listener for an input event, and check if the value of the input element ends in U+2063. If it does, you know the user has selected an option. You then should change the value of the input element to get rid of the U+2063 (unless the selection is going to trigger something that resets the value anyway).
This overcomes every issue you've stated because the user can't actually type in anything that matches something in the datalist, an option must be selected for anything to happen.
document.querySelector('input').addEventListener('input', function(){
if (this.value.slice(-1) === '\u2063') {
this.value = this.value.slice(0, -1);
let div = document.querySelector('div');
div.textContent = `you selected: ${this.value}`
div.classList.toggle('red'); //so you can see when this is called even if the text doesn't change
}
});
body {
display: flex;
}
div {
margin-left: 1em;
}
.red {
color: red;
}
<input list='test'>
<datalist id='test'>
<option>foo⁣</option>
<option>bar⁣</option>
<option>foobar⁣</option>
</datalist>
<div></div>

jQuery global focus hook

In my focus manager I need to store some info on the last focus and blur. I need to delegate this hook to body, but if I use * as a filter I will receive the focus event for every parent item of the actual thing getting focus.
Realistically, I could make a filter to accept something like a, input, button ... etc, but also I need to refine it to a:not([tabindex]=-1), *[tabindex!=-1] etc.
But this gets complicated when one considers disabled controls. Is there a jQuery selector for :focusable, or how do I work around this? I could debounce my function, but even that is iffy.
EDIT:
Alright, my bad, I think what I am looking for here is :tabbable.
you can check focus by using: $("yourselector").is( ":focus" )
If you want to stop bubbling up to the dom just return false in your function after doing your stuff.enter code here

How to initialize form additions for jquery methods?

I've got a form where I'm trying to do the sort of thing you often see with tags: there's a textfield for the first tag, and, if you put something into it, a new and similar textfield appears to receive another tag. And so on. I've gotten the basics of this working by setting up a jQuery .blur() handler for the textfield: after the value is entered and the user leaves the field, the handler runs and inserts the new field into the form. The handler is pretty vanilla, something like:
$('input.the_field_class').blur(function () { ... });
where .the_field_class identifies the input field(s) that collect the values.
My problem is that, while the new textfield is happily added to the form after the user enters the first value, the blur handler doesn't fire when the user enters something into the newly-added field and then leaves it. The first field continues to work properly, but the second one never works. FWIW, I've watched for and avoided any id and name clashes between the initial and added fields. I had thought that jQuery would pick up the added textfield, which has the same class markings as the first one, and handle it like the original one, but maybe I'm wrong -- do I need to poke the page or some part of it with some sort of jQuery initialization thing? Thanks!
Without seeing your code in more of its context, it's hard to know for sure, but my best guess is that you're attaching a handler to the first field, but there is no code that gets called to attach it to the new field. If that's the case, you have a few options, two of which are:
1) In your blur() handler, include code to attach the blur handler to the newly created field.
2) Use jQuery's event delegation to attach a handler to the field container, and listen for blur events on any field in the container:
<div class="tag-container">
<input class="the_field_class" /> <!-- initial tag field -->
</div>
<script>
var $tagContainer = $('.tag-container');
var createNewField = function() {
$tagContainer.append($('<input class="the_field_class" />');
};
$tagContainer.on('blur', 'input.the_field_class', createNewField());
</script>
Which is better will depend on your use case, but I'd guess that the 2nd option will be better for you, since you're unlikely to be dealing with tons of blur events coming from the container.

how to create a new text field below when pressing a key?

last.fm has this nifty feature when you're adding an event. you have the Artists field, and when you start typing in it, another text field appears beneath it. when you start typing in that next field, another new field appears beneath and so on. I've been trying to figure out how to mimic this functionality using jquery but I can make it work on the first, stationary, field only. any ideas?
edit:
actually, nope, it's not working even for the stationary one, since it adds the field on EVERY key press
edit2:
alrighty, so some fine folks have already solved my adding issue, now, how would one go about adding the field only after the first time a key is pressed.
You are probably only binding your handler to the first static input, and not the dynamically created ones. Use .live() to do your event binding. That will bind the event to future elements that match the selector.
To make sure you only add one new one, make sure you only add it when typing into the last textbox. Check $(this).closest(".container").next(".container").length to make sure there isn't already a new textbox.
$("input.myClass").live("keyup", function (e) {
var $container = $(this).closest(".container");
if (this.value && !$container.next(".container").length) {
var $newContainer $("<div>").insertAfter($container).addClass("container");
...
}
});
Live is the right idea but closest is overkill: jsFiddle
$("input").live("keyup", function (){
if ( !$(this).next('input').length ) $(this).after('<input type="text"></input>');
});

Categories

Resources