How to change event handlers dynamically with JavaScript? - javascript

I'm pretty new to anything AJAX and ran into the following problem. I hope someone here is able to help me out. I'm sure it's an easy fix but I just don't see it :(
What I'm trying to do is change the onmouseover and onmouseout events dynamically for the element, here are the relevant bits of code:
The HTML Element (notice there are multiple of these, hence the dynamic part of their id)
<?
if (ownsgame($id, $userid)) {?>
<a><img src="images/collection/got.gif" id="ownedimage<?=$id?>" title="<?=$itemtitle?> (<?=$platformdisplay?>) is in your collection" alt="You own this game" align="center" width="62" height="22" onclick="changeOwned('<?=$id?>')" onmouseover="changeImageSrc('ownedimage<?=$id?>', 'images/collection/del.gif')" onmouseout="changeImageSrc('ownedimage<?=$id?>', 'images/collection/got.gif')"/></a>
<? } else { ?>
<a><img src="images/collection/add.gif" id="ownedimage<?=$id?>" title="Add <?=$itemtitle?> (<?=$platformdisplay?>) to your collection" alt="You do not own this game" align="center" width="62" height="22" onclick="changeOwned('<?=$id?>')" onmouseover="changeImageSrc('ownedimage<?=$id?>', 'images/collection/add.gif')" onmouseout="changeImageSrc('ownedimage<?=$id?>', 'images/collection/add.gif')"/></a>
<?} ?>
The JavaScript function:
function changeImageSrc(id, src) {
document.getElementById(id).src = src;
}
The (relevant) AJAX code:
var http = createRequestObject();
var jsid = "";
function changeOwned(id) {
http.open('get', 'changeowned.php?id=' + id + '&user=<?=$userid?>');
jsid = id;
http.onreadystatechange = processResponse;
http.send(null);
}
function processResponse() {
if((http.readyState == 4) && (http.status == 200)){
var response = http.responseText;
var elementid = 'ownedimage' + jsid;
var element = document.getElementById(elementid);
if (response == "1") {
image = "images/collection/got.gif";
alt = "you own this game";
mouseoverstr = 'images/collection/del.gif';
mouseoutstr = 'images/collection/got.gif';
element.removeEventListener('mouseout', function(){changeImageSrc('ownedimage' + jsid, 'images/collection/add.gif')}, false);
element.removeEventListener('mouseover', function(){changeImageSrc('ownedimage' + jsid, 'images/collection/add.gif')}, false);
} else {
image = "images/collection/add.gif";
alt = "add this game to your collection";
mouseoverstr = 'images/collection/add.gif';
mouseoutstr = 'images/collection/add.gif';
element.removeEventListener('mouseout', function(){changeImageSrc('ownedimage' + jsid, 'images/collection/got.gif')}, false);
element.removeEventListener('mouseover', function(){changeImageSrc('ownedimage' + jsid, 'images/collection/del.gif')}, false);
}
element.src = image;
element.alt = alt;
element.addEventListener('mouseover', function(){changeImageSrc('ownedimage' + jsid, mouseoverstr)}, false);
element.addEventListener('mouseout', function(){changeImageSrc('ownedimage' + jsid, mouseoutstr)}, false);
}
}
It seems to work fine at first but produces some weird behaviour. The referenced PHP works fine and produces the correct response. The srcand alt of the image get changed as well. In fact, it first looks like the new mouseover/out work too. But when you click on more than one image on the site (which have different IDs) they suddenly start influencing each other. When you mouseover over one, the other changes its image aswell. Why is this happening? I really am clueless as the jsid part is fine and I don't understand why the mouseover suddenly changes two images. It looks as if multiple eventhandlers for different IDs are assigned to the same image element. No idea why that is though. I hope some of you with more AJAX knowledge can help me here, quite frustrated :(

A couple of things there: :-)
1) addEventListener and removeEventListener work with the newer DOM2 handler chain, which is completely separate from the older "DOM0" style (the onXYZ attributes). So you can't remove a handler via removeEventListener that you've originally assigned via an onXYZ attriubte. To do that, assign "" to the attribute's reflected property.
element.onmouseover = "";
2) When you use removeEventListener, you must pass in the same function reference that you used originally. So this will never remove anything:
element.removeEventListener('mouseout', function(){changeImageSrc('ownedimage' + jsid, 'images/collection/got.gif')}, false);
...because it creates a brand new function to pass into removeEventListener, and since that function isn't on the event handler chain, the call is ignored.
I'd probably approach the problem by having a single event handler, but then changing the data it works with. You can do that by storing the alternate image URLs on the actual element itself, using a data-xyz attribute (say, data-oversrc and data-stdsrc or some such). Then your two functions (one for mouseover, one for mouseout) that change the URL based on the image:
function handleMouseOver() {
this.src = this.getAttribute('data-oversrc');
}
function handleMouseOut() {
this.src = this.getAttribute('data-stdsrc');
}
I'd drop the onXYZ handlers from the elements entirely, and replace them with a one-time addEventListener (attachEvent on IE) that assigns both of those.
data-xyz attributes, like all custom attributes, are invalid in HTML4 and earlier. As of HTML5, they validate, and since no major browser has a problem with invalid attributes anyway, you can start using them right away.
Off-topic #1: These days, I usually recommend using a JavaScript library to smooth over browser differences and implement useful functionality for you. For instance, your code using addEventListener and removeEventListener will fail on IE prior to IE9 because it simply doesn't have those methods. If you use a library like jQuery, Prototype, YUI, Closure, or any of several others, they'll deal with that stuff for you so you can focus on your own value-add.
Off-topic #2: The mouseover event happens repeatedly when the mouse is passing over the element, and both it and mouseout bubbles up the DOM. This means that for hover effects like yours, if you can't use CSS (and you can't on IE6), you're better off using the mouseenter and mouseleave events, most of the time. But those are IE-specific events not supported by most other browsers. Enter any decent JavaScript library. :-) They'll emulate mouseenter and mouseleave on browsers that don't support them directly. Of the list above, I know for certain that jQuery and Prototype do that; I'd be surprised if the others don't have similar functionality.

