Link to home
Start Free TrialLog in
Avatar of andrewembassy
andrewembassy

asked on

Help understanding object variables within methods

So I've written a class intended to streamline the use of the prototype implementation of ajax, and things seem to be working pretty well.  The class has three functions - one that submits the parameters to the PHP document (ajaxx), one that displays the 'loading...' message div (showLoad), and one that inserts the returned data into a container div (showResponse).

I've attached the object as I've written it.

Now, when I instatiate this object in my page I go like this:

<script src="js/prototype.js" type="text/javascript"></script>
<script src="js/ajaxxer.js" type="text/javascript"></script>
<script type="text/javascript">
      var news = new ajaxxer("news_processor.php","loading_news","news_container");
      news.ajaxx("pg=1");
</script>

Everything goes according to plan, including getting back good data from my target PHP file, except inside of either showLoad() or showResponse() the object variables showLoading_id and container_id don't seem to be available (see lines 26 and 35) it returns them as undefined.

My question is- how do I make it so those object variables are available to those functions?  Any exposition on why they aren't available to those functions currently would be awesome too, I'm only a beginner at javascript.

Thanks!
 
function ajaxxer(target,showLoading_id,container_id) {
	this.target = target;
	this.showLoading_id = showLoading_id;
	this.container_id = container_id;
	this.okay = false;
}
 
ajaxxer.prototype.ajaxx = function(pars) {	
 
	alert(parent.container_id) // returns correct value
	alert(this.showLoading_id) // returns correct value
 
	this.okay = false;
	document.getElementById(this.showLoading_id).style.display = 'none';
	this['ajaxin'] = true;
	var myAjax = new Ajax.Request( this.target, {method: 'get', parameters: pars, onLoading: this.showLoad, onComplete: this.showResponse} );
}
 
ajaxxer.prototype.showLoad = function() {
 
	alert(this.showLoading_id) // returns undefined
	
	if (this.ajaxin) {
		document.getElementById(this.showLoading_id).style.display = 'block';
	}
}
 
ajaxxer.prototype.showResponse = function(originalRequest) {
	
	alert(parent.container_id) // returns undefined
	
	this.ajaxin = false;
	var newData = originalRequest.responseText;
	
	//alert(this.newData) // returns good data from target.php
 
	document.getElementById(this.container_id).innerHTML = newData;
}

Open in new window

Avatar of hielo
hielo
Flag of Wallis and Futuna image

You are running into scoping issues. By time showLoad is executed, this refers to the Ajax.Request object, no the ajaxx object. Try the code below:


ajaxxer.prototype.ajaxx = function(pars) {	
 var self = this;
	alert(parent.container_id) // returns correct value
	alert(this.showLoading_id) // returns correct value
 
	this.okay = false;
	document.getElementById(this.showLoading_id).style.display = 'none';
	this['ajaxin'] = true;
	var myAjax = new Ajax.Request( this.target, {method: 'get', parameters: pars, onLoading: self.showLoad, onComplete: this.showResponse} );
}

Open in new window

For a thorough discussion of this phenomenon, visit:

http://www.brockman.se/writing/method-references.html.utf8

It is fascinating reading ;-)
Avatar of andrewembassy
andrewembassy

ASKER

Okay, so I made a few changes based on Hielo's suggestion- but I still seem to be running into the same issue.  I've placed alerts in the code and what they return.

Upon reading some of the article Badotz posted, it seems like I need to do something akin to this:

function GreetingButton(greeting) {
    this.greeting = greeting;
    this.element = document.createElement("button");
    this.element.appendChild(document.createTextNode("Receive Greeting"));
    this.element.onclick = function () {
        greetingButton.greet();
    };
    this.element.onclick = createMethodReference(this, "greet");
}

GreetingButton.prototype.greet = function () {
    alert(this.greeting);
};

function createMethodReference(object, methodName) {
    return function () {
        object[methodName]();
    };
};

onload = function () {
    gb = new GreetingButton("Hello!");
    document.body.appendChild(gb.element);
    gb.greet();
};

But I don't have the mental wherewithal (at this point) to figure out how I would apply that principle to my code.  Can anybody help me?

Also: In that article there are lines that have a strikethrough on them- I've never encountered that before, what is it?  How do I signify that in my code?

