Finding the last focused element - javascript

I'm looking to determine which element had the last focus in a series of inputs, that are added dynamically by the user. This code can only get the inputs that are available on page load:
$('input.item').focus(function(){
$(this).siblings('ul').slideDown();
});
And this code sees all elements that have ever had focus:
$('input.item').live('focus', function(){
$(this).siblings('ul').slideDown();
});
The HTML structure is this:
<ul>
<li><input class="item" name="goals[]">
<ul>
<li>long list here</li>
<li>long list here</li>
<li>long list here</li>
</ul></li>
</ul>
Add another
On page load, a single input loads. Then with each add another, a new copy of the top unordered list's contents are made and appended, and the new input gets focus. When each gets focus, I'd like to show the list beneath it. But I don't seem to be able to "watch for the most recently focused element, which exists now or in the future."
To clarify: I'm not looking for the last occurrence of an element in the DOM tree. I'm looking to find the element that currently has focus, even if said element is not present upon original page load.
this image http://droplr.com/174l8H+
So in the above image, if I were to focus on the second element, the list of words should appear under the second element. My focus is currently on the last element, so the words are displayed there.
Do I have some sort of fundamental assumption wrong?

document.activeElement is what you want. It's part of HTML5 and supported by all modern browsers, including IE.

According to the documentation (see 'caveats'), .live() in jQuery 1.4.1 supports focus, mapping to focusin. I'd suggest creating an element in common scope to hold the last focused element. Perhaps like so:
var lastFocused;
$('input.item').live('focusin', function(){
lastFocused = $(this);
});