The issue is the removeEventListener, it's not working like you think it is. What's happening is when you do this:
element.removeEventListener('mouseout',function(){/* handlers */},false);
That function() { } is a new anonymous function you just created, not the existing one on the element as a listener...so when it goes to remove that listener, it just isn't there, because that was a different anonymous function (even if it had the exact same code, it's a different reference) that you assigned via addEventListener.
There are a few work-arounds for this, given that you're making AJAX requests I'm assuming you're not making hundreds/thousands here, so you could just store the handlers you assign for removing them later, like this:
var http = createRequestObject();
var jsid = "";
var handlers = {};
function changeOwned(id) {
http.open('get', 'changeowned.php?id=' + id + '&user=<?=$userid?>');
jsid = id;
http.onreadystatechange = processResponse;
http.send(null);
}
function processResponse() {
if((http.readyState == 4) && (http.status == 200)){
var response = http.responseText,
elementid = 'ownedimage' + jsid,
element = document.getElementById(elementid),
image, alt, mouseoverstr, mouseoutstr;
if (response == "1") {
element.src = "images/collection/got.gif";
element.alt = "you own this game";
mouseoverstr = 'images/collection/del.gif';
mouseoutstr = 'images/collection/got.gif';
} else {
element.src = "images/collection/add.gif";
element.alt = "add this game to your collection";
mouseoverstr = 'images/collection/add.gif';
mouseoutstr = 'images/collection/add.gif';
}
//Create a holder if this is the first time for this elementid
if(!handlers[elementid]) handlers[elementid] = {};
//Remove old handlers
element.removeEventListener('mouseover', handers[elementid].mouseover, false);
element.removeEventListener('mouseout', handers[elementid].mouseout, false);
//Store new handlers
handlers[elementid].mouseover = function(){changeImageSrc('ownedimage' + jsid, mouseoverstr)};
handlers[elementid].mouseout = function(){changeImageSrc('ownedimage' + jsid, mouseoutstr)};
//Add hew handlers as listeners
element.addEventListener('mouseover', handers[elementid].mouseover, false);
element.addEventListener('mouseout', handers[elementid].mouseout, false);
}
}

Related

Ideas for a correct function on my webpage?