Thanks for your help!
function ajaxxer(target,showLoading_id,container_id) {
	this.target = target;
	this.showLoading_id = showLoading_id;
	this.container_id = container_id;
	this.okay = false;
}
 
ajaxxer.prototype.ajaxx = function(pars) {      
 var self = this;
        alert(parent.container_id) // now returns undefined
        alert(this.showLoading_id) // returns correct value
 
        this.okay = false;
        document.getElementById(this.showLoading_id).style.display = 'none';
        this['ajaxin'] = true;
        var myAjax = new Ajax.Request( this.target, {method: 'get', parameters: pars, onLoading: self.showLoad, onComplete: self.showResponse} );
}
 
ajaxxer.prototype.showLoad = function() {
 
	alert('showloading: ' + this.showLoading_id) // returns undefined
	
	if (this.ajaxin) {
		document.getElementById(this.showLoading_id).style.display = 'block';
	}
}
 
ajaxxer.prototype.showResponse = function(originalRequest) {
	
	alert('showResponse: ' + this.container_id) // returns undefined
	
	this.ajaxin = false;
	var newData = originalRequest.responseText;
	
	//alert(this.newData) // returns good data from target.php
 
	document.getElementById(this.container_id).innerHTML = newData;
}

Open in new window

>>Also: In that article there are lines that have a strikethrough on them- I've never encountered that before, what is it?  How do I signify that in my code?

As he progresses further and further, lines that are no longer required are struck through. They are not included in your code; they are meant to be excluded.
That makes sense.  Any ideas on how to apply the principle in the article to my code?  It seems like I'm going to need to include this function:

function createMethodReference(object, methodName) {
    return function () {
        object[methodName]();
    };
};

And call it when I call the other functions, correct?  I guess I'm too much of a javascript beginner to figure out the syntax for all that...
In a nutshell
/*
 The following encapsulates the "bind" and "bindEventListener"
 examples. You can include this in your main javascript file,
 and it will silently execute and wait to bind your functions
*/
 
(function ()
{
	/*
	 * ####################################
	 * OO Event Listening through Partial 
	 * Application in JavaScript, from
	 * http://www.brockman.se/writing/method-references.html.utf8
	 * ####################################
	*/
	var wipe = new Object;
	
	Function.prototype.bind = function(object) 
	{
		var method = this;
		
		var oldArguments = toArray(arguments).slice(1);
		
		return function(argument) 
		{
			if (argument === wipe)
			{
				method = null;
				oldArguments = null;
			}
			else if (method == null)
			{
				throw "Attempt to invoke destructed method reference.";
			}
			else
			{
				var newArguments = toArray(arguments);
				
				return method.apply(object, oldArguments.concat(newArguments));
			}
		};
	}
// ------------------------------------
 
	Function.prototype.bindEventListener = function(object) 
	{
	    var method = this;
	    
	    var oldArguments = toArray(arguments).slice(1);
	    
	    return function(event) { return method.call(object, event || window.event, oldArguments); }
	}
// ------------------------------------
 
	function toArray(pra) { var ra = []; for (var i = 0; i < pra.length; i++) { ra.push(pra[i]); }; return ra; }
})();
 
/*
To bind a function, do something like this:
*/
 
