Observing events on dynamically inserted objects in the DOM - javascript

On load, I add a desired behavior on all textareas on a page.
Event.observe(window, 'load', function() {
$$('textarea').each(function(x) {
x.observe('keydown', dosomethinghere)
});
});
This works because the textareas are already in the DOM, but how should I treat textareas that are dynamically added after the page loads (ex: if I have a button that says "Add More"). I would like these newly created textareas to have the same behavior.

The way I do it is by just observing the new textarea when I add it, like this:
function doSomethingWithTextAreas(){
//do something.
}
document.observe("dom:loaded", function() {
$$('textarea').each(function(s){
s.observe('keydown', doSomethingWithTextareas);
});
$('add_more').observe('click', function(){
textarea = new Element('textarea');
textarea.observe('keydown', doSomethingWithTextareas); //Observes the new textarea.
Element.insert($('textarea_container'), {bottom:textarea});
});
});

Consider using jQuery Live.

$.live() would work as STAii mentions, but there is discussion of implementing a similar function in prototype as well. That would probably be of more benefit so you don't have to add another library.

Well, the answer is a bit tricky. The only way to do this is to maintain a cache of events listeners for your textareas. When adding a new textarea to your page, you would need to call Event.stopObserving on all your cached events. You would then call your $$('textarea').each(...) code again to bind to all the elements.
Thankfully, someone has done this for you already in a very handy lightweight prototype extension called lowpro: http://www.danwebb.net/2006/9/3/low-pro-unobtrusive-scripting-for-prototype
You can do what you wish as simply as:
Event.addBehavior({
'textarea:keydown': function(e) {
dosomethinghere(); // e.g. this.hide();
}
});
Then whenever you add a new textarea dynamically, you simply call Event.addBehavior.reload();
I should point out that "e" is the Event object, and "this" is the element inside the context of the function(e) {} definition.

A nice way of doing this is to have the javascript function which adds the text areas fire an event which any other function can observe and act on. So:
function add_textarea() {
// Code creates a new <textarea> and adds it to the page
var textarea = new Element("textarea");
$("some-form").insert(textarea);
textarea.fire("textarea:add")
}
document.observe("textarea:add", function(event) {
event.target.observe('keydown', dosomethinghere);
});
This allows your 2 functions--one that adds a new textarea and one which attaches observers--to be loosely coupled and not know anything about each other. One simply needs to fire a custom event which the other can observe.

Related

Is it possible to capture keydown globally on dynamically added text inputs?