I am having problems with a javascript function. I want to replace an icon by changing the class.
On my page, I have the following element:
<i class="wait icon" alt="{webui_botstatenotavailable}" title="{webui_botstatenotavailable}" id="{botname}"></i>
The following javascript should change the class, but it does not work:
function incomingBotStatusList(http_request, statusOff, statusOn)
{
if (http_request.readyState == 4)
{
if (http_request.status == 200)
{
if (http_request.responseText.length < 7)
{
// Error
}
else
{
var botStatusList = JSON.parse(http_request.responseText);
for (var key in botStatusList)
{
if (botStatusList.hasOwnProperty(key))
{
var botStatusImage = document.getElementById(key);
if (botStatusImage != null)
{
if (botStatusList[key] == 0)
{
botStatusImage.class.innerHTML = "images/bullet_red.png";
botStatusImage.title = statusOff;
botStatusImage.alt = statusOff;
}
else if (botStatusList[key] == 1)
{
botStatusImage.class.innerHTML = "<i class=\"checkmark green icon\">";
botStatusImage.alt = statusOn;
botStatusImage.title = statusOn;
}
}
}
}
}
}
}
}
Did someone from you know how it will work?
Thanks for your help!
Best Regards
Pierre
I see a couple of problems with your code. First, the <i> element is used to apply italic formatting to text. It is not the HTML code for an icon or an image.
Secondly, you write botStatusImage.class.innerHTML, but the Element.class does not exist, and Element.className is a string. It does not have an innerHTML attribute. So, you could write botStatusImage.className = "new_class_name"; and this would be more correct.
You should then change the image source by calling botStatusImage.setAttribute('src', new_url), where you have set new_url to the new image location.
Check out the javascript reference for the Element class that is returned from document.getElementById: check this link
My recommendation, start simple, then make it complex.
First, try to get the icon to change without the AJAX request. Try writing a function like this:
function changeIcon( imageId, newUrl ){
var element = document.getElementById( imageId );
element.setAttribute( "src", newUrl );
}
Then test this function in the console by passing calling it with the URL's manually.
Once that works, don't change it! Next add the AJAX call, and when you have the Icon url from your server response, all you do is call the function that you already wrote and tested. That way you separate the AJAX code from the image update code and you can test them separately.
The key is smaller functions. Build the easy stuff first, and then call those easy functions from the harder functions. Once you know the easy function works well, it becomes much easier to find problems in the harder functions.

javascript/html: Second onclick attribute

I have an image - image1.png. When I click a button the first time, I want it to change to image2.png. When I click the button for a second time, I want it to change to another image, image3.png.
So far I've got it to change to image2 perfectly, was easy enough. I'm just stuck finding a way to change it a second time.
HTML:
<img id="image" src="image1.png"/>
<button onclick=changeImage()>Click me!</button>
JavaScript:
function changeImage(){
document.getElementById("image").src="image2.png";
}
I'm aware I can change the image source with HTML within the button code, but I believe it'll be cleaner with a JS function. I'm open to all solutions though.
You'll need a counter to bump up the image number. Just set the maxCounter variable to the highest image number you plan to use.
Also, note that this code removes the inline HTML event handler, which is a very outdated way of hooking HTML up to JavaScript. It is not recommended because it actually creates a global wrapper function around your callback code and doesn't follow the W3C DOM Level 2 event handling standards. It also doesn't follow the "separation of concerns" methodology for web development. It's must better to use .addEventListener to hook up your DOM elements to events.
// Wait until the document is fully loaded...,
window.addEventListener("load", function(){
// Now, it's safe to scan the DOM for the elements needed
var b = document.getElementById("btnChange");
var i = document.getElementById("image");
var imgCounter = 2; // Initial value to start with
var maxCounter = 3; // Maximum value used
// Wire the button up to a click event handler:
b.addEventListener("click", function(){
// If we haven't reached the last image yet...
if(imgCounter <= maxCounter){
i.src = "image" + imgCounter + ".png";
console.log(i.src);
imgCounter++;
}
});
}); // End of window.addEventListener()
<img id="image" src="image1.png">
<button id="btnChange">Click me!</button>
For achieve your scenario we have to use of counter flag to assign a next image. so we can go throw it.
We can make it more simple
var cnt=1;
function changeImage(){
cnt++;
document.getElementById("image").src= = "image" + cnt + ".png";
}
try this
function changeImage(){
var img = document.getElementById("image");
img.src = img.src == 'image1.png' ? "image2.png" : "image3.png";
}
Just use an if statement to determine what the image's source currently is, like so:
function changeImage(){
var imageSource = document.getElementById("image").src;
if (imageSource == "image1.png"){
imageSource = "image2.png";
}
else if (imageSource == "image2.png"){
imageSource = "image3.png";
}
else {
imageSource = "image1.png";
}
}
This should make the image rotate between 3 different image files (image1.png, image2.png and image3.png). Bear in mind this will only work if you have a finite number of image files that you want to rotate through, otherwise you'd be better off using counters.
Hope this helps.
Check the below code if you make it as a cyclic:
JS
var imgArray = ["image1.png", "image2.png", "image3.png"];
function changeImage(){
var img = document.getElementById("image").src.split("/"),
src = img[img.length-1];
idx = imgArray.indexOf(src);
if(idx == imgArray.length - 1) {
idx = 0;
}
else{
idx++;
}
document.getElementById("image").src = imgArray[idx];
}
html
<button onclick=changeImage();>Click me!</button>
function changeImage(){
document.getElementById("image").attr("src","image2.png");
}