var obj = document.getElementById('YOUR_HTML_ELEMENT');
obj.onchange = this.YOUR_EVENT_HANDLER_NAME_HERE.bindEventListener(this, YOUR_ARGUMENT_#1, YOUR_ARGUMENT_#2, etc);
 
/*
Then define an event handler
*/
 
function YOUR_EVENT_HANDLER_NAME_HERE(e, args)
{
    //
    // 'e' will hold a reference to the element that triggered the event
    // kind of like it was 'this'
    //
    e = ((e.target) ? e.target : e.srcElement); // FF vs IE event model
    //
    // You can pull out the arguments if you wish
    //
    var arg0 = args[0];
    var arg1 = args[1];
//  continue with assignment of all passed arguments
//
// Add your code here
}

Open in new window

Hey Badotz -

Thanks for your help so far.  I *kind of* understand what's happening here, but I think my understanding of event handlers and stuff is too minute to get me to a workable solution on my own-

I don't really understand why I need to bind the function to an HTML element- ideally I'll be calling this with an onClick, or from within the script itself-

      var news = new ajaxxer("news_processor.php","loading_news","news_container");
      news.ajaxx("pg=1");

I'm well versed in PHP and I can tell this is mostly due to my lack of working knowledge with javascript, but I don't know how to combine the code you've provided with the code I've posted above...
The reason I use the "bind" and "bindEventListener" methods is because it handles the scope of the "this" elements.

Also, it seems (to me) to be a cleaner way of handling events, because there is no browser sniffing involved.
Yeah, that's largely greek to me.  I'll ask elsewhere.
Sorry :-/
It's okay- I think it's one of those things where my reach exceeds my grasp.  At this point I basically need somebody to hold my hand and basically do it for me explaining each step, and I don't want to waste your time.
Just for grins, see if this makes any difference:
ajaxxer.prototype.ajaxx = function(pars) {      
 
        alert(parent.container_id) // returns correct value
        alert(this.showLoading_id) // returns correct value
 
        this.okay = false;
        document.getElementById(this.showLoading_id).style.display = 'none';
        this['ajaxin'] = true;
	var self = this; // for the closure
        var myAjax = function() {
		return new Ajax.Request(self.target, {method: 'get', parameters: pars, onLoading: self.showLoad, onComplete: self.showResponse});
	}
}

Open in new window

That's similar to what Helio posted earlier- which makes sense- set the current ajaxxer object to 'self', and then use 'self' as the reference instead of 'this' - but the problem persists,

alert(parent.container_id) returns undefined (but if you switch it to this.container_id) it works,
alert(this.showloading_id) returns correctly.

But it looks like the other functions (ajaxxer.showLoad and ajaxxer.sjowResponse) aren't running.
Okay, so what if we tried to stick the variables into the Ajax object before we call the Request?

I'm sure I'm mangling the code below, but if the idea is that by the time showLoad and showResponse are called, they thing 'this' refers to the Ajax object, then wouldn't it make sense to try to stick those variables into the Ajax object so they'd be there?   Maybe a little ungraceful, but it could work...

When I run the code below, I get an error: "missing ; before statement" on the line "var Ajax.container_id = this.container_id;"  Again, this is my own brain thinking "well maybe this will work" and not informed about javascript syntax, so I'm sure I'm screwing something up.  Is my general idea okay though?

function ajaxxer(target,showLoading_id,container_id) {
	this.target = target;
	this.showLoading_id = showLoading_id;
	this.container_id = container_id;
	this.okay = false;
}
 
 
ajaxxer.prototype.ajaxx = function(pars) {      
 
        alert(this.container_id) // returns correct value
        alert(this.showLoading_id) // returns correct value
 
        this.okay = false;
        document.getElementById(this.showLoading_id).style.display = 'none';
        this['ajaxin'] = true;
 
        var myAjax = function() {
                new Ajax();
				var Ajax.container_id = this.container_id;
				var Ajax.showLoading = this.showLoading_id;
				Ajax.Request(this.target, {method: 'get', parameters: pars, onLoading: this.showLoad, onComplete: this.showResponse});
        }
		
}
 
ajaxxer.prototype.showLoad = function() {
 
	alert('showloading: ' + this.showLoading_id) // returns undefined
	
	if (this.ajaxin) {
		document.getElementById(this.showLoading_id).style.display = 'block';
	}
}
 
ajaxxer.prototype.showResponse = function(originalRequest) {
	
	alert('showResponse: ' + this.container_id) // returns undefined
	
	this.ajaxin = false;
	var newData = originalRequest.responseText;
	
	//alert(this.newData) // returns good data from target.php
 
	document.getElementById(this.container_id).innerHTML = newData;
}

Open in new window

You have the correct idea, but seem to be having trouble implementing. Probably because you may be having difficulty "what" is executing at some point in time.
On another note, not sure what this does:
 new Ajax();

since there is nothing that "catches" the new object. Typically when you create object, there is a "receiving" variable:
var x = new Ajax();

Look at the code below. Notice how I saved "this" onto a variable "self" at the beginning of ajaxxer.prototype.ajaxx:
function ajaxxer(target,showLoading_id,container_id) {
	this.target = target;
	this.showLoading_id = showLoading_id;
	this.container_id = container_id;
	this.okay = false;
}
 
 
ajaxxer.prototype.ajaxx = function(pars) {      
 var self=this;
        alert(this.container_id) // returns correct value
        alert(this.showLoading_id) // returns correct value
 
        this.okay = false;
        document.getElementById(this.showLoading_id).style.display = 'none';
        this['ajaxin'] = true;
 
        var myAjax = function() {
                new Ajax();
				var Ajax.container_id = self.container_id;
				var Ajax.showLoading = self.showLoading_id;
				Ajax.Request(self.target, {method: 'get', parameters: pars, onLoading: self.showLoad, onComplete: self.showResponse});
        }
		
}
 
ajaxxer.prototype.showLoad = function() {
 
	alert('showloading: ' + this.showLoading_id) // returns undefined
	
	if (this.ajaxin) {
		document.getElementById(this.showLoading_id).style.display = 'block';
	}
}
 
ajaxxer.prototype.showResponse = function(originalRequest) {
	
	alert('showResponse: ' + this.container_id) // returns undefined
	
	this.ajaxin = false;
	var newData = originalRequest.responseText;
	
	//alert(this.newData) // returns good data from target.php
 
	document.getElementById(this.container_id).innerHTML = newData;
}
 

Open in new window

Okay- the other night (before you posted the last one) I got it to work with the below code.  But now it's not working on Macs.  It works brilliantly on FF for PC, and intermittently on IE for PC, but on FF and Safari on mac it just sits there.  Any ideas?  No errors are thrown.

function ajaxxer(target,loading_id,container_id,ajaxMethod,ajaxOutputMode) {
	this.target = target;
	this.loading_id = loading_id;
	this.container_id = container_id;
	this.ajaxMethod = ajaxMethod; // GET or POST
	this.ajaxOutputMode = ajaxOutputMode;  // innerHTML or value
}
 
ajaxxer.prototype.ajaxx = function(pars) {      
 
	document.getElementById(this.loading_id).style.display = 'block';
 
	//alert(document.getElementById(this.loading_id).style.display);
	
	ajaxUrl = this.target + '?' + pars;
 
	// sniff browser
	if (document.all && !window.opera) {
		var ajaxHttp = new ActiveXObject("Microsoft.XMLHTTP");
	} else {
		var ajaxHttp= new XMLHttpRequest();
	}
 
	ajaxOutputMode = this.ajaxOutputMode;
	container_id = this.container_id;
	loading_id = this.loading_id;
 
	//alert(ajaxHttp);
	ajaxHttp.open(this.ajaxMethod, ajaxUrl); 
	ajaxHttp.onreadystatechange = function () {
		patchThru(ajaxOutputMode,container_id,loading_id); 
	};	
 
	function patchThru(ajaxOutputMode,container_id,loading_id) {
		if (ajaxHttp.readyState != 4) {
			// Show "Loading..."
		} else if (ajaxHttp.readyState == 4) {
			var ajaxResponse = ajaxHttp.responseText; 
			if (ajaxOutputMode == "innerHTML") {
				document.getElementById(container_id).innerHTML = ajaxResponse;
			} else if (ajaxOutputMode == "value") {
				document.getElementById(container_id).value = ajaxResponse;
			}
			document.getElementById(loading_id).style.display = 'none';
		} 
	}
	ajaxHttp.send(null);
}

Open in new window

Here's an example of where I'm at now: http://localhost/velella/index_sha.php

This page represents three iterations of the class, one for the news section, one for the shows section and one for the press section.

It works in firefox, but not in IE, or on Macs.

Now, when I add an alert anywhere early on, it seems to slow things down enough so each iteration of the class is able to execute smoothly, and each section loads itself fine.

Here's the most recent version of the javascrtipt.

It seems to me that the issue here is that in javascript, variables are global (right?) so the 'self' that we create isn't really a unique thing; each time we create a new 'self', it overwrites any of the other selfs that have been created by the other initiations of the ajaxxer object.  Is that right?

the 'patchThru' function is a stopgap to attempt to pass variables on to the onreadystatechange function.
function ajaxxer(target,loading_id,container_id,ajaxMethod,ajaxOutputMode) {
	this.target = target;
	this.loading_id = loading_id;
	this.container_id = container_id;
	this.ajaxMethod = ajaxMethod; // GET or POST
	this.ajaxOutputMode = ajaxOutputMode;  // innerHTML or value
}
 
ajaxxer.prototype.ajaxx = function(pars) {      
 
	self = this;
 
	ajaxUrl = this.target + '?' + pars;
 
	// sniff browser
	if (document.all && !window.opera) {
		self.ajaxHttp = new ActiveXObject("Microsoft.XMLHTTP");
	} else {
		self.ajaxHttp= new XMLHttpRequest();
	}
 
	//alert(ajaxHttp);
	self.ajaxHttp.open(this.ajaxMethod, ajaxUrl); 
	self.ajaxHttp.onreadystatechange = function () {
		patchThru(self.ajaxOutputMode,self.container_id,self.loading_id); 
	};	
 
	function patchThru(ajaxOutputMode,container_id,loading_id) {
		if (self.ajaxHttp.readyState != 4) {
			document.getElementById(self.loading_id).style.display = 'block';
		} else if (self.ajaxHttp.readyState == 4) {
			var ajaxResponse = self.ajaxHttp.responseText; 
			if (ajaxOutputMode == "innerHTML") {
				document.getElementById(container_id).innerHTML = ajaxResponse;
			} else if (ajaxOutputMode == "value") {
				document.getElementById(container_id).value = ajaxResponse;
			}
			document.getElementById(loading_id).style.display = 'none';
		}
	}
	self.ajaxHttp.send(null);
}

Open in new window

Then use:

var self = this;
var self = this didn't help at all.  It still works in FF on PC, but nothing else.

This is really frustrating to me. I appreciate your ideas, but if you had tried out any of them yourself you would have found that they don't work.  I thought paying for a membership here would mean I'd have people willing to work with me to solve an issue, not offer pat suggestions or point me in the direction of volumes of documentation that I don't have the wherewithal to understand. I can get those on any free messageboard.

Here's the code as it stands now, if anybody else has any ideas.
function ajaxxer(target,loading_id,container_id,ajaxMethod,ajaxOutputMode) {
	this.target = target;
	this.loading_id = loading_id;
	this.container_id = container_id;
	this.ajaxMethod = ajaxMethod; // GET or POST
	this.ajaxOutputMode = ajaxOutputMode;  // innerHTML or value
}
 
ajaxxer.prototype.ajaxx = function(pars) {      
 
	var self = this;
 
	ajaxUrl = this.target + '?' + pars;
 
	// sniff browser
	if (document.all && !window.opera) {
		self.ajaxHttp = new ActiveXObject("Microsoft.XMLHTTP");
	} else {
		self.ajaxHttp= new XMLHttpRequest();
	}
 
	self.ajaxHttp.open(this.ajaxMethod, ajaxUrl); 
	
	self.ajaxHttp.onreadystatechange = function () {
		if (self.ajaxHttp.readyState != 4) {
			document.getElementById(self.loading_id).style.display = 'block';
		} else if (self.ajaxHttp.readyState == 4) {
			var ajaxResponse = self.ajaxHttp.responseText; 
			if (self.ajaxOutputMode == "innerHTML") {
				document.getElementById(self.container_id).innerHTML = ajaxResponse;
			} else if (self.ajaxOutputMode == "value") {
				document.getElementById(self.container_id).value = ajaxResponse;
			}
			document.getElementById(self.loading_id).style.display = 'none';
		}
	};	
	self.ajaxHttp.send(null);
}

Open in new window

sorry guys, I'm a jerk.  Just been banging my head against this for 48 hours and am at my end.
While waiting for the server to complete (onreadtstate == 4), I start a timer-based function to display a status bar. Perhaps this is something you could do, rather than all that mishmash in your callback function?

But really, is there any reason you are not using a cross-browser javascript library to do this? There are any number of them to pick from:

prototype
YUI (Yahoo!)
jQuery
ext

to name a few. I only ask to try and figure out what it is you are *really* trying to do.
ASKER CERTIFIED SOLUTION
Avatar of andrewembassy
andrewembassy

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
That isn't necessary, although I *do* feel all warm and fuzzy ;-)

Keep the poinks for later on when someone actually provides you a solution.

Serioulsy, read that stuff from Brockman and fiddle with it until it makes sense. If nothing else, it will expand the size of the box you work in ;-)

Douglas Crockford has some interesting javascript stuff, too:

http://www.crockford.com/

JSON would replace XML if he had his way. I am leaning towards it myself.
Thanks for your help Badotz- I'll read that stuff as I have time.  The worst part about web design is no matter what language you speak, there's always a ton more to learn!