Set event handler to global object's method...?

Discussion in 'Javascript' started by Joakim Braun, Dec 22, 2004.

  1. Joakim Braun

    Joakim Braun Guest

    Why doesn't the below code work?

    I'm trying to create a global object and set an event handler to one of its
    methods. The function is called, but the object's mTest property is
    undefined.

    (What I'm trying to do is make a general-purpose solution for the situation
    where you have a list box with several associated form elements. When the
    element values are changed, you want to update the value of the selected
    list option, and when the list selection changes, you want to update the
    form elements. So here you could have a "watcher" object that is constructed
    with a bunch of element names and handles that stuff without any custom
    code.)

    Joakim Braun

    <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"
    "http://www.w3.org/TR/REC-html40/loose.dtd">
    <html>
    <head>
    <title>Test</title>
    <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">

    <script type="text/javascript">

    function cWatcher(){

    this.mTest = "Testing";
    this.wire = cWatcher_Wire;
    this.changeFunc = cWatcher_Changed;
    }


    function cWatcher_Wire( inFormName,
    inElementName){

    document.forms[inFormName].elements[inElementName].onchange =
    this.changeFunc;

    }

    function cWatcher_Changed(){

    alert("Changed, test=" + this.mTest);

    }
    </script>
    </head>

    <body>
    <form id="form1" action="">
    <select id="obj1">
    <option value="1">Data</option>
    <option value="2">More data</option>
    </select>
    </form>

    <script type="text/javascript">

    window.gWatcher = new cWatcher();
    window.gWatcher.wire("form1","obj1");
    </script>
    </body>
    </html>
    Joakim Braun, Dec 22, 2004
    #1
    1. Advertising

  2. On Wed, 22 Dec 2004 22:51:27 +0100, Joakim Braun
    <> wrote:

    > Why doesn't the below code work?
    >
    > I'm trying to create a global object and set an event handler to one of
    > its methods. The function is called, but the object's mTest property is
    > undefined.


    Read the following slowly. I always find it difficult to word properly. :(

    In the case of using the this operator in a function, it is set by the
    caller when the function is called. That is, the caller determines what
    this refers to when the call is made.

    When you assign a function reference to the property of an object and then
    call that function as a method, the this operator will refer to said
    object:

    var myObject = new Object();
    function myFunction() {}

    /* myFunction is a property of the global object so when the this
    * operator is used, it will refer to the global object.
    */
    myFunction();

    /* myFunction is now called as a method of the object, myObject.
    * Here the this operator will refer to myObject.
    */
    myObject.myMethod = myFunction;
    myObject.myMethod()

    So, when you assign a method from one object to another, the this operator
    won't point to the original object, it will point to the one it was called
    from.

    One way around this is to use a closure and reserve the this operator for
    getting the form element.

    function Watcher() {
    /* Define data here as local variables. */
    var test = 'Testing...';

    function change() {
    /* When this function is called as a result of the change
    * event, the this operator will refer to the form control
    * that triggered the event.
    */
    alert('Changed (test=' + test + ')');
    }

    this.wire = function(form, element) {
    document.forms[form].elements[element].onchange = change;
    };
    }

    var obj = new Watcher();
    obj.wire('form1', 'obj1');

    [snip]

    > function cWatcher(){
    >
    > this.mTest = "Testing";
    > this.wire = cWatcher_Wire;
    > this.changeFunc = cWatcher_Changed;
    > }
    >
    >
    > function cWatcher_Wire( inFormName,
    > inElementName){
    >
    > document.forms[inFormName].elements[inElementName].onchange =
    > this.changeFunc;
    >
    > }
    >
    > function cWatcher_Changed(){
    >
    > alert("Changed, test=" + this.mTest);
    >
    > }


    Those two functions should be added via the prototype:

    function cWatcher() {
    this.mTest = 'Testing';
    }
    cWatcher.prototype.wire = function(form, element) {
    document.forms[form].elements[element].onchange = this.changeFunc;
    };
    cWatcher.prototype.changeFunc = function() {
    alert("Changed, test=" + this.mTest);
    };

    Obviously, that will still suffer from the original problems. It was just
    a technical suggestion.

    [snip]

    > window.gWatcher = new cWatcher();
    > window.gWatcher.wire("form1","obj1");


    The 'window' isn't necessary.

    var gWatcher = new cWatcher();
    gWatcher.wire('form1', 'obj1');

    The var keyword in this instance isn't either; I just think it's good form.

    [snip]

    Hope that helps,
    Mike

    --
    Michael Winter
    Replace ".invalid" with ".uk" to reply by e-mail.
    Michael Winter, Dec 23, 2004
    #2
    1. Advertising

  3. Joakim Braun

    Joakim Braun Guest

    "Michael Winter" <> skrev i meddelandet
    news:eek:psjfpb8fxx13kvk@atlantis...

    <snip>

    > Read the following slowly. I always find it difficult to word properly. :(


    You're doing fine.

    <snip>

    > One way around this is to use a closure and reserve the this operator for
    > getting the form element.
    >
    > function Watcher() {
    > /* Define data here as local variables. */
    > var test = 'Testing...';
    >
    > function change() {
    > /* When this function is called as a result of the change
    > * event, the this operator will refer to the form control
    > * that triggered the event.
    > */
    > alert('Changed (test=' + test + ')');
    > }
    >
    > this.wire = function(form, element) {
    > document.forms[form].elements[element].onchange = change;
    > };
    > }
    >
    > var obj = new Watcher();
    > obj.wire('form1', 'obj1');


    OK, I think I get that, a bit vaguely. Couldn't figure out how to establish
    the right "this" context. And this allows for having several of these things
    around, each with its own "member variables", right?

    <snip>

    > Those two functions should be added via the prototype:
    >
    > function cWatcher() {
    > this.mTest = 'Testing';
    > }
    > cWatcher.prototype.wire = function(form, element) {
    > document.forms[form].elements[element].onchange = this.changeFunc;
    > };
    > cWatcher.prototype.changeFunc = function() {
    > alert("Changed, test=" + this.mTest);
    > };


    <snip>

    Why? (I mean, they got called anyway. Or didn't they? Is there any reason
    why functions should be treated differently than variables?)

    (I'll find that out, so don't answer if it's laborious to explain. I'm just
    longing for C++...)

    Joakim Braun
    Joakim Braun, Dec 23, 2004
    #3
  4. On Thu, 23 Dec 2004 10:43:31 +0100, Joakim Braun
    <> wrote:

    > "Michael Winter" <> skrev i meddelandet
    > news:eek:psjfpb8fxx13kvk@atlantis...


    [snip]

    >> function Watcher() {
    >> /* Define data here as local variables. */
    >> var test = 'Testing...';
    >>
    >> function change() {
    >> /* When this function is called as a result of the change
    >> * event, the this operator will refer to the form control
    >> * that triggered the event.
    >> */
    >> alert('Changed (test=' + test + ')');
    >> }
    >>
    >> this.wire = function(form, element) {
    >> document.forms[form].elements[element].onchange = change;
    >> };
    >> }
    >>
    >> var obj = new Watcher();
    >> obj.wire('form1', 'obj1');

    >
    > OK, I think I get that, a bit vaguely.


    It works on the principle that inner functions like change, and the
    anonymous function expression assigned to this.wire, can access variables
    in surrounding scopes. It avoids the problem where the this operator won't
    refer to the Watcher object by providing direct access to the data.

    A technical discussion of closures can be found in the FAQ notes
    (<URL:http://www.jibbering.com/faq/faq_notes/closures.html>). Another
    related text can be found on Douglas Crockford's website
    (<URL:http://www.crockford.com/javascript/private.html>).

    > Couldn't figure out how to establish the right "this" context.


    Could you elaborate? Perhaps show what you've tried and explain what you
    expected.

    > And this allows for having several of these things around, each with its
    > own "member variables", right?


    Absolutely. Each time a Watcher object is created, any "private" data
    (like test) will be unique to that object.

    >> Those two functions should be added via the prototype:
    >>
    >> function cWatcher() {
    >> this.mTest = 'Testing';
    >> }
    >> cWatcher.prototype.wire = function(form, element) {
    >> document.forms[form].elements[element].onchange =
    >> this.changeFunc;
    >> };
    >> cWatcher.prototype.changeFunc = function() {
    >> alert("Changed, test=" + this.mTest);
    >> };

    >
    > Why?


    Why via the prototype? As I said, it's a technical correction; using the
    prototype is the "proper" way to add methods to achieve the result you
    were aiming form. Also, it means you aren't adding addition identifiers to
    the global namespace.

    > Is there any reason why functions should be treated differently than
    > variables?)


    I'm afraid I'm not sure I follow[1]. What special treatment do you see?

    [snip]

    Mike


    [1] I've probably forgotten what I thought when I first read about this
    stuff, so please forgive my current inability to relate.

    --
    Michael Winter
    Replace ".invalid" with ".uk" to reply by e-mail.
    Michael Winter, Dec 23, 2004
    #4
  5. Joakim Braun wrote:

    > "Michael Winter" <> skrev i meddelandet
    > news:eek:psjfpb8fxx13kvk@atlantis...


    >>Those two functions should be added via the prototype:
    >>
    >> function cWatcher() {
    >> this.mTest = 'Testing';
    >> }
    >> cWatcher.prototype.wire = function(form, element) {
    >> document.forms[form].elements[element].onchange = this.changeFunc;
    >> };
    >> cWatcher.prototype.changeFunc = function() {
    >> alert("Changed, test=" + this.mTest);
    >> };

    >


    > Why? (I mean, they got called anyway. Or didn't they? Is there any reason
    > why functions should be treated differently than variables?)


    It is a matter of style and efficiency, in a language like Java or C++
    all instances created with
    new Watcher()
    would share the same methods but in JavaScript if you do
    function Watcher () {
    this.changeFunc = function () { ... }
    }
    then each time you create a
    new Watcher()
    a new function is created and assigned to the changeFunc property.
    If you use
    Watcher.prototype.changeFunc = function () { ... }
    then all instances created with
    new Watchwer()
    share that single function object.


    --

    Martin Honnen
    http://JavaScript.FAQTs.com/
    Martin Honnen, Dec 23, 2004
    #5
  6. Joakim Braun

    Joakim Braun Guest

    "Martin Honnen" <> skrev i meddelandet
    news:41cafa6c$0$29409$-online.net...
    > Joakim Braun wrote:
    >
    > > "Michael Winter" <> skrev i meddelandet
    > > news:eek:psjfpb8fxx13kvk@atlantis...

    >
    > >>Those two functions should be added via the prototype:
    > >>
    > >> function cWatcher() {
    > >> this.mTest = 'Testing';
    > >> }
    > >> cWatcher.prototype.wire = function(form, element) {
    > >> document.forms[form].elements[element].onchange = this.changeFunc;
    > >> };
    > >> cWatcher.prototype.changeFunc = function() {
    > >> alert("Changed, test=" + this.mTest);
    > >> };

    > >

    >
    > > Why? (I mean, they got called anyway. Or didn't they? Is there any

    reason
    > > why functions should be treated differently than variables?)

    >
    > It is a matter of style and efficiency, in a language like Java or C++
    > all instances created with
    > new Watcher()
    > would share the same methods but in JavaScript if you do
    > function Watcher () {
    > this.changeFunc = function () { ... }
    > }
    > then each time you create a
    > new Watcher()
    > a new function is created and assigned to the changeFunc property.
    > If you use
    > Watcher.prototype.changeFunc = function () { ... }
    > then all instances created with
    > new Watchwer()
    > share that single function object.


    I see, thanks.

    Joakim Braun
    Joakim Braun, Dec 26, 2004
    #6
  7. Joakim Braun

    Joakim Braun Guest

    "Michael Winter" <> skrev i meddelandet
    news:eek:psjgtolgsx13kvk@atlantis...
    <snip useful discussion>
    > > Couldn't figure out how to establish the right "this" context.

    >
    > Could you elaborate? Perhaps show what you've tried and explain what you
    > expected.


    I was thinking in terms of implicit (and "invisible", to the programmer)
    "this" pointers passed to C++ class member functions. (for instance, would
    someElement.onchange=someFunction pass an implicit "this" to someFunction,
    and how to change the "this" into some other object than the someElement)

    <snip>

    > >> Those two functions should be added via the prototype:

    <snip>
    > >> cWatcher.prototype.changeFunc = function() {
    > >> alert("Changed, test=" + this.mTest);
    > >> };

    > >
    > > Why?

    >
    > Why via the prototype? As I said, it's a technical correction; using the
    > prototype is the "proper" way to add methods to achieve the result you
    > were aiming form. Also, it means you aren't adding addition identifiers to
    > the global namespace.
    >
    > > Is there any reason why functions should be treated differently than
    > > variables?)

    >
    > I'm afraid I'm not sure I follow[1]. What special treatment do you see?


    this.mSomething = "variable value" (or var something = "value", plus "inner
    functions" that can access the something)
    vs
    cSomeObject.prototype.mSomething = function(){...}

    But Martin's reply explained that.

    Thanks again.

    Joakim Braun
    Joakim Braun, Dec 26, 2004
    #7
  8. On Sun, 26 Dec 2004 10:25:41 +0100, Joakim Braun
    <> wrote:

    [snip]

    > I was thinking in terms of implicit (and "invisible", to the programmer)
    > "this" pointers passed to C++ class member functions. (for instance,
    > would someElement.onchange=someFunction pass an implicit "this" to
    > someFunction, and how to change the "this" into some other object than
    > the someElement)


    Every function has an associated this value, but that value changes based
    on how the function is called. When an event listener assigned like this:

    someElement.onchange = someFunction

    is called, the this value for someFunction would be someElement.

    You can change what is used for the this value through the call method:

    func.call(obj, arg, ...);

    The function, func, would be called as if it were a method of the object,
    obj, and is passed the remaining arguments. Unfortunately, the call method
    was added as late as JScript 5.5 (though earlier to JavaScript), so you'll
    need to emulate it for the majority of users with IE 5.5 or earlier:

    if(Function.prototype && ('function' != Function.prototype.call)) {
    Function.prototype.call = function(obj, arg) {
    var prop = '__call', ret;
    while('undefined' != typeof obj[prop]) {prop += prop;}
    obj[prop] = this;
    ret = obj[prop](arg);
    delete obj[prop];
    return ret;
    };
    }

    Clearly, this only lets you pass one argument to the function, but that's
    easily changed. There is one other complication, though. If obj is an
    element reference, the use of the delete operator will cause an error in
    IE. The only option here is to use a fixed temporary property name and
    hope that there are no conflicts:

    if(Function.prototype && ('function' != Function.prototype.call)) {
    Function.prototype.call = function(obj, arg) {
    var prop = '__call';
    obj[prop] = this;
    return obj[prop](arg);
    };
    }

    [snip]

    > this.mSomething = "variable value" (or var something = "value", plus
    > "inner functions" that can access the something)
    > vs
    > cSomeObject.prototype.mSomething = function(){...}


    Oh, I see. In my mind, that had nothing to do with variables which is
    where to confusion arose.

    [snip]

    Mike


    Seasons Greetings

    --
    Michael Winter
    Replace ".invalid" with ".uk" to reply by e-mail.
    Michael Winter, Dec 26, 2004
    #8
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Replies:
    1
    Views:
    673
    Damien
    Feb 22, 2007
  2. Fred Basset
    Replies:
    8
    Views:
    188
    Fred Basset
    Sep 9, 2003
  3. Replies:
    5
    Views:
    157
    Thomas 'PointedEars' Lahn
    Jun 6, 2005
  4. trib
    Replies:
    0
    Views:
    100
  5. Rainer Hahnekamp

    global event handler

    Rainer Hahnekamp, May 31, 2007, in forum: Javascript
    Replies:
    1
    Views:
    73
    scripts.contact
    May 31, 2007
Loading...

Share This Page