4

I want to run some JS code when an image's CSS property "display" has been changed by any other JS script/functions. Is there any method to monitor that change and setup a callback function?

$(this).bind.('propertychange', function(){}) 

cannot do this, and setInterval is also a bad idea.

What else could be done?

AstroCB
  • 12,337
  • 20
  • 57
  • 73
Alix
  • 256
  • 3
  • 17
  • 3
    Why can't you override/intercept the other JS function rather than mess with binding to CSS property changes? – Jeff May 05 '12 at 02:58
  • @Jeff Thanks for your comments. The reason is I cannot change their code or their code is too complex to change. Another side, keep clean between different systems is a good practice. – Alix May 05 '12 at 03:05
  • I understand (believe me) but you could overwrite that function, do a conditional handle and then call the original function without affecting the other system. I've done it countless times without issue. – Jeff May 05 '12 at 03:14

4 Answers4

8

This is what you are looking for:

document.documentElement.addEventListener('DOMAttrModified', function(e){
  if (e.attrName === 'style') {
    console.log('prevValue: ' + e.prevValue, 'newValue: ' + e.newValue);
  }
}, false);
Marian Zburlea
  • 9,177
  • 4
  • 31
  • 37
  • @Marian - Great solution, but I can't seem to get this to work in my Chrome debugger? Do you know what I might be missing? I put an id on your profile image and attempted to hide the image using `$('#testt').attr("style","display:none");` and I don't see any log statements. Is "documentElement" supposed to be a placeholder for the actual element? Could you also include a link that describes this in more detail? Thank you. – jamesmortensen May 05 '12 at 03:24
  • @marian-zburlea - I got the same result as jmort253 . This code looks only working for Firefox not for Chrome. Is that true? – Alix May 05 '12 at 03:32
  • an alternative is to use a setInterval and listen for property change by comparing default one with current one, but this is resource consuming – Marian Zburlea May 05 '12 at 03:45
1

This is inside the legacy JavaScript files that you do not want to modify:

// this is your original, unmodified function
function originalFunction(sel) {
    alert(sel);
    $(sel).css("display","none");
}

This is in your code:

// here is a sample callback function you pass into the extended function below
function myCallback(s) {
    alert("The image with src = '" + $(s).attr("src") + "' has been modified!");
}


// here is how you can extend the function to do what you want 
  // without needing to modify the actual code above
originalFunction = (function(legacyFn, callback) {

    // 1 arg function to be returned and reassigned to originalFunction
    return function(sel) {

        // call "original" originalFunction, with alert and image hide.
        legacyFn(sel);  

        if(callback) callback(sel);  // invoke your callback
    }

})(originalFunction, myCallback);

The variable originalFunction is assigned a function that takes one argument. The function that takes one argument is returned by an anonymous, self-executing function that takes 2 arguments, the reference to the originalFunction before it is modified, and the reference to the callback function. These two function references become "locked" inside the closure so that when the originalFunction is then assigned a new value by the self-executing function, the legacyFn parameter still contains a reference to the originalFunction prior to it being modified.

In summary, at a higher level, originalFunction and myCallback are passed in as parameters to the self-executing anonymous function and are passed into the variables legacyFn and callback, and a new function is then assigned to originalFunction.

Now, when you call originalFunction('.someClassOnAnImage'), the legacyFn will fire, which will alert the selector and set the display property to none. Afterwards, the callback function, if it exists, will fire, and you'll then see:

The image with src = '.someClassOnAnImage' has been modified!

While this isn't as nice as a hypothetical or platform-specific addEventListener, it does allow you to modify the behavior of the functions in the legacy code without having to physically crack open those files and modify them. This simply extends the functions to perform additional behaviors but without needing to modify the original functions or even the original files for that matter.

You could neatly include all of your extensions in a separate JavaScript file (or whatever JavaScript file you're working in) and if you ever want to go back to the original behavior, you simply remove your extended functions.

jamesmortensen
  • 33,636
  • 11
  • 99
  • 120
  • Yes. This is an useful answer, but not work for me. Because this solution has 2 conditions. 1st: their function should not anonymous but named (even in global name speace). 2nd: I must konw all names of their functions (maybe more than one function) which changed that CSS. These conditions makes this solution mixed with their code and logic. Once they changed their code, we must update this code. Thanks for your help. :) – Alix May 08 '12 at 16:15
  • Good point! Funny you mention that. I'm actually having to go back and refactor some of my methods so that they are named. While I think this is a great practice for me and my scenario, I can see how this solution could be limited in your specific scenario. Thanks for the follow up! – jamesmortensen May 08 '12 at 20:25
1

The Answer: See this other post >> is there an alternative to DOMAttrModified that will work in webkit

The Rant: The DOM Mutation events hold the key to your problem. However, in the new wave of browser wars, Wekit and Gecko can't agree on stuff. While Gecko has DOMAttrModified, webkit has something called mutation observer (which breaks the pattern of event handlers being attached to events but hey who cares for consistency when we want to lock users/coders in right? ;)

P.S: Just adding this here for future seekers of the same wisdom.

Community
  • 1
  • 1
SpeedySan
  • 572
  • 4
  • 10
0

Building upon Jeff's suggestion, I would recommend writing a single function that modifies the image style property and then using that function as the bottleneck that all other functions must go through to modify that image style property.

function showImage(selector, callback) {
    $(selector).css("display","block");
    if(callback)
        callback();
}

function hideImage(selector, callback) {
    $(selector).css("display","none");
    if(callback)
        callback();
}

Something like the above two functions can be invoked from anywhere in your JavaScript when you must change the image CSS property. The functions also take a function as a parameter, which would be executed afterwards assuming the function was passed in as the 2nd parameter.

You could further simplify this into a single function, but I'll leave that to you as I don't know exactly what your goals are in doing this.

jamesmortensen
  • 33,636
  • 11
  • 99
  • 120
  • This method is works. But I want know if we can resolve this issue without any changes in other code. Thanks. – Alix May 05 '12 at 03:14