I've a statically created table:
<table id="table" style="width:100%">
<tbody id="tbody">
</tbody>
</table>
I've dinamically created some elements:
var tbody = document.getElementById('tbody');
for (var i = 0; i < 8; i++) {
var tr = document.createElement('tr');
for (var j = 0; j < 8; j++) {
var td = document.createElement('td');
td.appendChild( createDiv(i,j)); // div id is = i + ' ' + j
tr.appendChild(td);
}
tbody.appendChild(tr);
}
But now I'm trying to add a callback function for each div, but this is not working. I think that the problem is with the jquery on function but I don't know how to solve it.
for(var x=0;x<8;x++){
for(var y=0;y<8;y++){
$(document.body).on('mousedown', '#' + x + ' ' + y, function(){
var audio = document.getElementById('audio');
audio.currentTime = 0;
audio.play();
});
}
}
When I try to do the same with static elements it works fine, does anybody know what is happening? Thanks
There are other answers here that are correct in their answer, but I wanted to add a bit more clarification.
The purpose of jQuery's on() is to attach event handlers to selected elements. The purpose of the selector parameter is to create a delegated handler. From the jQuery documentation:
The handler is not called when the event occurs directly on the bound element, but only for descendants (inner elements) that match the selector.
In addition:
Delegated events have the advantage that they can process events from descendant elements that are added to the document at a later time.
(I would recommend you read the whole section on Direct and delegated events by the way.)
In any case, for your particular example, you want to build your DOM in such a way that there is a top-level element that is guaranteed to exist at the time the delegated event is attached. It may be your table, or a div element above your table. You could also attach to the body or the document, but, it is better to attach the event to the closest element that will be guaranteed to exist per this documentation:
Attaching many delegated event handlers near the top of the document tree can degrade performance. Each time the event occurs, jQuery must compare all selectors of all attached events of that type to every element in the path from the event target up to the top of the document. For best performance, attach delegated events at a document location as close as possible to the target elements. Avoid excessive use of document or document.body for delegated events on large documents.
In your particular case, it appears that the table is a guaranteed element. Because of that, you can attach to that and delegate the divs that are inside. (Of course, appropriate adjust your selectors to get the correct divs.) Assuming you want all divs to bubble up, then your attachment would be something like this:
$('#table').on('mousedown', 'div', function(e) {
console.log('Do some stuff here.');
});
Of course, you'll want to do this inside the document ready handler to ensure that your elements are present.
Also, be aware that IDs cannot start with a number in the HTML 4 spec. If you are developing HTML 5, however, IDs can basically be anything as long as they are unique, don't contain any spaces, and are at least a single character.
$(document.body).on('mousedown', '#tbody td', function(e){
//TODO::add code here
});
Need not bind event to every td dom.
Regarding your armada of event handlers.
Use 3 steps to make it 1 handler for all the dom elements:
Make the sequenctial number part of the id, to have them distinguishable.
Then only instantiate one eventhandler for the body mousedown call. No more for in for loop.
Distinguish via the event object (argument of your event callback), which id was clicked. (i think it was event.target or the alike)
Regarding the usage of this like pointed out in the comment, it might be generally useful to reference the id of an dom element that refers to a domain object. Thus the explicit id can directly relate to the underlying resource identifier( pk ...).
Creating a delegate for each #id is overkill
You can directly attach the click event to each ID in the loop or simple create a single delegate event listener.
$("#table").on("click", "div", function(e){
// use div.class to limit delegate
// split id, pull values from the dom or innerText
var el = this;
var id = this.id;
});
Related
I have a form that is collecting personal info on family members. There is a button that that allows for additional form fields (SSN1, SSN2 and so on) I have a function that formats the SSN. When I try to add a counter to it so that if the user adds additional SSN fields the function will also format the additional fields.
var counter = 1;
$("##SSN"+counter).keyup(function() {
counter++;
var val = this.value.replace(/\D/g, '');
var newVal = '';
var sizes = [3, 2, 4];
for (var i in sizes) {
if (val.length > sizes[i]) {
newVal += val.substr(0, sizes[i]) + '-';
val = val.substr(sizes[i]);
}
else
break;
}
newVal += val;
this.value = newVal.substr(0,11);
}).focusout(function(){
showRequiredDatabaseCheck();
});
Event handlers aren't dynamic like this. The code to attach this handler runs once, when the page loads, and finds only the matching elements at that time. Since the elements are being added dynamically during the interaction with the page, it sounds like event delegation is useful here.
Specifically the use of jQuery's .on().
To make it easier, we should also remove the counter (since its only purpose here is to identify an id) and use a class instead. Give your inputs a common class, such as ssn-input:
<input type="text" class="ssn-input" />
(Or however you build your inputs, this is just an example.)
For your event handler, instead of attaching it directly to the inputs themselves (since they don't exist yet when the page first loads), attach them to a common parent element that's already there. Any parent element will do, but for this example we'll go all the way up the hierarchy to document:
$(document).on('keyup', '.ssn-input', function() {
// the event handler logic you already have
});
In this case the second argument to .on() is a kind of filter to use when the event is handled. Since the event is attached to document, all keyup events which bubble up to that level will be caught by this handler. When the optional filter is used (here it's '.ssn-input'), the handler will ignore any events which don't originate from that matching filter.
So in this case the one event handler should cover all of your SSN inputs as you add more to the page.
I'm working on something that creates a form based on choices you give it (text/textfield/dropdown). When I create 2 dropdowns at once I am not sure how to write in a way that the computer can tell the 2 elements aren't the same, specifically when adding new inputs in my dropdown creator it can't tell multiple dropdowns apart.
Here's the Javascript function I'm using for it
$('#submit').on('click', function() {
$("select").each(function() {
switch($(this).val()) {
case "text":
$("#form").append('<p><input type="text"/> <button class="remInput">Remove</button></p>');
break;
case "note":
$("#form").append('<p><textarea></textarea> <button class="remInput">Remove</button></p> ');
break;
case "dd":
$("#form").append('<p><select id="ddFinal"><option></option></select> <button class="remInput">Remove</button></p>');
$("#ddCreator").append('<button id="ddAddInput">Add Another Field</button> <p class="ddP"><input type="text" name="ddText"/></p> <button id="ddSubmit">Submit</button>');
break;
}
});
$('#inputDiv').empty();
$('#ddAddInput').on('click', function() {
$("p[class*='ddP']").append('<input type="text" name="ddText"/>');
});
$('#ddSubmit').on('click', function() {
$("input[name*='ddText']").each(function() {
$('#ddFinal').append('<option>'+$(this).val()+'</option>');
});
$('#ddCreator').empty();
$('#ddFinal').removeAttr('id');
});
});
The problem is with the ddAddInput function because it doesn't specify between multiple p class="ddp" elements but I'm not really sure what I should be putting instead. Hope this question makes sense.
Here's the fiddle I'm currently working on http://jsfiddle.net/me74Z/15/
There are several flaws in approach that need to be remedied.
First: - element Id's are unique in a page by definition. Use classes instead.
Next, your event handlers are being added from within another event handler. If you were to change all the ID's you have to classes and the corresponding selectors in the event handlers, you would end up compounding events.
What compounding events means is if you add the same handler twice to an element, it will fire twice.
Using event delegation would be easier. Set up your event delegation outside of the current click handler for #submit
$(document).on('click', '.ddSubmit', function() {....});
Now, as for how to handle instances, within every event handler this is the element that is the target of the event.
Using $(this) and jQuery traverse methods you can work with the target's parent and siblings or whatever you need to do.
I would suggest you wrap each level in a container to simplify the traversals. I'll call container class row so that a generic traverse from an event target can look something like:
var someField= $(this).closest('.row').find('.someField');
WHen remove button is clicked, simply remove the parent row
This question is connected with that
This code hides div when user type data to inputs and focus on another div
$(".Q,.A").blur(function(e) {
if ($(this).val().length > 0 && $(this).siblings("input").val().length > 0) {
$(this).parent().fadeOut(1000);
getData("ajaxPHP/insertNewWords.php?q='" + $(this).siblings('input').val() + "'&a='" + $(this).val() + "'&zestawID="+zestawID, "console");
$(".main").append("<div><input type='text' class='Q'></input><input type='text' class='A'></input></div><br>");
}
});
And when user enter data, this div hides and script creates new div (so user can enter infinite amount of data).
The problem is: new created divs don't hide.
So what should I do, if I want to involve new created divs into "$(".Q,.A")"?
The problem is (as I understand it), that you have a behaviour attached to a set of nodes on your page, and new nodes added to the page do not pick up this behaviour.
This is because of the way JQuery works. When you define a selector like $(".Q,.A") this selector evaluates to a set of known nodes on your page. The code that follows only applies to those found elements. This selector is never evaluated again, so any new nodes never get a chance to gain your desired behaviour.
The solution is to get JQuery to re-evaluate the selector every time the event occurs. So you need to listen for the event globally, then filter to only handle the elements that match your selector.
The correct way to do this is on
$(document).on("blur", ".Q,.A", function(){ ... });
See: http://jsfiddle.net/sAT6L/
Live has some discussion on how it used to be done in each version of JQuery.
Note: You should be able to restrict the scope to something more local than $(document).
You can use jquery's .on method on the parent container, because events "bubble" to the parent container. The on function also allows you to specify a selector to filter the children elements, which gets applied dynamically, so you can use your ".Q,.A" selector there:
$(document).ready(function(){
$("#container").on("blur", ".Q,.A", function(e){
if($(this).val().length>0 && $(this).siblings("input").val().length>0){
$(this).parent().fadeOut(1000);
$("#container").append('<div><input type="text" class="Q"><input type="text" class="A"></div>');
}
});
});
Fiddle: http://jsfiddle.net/rK3HS/1/
After html is entered into a container from a ajax response I would like to bind an event to various elements. I know this can be achieved by running addEventListener or on+event=function(){} right after the html is inserted.
My problem is that I am unsure of the best method to do so with dynamic content, whereas the data passed into the event is different each time.
For example, the following html is loaded into a container:
<button id="myButton">go</button>
Now, I need to bind an onclick function to the button element that contains data that was rendered on the remote side - the onclick function would be something like:
myFunction($data1,$data2,$data3);
whereas $data1,$data2,$data3 are variables with the dynamic data.
One way I can have this function bound to the button is by outputting a script to be evaluated after the HTML data is inserted into the container, so the HTML output would go like this:
<button id="myButton">go</button>
<script>document.getElementById('myButton').addEventListener('click',function(){myFunction(<?php echo $data1.','.$data2.','.$data3 ?>)});})</script>
Is there anyway I can achieve the result from the above code in a more flexible way without having to output that script line for every element and for each request?
A way that I thought of is to have a bind function that is called after every request is completed, and in this function every element that needs an event bound is stored in an array. A loop goes through the array and binds the appropriate event and function - but this gets complicated with dynamic data. Any ideas?
Use your own form of event delegation:
var container = document.getElementById("container");
container.addEventListener("click", function (e) {
e = e || window.event;
var target = e.target || e.srcElement;
if (target.tagName === "BUTTON") {
// execute handler code for buttons
}
}, false);
Where the container element is the closest, stable (not being added/removed from the DOM) element that these dynamic elements/buttons are being added to at some level.
The event is bound once to one containing element, but is triggered for any click event that bubbles up from descendants (the default behavior of click events).
This will simply check if the tagName of the element is "button". This is pretty broad, but if this is the thing you want to filter out, that's what you can use. If you want to use a class, add a specific class (maybe "special-class") to the dynamic buttons and use:
if (~(" " + target.className + " ").indexOf(" special-class ")) {
// execute handler for buttons with class "special-class"
}
If you need to pass specific data with the buttons/elements, add a specific data-* attribute that contains it, to the buttons when generating them:
<button id="whateverId" data-data1="$data1" data-data2="$data2" data-data3="$data3">go</button>
And in the event handler where you know it's the targeted buttons, you can use:
var data1 = target.getAttribute("data-data1");
var data2 = target.getAttribute("data-data2");
var data3 = target.getAttribute("data-data3");
Reference:
http://webdesign.tutsplus.com/tutorials/htmlcss-tutorials/all-you-need-to-know-about-the-html5-data-attribute/
http://caniuse.com/dataset
Using:
$('#foo').data('events').click
We are able to access an iterative object of click handlers added to the element '#foo' but only when they were added with .bind()
Is there a way to get the handlers for an event added with .live()?
Is there any other way to know if an element has a click handler assigned?
live events are added to the document. Use
$(document).data('events').click
The above will return an array of objects containing information about each bound click handler. Each of these objects has a selector property containing the selector that was used at the time of binding with $(selector).live(.., ..).
Any of these selectors that matches the element with id foo will get triggered when #foo is clicked. Note that the selector does not have to be exactly #foo for that to happen. There are many other selectors that can be used to target an element. For example if #foo was a <p>, then a live click handler such as
$("p").live("click", function..)
will also target #foo.
Here's one approach. Loop through each object, and see if any of the elements matching the selector property include #foo.
var handlers = $(document).data('events').click;
// jQuery quirk: $.map callback takes arguments (obj, index) and
// $(..).map takes callback arguments as (index, obj)
var fooClickHandlers = $.map(handlers, function(handler) {
if($(handler.selector).is('#foo')) {
return handler;
}
return null;
});
// fooClickHandlers is a list of all handlers that will fire on #foo click