javascript: created img onclick not works

Seems weird problem, but perhaps I'm just a noob.
Dinamically created IMG, every element (src,id,border,title) ok, except onclick: comment in code.
var img = document.createElement('IMG');
var href = result;
var idx = result.lastIndexOf("/");
img.src = result.substr(0,idx+1) + 'thumbs/' + result.substr(idx+1);
img.border = 0;
img.id = "qq1";
/* the following checked one by one
img.onclick = 'alert("Kakukk")' ; //does nothing - at stop (error: toSource) img=>onclick null
img.onclick = alert("Kakukk") ; // prints "Kakukk" here
img.onclick = function(){alert("Kakukk");}; //does nothing; at stop img=>onclick missing on debug => local variables !!
var myf = 'alert("Kakukk")';
img.onclick=myf; //does nothing - at stop img=>onclick null
img.onclick='function(){alert("Kakukk");}'; //does nothing - at stop img=>onclick null
*/
img.addEventListener('click', function() {alert("Kakukk"); }, false); //does nothing - at stop img=>onclick null
img.title = 'Marci';
alert("myObject4 is " + img.toSource()); // stop IE on error, check variables on debug screen
What the hell I missed?
Result: Most of code working well, onclick fine, I was just turning round and round around IE debug, what shows wrong value. Not javascript problem. Thx 4 all. Never belive n00bs ;)
Native emacScript should be written addEvenListener and for iE attachtEvent
You need to add the img to the document:
document.body.appendChild(img);
Working example here:
http://jsbin.com/limomulora/1/edit
(PS: You should really be using addEventListener anyway)
You have not added the Image in DOM. Use jQuery's Append or before or after method to add your IMG element on DOM.
e.g.
$('#demoDiv').append(img); // appends img after div with id demoDiv

JS Class and overriding the JQuery event handlers