How to determine which html page element has focus?
Has your answer (using document.activeElement gets you there for many browsers, but to make the ones that don't support it work you'll want to add the Javascript from that question's answer).

In the end, it was an error in code elsewhere that was confusing the DOM about who had focus.
The line was this: $('#item-add').find('input.item').focus();
And it needed to be this: $('#item-add:last').find('input.item').focus();
Because the added item is always last in the list.
Much has been learned, and I've tried to start and upvote accordingly. Particularly of note to the question at large:
.live events are not cumulative. Only code cruft is.
Set a variable outside of your function and update it within your function so you can access it in other functions as well.
jsfiddle.net and jsbin.com are awesome.
Holy HTML5, document.activeElement is good to know.
Thanks so much, SO, for all your help on this issue.

You can use the :last selector to only handle the event on the last <input> in the document (last at the time that the event was fired)
$('input.item:last').live('focus', function(){
$(this).siblings('ul').slideDown();
});

Related

Using .jquery .on() without an event

I have input elements that get appended to my HTML document that I need to get the value of. I understand that .on() should be used to get appended elements, but .on() expects an event to activate it, and I don't need an event.
Below is the code. #save_design is on a different part of the page. #fields_cnt is the parent that holds all my appended forms. .reg_field is the element I'm trying to get the value of. The first child works because that form is present when the document is loaded, or in other words, it is not appended. The other children all returns values undefined, or false depending on the input type and the logic I'm applying to it. This leads me to believe that .on() or something close to it should be used.
To clarify, I am trying to get the value of an appended input when #save_design is clicked.
$('#save_design').click( function() {
group_text[form_count] = $('#fields_cnt .field_group:nth-child(' + child + ') .reg_field').val();
I have tried replacing the originating click event as so. It has not worked.
$('body').on('click', '#save_design', function(){
I have tried using .on() without an event or event object. It has also not worked.
$('body').on('', '', function(){
group_text[form_count] = $('#fields_cnt .field_group:nth-child(' + child + ') .reg_field').val();
});
I have found a plugin that should work.
JQuery selecting dynamically generated html without adding event (with live)
This is not ideal, because as stated in the github readme, it is being rewritten. Also, the SO answer is 3 years old now, so it may no longer be the correct answer.
https://github.com/brandonaaron/livequery
I'd like to find a more direct solution. Is there a way to do this natively in jQuery? Is there a way to use .on() without an event, and if so, what's the appropriate syntax?
UPDATE: Clarification: The input fields get appended when an add field button is pressed. Their values change after they are appended. I would prefer not to rerecord the value of an input, every time it is altered.
UPDATE Here is the HTML structure to give a better idea of how I'm defining child.
<div id="fields_cnt">
<div id="field_group_1" class="field_group form-group">
<input placeholder="Field" class="reg_field form-control" />
</div>
<!-- This is appended by jQuery. There may be multiple of these -->
<div id="field_group_2" class="field_group form-group">
<input placeholder="Field" class="reg_field form-control" />
</div>
</div>
I have found a great solution to my problem. It takes a different approach on selecting the element, but ultimately works and results in drier code.
Here, I use each, to grab each instance of the appended element I am trying to select. This returns the desired value.
$('.reg_field').each( function() {
group_text[form_count] = $(this).val();
form_count++;
});
I suspect the issue was my previous selector was unable to use $(this), which seems to work in more cases for appended elements. Another possibility that was brought up in the comments was the use of nth-child in the middle of a selection. I am not sure on these points, and would appreciate a better explanation. Hopefully this will help for someone who faces the same issue. Also, I appreciate the help on getting me to focus on the selection rather than the binding.

Child's event is not fired when parent's HTML is changed

Here's an example http://jsbin.com/ukoqud/3/edit
If you click on a red box, you'll get an alert.
If you click on a link, everything in a blue box will be replaced with just a red box. Link will disappear and if you click on a red box then, you'll get no alert.
Why this happens?
Is it related to innerHTML?
Does it work the same way in all browsers?
Here's one more example http://jsbin.com/ukoqud/1/edit In this one you'll get an alert after clicking on a link. Things happen in a quite similar way, but result is different.
I would like to understand the reason, there's no need to fix my code.
When you call $(".red"), it returns a collection of DOM elements that exist at that moment. So $(".red").click(function...) just binds a handler to the click event on those elements. If you later create new elements with the same class, they weren't in this collection, so they don't have the handler bound to them. jQuery doesn't watch the DOM for changes and update the handlers dynamically -- the bindings are just on the elements you matched at the time you called click().
You either need to bind the handler again after adding the new HTML, or use delegation with .on():
$(".blue").on("click", ".red", function(){
alert('click on a red box detected');
});
This works by binding a handler to $(".blue"), which doesn't change dynamically. The handler checks whether the element you clicked on matches the ".red" selector, so it's able to handle dynamically-added elements without requiring rebinding.
I think the reason why it works in your second example is because the red block isn't inside the blue box to start. When you move it inside, jQuery reuses the same DOM elements, so the bindings go along with it. In the first example, the red box starts out inside the blue box. When you do $('.red').parent().html(...), the first thing it does is empty $('.red').parent() (the blue box), so the original red element is removed from the DOM, and its bindings are lost.
We need to understand how setting html of an element works. Then you will figure out your answer yourself.
Take a look at this bin Updated Bin
When we set HTML of an element, it first removes all the elements inside it.
Those elements are not removed from memory depending upon whether they are garbage collected or not.
If any of the child is having a reference, then that particular child won't be garbage collected.
In your case, we are having a reference to red element so it is still present in memory but not a part of document.
When we say blue.html(red) in my example, red element becomes a part of document again but this time there won't be any handlers on it So your click does not work.
While in your example2,
red element is always a part of document hence no handlers were lost when red element is moved inside blue element.
I hope this will help.
because when u click the link, you delete everything on screen and create everything from a scratch and event binding goes away. so you should use this
$(".blue").on("click", ".red", function(){
alert('');
});
this way, binding is done differently. it doesnt bind it statically

HTML5,draggable and contentEditable not working together

When a draggable attribute is enabled on a parent element(<li>) I cant make contenteditable work on its child element (<a>).
The focus goes on to child element (<a>),but I cant edit it at all.
Please check this sample
http://jsfiddle.net/pavank/4rdpV/11/
EDIT: I can edit content when I disable draggable on <li>
I came across the same problem today, and found a solution [using jQuery]
$('body').delegate('[contenteditable=true]','focus',function(){
$(this).parents('[draggable=true]')
.attr('data-draggableDisabled',1)
.removeAttr('draggable');
$(this).blur(function(){
$(this).parents('[data-draggableDisabled="1"]')
.attr('draggable','true')
.removeAttr('data-draggableDisabled');
});
});
$('body') can be replaced by anything more specific.
If new contenteditable elements are not added in the runtime, one can use bind instead of delegate.
It makes sense that the draggable and contenteditable properties would collide. contenteditable elements, like any text field, will focus on mousedown (not click). draggable elements operate based on mousemove, but so does selecting text in a contenteditable element, so how would the browser determine whether you are trying to drag the element or select text? Since the properties can't coexist on the same element, it appears that you need a javascript solution.
Try adding these two attributes to your anchor tag:
onfocus="this.parentNode.draggable = false;"
onblur="this.parentNode.draggable = true;"
That works for me if I add it to the <a> tags in your jsFiddle. You could also use jQuery if it's more complicated than getting the parentNode.
Note: This is a workaround since I believe the inability for these two functionalities to work together resides in the HTML spec itself (i.e. the not working together thing is intentional since the browser can't determine whether you want to focus or drag on the mousedown event)
I noticed you explicitly set 'no libraries', so I will provide a raw javascript/HTML5 answer
http://jsfiddle.net/4rdpV/26/
This was my crack at it.
First of all, it might be better to include the data in one single localStorage item, rather than scatter it.
storage={
'1.text':'test 1',
'2.text':'test 2'
}
if(localStorage['test']){
storage=JSON.parse(localStorage['test'])
}
this creates that ability, using JSON to convert between object and string. Objects can indeed be nested
I also added (edit) links next to the items, when clicked, these links will transform the items into input elements, so you can edit the text. After hitting enter, it transforms it back and saves the data. At the same time, the list items remain draggable.
After saving, hit F12 in chrome, find the console, and look in the localStorage object, you will see all the data was saved in localStorage['test'] as an Object using JSON.stringify()
I tried my best to design this to be scaleable, and I think I succeeded well enough; you just need to replace the HTML with a container and use a javascript for loop to write out several items, using the iterator of your choice to fill the parameter for edit(). For example:
Say you changed storage to hold "paradigms" of lists, and you have one called "shopping list". And say the storage object looks something like this:
{
"shopping list":{
1:"Milk",
2:"Eggs",
3:"Bread"
}
}
This could render that list out:
for(i in storage['shopping list']){
_item = storage['shopping list'][i];
container.innerHTML+='<li draggable=true><a id="item'+i+'">'+_item+'</a> (edit)</li>'
}
Of course, if you were to edit the structure of the storage object, you would need to edit the functions as well.
The output would look something like this:
Milk (edit)
Eggs (edit)
Bread (edit)
Don't worry about the input elements if that worries you; CSS can easily fix it to look like it didn't just change.
If you don't want the (edit) links to be visible, for example, you can do this in CSS:
a[href="#"]{
display:none;
}
li[draggable="true"]:hover a[href="#"]{
display:inline;
}
Now the edit links will only appear when you hover the mouse over the list item, like this version:
http://jsfiddle.net/4rdpV/27/
I hope this answer helped.
Using html5sortable and newer JQuery events (delegate is deprecated, answer 3 years after initial question), bug still affects Chrome 37. Contenteditable spans and html5sortable seem to play nice in other browsers. I know this is only partially relevant, just keeping documentation on changes I've noticed.
$(document).on('focus', 'li span[contenteditable]', function() {
$(this).parent().parent().sortable('destroy'); // removes sortable from the whole parent UL
});
$(document).on('blur', 'li span[contenteditable]', function() {
$(this).parent().parent().sortable({ connectWith: '.sortable' }); // re-adds sortable to the parent UL
});

Cloning li elements in JQuery

I'm using EasySlider, but unlike any of the examples of this plugin that I've found, I show multiple elements at any one time and the width is 100%. The separate images make up a full, long consecutive image, which is Photoshopped so even the borderline between first and last looks natural. It's configured to be a continuous slide automatically.
In the plugin there is this code:
if(options.continuous){
$("ul", obj).prepend($("ul li:last-child", obj).clone().css("margin-left","-"+ w +"px"));
$("ul", obj).append($("ul li:nth-child(2)", obj).clone());
$("ul", obj).css('width',(s+1)*w);
};
My issue is that only the first element is cloned after the last one, so upon the second rotation of this slider, only the first element is shown, until it gets to the far left, then the other images appear to "pop" in. (similar: [EXAMPLE] if you imagine and all images to the right of the fairground not appearing until the fairground gets to the far left).
Is there any better way to manage this cloning of elements so that ALL the images are cloned? or perhaps someone can think of a better way? I'm new to JQuery
NOTE: I'm trying to create an operation whereby as an element leaves the screen on the left, it is placed back onto the right. Is there a way to ultimately achieve this?
I think you may want to consider a different plugin.
If you change the plugin, updating will require reapplying your patches.
I'd recommend
http://www.gmarwaha.com/jquery/jcarousellite/
or
http://sorgalla.com/jcarousel/
These both support what you are talking about.
I agree with troynt, in this case it would be better to use something that meets your requirements.
I just made a simple "plugin" what only does, what you need, maybe it's useful to you.
http://jsfiddle.net/doktormolle/4c5tt/
You can setup delay and duration and choose to pause on hover.
Given you have a list of items in your scroller like this:
<ul>
<li>...</li>
<li>...</li>
<li>...</li>
<li>...</li>
</ul>
And assuming that every time you advance the scroller, the first-most li moves off screen, you can continuously pluck the first li off the front of the list and append it back to the end on each click of a "next" button or scroll event. If you're using jQuery >= 1.4, you can do this by using the detach() method, which will remove the element from the DOM but keep all its associated data so you can easily reattach it later:
$('ul li:first').detach().appendTo('ul');
Dont know, if I unterstand it the right way, you may try this:
//at first run add a class "init" to the inital li-elements,
//so that later only them will be cloned
if(!$('ul li.init',obj).length)$('ul li',obj).addClass('init');
//prepend clones of all li-elements with init-class and remove their init-class
$("ul", obj).prepend($("ul li.init", obj).clone().removeClass('init').css({}));

jquery set focus on dynamic content?

In jquery I've appended a <li> element to an unordered list.
How do I focus on the newly created <li> ?
If I do the following:
$("ul").append('<li><input type="text" value="Hi!"></li>');
$("li:last").focus(); //doesn't work because new <li> isn't in dom yet
the focus doesn't work, as noted above.
I know jquery 1.4.2 has a live() event handler which allows you load event handlers to dynamically added elements, but I'm not sure what I'm doing wrong:
$(document).ready(function () {
$('li').live('load', function () {
alert("hi!");
$("li:last").focus();
});
});
You can only set the focus to elements which can hold the focus. By default a list item cannot. This is why your first example fails, not because it isn't in the DOM (it is in the DOM, that is what append does)
In general you should use elements designed to hold the focus (i.e. set the focus on the input not the list item). You can also (but this is less backwards compatible and less logical) use HTML5's tabindex (probably setting it to 0).
onload will not work because list items do not load external content.
You can try this, $(YourElement).trigger("focus").
This is an old post I know, but a simple way to solve this issue is to create a text input in your HTML and set its CSS to "display: none;". On the LI's click event, set the focus in this input and listen to its keypress events.
I've done it and it works like a charm.

Categories