I'm using the following global jQuery to show and hide a loading div for $.ajax calls:
$(document).ajaxStart(function(){
$("#loading").show();
}
$(document).ajaxStop(function(){
$("#loading").hide();
}
This works fine, but I do not want to show the loading div for autocompletes, so I added this:
$("input[type=text]").keydown(function(){
if($(this).hasClass('ui-autocomplete-input')) {
window.suppressGlobal = true;
}
});
Then, to reset suppressGlobal for "normal" $.ajax calls, this:
var origAjax = $.ajax;
$.ajax = function() {
if (window.suppressGlobal) {
arguments[0].global = false;
}
origAjax.apply(this, arguments);
window.suppressGlobal = false;
};
This all works nicely for text inputs that are constructed with page load. However, I have several situations where text inputs are inserted dynamically on the client-side using jQuery/Javascript, in which case the keydown event does not get bound to the global function. I also tried on:
$("input[type=text]").on('keydown', function(){
if($(this).hasClass('ui-autocomplete-input')) {
window.suppressGlobal = true;
}
});
But that doesn't work either. Is there a way that I can globally capture the keydown event regardless of when the text input was added? Could I somehow globally capture the addition of text inputs to the DOM and attach the event handler then?
you will have to use $(document).on() for dynamically created controls.
jsfiddle: http://jsfiddle.net/G9qJE/
also you can use: $('body').on
explanation:
When an event is assigned, it's only assigned to elements that currently exist on the page. If you later on other elements, there is nothing watching that watches for those elements too allow them to be used as well.
That is why you need something sitting at the document level which is aware of the event and the elements you want to apply it to, so that it can watch for any new elements that match and apply that event to them as well.
$(document).on("keydown", "input[type=text]", function() {
if($(this).hasClass('ui-autocomplete-input')) {
window.suppressGlobal = true;
}
});

What is the jQuery or javaScript syntax to make functions work only on the active/focused input/button/text area?

I am a beginner & self interested web coder.
I have been testing, asking, retesting, trying, reading up on different functionality solutions in javaScript to an online form which will consist of a multitude of <textarea>'s once I am done.
I am fairly ok, with the current state of functions, which are based upon several js events. Example code would be (written funny so it is easier to read in the forum, actual code is one line obviously):
<textarea
data-id="0"
class="classOne classTwo"
id="dataInput_0"
name="xInput_row_1"
onFocus="functionOne();"
onBlur="functionTwo();"
onKeyUp="functionThree();">
</textarea>
I built and tested all the functions to work specifically on the id="dataInput_0" using getElementById. Example:
var d = document.getElementById("dataInput_0");
So my question is how to I make the functions trigger for other "dataInput" id's?
In other words:
var d = document.getElementById('whichever dataInput that is active/focused');
Thanks!
The simplest way to work with your current code would be to do this:
onFocus="functionOne(this);"
...and then define your function:
function functionOne(el) {
// el is the element in question, used e.g.:
alert(el.name);
}
Within the onFocus=... the browser sets this to the element in question, so you can then pass it as a parameter to your function. Your function then just uses it directly rather than having to go via getElementById().
But since you mentioned jQuery, you could remove the inline onFocus and other onXYZ handlers from your html and just do it all in your JS as follows:
$("textarea").focus(function() {
// here this is the element in question, e.g.:
alert(this.value);
});
That defines a focus handler for all textareas on the page - to narrow it down to just textareas with class "classOne" do $("textarea.classOne"). Within the function this refers to the focused element. You could use the .blur() and keyup() methods to assign handlers for the other events shown in your code.
My suggestion is to use attribute selector $('input[id^="dataInput_"]') for this and use the jQuery's .on() handler this way:
$('input[id^="dataInput_"]').on({
focus: function{
functionOne($(this));
},
blur: function(){
functionTwo($(this));
},
keyup: function(){
functionThree($(this));
}
});
and the functions:
functionOne(obj){
console.log(obj.val());
}
functionTwo(obj){
console.log(obj.val());
}
functionThree(obj){
console.log(obj.val());
}

How to make an <a> tag element to trigger multiple functions with javascript/jquery

There is a link in my webpage, the link itself triggers a function that I could not modify, but I want to make the link, when clicked, also calls another JavaScript function at the same time or preferably after the first function is done. So one click to call two functions...could it be implemented? Thanks
<a title="Next Page" href="javascript:__doPostBack('Booklet1','V4504')">Next</a>
is the sample tag I want to modify, how could make it also call "myFunc" at the same time or preferably after _doPostBack is done.
P.S. the function parameter for _doPostBack such as V4504 is dynamically generated by the ASP user control. So I cannot simply treat it as a static function and bind it with another. I think I could only append some function to it? Unless I parse the whole page first and extract the function name with its current parameters...Since every time I click the link, the parameter such as V4504 changes its value....
Thanks!
You should be able to attach multiple event handlers to a single anchor tag, either with .onclick or .addEventListener('click', function)
https://developer.mozilla.org/en/DOM/element.addEventListener
You can attach a handler to an element click event using plain Javascript in such a way:
function hello()
{
alert("Hello!")
}
var element = document.getElementById("YourAElementID");
if (element.addEventListener)
{
element.addEventListener("click", hello, false);
}
else
{
element.attachEvent("onclick", hello);
}
It supprots all common browsers.
Yes, you can do this MANY ways (I use both $(this) and $('identifier') as you don't say how the functions are bound) :
$(this).click(function(){
my_function_1();
my_function2()
});
Or
$('my element').click(function(){
my_function_1();
});
$('my element').click(function(){
my_function_2();
});
Or, if the functions reside on another object:
$(this).click(function(){
my_function_1();
$('#other_element_id').trigger('click'); //there are a bunch of syntaxes for this
});
Sans JQuery, you can use:
var myObj = document.getElementById('element name');
myObj.addEventListener('click', function(){
alert('first!');
});
myObj.addEventListener('click', function(){
alert('second!');
});
Clicking will result in two sequential alert prompts

Jquery bind()/live() within a function

I wrote a little pager which removes and rewrites content. I have a function called after loading the page, it shall be executed after changing the page as well. Because I do not wat to implement the function twice (on initialisation and after changing the page) I tried bind()/live() and a simple function.
The function looks like this:
jQuery('.blogentry').each(function (){
jQuery(this).click(function(){
//Clicking on the element opens a layer, definitely works - I tested it
});
});
It is executed after initialisation, for executing it after page changes as well I tried the following:
jQuery('.nextPage, .prevPage').click(function changePage(){
// Changing page and rewriting content
showEntry();
});
//...
showEntry();
//...
function showEntry(){
jQuery('.blogentry').each(function (){
jQuery(this).click(function(){
//Clicking on the element opens a layer, definitely works - I tested it
});
});
}
But the function is not executed if put inside a function (lol) and called via showEntry();
Afterwards I tried to bind the function...
jQuery('.nextPage, .prevPage').click(function changePage(){
// Changing page and rewriting content
jQuery('.blogentry').bind("click", showEntry);
});
//...
jQuery(this).click(function showEntry(){
//Clicking on the element opens a layer, definitely works - I tested it
});
Did not work either. Code after the bind()-line would not execute as well.
I thought maybe it's a problem to bind to an event function, if an event is already given via the parameter so i also tried this:
jQuery('.nextPage, .prevPage').click(function changePage(){
// Changing page and rewriting content
jQuery('.blogentry').bind("click", showEntry);
});
//...
function showEntry(){
//Clicking on the element opens a layer, definitely works - I tested it
});
}
No success at all. Maybe I cannot call the function from inside the function regarding to the bind()? Maybe I just do not understand the bind()-function at all? I also tried the live() function since it seemed to fit better, as I am rewriting the content all the time. But it had the same effect: none...
The simplest way to implement this should be
jQuery('.blogentry').live('click', function() { /* onclick handler */ });
This should bind the function to every blogentry on the page at the moment of the call and all the blogentries that are added to the page later on.
Additional notes:
In $(foo).each(function() { $(this).click(fun); }); the each is unnecessary - $(foo).click(fun); is enough.
$(foo).bind('click', fun); is functionally equivalent to $(foo).click(fun) - it does not matter which one you use.
You can use delegate or bind. don't call the function like that, just create a delegate with .blogentry and it should update even after you load a new page via ajax. It will automatically do this.
$("#blogcontainer").delegate(".blogentry", "click", function(){ //open layer });
This should work for you
$(body).delegate(".blogentry", "click", function(){
showEntry();
});
alternaltivly you can use event delegation
$(document).ready(function () {
$('#blogcontainer').click( function(e) {
if ( $(e.target).is('.blogentry') ) {
// do your stuff
}
});
});
hence, no need to bind each blogentry at creation or reload, and it's (slightly) faster.

preventing effects to be applied to the same object twice when adding objects with ajax

I'm having a little issue with an application I'm making. I have a page where the user edits a document via dragging modules into the page or "canvas" area.
http://thinktankdesign.ca/temp_img.jpg
When the page is loaded, javascript haves the modules collapsible (like above). However after the user drags in a new module the effect is applied again some new modules can collapse as well. here is the problem. each time a module loads the same effect gets applied to the modules that already can collapse. It ends up breaking their animations.
heres the code that gets executed on page load.
//make colapsible
$("h1.handle").click(function() {
var object = $(this);
v$(this).next().toggle("fast", colapsible_class(object));
vreturn false;
}).addClass("open");
and heres the code that gets executed in the creation of a module via ajax
function get_module(id){
var template = $('input[name=template]').val();
$.post(window.location.href, { template: template, module: id, mode: 'create' },
function(data){
$(data).insertBefore(".target_wrapper");
//enable deletion of module
$(".js_no_modules").slideUp("slow");
$(enable_module_deletion());
//show delete button
$("button[name=delete]").show();
//make colapsible
$("h1.handle").click(function() {
var object = $(this);
$(this).next().toggle("fast", colapsible_class(object));
return false;
}).addClass("open");
}
);
}
I need a solid way of preventing the toggle effect to be applied to the same module twice
Use jQuery 1.3 live events instead.
//make colapsible
$("h1.handle").live("click", function() {
var object = $(this);
v$(this).next().toggle("fast", colapsible_class(object));
vreturn false;
}).addClass("open");
and then eliminate the click declaration in the second block of code, changing it to $("h1.handle").addClass("open");
Live events bind all current and future matching elements with an event.
In your Ajax success handler try the following:
//make collapsible
$("h1.handle:not(.open)").click(function() {
var object = $(this);
$(this).next().toggle("fast", colapsible_class(object));
return false;
}).addClass("open");
The best way to solve your problem is, instead of using $("h1.handle") on the AJAX callback, go for $(data).find("h1.handle"). Something like,
var x = $(data);
x.insertBefore(...);
/* your other code */
x.find('h1.handle').click(...).addClass(...);
Like that, only the newly added items will have the event bounded. The already present ones will not be touched.
If we want to answer your question instead of just solving your problem, then we have several alternatives, such as:
store, in your objects, that the onclick event handler has been set so that you don't set it twice
always bind the onclick event, but always unbind it first
use jQuery's live events and the addClass open only on the newly created items.
IMO, the first one is the easiest. You can accomplish it by using jQuery's data(). Then you could do something like:
$("h1.handle").each(function() {
var me = $(this);
// if already has click handler, don't do anything
if (me.data('click_set') != null) { return true; }
// otherwise, store the data and bind the click event
me.data('click_set', true).click(function() {
/* the code you already have on the click handler */
}).addClass('open');
}
The second alternative involves storing the function that you pass inline to the click event binder in a variable, and then using jQuery's unbind to disable it.

Categories