Is there a way to prevent 'this' from changing when I end the function?

I want all buttons to take action before and after the normal onclick event. So I came up with the "brilliant" idea of ​​traversing all these elements and creating a wrapper function.

This turned out to be very good when I tested it, but when I included it in our application, it fell apart. I traced it back to the 'this' value changed by my wrapper. The sample code illustrates this; before you apply the event handlers, each button will display the button ID when you click, but after it displays the display name is "undefined" in this example, or "Form1" if you ran it from a form.

Does anyone know a better way to do the same? Or is it a good way to keep the originally calculated 'this' values?

As you can imagine, I don't want to change any of the existing event handler code in the target buttons.

Thanks in advance.

PS - target browser is IE6 and above, no cross-browser function required

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
<script language="javascript" type="text/javascript">
    function btnWrap_onClick()
    {
        var btns = document.getElementsByTagName("button");
        for( var i = 0; i < btns.length; i++)
        {
            var btn = btns[i];

            // handle wrap button differerntly
            if( "btnWrap" == btn.id)
            {
                btn.disabled = true;
                continue; // skip this button
            }

            // wrap it
            var originalEventHandler = btn.onclick;
            btn.onclick = function()
            {
                alert("Starting event handler");
                originalEventHandler();
                alert("Finished event handler");
            }
        }

        alert("Buttons wrapped successfully");
    }
</script>
<body>
    <p>
    <button id="TestButton1" onclick="alert(this.id);">TestButton1</button>
    <button id="TestButton2" onclick="alert(this.id);">TestButton2</button>
    </p>
    <button id="btnWrap" onclick="btnWrap_onClick();">Wrap Event Handlers</button>
</body>
</html>

      

+1


source to share


3 answers


As Paul Dixon said, you can use invocation , but I suggest you use instead .

However, the reason I answer is because I found an alarming error: You are actually replacing all event handlers with the last button event handler. I don't think this was what you intended, do you? (Hint: you are replacing the value for originalEventHandler on each iteration)

You will find a working cross-browser solution in the code below:

function btnWrap_onClick()
{
    var btns = document.getElementsByTagName("button");
    for( var i = 0; i < btns.length; i++)
    {
        var btn = btns[i];

        // handle wrap button differerntly
        if( "btnWrap" == btn.id)
        {
            btn.disabled = true;
            continue; // skip this button
        }

        // wrap it

        var newOnClick = function()
        {
            alert("Starting event handler");
            var src=arguments.callee;
            src.original.apply(src.source,arguments);
            alert("Finished event handler");
        }
        newOnClick.original = btn.onclick; // Save original onClick
        newOnClick.source = btn; // Save source for "this"
        btn.onclick = newOnClick; //Assign new handler
    }
alert("Buttons wrapped successfully");
}

      



First, I create a new anonymous function and store it in the newOnClick variable. Since a function is an object, I can create properties on a function object like any other object. I use this to create an original property that is the original onclick handler, and the origin is the original element that will be when the original handler is called.

Inside the anonymous function, I need to get the function reference in order to get the value of the original and source properties. Since the anonymous function has no name, I use arguments.callee (which is supported since MSIE5.5) to get this reference and store it in the src variable.

Then I use the apply method to execute the original onclick handler. apply takes two parameters: the first will be the value of this, and the second will be an array of arguments. it should be the element that the original onclick handler was attached to and this value was stored in the source. arguments are an internal property of all functions and contain all the arguments with which the function was called (note that the anonymous function has no defined parameters, but if it is called with some parameters anyway, they will be found in the arguments property).

The applicable reason is that I can redirect all the arguments that the anonymous function was called with, and makes that function transparent and cross-browser. (Microsoft puts the event on window.event, but other browsers supply it in the first parameter of the handler call)

+3


source


You can use the call method to resolve the binding, eg.originalEventHandler.call(btn);

Alternatively, a prototype-like library might help - its bind method allows you to build a new function bound to the specified object, so you would declare originalEventHandler asvar originalEventHandler = btn.onclick.bind(btn);



Finally, for good support for binding problems, see also Exiting Binding Situations in JavaScript

+4


source


Your problem is how blocking works in JavaScript. To be honest, I would recommend using a framework. Either of these should make event handling much nicer than doing it manually.

+1


source







All Articles