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

J

Joakim Braun

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>
 
M

Michael Winter

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
 
J

Joakim Braun

"Michael Winter" <[email protected]> skrev i meddelandet

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

You're doing fine.

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?

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
 
M

Michael Winter

"Michael Winter" <[email protected]> skrev i meddelandet

[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
( said:
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.
 
M

Martin Honnen

Joakim said:
"Michael Winter" <[email protected]> skrev i meddelandet
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.
 
J

Joakim Braun

Martin Honnen said:
Joakim said:
"Michael Winter" <[email protected]> skrev i meddelandet
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
 
J

Joakim Braun

"Michael Winter" <[email protected]> skrev i meddelandet
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)


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
 
M

Michael Winter

On Sun, 26 Dec 2004 10:25:41 +0100, Joakim Braun

[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
 

Ask a Question

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

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,995
Messages
2,570,230
Members
46,819
Latest member
masterdaster

Latest Threads

Top