	if(!com) var com = {};
	if(!com.qwidget) com.qwidget = {};

	com.qwidget.View = Class.extend({
		containerQwid: undefined,
		container: undefined,
		refreshLists: {},
		cqq: com.qwidget.qwids,
		init: function (node)
		{
			if (node && this.containerQwid)
				this.container = jQuery(_qwid(this.containerQwid), node).len(1);

			var x = 0;
		},
		refresh: function (qwidMap, container)
		{
			var error = function (msg) {com.qwidget.error('View.refresh - ' + msg);}

			if (!container || container.length == 0) container = this.container;
			if (!container || container.length == 0) container = jQuery(_qwid(this.containerQwid)).len(1);
			if (!container || container.length == 0) error('unable to get viable container');
			
			
			for (var i = 0; i < this.refreshLists.functions.length; i++) // run first in case a function adds items to refreshHtml or refreshText...
			{
				this.refreshLists.functions[i](qwidMap, container, this.cqq, this.refreshLists);
			}
			for (var i = 0; i < this.refreshLists.html.length; i++)
			{
				var qwid = this.refreshLists.html[i];		if (qwid == undefined)	{error('refreshLists.html['+i+'] - qwid: undefined'); continue;}
				this.processQwid(qwid, qwidMap[qwid], container, 'html');
			}
			for (var i = 0; i < this.refreshLists.text.length; i++)
			{
				var qwid = this.refreshLists.text[i];		if (qwid == undefined)	{error('refreshLists.text['+i+'] - qwid: undefined'); continue;}
				this.processQwid(qwid, qwidMap[qwid], container, 'text');
			}
		},
		processQwid: function (qwid, value, node, mode) // mode is either 'html' or defaults to 'text'...
		{
			var error = function (msg) {com.qwidget.error('View.processQwid - ' + msg);}
			var warn = function (msg) {com.qwidget.warn('View.processQwid - ' + msg);}
			
			if (qwid == undefined)	{error('qwid: undefined'); return;}
			if (value == undefined)	{warn('value: undefined (qwid: '+qwid+')'); return;} // TODO? - should we replace undefined values with '' and process?
			
			var q = jQuery(_qwid(qwid), node);			if (q.length != 1) {warn('qwid: '+qwid+' q.length: ' + q.length); return;}
			switch (q[0].nodeName)
			{
			 case 'DIV':
			 case 'SPAN':
			 case 'TD':
			 case 'TBODY':
				mode == 'html' ? q.html(value) : q.text(value);
				break;
			 case 'IMG':
				mode == 'html' ? q.html(value) : q[0].src = value;
				break;
			 case 'INPUT':
				if (mode == 'html') {warn('html mode with INPUT tag...'); break;} // TODO? - do we need this combination?
				if (q[0].type == 'checkbox') {q[0].checked = value; break;}
				q.val(value);
				break;
			 case 'IFRAME':
				if (mode == 'html') {warn('html mode with A tag...'); break;} // TODO? - do we need this combination?
				q.attr('src', value);
				break;
			 case 'A':
				if (mode == 'html') {warn('html mode with A tag...'); break;} // TODO? - do we need this combination?
				if (typeof value == 'string') // if value is a string use it for both href and text
				{
					q[0].href = value;
					q.text(value);
				}
				else // otherwise expect object with {href: '...', text: '...'}
				{
					q[0].href = value.href;
					q.text(value.text);
				}
				break;
			 case 'SELECT':
				q.val(value);
				break;
			 default:
				error('unknown nodeName: ' + q[0].nodeName);
				break;
			}
		},
		hide: function ()
		{
			this.container.hide();
		},
		show: function ()
		{
			this.container.show();
		},
		isHidden: function ()
		{
			return this.container.is(":hidden");
		},
		qwidMapViaList: function (qwids, qwidMap, node)
		{
			// populate a qwidMap from a list of qwids
			var error = function (msg) {com.qwidget.error('View.qwidMapViaList - ' + msg);}
			var warn = function (msg) {com.qwidget.warn('View.qwidMapViaList - ' + msg);}
			
			if (qwids == undefined) {error('qwids undefined'); return;}
			
			var value = undefined;
			for (var i = 0; i < qwids.length; i++)
			{
				var qwid = qwids[i];										if (qwid == undefined) {error('qwids['+i+']: undefined'); continue;}
				var q = jQuery(_qwid(qwid), node); 					if (q.length != 1) {warn('qwid: '+qwid+' q.length: ' + q.length); continue;}
				switch (q[0].nodeName)
				{
				 case 'DIV':
				 case 'SPAN':
				 case 'INPUT':
					if (q[0].type == 'checkbox') {value = q[0].checked; break;}
					value = q[0].value;
					break;
				 case 'IFRAME':
					value = q[0].src;
					break;
				 case 'SELECT':
					value = q.val();
					break;
				 default:
					error('unknown nodeName: ' + q[0].nodeName);
					break;
				}
				qwidMap[qwid] = value;
			}
		}
	});
	
	com.qwidget.SurveyView = com.qwidget.View.extend({
		surveyQuestion: '',
		inputName: '',
		onClickAnswer: undefined,
		onRefreshComplete: undefined,
		init: function (node, onClickAnswer, onRefreshComplete)
		{
			this.containerQwid = com.qwidget.qwids.survey;
			this.onClickAnswer = onClickAnswer;
			this.onRefreshComplete = onRefreshComplete;
			
			this.refreshLists = {
				functions: [this.refreshAnswers],
				html: [],
				text: [this.cqq.survey_question]
			},
			
			this._super(node);

			jQuery(_qwid(this.cqq.survey_answerList), this.container).len(1).hide();
			com.qwidget.debug('SurveyView:init - answerList.hide');
		},
		refresh: function (qwidMap, container)
		{
			this.inputName = "survey_answer_" + qwidMap[this.cqq.survey_questionId];
			qwidMap[this.cqq.survey_inputName] = this.inputName;
			this.surveyQuestion = qwidMap[this.cqq.survey_question];
			this._super(qwidMap, container);

			// add a click handler for each answer radio button
			var q = jQuery(_qwid(this.cqq.survey_answerList), this.container).len(1);
			var that = this;
			jQuery('input', q).each(
				function ()
				{
					this.onclick = that.onClickAnswer;	
				}
			);
			com.qwidget.debug('SurveySection.init - wire up answer click event')

			jQuery(_qwid(this.cqq.survey_answerList), this.container).len(1).show();
			com.qwidget.debug('SurveyView:refresh - answerList.show');
			
			if (this.onRefreshComplete)
				this.onRefreshComplete();
		},
		refreshAnswers: function (qwidMap, container, cqq, refreshLists)
		{
			var answers = qwidMap[cqq.survey_data]; // TODO? - decouple raw data from the model
			var memberAnswerId = qwidMap[cqq.survey_memberAnswerId];
		    var buttons = "";
			// TODO - use a template instead of html...
		    for (var i = 0; i < answers.length; i++) {
		        var a = answers[i];
		        buttons += '<label><input type="radio" name="'+qwidMap[cqq.survey_inputName]+'" value="'+a.id+'"'+ (a.id == memberAnswerId ? 'checked="checked"' : '') +' />'+a.text+'</label>';
		    }

			refreshLists.html.push(cqq.survey_answerList);
			qwidMap[cqq.survey_answerList] = buttons;
		},
		qwidMap: function (qwidMap)
		{
			var q = jQuery('input[@name="'+this.inputName+'"]:checked', this.container).len(1, 'SurveyView.qwidMap - yikes, no answer checked');
			var value = q.val();
			qwidMap[this.cqq.survey_answerId] = value;
		},
		question: function ()
		{
			return this.surveyQuestion;
		},
		clickAnswer: function (answerId)
		{
			var q = jQuery('input[@name="'+this.inputName+'"]', this.container);
			for (var i = 0; i < q.length; i++)
			{
				com.qwidget.debug('SurveySection.clickAnswer - q.['+i+'].value: ' + q[i].value);
				if (q[i].value == answerId)
					q[i].click();
			}
		},
		displayMode: function (mode, unreadMessages)
		{
			// set the open/close button image
			var q = jQuery("#open_close", this.container);
			if (q.length > 0)
			{
					var  src = q[0].src;
					q[0].src = (mode == 'expand' ? src.replace('open_plus', 'close_minus') : src.replace('close_minus', 'open_plus'));
					q[0].title = (mode == 'expand' ? 'Minimize' : 'Open');
			}
		// for always show the unread message lin...
			// var q = jQuery("#unread_messages", this.container);
			// if (q.length > 0)
			// {
			// 	// show or hide the unread messages link
			// 	if (unreadMessages)
			// 		q.parent().show();
			// 	else
			// 		q.parent().hide();
			// }
		}
	});

	com.qwidget.MenuView = com.qwidget.View.extend({
		init: function(node)
		{
			this.containerQwid = 'menu';
			
			this.refreshLists = {
				functions: [],
				html: [],
				text: []
			},

			this._super(node);
		},
		loggedIn: function (loggedIn)
		{
	        jQuery(_qwid(this.cqq.menu_loggedIn) + ', ' + _qwid(this.cqq.menu_loggedOut), this.container).len(2, 'MenuView.loggedIn').hide(); 
	        jQuery(loggedIn ? _qwid(this.cqq.menu_loggedIn) : _qwid(this.cqq.menu_loggedOut), this.container).len(1, 'MenuView.loggedIn').show(); 
		},
		unreadMessageCount: function (unreadMessageCount)
		{
			jQuery(_qwid(this.cqq.menu_loggedIn_messages), this.container).len(1, 'MenuView.loggedIn').text('Messages ('+unreadMessageCount+')');
		}
	});

	com.qwidget.ResultsView = com.qwidget.View.extend({
		resultNode: undefined,
		init: function(node)
		{
			this.containerQwid = 'results';
	
			// NOTE - the template is the set of TDs found inside the TR...
			var q = jQuery(_qwid(this.cqq.results_list_item), node).len(1);
			var templateHtml = q.html();
	 		this.resultNode = jQuery(templateHtml);
	
			com.qwidget.adjustStaticImageFilepaths(this.resultNode);
			
			this.refreshLists = {
				functions: [],
				html: [],
				text: [
					this.cqq.results_list_item_answer,
					this.cqq.results_list_item_total,
					this.cqq.results_list_item_percent
				]
			},

			this._super(node);
		},
		refresh: function (qwidMap)
		{
			var results = qwidMap[this.cqq.results_data];
			// TODO? - can we avoid a template that is a series of TDs?
			var resultsList = jQuery(_qwid(this.cqq.results_list_item), this.container).len(1, 'ResultsView.refresh');
			
			resultsList.empty();
			for (var i = 0; i < results.length; i++)
			{
				var result = results[i];
				var node = this.resultNode.clone();
				this._super(result, node);
				resultsList.append(node);
			}
		}
	});

	com.qwidget.CommentView = com.qwidget.View.extend({
		init: function(node)
		{
			this.containerQwid = 'comment';
			
			this.refreshLists = {
				functions: [],
				html: [],
				text: []
			};

			this._super(node);
		},
		qwidMap: function (qwidMap)
		{
			var q = jQuery(_qwid(this.cqq.comment_text), this.container);
			var comment = q[0].value;
			qwidMap[this.cqq.comment_text] = (comment == "Why?" ? null : comment); // TODO - manage the prompt display via qw_prompt="Why?"
		},
		charactersRemainingNode: undefined,
		charactersRemaining: function (count)
		{
			if (!this.charactersRemainingNode)
				this.charactersRemainingNode = jQuery(_qwid(this.cqq.comment_characterCount_submitted), this.container).len(1, 'comment_characterCount_submitted');
			this.charactersRemainingNode.text(count == 200 ? '' : count);
		},
		submitSuccessful: function ()
		{
			jQuery(_qwid(this.cqq.comment_characterCount_submitted), this.container).len(1, 'comment_characterCount_submitted').text('Submitted');
			var q = jQuery(_qwid(this.cqq.comment_text), this.container).len(1).val('');
		}
	});

	com.qwidget.ResponsesView = com.qwidget.View.extend({
		responseNode: undefined,
		userInputCache: new Array(),
		init: function (node, parent)
		{
			this.containerQwid = 'responders';
			this.parent = parent;
			
			this.refreshLists = {
				//functions: [this.refreshResponses],
				functions: [
				],
				html: [
					this.cqq.responder_item_location
				],
				text: [
					this.cqq.responder_item_name,
					this.cqq.responder_item_photo,
					this.cqq.responder_item_comment,
					this.cqq.responder_item_permalink,
					this.cqq.responder_item_answer
				]
			};
			
			var q = jQuery(_qwid(this.cqq.responder_item), node).len(1, 'ResponsesView.init - '+this.cqq.responder_item);
	 		this.responseNode = q.clone(); // save the responder template
			q.remove();
			com.qwidget.adjustStaticImageFilepaths(this.responseNode);

			this._super(node);
		},
		refresh: function (qwidMap)
		{
			// TODO - modify base class handle multiples - e.g. messages...
			var responses = qwidMap[this.cqq.responses_data];
			var responsesList = jQuery(_qwid(this.cqq.responder_list), this.container).len(1, 'ResponsesView.refresh');
			//if (responsesList.length != 1) {com.qwidget.error('ResponsesView.refresh - cannot find template...'); return;}

			var permalinkObject = function (permalink)
			{
				var t = permalink.replace("http://", "");
				t = t.replace("www.", "");
				t = t.substr(0, t.indexOf("/"));
				return {href: permalink, text: 'Answered at ' + t};
			}
			var formattedAnswer = function (answer)
			{
				var color = com.qwidget.answerColorClasses[answer];
				return '<strong '+(color ? 'class="'+color+'"' : '')+'>'+answer+' </strong>';
			}
			
			responsesList.empty();
			
			for (var i = 0; i < responses.length; i++)
			{
				var permalink = responses[i][this.cqq.responder_item_permalink];
				responses[i][this.cqq.responder_item_permalink] = permalinkObject(permalink);
				
				responses[i][this.cqq.responder_item_answer] = com.qwidget.staticImagePath() + com.qwidget.answerImagePaths[(responses[i][this.cqq.responder_item_answer])];
				
				var that = this;
				
				var node = this.responseNode.clone();
				
				var memberReplyDate = responses[i][this.cqq.response_memberReplyDate];
				
				var replyData = responses[i][this.cqq.response_reply_data];
				var indicateReplySentFunction = function (s, n)
				{
					var replySentText = 'Reply sent ' + (s ? s : '');
					return function () {n.text(replySentText); n.show();}
				}
				var sendReplyFunction = function (x)
				{
					// make sure we get current replyData and node not just the final state...
					var replyTextNode =  jQuery(_qwid(that.cqq.responder_item_reply_text), node).len(1);
					var replySentNode =  jQuery(_qwid(that.cqq.responder_item_replySent), node).len(1);

					if (memberReplyDate)
					{
						indicateReplySentFunction(memberReplyDate, replySentNode)();
						replyTextNode.text('');
					}
					else
						replySentNode.hide();
					
					return function () {that.sendReply(x, replyTextNode, indicateReplySentFunction('', replySentNode)); return false;} 
				}
				var q = jQuery(_qwid(this.cqq.responder_item_reply_submit), node);
				q[0].onclick = sendReplyFunction(replyData);
			
				node.agree = responses[i][this.cqq.response_viewerAgreed];
				node.agreeCount = responses[i][this.cqq.response_agreeCount];
				node.disagreeCount = responses[i][this.cqq.response_disagreeCount];
			
				var responseId = responses[i][this.cqq.response_id];
				var responderId = responses[i][this.cqq.responder_item_memberId];
				var agreeDisagreeflagFunction = function (responseId, responderId, mode, node)
				{
					return function () {that.agreeDisagreeFlag(responseId, responderId, mode, node); return false;}
				}
				
				if (this.userInputCache[responseId])
				{
					q = jQuery(_qwid(that.cqq.responder_item_reply_text), node).len(1);
					q.val(this.userInputCache[responseId]['body']);
				}
				
				q = jQuery(_qwid(this.cqq.responder_menuAgree), node).len(1);
				q[0].onclick = agreeDisagreeflagFunction(responseId, responderId, 'agree', node);
				q = jQuery(_qwid(this.cqq.responder_menuDisagree), node).len(1);
				q[0].onclick = agreeDisagreeflagFunction(responseId, responderId, 'disagree', node);
				q = jQuery(_qwid(this.cqq.responder_menuFlag), node).len(1);
				q[0].onclick = agreeDisagreeflagFunction(responseId, responderId, 'flag', node);
				
				this._super(responses[i], node);
				this.indicateAgreeDisagree(node.agree, node);
				q = responsesList.append(node);
			}
		},
		sendReply: function (replyData, node, replySent)
		{
			replyData['body'] = (node[0].value == "Enter your reply here" ? "" : node[0].value); // DRY the prompt string with the corresponding HTML string...
			
			if (com.qwidget.AccountManager.isLoggedIn())
			{
				this.parent.sendReply(replyData, replySent);
				node.val(''); // clear the textarea...
				this.userInputCache = new Object();
			}
			else
			{
				this.userInputCache[replyData['responseId']] = replyData;
				com.qwidget.AccountManager.showLogIn();
			}
		},
		agreeDisagreeFlag: function (responseId, responderId, mode, node) // yikes! mode and node...
		{
			var forceSwap = (com.qwidget.AccountManager.isLoggedIn() ? false : (node.agree == undefined ? false : true));
			switch (mode)
			{
			 case 'agree':
				if (node.agree == true)
					return;
				node.agree = true;
				break;
			 case 'disagree':
				if (node.agree == false)
					return;
				node.agree = false;
				break;
			 default:
				break;
			}
			if (mode != 'flag')
			{
				node.agreeCount += (mode == 'agree' ? 1 : (node.agreeCount > 0 ? -1 : 0));
				node.disagreeCount += (mode == 'disagree' ? 1 : (node.disagreeCount > 0 ? -1 : 0));
				this.indicateAgreeDisagree(mode == 'agree', node);
			}
			this.parent.agreeDisagreeFlag(responseId, responderId, mode, forceSwap);
		},
		indicateAgreeDisagree: function (agree, node)
		{
			var agreeImage = 'thumbs_up.gif';
			var disagreeImage = 'thumbs_down.gif';
			
			switch (agree)
			{
			 case true:
				agreeImage = 'thumbs_up2.gif';
				break;
			 case false:
				disagreeImage = 'thumbs_down2.gif';
				break;
			 default:
				// use the default up/down images...
				return;
			}

			var changeImage = function (qwid, image, node, count)
			{
				var q = jQuery(_qwid(qwid), node).len(1);
				var i = jQuery('img', q).len(1);
				i[0].src = com.qwidget.staticImagePath() + image;
				if (count != undefined)
				{
					var s = jQuery('span', q).len(1);
					s.text(' ('+count+')');
				}
			}
			changeImage(this.cqq.responder_menuAgree, agreeImage, node, node.agreeCount);
			changeImage(this.cqq.responder_menuDisagree, disagreeImage, node, node.disagreeCount);
		},
		selectTab: function (mode)
		{
			var tabs = [
				{mode: 'recent',	qwid: this.cqq.responders_menu_recent},
				{mode: 'agree',		qwid: this.cqq.responders_menu_mostAgree},
				{mode: 'disagree',	qwid: this.cqq.responders_menu_mostDisagree}
			];
			var addClass = function (node, selected)
			{
				var p = node.parent();
				p.prev().removeClass();
				p.prev().addClass(selected ? 'qw_tab_left_selected' : 'qw_tab_left');
				p.removeClass();
				p.addClass(selected ? 'qw_tab_selected' : 'qw_tab');
				p.next().removeClass();
				p.next().addClass(selected ? 'qw_tab_right_selected' : 'qw_tab_right');
			}
			for (var i = 0; i < tabs.length; i++)
			{
				var tab = tabs[i];
				var q = jQuery(_qwid(tab.qwid), this.container).len(1);
				addClass(q, tab.mode == mode);
			}
		}
	});

	com.qwidget.FooterView = com.qwidget.View.extend({
		init: function (node)
		{
			this.containerQwid = 'footer';
			
			this.refreshLists = {
				functions: [],
				html: [],
				text: []
			};

			this._super(node);
		},
		hide: function ()
		{
			this._super();
			this.adjustBorder('hide');
		},
		show: function ()
		{
			this._super();
			this.adjustBorder('show');
		},
		adjustBorder: function (mode)
		{
			var q = jQuery(mode == 'hide' ? '.qw_bottom_border' : '.qw_bottom_closed', this.container.parent().parent());
			if (q.length == 1)
			{
				q.removeClass();
				q.addClass(mode == 'hide' ? 'qw_bottom_closed' : 'qw_bottom_border');
			}
		}
	});