This should be simple, but for reason I can't quite fathom it doesn't work and - more importantly - I can't find the right question to ask of Google!
What I want to do is prototype an event binder for a JS class as per below, what actually happens is nothing, unless I call .bind() directly on the box object.
function Box()
{
this.Width = 200;
this.Height = 200;
this.WrapperClass = "boxWrapper";
this.TitleClass = "boxTitle";
this.ContentClass = "boxContents";
this.Title = "This is a box";
this.Content = "This is some box contents";
}
Box.prototype.Html = function()
{
var box = $('<div></div>').addClass(this.WrapperClass);
box.append($("<div></div>").addClass(this.TitleClass).append(this.Title));
box.append($("<div></div>").addClass(this.ContentClass).append(this.Content));
box.width(this.Width);
box.height(this.Height);
return box.outerHTML();
}
Box.prototype.Bind = function(event, eventDelegate)
{
this.bind(event, eventDelegate);
}
function clickDelegate(message)
{
alert(message);
}
$(document.ready(function() {
var box = $(new Box().Html());
box.Bind('click', clickDelegate('text'));
$('body').append(box);
}
You have to append box before binding events.
I have created a little fiddle starting with your code and debugging it.
FYI : jQuery object has no method outerHTML, So I've embedded the newly created div in one other.
I've made several changes to your code that's why I'm not posting it here.

jquery show() question

I have a script that builds a dynamic div with contents. The button that initializes the build uses an onclick command and a ajax call to retrieve the appropriate info, then builds the div. This all works great, it builds the div, and jquery shows the div and the button changes to the close button. The problem now is that the button still has the onclick command attached to it, and I was to strip this command off, and reapply it if the user wants to view the information again.
The button code on initial load:
<img class="showPixFeature hidden" id="butShowImage_<?php echo $row_rsFeatureAds['AdID']; ?>" src="../images/class_see_more.png" align="absmiddle" onclick="getRemoteInfo('PicDetailFeature_<?php echo $row_rsFeatureAds['AdID']; ?>')" style="cursor: pointer" />
Script that builds the div:
function showImage(IDS, selectedID){
var adType = new Array();
adType = IDS.split("_");
//alert(adType);
var thumbs = new Array();
thumbs = adType[1].split("~");
var adID = thumbs[0];
//alert(adType[0] + '_' + thumbs[0]);
var picImage = document.getElementById(adType[0] + '_' + thumbs[0]);
removeChildren(picImage);
var picThumbs = document.createElement('div');
arLen = thumbs.length;
//alert(arLen);
if(arLen > 2){
for ( var i=1, len=arLen; i<len; ++i ){
//alert(thumbs[i]);
var thumbNail = document.createElement('img');
thumbNail.src = "../images/listings/" + adID + "_" + thumbs[i] + "_sm.jpg";
thumbNail.className = "thumbNails";
thumbNail.id = adID + '_' + thumbs[i];
picThumbs.appendChild(thumbNail);
picImage.appendChild(picThumbs);
addHandler(adID, thumbs[i], 1);
}
}
var previewImageContainer = document.createElement('div');
var previewImage = document.createElement('img');
previewImage.id = 'full_' + adID;
previewImage.src = "../images/listings/" + adID + "_" + "1_.jpg";
previewImage.className = 'thumbNails';
previewImageContainer.style.width = "700px";
previewImageContainer.style.textAlign = 'center';
previewImageContainer.appendChild(previewImage);
picImage.appendChild(previewImageContainer);
var closeButton = document.createElement('img')
closeButton.src = '../images/close_pix.png';
closeButton.id = 'close_' + adType[0] + '_' + adID;
picImage.appendChild(closeButton);
addHandler(adID, 'close_' + adType[0], 2);
$("#" + adType[0] + '_' + adID).show('blind', {}, '1300');
$("#butShowImage_" + thumbs[0]).attr("src", "../images/close_pix.png");
$("#butShowImage_" + thumbs[0]).removeClass('hidden').addClass('shown');
}
Is there a way of stipping that onclick command off?
Thanks for you help!
I prefer to .delegate() and .undelegate() methods for binding event stuffs like that. Delegate is little bit different from the .bind() and .live() methods
Here is the great explaination about the diffrences
http://net.tutsplus.com/tutorials/javascript-ajax/quick-tip-the-difference-between-live-and-delegate/
kep it in mind :)
There are several issues I would fix with your code. First and foremost is to separate your functionality from your markup. This means removing the onclick events that you are firing through your elements. Now, this gets more complicated because you are passing PHP vars through your markup as well.
So your issue can be broken down into to sections.
Removing event handlers from your markup. This is, among other things i'm sure, a poor way to organize functionality.
A more robust means of communicating between JavaScript/PHP. In order to really take advantage of the web-based environment it will save you a lot of trouble passing variables in a more organized fashion. I would recommend looking into pairing Ajax with JSON. jQuery has some good implementations of both of these ($.Ajax() | $.parseJSON()).
The primary goal here is to clean up your markup to make it more readable, and better contain the different functions of your application - Functionality, Style, and information.
First, clean up your element a bit by removing the onclick event listener
<img src="example.jpg" id="someId_<?php echo id;?>;" class="showPixFeature" />'
Second, attach your event listener in whichever fashion you would like. Use $.delegate if you are dynamically generating the images. Use $.bind if you are not.
$('.showPixFeature').bind('click', function(){
// this function will execute everytime an element is clicked
// you have access to the element ID here via 'this' because
// the functions context is the element that fires it
alert($(this).attr('id'));
});
You can then remove the bound event with $.unbind() API. OR $.undelegate if you are using delegate. From here we can add an ajax call to
$('#my-element').unbind('click');
You could use .one(). It's the same as using .bind(), and it unbinds it after it's used once.

Categories