Introduction
Often it is required, or just case of convenience, to bind event to DOM elements that do not yet exist. Often, such element is generated by third party code and there is no easy way to predict when the elements are actually added to the page. Another typical example - the elements of the same type, with same behavior are generated by user actions, for instance, by adding new item to table, list or tree by button click. There are a lot of other examples. I believe that each developer has experience to combat this issue. There are few common workarounds. I would like to explain one of them that uses JQuery on
function.
Code Example
Let's say that there is a page with a tree. Initially, the tree has only root node. Each node has some text and button. On button click, child node is added to the tree.
<html>
<header>
<style>
a{
border:1px solid blue;
display:inline-block;
margin-right:10px;
background-color:#CEDDFF;
color:#004E8E;
text-decoration:none;
padding-left:5px;
padding-right:5px;
}
li{
padding-bottom:5px;
}
</style>
</header>
<body>
<ul>
<li>
<div><a class="add-new-child" href="#"
data-child-count="0" title="Add Child">+</a>
<span>Has children: 0</span></div>
</li>
</ul>
<script src="http://code.jquery.com/jquery-latest.min.js"
type="text/javascript"></script>
<script>
(function() {
$(".add-new-child").click(function (event) {
var li = $(event.target).closest("li");
if (li.find(">ul").length === 0) {
li.append("<ul></ul>");
}
var ul = li.find(">ul");
ul.append("<li><div><a class='add-new-child'
href='#' data-child-count='0' title='Add Child'>+
</a><span>Has children:0</span></div></li>");
var count = parseInt(li.find("> div a").attr("data-child-count")) + 1;
li.find("> div a").attr("data-child-count", count);
li.find("> div > span").html("Has children: " + count);
});
})();
</script>
</body>
</html>
The page contains HTML and JavaScript with click
event binding that fulfills the desired behaviour. However, after a quick test, it is obvious that there is a bug. The button works only for root node that exists at the moment when the attach event statement is executing. For all nodes that are generated by user click, nothing happens.
Solution
Let's use JQuery on
(http://api.jquery.com/on/) function to attach event. According to the documentation, this function can have four parameters:
event
- the string
with event name, click
in this case selector
- standard JQuery selector to filter events by origin data
- data to be passed to the handler handler
- handler function
I would like to look at parameter selector
closer. What it actually means? If element is bound to event, it reacts not only on action originated at itself, but also on all acts bubbled from children DOM elements. Selector
parameter eliminates possible origin of event for children that satisfied it. Look at the DOM tree below:
Consider few examples:
$(A).on("click", function(){alert("On click!")});//click on all elements triggers alert
$(A).on("click", "C", function(){alert("On click!")});//click on C, D, E triggers alert
$(A).on("click", "D", function(){alert("On click!")});//click only on D triggers alert
It is important to understand that actually such approach binds event to element corresponding to selector in right part - $(A)
and this element should exist at the moment attachment statement is executing. At the same time, filtering by origin is happening at the moment of action. In such a way, it is a simulation of binding to element that does not exist yet, the actual event is attached to parent element. The last fact brings some limitation: if bubbling of events is stopped between origin and $(A)
element, nothing happens. The element from the right part ($(A)
) and left (for example C
) should be the closest possible in the DOM tree to avoid the bug mentioned above.
In such a way to fix the example above, the line of code:
$(".add-new-child").click(function (event) {
should be substituted with:
$("body").on("click", ".add-new-child", function (event) {
Conclusion
The JQuery on function provide a way to simulate binding event to DOM elements not yet created. Despite some limitations, this approach seems to be the most straight forward for me.